Skip to content

Auto-generate and persist TOKEN_SECRET on first run #280

Description

@melvincarvalho

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions