Skip to content

operations: rotate the deletion tokens in place, and prove it took - #188

Merged
Maxerns merged 10 commits into
mainfrom
ops/rotate-deletion-tokens
Sep 10, 2026
Merged

operations: rotate the deletion tokens in place, and prove it took#188
Maxerns merged 10 commits into
mainfrom
ops/rotate-deletion-tokens

Conversation

@Maxerns

@Maxerns Maxerns commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Why

Both organisation-deletion tokens need rotating, and the reason is mine: I printed them into a
session transcript while proving a line had landed in .env, when grep on the one variable
would have answered the question. They have been due for rotation since.

Rotation is also the kind of thing that gets done once, by hand, slightly differently each time,
and is then hard to be sure actually happened. So it is a script rather than a paste-able
command, and it verifies itself.

The part that makes it worth having

It checks the new token is accepted and that the old one is now refused. Only the second half
distinguishes a real rotation from a no-op edit: a server that restarted without re-reading its
environment accepts the new value from nowhere and keeps honouring the old one, and from outside
that looks exactly like success.

That is not hypothetical. docker compose restart reuses the existing environment, which is why
this uses up -d, and it is a mistake this deployment has made before.

No token is printed

Not the old ones, not the new ones, not to the terminal and not to a log. The values are written
to .env and read back from there for the checks. A rotation that shows you the replacement on
the way past has published it to whatever is recording the session, which is precisely how the
current pair needs rotating in the first place.

Fixed in review

Four findings, all valid, and the first is the uncomfortable one: the bearer tokens were passed
as curl -H arguments, so ps on a shared host would have handed them to any local user for the
duration of each request. That is the same exposure I fixed in scripts/check-restore days ago,
in the same way, and then reproduced here. Each request now reads its header from a file at mode
600, removed immediately and on the way out if something fails first.

The other three: read_var took every matching line, so a key repeated in .env would have
produced a multi-line value and, in an Authorization header, header injection rather than a
wrong token. The health wait fell through on timeout, turning one clear problem into five
confusing ones. And status() inside an assignment would have ended the script under set -e
when curl could not connect, silently, at the moment the operator most needed telling; it now
always prints three digits, using curl's own 000 for no response.

Testing

Dry-run end to end against stubbed docker and curl, both paths:

  • Success: all five checks pass, exits 0, both tokens become 64 hex characters, the old
    values are gone, and the other entries in .env (the Stripe key, the image pin) are untouched.
  • Failure, simulated as a server that kept the old tokens live: four of five checks fail,
    it names the backup and the command to roll back, and exits 1.

Two portability problems surfaced by testing rather than by review. declare -A needs bash 4 and
macOS ships 3.2, so the script could not run where it was written; and sed -i means different
things to GNU and BSD sed, so in-place editing is only testable on whichever the author happens
to have. Both are gone: no associative arrays, and the file is rewritten through a temporary at
mode 600 and moved, which is a better shape for a secrets file than mutating it in place.

What this does not cover

The Stripe test key, which rotates in Stripe's own dashboard and has nothing to do with .env
on the host.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new script can exit prematurely on curl errors due to set -e (breaking verification), and should also fail clearly if the server never becomes healthy.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds an operational script to rotate both organisation-deletion bearer tokens on the production host, then verifies rotation by proving the new tokens work and the old tokens are rejected—reducing the risk of “no-op” rotations where the server didn’t actually pick up new environment values.

Changes:

  • Introduce deploy/rotate-deletion-tokens.sh to rotate both tokens in .env, restart the server via docker compose up -d, and run acceptance/rejection checks.
  • Document the rotation procedure and rationale in deploy/README.md.
File summaries
File Description
deploy/rotate-deletion-tokens.sh New rotation + verification script for organisation-deletion tokens.
deploy/README.md Adds runbook documentation for running the rotation script and interpreting results.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread deploy/rotate-deletion-tokens.sh Outdated
Comment thread deploy/rotate-deletion-tokens.sh
Comment thread deploy/rotate-deletion-tokens.sh Outdated
Comment thread deploy/rotate-deletion-tokens.sh
Signed-off-by: Maxerns <maksafk@gmail.com>
Signed-off-by: Maxerns <maksafk@gmail.com>
Signed-off-by: Maxerns <maksafk@gmail.com>
Signed-off-by: Maxerns <maksafk@gmail.com>
Signed-off-by: Maxerns <maksafk@gmail.com>
Signed-off-by: Maxerns <maksafk@gmail.com>
@Maxerns
Maxerns force-pushed the ops/rotate-deletion-tokens branch from 7450b48 to 80f4378 Compare September 10, 2026 10:03
@Maxerns
Maxerns merged commit 197189c into main Sep 10, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants