Skip to content

SOP-102 — Slack credential rotation (signing secret + OAuth token)

Guide

<!-- last-reviewed: 2026-06-02 · owner: Security & Privacy (Security Director) -->

SOP-102 — Slack credential rotation (signing secret + OAuth token)

SOP / runbook · owner: Security & Privacy (Security Director) (content + policy); Operations (Process Engineer) (Vercel/CI wiring); Principal (executes the Slack admin + Vercel steps). Layer: SOP/Runbook (docs/canon.md). The first-response runbook for rotating SLACK_SIGNING_SECRET and SLACK_OAUTH_TOKEN after exposure. Frame: SOP-083 (incident runbook), SOP-004 (docs/sop/SOP-004-secrets-management.md), hr/policies/security.md.

Trigger context (the P0 this closes)

On 2026-06-02 EOD (UTC canonical; ET shown), SLACK_OAUTH_TOKEN and SLACK_SIGNING_SECRET were exposed in a transcript. Both are managed-store secrets ({{project-slug}}-api/api/slack/events.ts requires them). Per SOP-083 this is SEV-2 (credential of known exposure on a live external surface) — and because SLACK_SIGNING_SECRET is the only authenticator on the no-platform-auth {{project-slug}}-api Slack endpoint, an attacker holding it can forge HMAC-signed events; SLACK_OAUTH_TOKEN lets them call the Slack Web API as the app. Contain first: rotate now, do not wait for a report cycle.

Interface Contract

  • Inputs: exposed-Slack-credential signal (P0, 2026-06-02 EOD) ← from <any agent / Security Analyst / monitoring> · trigger: review requested
  • Outputs: env-update requirement (rotated values into Vercel, both projects) → to Engineering Director · trigger: requirements issued
  • Outputs: rotation incident report + secret-register date update → to Archivist · trigger: artifact approved
  • Outputs: runbook / posture change → to Documentarian · trigger: document changed
  • Outputs: escalation (active credential exposure) → to Principal · trigger: (exempt — Principal edge, interface-contract-spec.md §3.1 R2)
  • Handoff trigger (in): a Slack credential is reported or detected exposed
  • Handoff trigger (out): both credentials revoked + rotated, Vercel updated on both projects, webhook re-verified, repo confirmed secret-free, report archived
  • Definition of Done before handoff:
    • [ ] SEV-2 confirmed and Principal notified immediately (SOP-083)
    • [ ] Both credentials revoked at Slack first, then rotated (SOP-004 — revoke before replace)
    • [ ] Rotated values set in Vercel for both projects (intranet + {{project-slug}}-api); old values purged
    • [ ] Webhook re-verified (Slack URL-verification challenge → 200; a signed test event ingests)
    • [ ] Repo confirmed free of any committed Slack secret value (grep evidence below)
    • [ ] Secret-register date updated (SOP-004 §register); dated report handed to Archivist (artifact approved)
    • [ ] Learning captured to docs/knowledge/security/

Triggers are drawn verbatim from docs/handoff-triggers.md (review requested, requirements issued, artifact approved, document changed). The in-edge reuses review requested per the registry’s explicit disposition of incident detected (“reuse, do not add”; docs/handoff-triggers.md §“Not registered (no live edge)”). No new trigger is required for this SOP. The Principal escalation edge is exempt (§3.1 R2).

Procedure (the Principal executes; Security commands the incident)

1. Contain — revoke at Slack first (do not just edit env)

  1. Open the Slack app config: api.slack.com/apps → [the {{PROJECT_NAME}} app] → Basic Information.
  2. Signing Secret: Basic Information → App Credentials → Signing Secret → Regenerate. Regenerating invalidates the exposed secret immediately. Copy the new value.
  3. OAuth token: OAuth & Permissions → revoke/rotate the bot token. Reinstall the app to the workspace if revocation requires re-auth; copy the new xoxb-… bot token. (Revoking invalidates the exposed SLACK_OAUTH_TOKEN — the goal of SOP-004 “revoke first.”)
  4. Do not paste either new value into a chat, PR, issue, doc, or log (charter rule 3; hr/policies/security.md). They go straight into the managed store (step 2).

2. Update the managed store — Vercel env, BOTH projects

