Problem
In production (NODE_ENV=production), JSS hard-exits if TOKEN_SECRET is not set:
SECURITY ERROR: TOKEN_SECRET environment variable must be set in production
Generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
This is surfacing as a real onboarding footgun for beginners trying to put JSS behind pm2. See serverproject-dev/solidweb.app#1: the operator had two pm2 configs, one working (config.json — no NODE_ENV=production set, so JSS falls through to the dev warn path with an ephemeral secret) and one broken (ecosystem.config.js — sets NODE_ENV=production explicitly, triggering the hard exit).
The suggested mitigation — shell-generate a 64-hex-char secret and paste it into the pm2 config — is non-obvious for new operators (the error even tells them to run node -e …, which they may not have in their shell path).
Proposal
Auto-generate and persist TOKEN_SECRET on first run.
1. if env TOKEN_SECRET is set → use it (unchanged)
2. else try to read ~/.jss/token.secret
3. if missing → generate 32 random bytes, write to ~/.jss/token.secret
with dir mode 0o700 and file mode 0o600
4. only hard-exit when even the write fails AND NODE_ENV=production
Result: a beginner who pm2 start ecosystem.config.js --env production with no secret in the config boots cleanly on first run, secret survives restarts, tokens are stable — zero config.
Operators who want to manage the secret themselves still override via the env var and see no behavior change.
Why user home, not <dataRoot>/.jss/
urlToPath() does no filtering on dotfiles; the path-traversal guard only blocks ... A GET /.jss/token.secret against a server rooted at <dataRoot> would resolve to the secret file and JSS would happily serve it. Placing the file outside dataRoot (e.g. ~/.jss/) sidesteps this entirely.
Alternative if a pod-local location is preferred: explicitly block .jss/ in the HTTP handlers (parallel to how .well-known, .acl, etc. are already treated specially in getPodNameFromPath).
Back-compat
None required. The only behavior change is that a previously-fatal path now succeeds. Anyone who already sets TOKEN_SECRET explicitly is unaffected.
Willing to PR
Happy to put up a PR.
Problem
In production (
NODE_ENV=production), JSS hard-exits ifTOKEN_SECRETis not set:This is surfacing as a real onboarding footgun for beginners trying to put JSS behind pm2. See serverproject-dev/solidweb.app#1: the operator had two pm2 configs, one working (
config.json— noNODE_ENV=productionset, so JSS falls through to the dev warn path with an ephemeral secret) and one broken (ecosystem.config.js— setsNODE_ENV=productionexplicitly, triggering the hard exit).The suggested mitigation — shell-generate a 64-hex-char secret and paste it into the pm2 config — is non-obvious for new operators (the error even tells them to run
node -e …, which they may not have in their shell path).Proposal
Auto-generate and persist
TOKEN_SECRETon first run.Result: a beginner who
pm2 start ecosystem.config.js --env productionwith no secret in the config boots cleanly on first run, secret survives restarts, tokens are stable — zero config.Operators who want to manage the secret themselves still override via the env var and see no behavior change.
Why user home, not
<dataRoot>/.jss/urlToPath()does no filtering on dotfiles; the path-traversal guard only blocks... AGET /.jss/token.secretagainst a server rooted at<dataRoot>would resolve to the secret file and JSS would happily serve it. Placing the file outsidedataRoot(e.g.~/.jss/) sidesteps this entirely.Alternative if a pod-local location is preferred: explicitly block
.jss/in the HTTP handlers (parallel to how.well-known,.acl, etc. are already treated specially ingetPodNameFromPath).Back-compat
None required. The only behavior change is that a previously-fatal path now succeeds. Anyone who already sets
TOKEN_SECRETexplicitly is unaffected.Willing to PR
Happy to put up a PR.