Silent fallback to a public express-session secret
- Dominant language
- TypeScript
- Stars
- 634
- Forks
- 132
- PR merge metrics
- No merged PRs in 30d
Description
# Silent fallback to a public express-session secret
## Summary
WebAuthnDemo silently falls back to the publicly known value `secret` when the `SECRET` environment variable is missing:
[`src/libs/config.mts:74`](https://github.com/google/webauthndemo/blob/f1cd94214ebafabe827ac888d3cca5327194933c/src/libs/config.mts#L74)
```ts
return session({
name: session_name,
secret: process.env.SECRET || 'secret',
resave: false,
saveUninitialized: false,
store: new FirestoreStore({
dataset: store,
kind: 'express-sessions',
}),
});
```
The fallback is not rejected at startup. The production and development App Engine configuration files do not define `SECRET`:
- [`app.yaml:1`](https://github.com/google/webauthndemo/blob/f1cd94214ebafabe827ac888d3cca5327194933c/app.yaml#L1)
- [`dev.yaml:1`](https://github.com/google/webauthndemo/blob/f1cd94214ebafabe827ac888d3cca5327194933c/dev.yaml#L1)
The repository also contains the predictable development value `SECRET='this is a secret'`:
- [`src/.env.development:1`](https://github.com/google/webauthndemo/blob/f1cd94214ebafabe827ac888d3cca5327194933c/src/.env.development#L1)
## Security impact
`SECRET` signs the `express-session` cookie. In remote deployments the cookie is named `__Host-session`; in local development it is named `session`:
[`src/libs/config.mts:74`](https://github.com/google/webauthndemo/blob/f1cd94214ebafabe827ac888d3cca5327194933c/src/libs/config.mts#L74)
An attacker who knows the fallback can generate a valid signature for a known session ID. This can enable replay of a server-side session when the attacker obtains the corresponding session ID without its valid signature, for example through session-ID disclosure or a separate session-fixation condition.
This finding does **not** prove a direct arbitrary-user login bypass by itself. The application uses `FirestoreStore`, so the session contents are stored server-side. The authorization middleware requires `req.session.user_id`:
- [`src/server.mts:37`](https://github.com/google/webauthndemo/blob/f1cd94214ebafabe827ac888d3cca5327194933c/src/server.mts#L37)
- [`src/libs/helper.mts:55`](https://github.com/google/webauthndemo/blob/f1cd94214ebafabe827ac888d3cca5327194933c/src/libs/helper.mts#L55)
A signed cookie for an ID with no matching Firestore session is therefore not sufficient to create an authenticated user. Login itself verifies a Firebase ID token before writing `user_id` and other identity data into the session:
[`src/libs/auth.mts:43`](https://github.com/google/webauthndemo/blob/f1cd94214ebafabe827ac888d3cca5327194933c/src/libs/auth.mts#L43)
The nearby random fallback at [`src/libs/config.mts:106`](https://github.com/google/webauthndemo/blob/f1cd94214ebafabe827ac888d3cca5327194933c/src/libs/config.mts#L106) does not mitigate this issue. `initializeSession()` reads `process.env.SECRET` independently and still selects the literal `secret`.
## Proof of concept
The following generates a valid `express-session` signature for an attacker-chosen session ID using the published fallback:
```sh
node <<'NODE'
const crypto = require('node:crypto');
const secret = 'secret';
const sessionId = 'known-session-id';
const signature = crypto
.createHmac('sha256', secret)
.update(sessionId)
.digest('base64')
.replace(/=+$/, '');
console.log(`s:${sessionId}.${signature}`);
NODE
```
Use the resulting value only against a local or authorized deployment:
```sh
curl -i -X POST \
-H 'Cookie: __Host-session=' \
https://target.example/auth/userInfo
```
If `known-session-id` has no corresponding Firestore record, the expected response is `401 Unauthorized`. If an attacker has obtained a valid Firestore session ID through an independent weakness, the same signed cookie can cause that server-side session to be accepted.
## Recommended fix
Treat a missing `SECRET` as a deployment error. Production and hosted development deployments should fail closed during startup unless `SECRET` is explicitly supplied as a deployment-specific, high-entropy value. Do not use `secret` or `this is a secret` as a fallback. If a generated value is used for local development, persist it securely; generating a new value on every restart invalidates existing sessions.
## Classification
- Root cause: public static fallback for the `express-session` signing secret.
- Authentication model: server-side session authentication backed by Firestore, not JWT authentication.
- Direct impact from the fallback alone: no demonstrated arbitrary-user authentication bypass.
- Conditional impact: session replay or fixation when combined with session-ID disclosure or another session-management weakness.
This report is part of my ongoing research on authentication mechanism security. Thank you for taking the time to review it. If you have any questions or would like additional validation, please feel free to @mention me or contact me at any time. I would be very glad to contribute, however modestly, to improving the security of WebAuthnDemo.
Contributor guide
Research direction
Start with initializeSession() in src/libs/config.mts and compare its SECRET handling with app.yaml, dev.yaml, and src/.env.development. Review the related authorization and login paths in src/server.mts and src/libs/auth.mts, then verify that deployments without an explicit SECRET fail closed and that supplied secrets are used without predictable fallbacks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- authentication, backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100