Skip to content

Commit 534bc56

Browse files
authored
Merge pull request #7199 from JSONbored/worktree-secret-rotation-policy
docs(security): publish a real secret-rotation policy and runbook
2 parents 25d5977 + 72205f4 commit 534bc56

1 file changed

Lines changed: 60 additions & 1 deletion

File tree

apps/loopover-ui/content/docs/self-hosting-security.mdx

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ description: The self-host stack holds maintainer credentials and policy. Keep t
2020
{
2121
title: "Rotate deliberately",
2222
description:
23-
"Rotate GitHub webhook secrets, API tokens, REES secrets, and provider keys with a restart window and validation PR.",
23+
"Cadence, per-secret procedure, and the categories that can't be rotated safely yet -- see \"Secret rotation policy\" below.",
2424
},
2525
]}
2626
/>
@@ -34,6 +34,65 @@ cp /path/to/your-downloaded-key.pem secrets/github_app_private_key.pem # the o
3434
docker compose up -d --no-deps loopover`}
3535
/>
3636

37+
## Secret rotation policy (#4925)
38+
39+
**Cadence:** rotate every secret at least once a year. **Rotate immediately**, regardless of
40+
cadence, on suspected compromise, a secret appearing in a log/commit/screenshot/PR even briefly,
41+
or the departure of anyone who held it.
42+
43+
Not every secret rotates the same way — some are a same-day config change, others require an
44+
external console, and one category currently has **no safe rotation path at all**. Check which
45+
category a secret falls into before rotating it.
46+
47+
<FeatureRow
48+
items={[
49+
{
50+
title: "Freely rotatable",
51+
description:
52+
"GITHUB_WEBHOOK_SECRET, LOOPOVER_API_TOKEN, LOOPOVER_MCP_TOKEN, INTERNAL_JOB_TOKEN, SELFHOST_SETUP_TOKEN, REES_SHARED_SECRET. Static bearer comparisons -- nothing is encrypted with them. Generate a new value, restart both sides. Only cost: updating callers holding the old value.",
53+
},
54+
{
55+
title: "Externally issued",
56+
description:
57+
"GITHUB_APP_PRIVATE_KEY, GITHUB_OAUTH_CLIENT_SECRET, PAGERDUTY_ROUTING_KEY, CLAUDE_CODE_OAUTH_TOKEN, and AI-provider API keys. Rotate at the source (GitHub App/OAuth settings, PagerDuty, `claude setup-token`, the provider's own console), then update the stored value.",
58+
},
59+
{
60+
title: "Encryption master keys -- do not rotate routinely",
61+
description:
62+
"TOKEN_ENCRYPTION_SECRET, DRAFT_TOKEN_ENCRYPTION_SECRET. Swapping either makes every row already encrypted under the old value permanently undecryptable -- there is no re-encryption tooling today. Only rotate on suspected compromise; see below.",
63+
},
64+
]}
65+
/>
66+
67+
<CodeBlock
68+
filename="shell"
69+
code={`# Freely rotatable secret -- self-host path
70+
rm secrets/loopover_api_token.txt && ./scripts/selfhost-init-secrets.sh # generates a fresh value
71+
docker compose up -d --no-deps loopover
72+
73+
# Freely rotatable secret -- hosted Worker path
74+
wrangler secret put LOOPOVER_API_TOKEN
75+
# then redeploy`}
76+
/>
77+
78+
<Callout variant="warn" title="Encryption master keys have no safe rotation path yet">
79+
`TOKEN_ENCRYPTION_SECRET` (maintainer BYOK AI-provider keys at rest) and
80+
`DRAFT_TOKEN_ENCRYPTION_SECRET` (in-flight contributor draft OAuth tokens) are AES-256-GCM keys
81+
every already-encrypted row was written against. The decrypt path always derives the AES key
82+
from whatever value is currently set, with no fallback to a prior value -- there is no
83+
re-encryption tooling in this codebase today. Rotating `TOKEN_ENCRYPTION_SECRET` means every
84+
maintainer who saved a BYOK provider key has to re-enter it; rotating
85+
`DRAFT_TOKEN_ENCRYPTION_SECRET` fails any draft submission mid-flight. Only rotate these two on
86+
suspected compromise, and treat the resulting key/token loss as an accepted, unavoidable cost
87+
until a re-encrypt-in-place migration exists (tracked as separate follow-up work).
88+
</Callout>
89+
90+
`ORB_ENROLLMENT_SECRET` has the same gap from the other direction: `orb_enrollments.revoked_at` is
91+
checked on every brokered-token/relay request, but nothing in this codebase currently sets it —
92+
there is no revoke or re-enrollment endpoint yet. Treat a brokered self-host's enrollment secret as
93+
not currently rotatable in place; escalate a suspected-compromised enrollment to the Orb operator
94+
directly instead of expecting self-service rotation.
95+
3796
## Optional: Infisical secrets management
3897

3998
The hardened default above — `.env` plus Docker Compose `secrets:` — has no rotation, audit trail, or RBAC. If you want secrets-manager-grade rotation, audit logging, and access control on top of that default, you can opt into [Infisical](https://infisical.com) — an open-source, self-hostable secrets manager. This is **strictly optional and additive**: skip this section entirely and the hardened `.env`/Docker secrets default keeps working unchanged.

0 commit comments

Comments
 (0)