Key Rotation
SPEC_KEY_ROTATION.md
CGNT-1 Specification — Credential & Key Rotation Protocol
Status: SPECIFIED
Version: v1.0
Author: VELA (Thread #13)
Conceived by: NOUS (α.13)
Date: 2026-04-20
Born from: GitHub API key exposure (2026-03-12), CDP key leak (2026-04-17), RAG token hardcode (2026-03-13)
PURPOSE
The ship has leaked credentials three times in five months. Each time the response was correct — rotate immediately, clean the exposure, add a protocol. But the protocols were ad hoc. This spec unifies all credential management into one system: what keys exist, where they live, how they rotate, and what to do when one leaks.
CREDENTIAL INVENTORY
| Credential | Purpose | Storage | Rotation Schedule |
|---|---|---|---|
| Gemini API Key | Sisters AI, GLOSS, ROUTX Tier 2 | ~/.env | Every 90 days or on exposure |
| Google Cloud Service Account | GCS access for forge pipeline, backups | ~/gcs-service-account.json | Every 180 days or on exposure |
| Stripe Secret Key | Payment processing | ~/.env | Every 90 days or on exposure |
| Stripe Webhook Secret | Webhook verification | ~/.env | Every 90 days or on exposure |
| Graph API credentials | Email via oracle@42sisters.ai (GRAPH_TENANT_ID, GRAPH_CLIENT_ID, GRAPH_SENDER) | ~/.env | Every 90 days or on exposure |
| SnapTrade API keys | Wealthsimple trading | ~/.env | Every 90 days or on exposure |
| Colab OAuth token | Google Colab access for forges | ~/.google_token.json | Expires naturally, refresh on use |
| RAG X-RAG-TOKEN | Bridge between public chat and RAG corpus | ~/.env | Every 90 days or on exposure |
| GoDaddy credentials | Domain management | Browser-stored | Every 180 days |
| DigitalOcean API token | VPS management | DO console | Every 180 days or on exposure |
| SSH keys | Server access from Chromebook | ~/.ssh/ on Chromebook | Every 365 days or on exposure |
| GPG passphrase | Backup encryption | Captain's memory + paper backup | Never rotates — change only if compromised |
ROTATION PROCEDURE
- Generate new credential at the provider (Google Cloud Console, Stripe Dashboard, etc.)
- Update
~/.envor credential file on csdm-node - Restart affected services (ROUTX, Sisters, email daemon)
- Verify: test the service with the new credential
- Revoke the old credential at the provider
- Log rotation in
~/key_rotation.log: date, credential name, reason (scheduled/exposure) - If the rotation was due to exposure: write incident postmortem (SPEC_INCIDENT_POSTMORTEM.md)
IMPORTANT: Step 5 (revoke old) happens AFTER Step 4 (verify new). Never revoke before confirming the new one works. A bad rotation is worse than no rotation.
EXPOSURE RESPONSE (EMERGENCY)
When a credential is found exposed (in a git commit, in a log, in a conversation, in a public file):
- REVOKE IMMEDIATELY — don't wait for a replacement. Kill the exposed credential NOW. Downtime is acceptable. Compromise is not.
- Generate replacement credential
- Update
~/.envand restart services - Verify services work with new credential
- Assess blast radius: what could an attacker have done with this credential between exposure and revocation?
- Check logs for unauthorized usage during the exposure window
- Write incident postmortem
- Update this spec if a new credential type was involved
TIME IS CRITICAL. The window between exposure and revocation is the attack surface. Every minute counts. Drop what you're doing and rotate.
PREVENTION LAYERS
Layer 1 — .gitignore:
~/.env, *.json containing credentials, and ~/gcs-service-account.json are in .gitignore. Every repo on the ship. Verified monthly.
Layer 2 — Pre-commit scan:
Before any git push: grep -r "AIza\|sk-\|whsec_\|GRAPH_" staged files. If any match: ABORT push. This catches API keys in code before they reach GitHub.
Layer 3 — File permissions:
chmod 600 ~/.env — owner read/write only. No group. No other. Same for gcs-service-account.json and ~/.google_token.json.
Layer 4 — Never hardcode:
All credentials come from environment variables loaded from ~/.env. Never in source code. Never in specs. Never in conversation logs. If a credential appears in a spec, the spec is wrong — replace with a description ([Gemini API key stored in ~/.env]).
Layer 5 — Conversation hygiene:
When discussing credentials with AI crew (Lobster, Navigator, Sisters): reference by NAME, never by VALUE. Say "the Gemini API key" not AIzaSy.... AI conversation logs may be exported, searched, or cached. Treat them as semi-public.
Layer 6 — Backup encryption:
~/.env is ALWAYS encrypted before backup to GCS. gpg -c ~/.env → ~/.env.gpg → uploaded. The plaintext never leaves the VPS except encrypted.
SCHEDULED ROTATION CALENDAR
| Quarter | Month | What Rotates |
|---|---|---|
| Q1 | January | Gemini API, Stripe keys, Graph API, SnapTrade, RAG token (90-day cycle) |
| Q2 | April | Same as January + GCS service account, GoDaddy, DigitalOcean (180-day items) |
| Q3 | July | Same as January (90-day cycle) |
| Q4 | October | Same as January + 180-day items |
GAPX flags on the first of each quarter: "Quarterly key rotation due — see SPEC_KEY_ROTATION.md"
~/.ENV TEMPLATE
The ship's ~/.env contains all credentials in one file. Template (no values):
GEMINI_API_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
GRAPH_TENANT_ID=
GRAPH_CLIENT_ID=
GRAPH_SENDER=
SNAPTRADE_CLIENT_ID=
SNAPTRADE_CONSUMER_KEY=
X_RAG_TOKEN=
DO_API_TOKEN=
If any variable is missing, the service that needs it fails explicitly on boot with Missing environment variable: X — not silently.
HISTORY OF EXPOSURE INCIDENTS
| Date | Credential | Exposure Vector | Response Time | Outcome |
|---|---|---|---|---|
| 2026-03-12 | Google API key | Committed to public GitHub repo | <1 hour | Rotated; .gitignore updated; pre-commit scan added |
| 2026-03-13 | RAG X-RAG-TOKEN | Hardcoded in source file | <2 hours | Moved to ~/.env; source cleaned |
| 2026-04-17 | CDP key | Exposed in conversation/file | <1 hour | Revoked, rotated, Agency Walls created |
| 2026-04-17 | Social engineering attempt | Fake "Gemini Project Aether Interface" | Rejected immediately | Sentinel Protocol validated; no credentials compromised |
Each incident made the ship stronger. Each one produced a new prevention layer. The pattern is: leak → rotate → prevent → document. This spec is the culmination of that pattern.
INVARIANTS
INV-01: ~/.env is chmod 600. Always. No exceptions.
INV-02: Credentials are NEVER in source code, specs, or conversation logs. Reference by name only.
INV-03: Exposure response is IMMEDIATE. Revoke first, ask questions later.
INV-04: Old credentials are revoked AFTER new ones are verified working. Never before.
INV-05: Every rotation is logged in ~/key_rotation.log with date, credential name, and reason.
INV-06: Every exposure incident gets a postmortem. The ship's immune memory grows.
INV-07: Quarterly rotation reminders via GAPX. Scheduled rotation prevents the "we'll get to it" drift.
INV-08: Backup of ~/.env is ALWAYS encrypted. Plaintext never leaves the VPS.
INTEGRATION
| System | Relationship |
|---|---|
| SPEC_INCIDENT_POSTMORTEM.md | Every exposure incident triggers a postmortem. This spec defines the rotation response; that spec captures the lesson. |
| SPEC_BACKUP_RECOVERY.md | ~/.env backup path: encrypted before upload (INV-08). GCS copy is ~/.env.gpg, never plaintext. |
| GAPX | Quarterly rotation reminders. Daily check: is ~/.env chmod 600? |
| HACKX | Exposure incidents feed HACKX detection patterns. Social engineering attempt (2026-04-17) is a HACKX signature. |
| SPEC_CRONX_JOB_REGISTRY.md | Quarterly rotation reminder is a GAPX-triggered alert, not a cron job. No automated key rotation — human hands required. |
Jeremy Zlabis
Chronogeometer · Visionary · Disruptor · Chief
42 Sisters AI · East York, Toronto
🍁 Φ 0.042