fix(api): refuse to run without a strong JWT secret - #230
Merged
Merged
Conversation
core/auth/jwt.py fell back to the key "dev-secret-change-in-production" when JWT_SECRET_KEY was unset. Every login token is HS256-signed with that key, so on any deployment that never set the variable, anyone who has read the source could mint an access token for any user, org and role. Nothing warned that the variable was missing. There is no fallback now. jwt_secret_key() returns JWT_SECRET_KEY, or raises JWTSecretKeyError when it is unset or shorter than 32 bytes (the HS256 minimum from RFC 7518 section 3.2, which pyjwt 2.15 also warns about). Issuing and verifying tokens both call it, including the SSE ?token= path. create_app() calls it first, so the CE and EE APIs refuse to start without a key. The Temporal worker signs no tokens and needs none. Local flows keep working: - just dev, dev-backend and dev-backend-ce keep a 32+ byte key from .env or the shell, and otherwise use a random key for that run - just demo generates a key when neither the shell nor .env has a good one - docker-compose passes JWT_SECRET_KEY to the api service - .env.example, infra/check-env.sh, the quickstart and the deployment docs say to set it (openssl rand -hex 32) The CE and EE test conftests set a 32+ byte key, so pyjwt's InsecureKeyLengthWarning is gone. New tests check that: - no token is issued or verified with an unset, empty, 31-byte or old-default key - a token forged with the old default key is rejected - create_app() and create_ee_app() refuse to start without a key Operators: set JWT_SECRET_KEY before upgrading, or the API will not start. Tokens signed with the old default stop verifying, so users sign in again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com>
|
Deployment failed for project dataing-docs with the following error: Learn More: https://vercel.com/bordumbs-projects?upgradeToPro=build-rate-limit |
|
Deployment failed for project dataing with the following error: Learn More: https://vercel.com/bordumbs-projects?upgradeToPro=build-rate-limit |
|
Deployment failed for project dataing-app with the following error: Learn More: https://vercel.com/bordumbs-projects?upgradeToPro=build-rate-limit |
github-actions Bot
pushed a commit
that referenced
this pull request
Sep 28, 2026
## [1.25.4](v1.25.3...v1.25.4) (2026-09-28) ### Bug Fixes * **api:** refuse to run without a strong JWT secret ([#230](#230)) ([58e44b0](58e44b0))
bordumb
added a commit
that referenced
this pull request
Sep 29, 2026
…ate it (#232) * fix(frontend): keep orval 8 from turning GET hooks into mutations #221 bumped orval from 6.31 to 8 without regenerating the client. Run on the committed spec, orval 8 rewrote 293 files: every GET hook became a useMutation and every POST/PUT/DELETE hook a useQuery, request functions switched to the fetch client's (url, init) call that customInstance does not accept, imports gained a ".ts" suffix, and models lost their property order and named nullable types. Orval 8 applies override.query.useQuery and useMutation to every verb, and a GET with both becomes a mutation. Leave them unset: GETs then get query hooks and the other verbs mutation hooks, as under orval 6. Set the orval 6 behaviour that orval 8 changed: - httpClient axios: customInstance takes one request config object - propertySortOrder Alphabetical: model properties keep their order - aliasCombinedTypes: nullable properties keep their named types - a tsconfig without allowImportingTsExtensions, so imports stay extensionless Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com> * chore(frontend): regenerate the API client with orval 8 Output of `pnpm orval` on the committed openapi.json with the orval 8 config from the previous commit. A second run changes nothing. The hooks consumers use keep their shape. The 28 regenerated tag files remove or rename no export; their 76 GET hooks stay useQuery and their 90 other hooks useMutation; all 76 query keys are unchanged. Model types match except for JSDoc and three tighter types (InvestigationBrief.version is the literal 1, an upload is Blob | File, UpdateUserRequestRole also exports its values). Orval 8's React Query v5 output makes most of the size: - query hooks gain initialData overloads, a DataTag query key and an optional queryClient argument - mutations gain a mutation key and a named variables type - non-GET request functions take an optional AbortSignal - path-param queries are enabled when the param is not null or undefined (it was truthy), so an empty string now runs the query; no caller passes one - headers drop the orval version, so upgrades stop touching every file Orval 8 also writes generated/index.ts, which re-exports every tag. Nothing imports it; it is kept so a regeneration leaves no untracked file. Turning it off (indexFiles: false) would also stop orval maintaining model/index.ts. The 125 files the committed spec no longer produces (the audit, feedback, runs, scim, settings and sso tags and 119 models) are left alone and keep their orval 6 header. The detect-secrets hook moved the baseline entry for configFieldType.ts up a line, since orval 8 drops an eslint-disable comment there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com> * fix(infra): let just generate-client export the schema without a JWT key Since #230 the app refuses to build without a JWT signing key of 32+ bytes, so export_openapi.py, which imports it, fails before orval runs. The schema doesn't depend on the key: use a throwaway one when none is set, as the dev recipes do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com> --------- Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security fix. The API signed and verified every login token (HS256) with the key
"dev-secret-change-in-production"wheneverJWT_SECRET_KEYwas unset, and nothing warned about it. On any deployment that never set the variable, anyone who has read the public source could mint an access token for any user, org and role.JWT_SECRET_KEYto 32+ random bytes:openssl rand -hex 32. Without it the API exits at startup with:JWTSecretKeyError: JWT_SECRET_KEY is not set; it must be at least 32 random bytes. Generate one with: openssl rand -hex 32What changed
core/auth/jwt.pyhas a newjwt_secret_key(). It returnsJWT_SECRET_KEY, or raisesJWTSecretKeyErrorif the key is unset or shorter than 32 bytes (the HS256 minimum from RFC 7518 §3.2; pyjwt 2.15 also warns below it). All token paths call it: issuing, verifying, refresh, and the SSE?token=query path.create_app()checks the key first. The CE and EE apps (create_ee_app()builds oncreate_app()) both refuse to start without one. The Temporal worker signs no tokens and needs no key.just dev,just dev-backendandjust dev-backend-cekeep a 32+ byte key from.envor the shell. Otherwise they use a random key for that run.just demogenerates a key if neither the shell nor.envhas a valid one.just demo-infrastarts no API and needs no key.docker-compose.ymlpassesJWT_SECRET_KEYto theapiservice.demo/docker-compose.demo.ymlreuses that service unchanged..env.example,infra/check-env.sh,docs/test-quickstart.sh, the quickstart and the deployment docs all require the key.jwt.decodeverifies IdP tokens against the IdP's keys, so it is unaffected.Tests
InsecureKeyLengthWarning: 0 in all four runs below.create_app()andcreate_ee_app()refuse to start.uv run pytest python-packages/dataing/testsuv run pytest python-packages/dataing-ee/tests-m integration, pgvector:pg16 throwaway container-m integration, same containermypy, ruff check and format, detect-secrets🤖 Generated with Claude Code