From c821282918d71a1f1f10ef1991e9c37cc216f624 Mon Sep 17 00:00:00 2001 From: Adam Wright Date: Fri, 18 Sep 2026 05:15:49 +0000 Subject: [PATCH] Document the key an operator cannot start beta without The key exchange happened today: the website generated a keypair, sent the public half, and it is installed on beta. Neither deploy/beta/README.md nor env.beta.template mentioned any of it, and .env.beta is gitignored -- so the template was the only tracked record of what the environment needs, and it was missing the one variable whose absence stops the container. Someone setting beta up from these docs would have got a container that refuses to start, with nothing in the documentation explaining why. The refusal is deliberate and well argued in the code; it was undiscoverable from here. The README now has the step that produces the file, says where the public half comes from, and says to delete any private half once a real key is installed -- on the verifying side it is pure liability, which is the mistake this repo made until today. Co-Authored-By: Claude Opus 5 --- deploy/beta/README.md | 32 ++++++++++++++++++++++++++++++-- deploy/beta/env.beta.template | 9 +++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/deploy/beta/README.md b/deploy/beta/README.md index 2c2cdc50..f0e880a3 100644 --- a/deploy/beta/README.md +++ b/deploy/beta/README.md @@ -42,7 +42,35 @@ Copy `env.beta.template` to `.env.beta` and fill in the two keys. Do not reuse prod's `CHAINLIT_URL`, `CHAINLIT_ROOT_PATH` or OAuth values — they point at `reactome.org` and will break asset URLs and logins on beta. -## 4. Run +## 4. The caller-token verifying key + +The answer endpoint (`/chat/guest/api/answer`) verifies a token the website mints +for each call. This host holds **only the public half** and can therefore verify +but never mint -- which is the point of choosing an asymmetric algorithm, and why +no private key for this path should exist here. + +The app **refuses to start** without the key. That is deliberate: an endpoint that +accepts everything because its key is missing is the worst outcome available, and +it would test clean. But Chainlit is mounted on the same app, so a missing key +takes `/chat` down with it -- which is why `update-beta-chat.sh` checks the key is +readable *before* it stops the running container. + +The public half comes from the website team. Ask them for it, write it to +`deploy/beta/caller_token_public.pem` (mode 644 -- the image runs as `appuser` and +must read it), and restart. Tokens signed by the previous key stop verifying the +moment you do, which is what makes rotation a file write plus a restart. + +If you need a keypair for local testing rather than the real exchange: + +```bash +./bin/make-caller-token-keypair.py deploy/beta +``` + +It refuses to overwrite an existing key, because silently replacing one would +invalidate every token in flight with no way back. **Delete the private half once +a real key is installed**: on the verifying side it is pure liability. + +## 5. Run Bound to loopback: Apache is the only thing that should reach it. @@ -64,7 +92,7 @@ Note: the landing page shows both a **Guest Access** and a **Log In** button. On Guest Access works in this setup; wiring Log In needs a second container on :8001 with `CHAINLIT_URI=/chat/personal`, plus Postgres and OAuth. -## 5. Apache +## 6. Apache See `../../../WebsiteAngular/deploy/apache/install-beta-chat-proxy.sh`. diff --git a/deploy/beta/env.beta.template b/deploy/beta/env.beta.template index 13e11b5f..e15af96a 100644 --- a/deploy/beta/env.beta.template +++ b/deploy/beta/env.beta.template @@ -31,3 +31,12 @@ CHAINLIT_URL=https://beta.reactome.org # 400 for anything else. Set this only for a model that rule does not yet know # about; see resolve_temperature in src/agent/graph.py. #LLM_TEMPERATURE= + +# The answer endpoint verifies the website's caller token against this public +# key, and the app REFUSES TO START without it -- deliberately, because an +# endpoint that accepts everything is worse than one that is down. Chainlit is +# mounted on the same app, so a missing key takes /chat down too. +# +# update-beta-chat.sh mounts deploy/beta/caller_token_public.pem here and checks +# it is readable before touching the running container. See README section 4. +CALLER_TOKEN_PUBLIC_KEY_PATH=/run/secrets/caller_token_public.pem