Repository navigation
Provision the verifying key, so deploying the endpoint cannot take /chat down - #239
Merged
Merged
Conversation
…hat down `bin/chat-fastapi.py` raises at startup when HUMAN_TOKEN_PUBLIC_KEY_PATH is missing -- deliberately, because an endpoint that accepts everything is worse than one that is down. Chainlit is mounted on the same app, so that failure takes `/chat` with it, and the key was configured nowhere: not in compose, not in the beta env, not in SECRET_NAMES. Deploying current main to beta would have stopped the working chat. Verified against the published image for main (a6fa184), on a spare port with beta untouched: - with the key mounted: starts, and a valid token gets an answer -- 12 citations, first token 14.0s, no anchors. Missing and expired tokens are refused in 0.0s - without it: exits 3, `Cannot read the verifying key at /run/secrets/human_token_public.pem` So `~/update-beta-chat.sh` now checks the key is readable *before* it stops the running container, alongside its existing image checks, and mounts it read-only at /run/secrets. A missing key now fails the update with an instruction rather than a stopped service. `*.pem` and `*.key` are ignored first, before any key existed, so neither half can be committed. The public key is 0644 because the image runs as appuser and must read it -- checked in a throwaway container. The private half is only for whoever mints tokens, which is D1 and still open. Rotation is the script plus a restart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
**I edited the wrong compose file.** Both compose.yaml and docker-compose.yml are tracked, and Docker Compose prefers compose.yaml when both exist -- so the key went into the file Compose ignores. compose.yaml also already had a secrets convention, six external secrets matching SECRET_NAMES, which the first version of this branch did not follow. **A missing bind mount makes Docker create a root-owned directory.** Verified: mounting ./deploy/beta/human_token_public.pem when the file is absent leaves a root-owned directory at that path, which then needs sudo to clear and which the key generator would refuse to write over. A compose secret errors instead and touches nothing on the host. Both files now use a secret. **The repo's own tripwire caught the inconsistency**: a test asserts every secret compose declares is one SECRET_NAMES loads, because a secret mounted and never read silently falls back to the environment. This key genuinely is read -- as a file, through HUMAN_TOKEN_PUBLIC_KEY_PATH -- so FILE_SECRET_NAMES records that category rather than the name being added to SECRET_NAMES, which would have claimed the PEM is loaded into the environment when nothing reads it there. A second test keeps that from becoming an escape hatch: a name listed there must be read as /run/secrets/<name> somewhere. Both are mutation-checked. **cryptography was only a transitive dependency**, via pyjwt's crypto extra, while the keypair script and the token tests import it directly -- the same shape as the pyjwt lock failure earlier. Declared, lock regenerated, and nothing else moved: the only change is the content hash. Co-Authored-By: Claude Opus 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.
Deploying current
mainto beta would have stopped the working chat. This fixes that before it happens.The problem
bin/chat-fastapi.pyraises at startup whenHUMAN_TOKEN_PUBLIC_KEY_PATHis missing — deliberately, because an endpoint that accepts everything is worse than one that is down. But Chainlit is mounted on the same FastAPI app, so the blast radius is/chat, not just the new endpoint.And the key was configured nowhere: not in
docker-compose.yml, not indeploy/beta/.env.beta, not inSECRET_NAMES.Verified against the published image for main (
a6fa184)Run on a spare port, beta untouched throughout:
answered, 12 citations, first token 14.0s, 0 anchorsrefusedin 0.0srefusedin 0.0sCannot read the verifying key at /run/secrets/human_token_public.pemThat last row is the one that matters: it is
/chatgoing down.What changed
~/update-beta-chat.shchecks the key is readable before it stops the running container, alongside its existing image checks, and mounts it read-only at/run/secrets/. A missing key now fails the update with an instruction instead of leaving a stopped service.bin/make-human-token-keypair.pygenerates the pair. It refuses to overwrite an existing key, since silently replacing one would invalidate every token in flight with no way back. Rotation is this script plus a restart.docker-compose.yml— both services that run the app image get the env var and the mount..gitignore—*.pemand*.key, added before any key existed, so neither half can be committed.The public key is
0644because the image runs asappuser:appgroupand must read it; checked in a throwaway container rather than assumed.Still open
The private half belongs to whoever mints tokens, which is D1 and undecided. What exists now is a beta key that lets the service run and be tested end to end; when D1 lands, regenerate and hand the private half to the minter.
🤖 Generated with Claude Code