The Slack secrets are consumed by code in two Vercel projects; update both or the webhook breaks / stays compromised:

  1. {{project-slug}}-api project ({{project-slug}}-api/api/slack/events.ts — the live webhook): Vercel → [{{project-slug}}-api project] → Settings → Environment Variables. Set SLACK_SIGNING_SECRET and SLACK_OAUTH_TOKEN to the new values (Production + Preview as scoped). Delete the old entries (do not leave a stale value).
  2. intranet project (intranet/src/lib/slack.ts uses SLACK_OAUTH_TOKEN): Vercel → [intranet project] → Settings → Environment Variables. Update SLACK_OAUTH_TOKEN to the new bot token. (The intranet does not verify inbound HMAC, so it does not hold the signing secret unless present — update it only where set.)
  3. Redeploy each project so the running functions pick up the new env (env changes apply on next deploy). Respect GR-7 — this is a deliberate security checkpoint, batching is fine.

3. Re-verify the webhook

  1. In Slack app config → Event Subscriptions, click Retry / re-verify the Request URL (…/api/slack/events). Expect the URL-verification challenge to return 200 with the echoed challenge (handler lines 159–161).
  2. Send a benign test message in a non-blocklisted channel; confirm it appears in the intranet chatter feed (HMAC now validates against the new signing secret — old-secret forgeries now fail 403, which is the point).
  3. If verification fails: the new signing secret in Vercel does not match Slack — re-copy and redeploy. Do not roll back to the exposed secret.

4. Confirm no Slack secret value is committed in the repo

Run (read-only; evidence for the incident report):

grep -rnIE "(xox[bpas]-[0-9A-Za-z-]{10,}|SLACK_[A-Z_]+\s*[:=]\s*['\"][0-9a-fA-F]{16,})" . \
  --include="*.ts" --include="*.js" --include="*.json" --include="*.md" \
  --include="*.env*" --include="*.yml" --include="*.yaml" | grep -v node_modules

Expect no matches for any literal token/secret value — only process.env.SLACK_* references (env-var names) are acceptable. Confirm .env / .env.* are gitignored and no .env file is tracked (git ls-files | grep '\.env' should show only .env.example, which must hold empty placeholders, never a value). See the 2026-06-02 scan result recorded in the rotation report.

5. Document, register, archive

  1. Update the secret register (SOP-004 §register) with the rotation date — never the value.
  2. Issue the env-update requirement to Engineering if any code/config change is needed (requirements issued).
  3. File a dated rotation report (timeline · what was exposed · containment · re-verify result · residual) to the Archivist (artifact approved); capture the learning to docs/knowledge/security/.

Roles / approvers

Security Director = incident commander. Principal executes the Slack-admin + Vercel steps (the credentials are the Principal’s to rotate) and is notified immediately (SOP-083, SEV-2). Engineering / Process Engineer handle any env/CI wiring. Security Analyst verifies the repo-clean scan (SOP-084). Archivist preserves the report.

Registry note (for Operations — do NOT self-edit the ledger)

This SOP needs a row in docs/sop/registry.md. Proposed row for Operations to add:

| SOP-102 | Slack credential rotation (signing secret + OAuth token) | Security & Privacy (Security Director) | docs/sop/SOP-102-slack-credential-rotation.md |

102 is in the Security band’s natural next slot beyond 081–084; Operations is the numbering authority and assigns/confirms the ID. No new handoff trigger is required — the Interface Contract above reuses registered phrases only.

Guardrails

  • Revoke before replace (SOP-004) — regenerating at Slack invalidates the exposed value; do not merely overwrite Vercel and leave the old secret valid.
  • Never paste a secret into a chat/PR/doc/log — straight to the managed store (hr/policies/security.md, charter rule 3).
  • Both projects — a half-rotation ({{project-slug}}-api updated, intranet stale, or vice-versa) leaves an exposed credential live.
  • Verify before done — the webhook must re-verify and the repo must scan clean before the incident is closed.

See also: SOP-083 (incident runbook) · SOP-004 (secrets management) · docs/security/threat-models/slack-neon.md (T5) · hr/policies/security.md · {{project-slug}}-api/api/slack/events.ts · intranet/src/lib/slack.ts · docs/handoff-triggers.md.