The backend currently registers @fastify/jwt with a hardcoded fallback secret:
secret: process.env.JWT_SECRET || 'dev-secret-change-me'
If JWT_SECRET is absent from the production environment, the application silently falls back to the publicly known default value 'dev-secret-change-me'.
Because this value is visible in the repository source, any attacker can forge valid JWTs for arbitrary users and gain authenticated access to protected API routes.
Vulnerable Code
File: apps/backend/src/app.ts
await app.register(jwt, {
secret: process.env.JWT_SECRET || 'dev-secret-change-me',
});
Root Cause
The || fallback silently succeeds when JWT_SECRET is undefined or empty.
There is currently no startup validation ensuring that required authentication secrets are configured before the server begins accepting requests.
As a result:
- the application boots normally,
- JWT signing and verification continue using the public fallback secret,
- authentication operates in an insecure state without any visible warning.
Security Impact
This creates a complete authentication bypass vulnerability.
An attacker can:
- read the publicly exposed fallback secret from the repository,
- generate arbitrary JWTs,
- impersonate any user by crafting tokens with arbitrary payloads,
- access protected API routes as authenticated users.
Example attack flow:
const jwt = require('jsonwebtoken');
const token = jwt.sign(
{
id: 'target-user-id',
username: 'victim'
},
'dev-secret-change-me',
{ expiresIn: '30d' }
);
console.log(token);
The forged token can then be supplied through:
Authorization: Bearer <token>
- authentication cookies
The backend will successfully verify the signature and execute requests as the impersonated user.
This affects all routes protected by app.authenticate.
Steps to Reproduce
- Start the backend without defining
JWT_SECRET.
- Generate a JWT using the known fallback secret.
- Send the forged token to an authenticated endpoint such as:
- Observe that the backend accepts the forged token and returns authenticated user data.
Expected Behavior
The server must fail fast if JWT_SECRET is missing or insecure in a production environment.
The application should:
- refuse to start,
- log a clear actionable error,
- never register JWT authentication with a public fallback secret.
Actual Behavior
The server starts successfully and silently uses the hardcoded development secret for JWT signing and verification.
Proposed Fix
Add startup validation before JWT plugin registration.
Suggested approach:
const REQUIRED_SECRETS = ['JWT_SECRET', 'ENCRYPTION_KEY'] as const;
for (const key of REQUIRED_SECRETS) {
if (!process.env[key]) {
console.error(
`FATAL: Environment variable "${key}" is not set. Refusing to start.`
);
process.exit(1);
}
}
if (
process.env.NODE_ENV === 'production' &&
process.env.JWT_SECRET === 'dev-secret-change-me'
) {
console.error(
'FATAL: JWT_SECRET is using the default development value.'
);
process.exit(1);
}
Then remove the insecure fallback:
// Before
secret: process.env.JWT_SECRET || 'dev-secret-change-me',
// After
secret: process.env.JWT_SECRET!,
Acceptance Criteria
Testing Requirements
Additional Notes
This is a fail-fast security hardening fix with minimal implementation complexity and low regression risk.
The issue primarily affects deployments where environment configuration is incomplete or misconfigured (Docker, Railway, Render, CI/CD environments, etc.).
No dependency changes are required.
The backend currently registers
@fastify/jwtwith a hardcoded fallback secret:If
JWT_SECRETis absent from the production environment, the application silently falls back to the publicly known default value'dev-secret-change-me'.Because this value is visible in the repository source, any attacker can forge valid JWTs for arbitrary users and gain authenticated access to protected API routes.
Vulnerable Code
File:
apps/backend/src/app.tsRoot Cause
The
||fallback silently succeeds whenJWT_SECRETis undefined or empty.There is currently no startup validation ensuring that required authentication secrets are configured before the server begins accepting requests.
As a result:
Security Impact
This creates a complete authentication bypass vulnerability.
An attacker can:
Example attack flow:
The forged token can then be supplied through:
Authorization: Bearer <token>The backend will successfully verify the signature and execute requests as the impersonated user.
This affects all routes protected by
app.authenticate.Steps to Reproduce
JWT_SECRET.Expected Behavior
The server must fail fast if
JWT_SECRETis missing or insecure in a production environment.The application should:
Actual Behavior
The server starts successfully and silently uses the hardcoded development secret for JWT signing and verification.
Proposed Fix
Add startup validation before JWT plugin registration.
Suggested approach:
Then remove the insecure fallback:
Acceptance Criteria
JWT_SECRETis missing.JWT_SECRET === 'dev-secret-change-me'in production.ENCRYPTION_KEYreceives the same startup validation.Testing Requirements
JWT_SECRETis undefined.JWT_SECRETequals the development default in production.Additional Notes
This is a fail-fast security hardening fix with minimal implementation complexity and low regression risk.
The issue primarily affects deployments where environment configuration is incomplete or misconfigured (Docker, Railway, Render, CI/CD environments, etc.).
No dependency changes are required.