Mass Assignment via Onboarding Endpoint Allows Unauthenticated JWT_SECRET Overwrite · Advisory · hoppscotch/hoppscotch · GitHub
//repos/advisories/show" data-turbo-transient="true" />
Skip to content
Search or jump to...
Search code, repositories, users, issues, pull requests...
-->
Search
Clear
Search syntax tips
Provide feedback
--><br>We read every piece of feedback, and take your input very seriously.
Include my email address so I can be contacted
Cancel
Submit feedback
Saved searches
Use saved searches to filter your results more quickly
-->
Name
Query
To see all available qualifiers, see our documentation.
Cancel
Create saved search
Sign in
//repos/advisories/show;ref_cta:Sign up;ref_loc:header logged out"}"<br>Sign up
Appearance settings
Resetting focus
You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
{{ message }}
hoppscotch
hoppscotch
Public
Uh oh!
There was an error while loading. Please reload this page.
Notifications<br>You must be signed in to change notification settings
Fork<br>5.9k
Star<br>79.6k
Mass Assignment via Onboarding Endpoint Allows Unauthenticated JWT_SECRET Overwrite
Critical
jamesgeorge007<br>published<br>GHSA-j542-4rch-8hwf<br>May 28, 2026
Software
hoppscotch-backend (Self-Hosted)
Affected versions
Patched versions
2026.5.0
Description
Summary
The POST /v1/onboarding/config endpoint allows an unauthenticated attacker to inject arbitrary InfraConfig keys -- including JWT_SECRET and SESSION_SECRET -- into the database via mass assignment. These keys are not declared in the SaveOnboardingConfigRequest DTO , but because the NestJS ValidationPipe does not strip extra properties, they pass through to the service layer where Object.entries(dto) iterates all keys without restriction.
This results in full server compromise: the attacker controls the JWT signing key and can forge tokens for any user including admin.
The attack works only on fresh installs before onboarding completes (or when usersCount === 0). However, self-hosted Hoppscotch instances are exposed to the internet during initial setup, and the window between deployment and onboarding completion is the exact moment the instance is most vulnerable.
Confirmed with a live proof-of-concept on a fresh Hoppscotch AIO Docker deployment.
Affected Components
packages/hoppscotch-backend/src/main.ts (lines 93--97) -- ValidationPipe configuration
packages/hoppscotch-backend/src/infra-config/infra-config.service.ts (lines 538--553) -- unconstrained key iteration
packages/hoppscotch-backend/src/infra-config/infra-config.service.ts (line 806) -- validateEnvValues switch with default: break
packages/hoppscotch-backend/src/infra-config/onboarding.controller.ts (line 58) -- unauthenticated endpoint
packages/hoppscotch-backend/src/types/InfraConfig.ts (lines 5--6) -- JWT_SECRET and SESSION_SECRET as valid InfraConfigEnum values
Root Cause
Four independent weaknesses combine to enable this attack:
Weakness 1 -- ValidationPipe missing whitelist: true (main.ts:93--97)
app.useGlobalPipes(<br>new ValidationPipe({<br>transform: true,<br>// whitelist: true -- MISSING: extra properties are NOT stripped<br>}),<br>);
Without whitelist: true, NestJS copies all properties from the request body to the DTO object, including properties not declared in the SaveOnboardingConfigRequest class. JWT_SECRET, SESSION_SECRET, and other security-critical keys are not DTO fields -- they are extra properties that should be stripped but are not.
Weakness 1 alone is sufficient to block this attack. Weaknesses 2--4 should also be addressed as defense in depth.
Weakness 2 -- Unconstrained Object.entries(dto) (infra-config.service.ts:538--543)
value !== undefined)<br>.map(([key, value]) => ({<br>name: key as InfraConfigEnum, // TypeScript cast, no runtime validation<br>value,<br>})),<br>];">const configEntries: InfraConfigArgs[] = [<br>...Object.entries(dto)<br>.filter(([_, value]) => value !== undefined)<br>.map(([key, value]) => ({<br>name: key as InfraConfigEnum, // TypeScript cast, no runtime validation<br>value,<br>})),<br>];
The cast key as InfraConfigEnum performs no runtime check. Object.entries(dto) iterates every property on the DTO object, including the extra properties that leaked through from Weakness 1. Since JWT_SECRET is a valid InfraConfigEnum value (defined in types/InfraConfig.ts:5), the attacker-supplied key is treated as a legitimate config entry and written to the database.
Weakness 3 -- validateEnvValues has default: break (infra-config.service.ts:806)
The validateEnvValues method uses a switch statement over InfraConfigEnum values to validate incoming config entries. The default case is:
default:<br>break; // unrecognized keys silently pass validation
JWT_SECRET and SESSION_SECRET do not have explicit validation cases in this switch. They fall through to default: break...