Conversation
…n JWT_SECRET is missing
`env.String("JWT_SECRET", "secret")` made the signing key fall back to the
committed default `secret` whenever `JWT_SECRET` was unset or empty. Since the
value is in the source tree, anyone could mint an HS256 token that passes
signature verification for any user_id / eid.
- jwt: drop the fallback, add ErrSecretNotConfigured + SecretConfigured(), and
guard all four exported entry points (UserGenerateJWT, UserParseJWT,
GenerateUploadDelegateJWT, ParseUploadDelegateJWT)
- sandboxdl: stop falling back to "secret" through JWT_SECRET, guard
GenerateDownloadToken / ValidateDownloadToken
- main: refuse to start when JWT_SECRET is missing, before any other init
- env templates: document the previously missing JWT_SECRET /
SANDBOX_DOWNLOAD_TOKEN_SECRET keys
Verified against the pre-fix tree: a token forged with the public default
"secret" is accepted by the old code (uid=1 eid=1) and rejected by the patched
code, while all legitimate round-trips still work once a real secret is set.
Refs 53AI#97
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (8)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughThe change removes hardcoded JWT and sandbox token secrets, documents required environment variables, adds fail-closed token operations, and stops application startup when ChangesSigning secret configuration
Priority: ⬆️ High Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: High Suggested reviewers: Merge Risk: ⚪ Minimal · up to The change removes the public signing fallback, requires JWT_SECRET at startup, and fails token operations closed when signing keys are absent; the documented deployment requirement introduces no identified current-head merge-blocking risk. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Linked Issues checkExplanation Issue Resolution Validate Full details: Docstring CoverageExplanation Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 6 files. (2 skipped: 2 unsupported.)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. A rabbit found a secret bare, Comment |
Closes #97.
What
env.String("JWT_SECRET", "secret")silently fell back to the committed defaultsecretwheneverJWT_SECRETwas unset or empty. Because that value lives in the source tree, anyone can mint an HS256 token that passes signature verification for anyuser_id/eid.This PR removes every committed fallback signing secret and makes the affected paths fail closed.
Changes
1. The signing secret is now required —
api/common/utils/jwt/jwt.gosecretKeydefault changes from"secret"to""ErrSecretNotConfiguredandSecretConfigured()ErrSecretNotConfiguredinstead of signing / verifying with an empty key:UserGenerateJWT,UserParseJWT(jwt.go)GenerateUploadDelegateJWT,ParseUploadDelegateJWT(upload_delegate.go) — the delegated batch-upload paths referenced in the issue; they share the same package-level key, so they needed the same guard2. Sandbox download tokens —
api/common/utils/sandboxdl/token.go"secret"throughJWT_SECRET; it still prefers its ownSANDBOX_DOWNLOAD_TOKEN_SECRETGenerateDownloadToken/ValidateDownloadTokenfail closed when neither is configured3. Fail fast at startup —
api/main.gojwt.SecretConfigured()before any other initialization and refuses to boot with an actionable messagelogger.FatalLog:shouldLog()returnsfalsefor every level whenLOG_LEVEL=NONE, so the check would be silently swallowed. It writes to stderr and exits directly.4. Env templates —
api/.env.example,docker/.env.example,docker/.env,api/docker/.envJWT_SECRETandSANDBOX_DOWNLOAD_TOKEN_SECRETkeysJWT_SECRETdid not appear in any env template, so existing deployments were running on the public default. After this change they must set it:and assign the result to
JWT_SECRET. A deployment that does not will refuse to start (the message says exactly this) instead of booting with a forgeable key. Sessions signed with the old default also stop validating — that is intended, since the exposed value must be rotated.Verification
Reproduced against current
main(da0d5e45) and against the patched tree, using a token forged with the public defaultsecret:UserParseJWT(forged)uid=1 eid=1 err=<nil>JWT_SECRET is not configuredtoken signature is invalidWith a real secret configured the legitimate paths still round-trip:
UserGenerateJWT/UserParseJWT,GenerateDownloadToken/ValidateDownloadToken,GenerateUploadDelegateJWT/ParseUploadDelegateJWT.gofmtclean,go vetclean,go build ./...passes on go1.25.9.Related finding, intentionally not fixed here
api/config/encryption.gohas the same shape of bug:53AIHub_ENCRYPTION_KEYfalls back to the committed"default-encryption-key-32-bytes-long". I left it out on purpose, because that key encrypts WeChat Pay configuration at rest (api/service/payment/wechatpay.go), so changing it needs a rotation / migration story for rows that were already encrypted with the default. It does not belong in this patch. Happy to send it as a separate PR if useful.Summary by CodeRabbit
Security
JWT_SECRETis missing.SANDBOX_DOWNLOAD_TOKEN_SECRET, which uses the JWT secret when not separately configured.Documentation