Playbook: DISCORD_BOT_TOKEN Rotation

Superseded — the canonical, more complete playbook is rotate-discord-bot-token.md (adds GH Secret rotation, /api/v10/applications/@me verification, n8n restart, and the browser-close security step). Prefer that one; this file is retained for backward-compatible links.

Service: Discord Developer Portal (https://discord.com/developers/applications) Secret: DISCORD_BOT_TOKEN SOPS file: secrets/monitoring.env.sops Rotation frequency: 90 days (or immediately on suspected exposure) Last rotated: 2026-06-27 Next due: 2026-09-27


What uses this credential

ConsumerHowEffect if missing
n8n workflows on bms-4HTTP Request nodes posting to Discord via Bot APIBot messages fail; webhook fallback may still work
Monitoring alert automationDiscord bot posts structured embeds to alert channelsAlert embeds stop; only raw webhook messages remain

Note: Discord webhook URLs (P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL, etc.) are separate from the bot token. Webhooks do not require the bot token and are unaffected by this rotation.


Where stored

LocationKey nameHow to update
secrets/monitoring.env.sopsDISCORD_BOT_TOKENSOPS write pattern
vps-i1 deployed env/opt/p24-infra/monitoring/.envAuto-synced by secrets-sync.yml on merge

Login credentials

  • URL: https://discord.com/developers/applications
  • Email: radieu@gmail.com
  • Password: stored in discord_radieu_password key in secrets/monitoring.env.sops
    • Safe extraction: (sops --decrypt ... secrets\monitoring.env.sops | Select-String "^discord_radieu_password=").ToString().Split('=',2)[1]

Automation status

Playwright-automatable — Discord Developer Portal bot token reset is accessible via web UI. No additional MFA required after login (2FA may be on the account; check if TOTP is needed or if the session stays active).


Rotation steps

Option A — Playwright agent (preferred)

You are rotating DISCORD_BOT_TOKEN for the Discord bot used by p24-infra automation.

IMPORTANT: Never display any secret value in your response. Reference key names only.

1. Read login credentials safely (never print values):
   $env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
   $env:DISCORD_PASS = (sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops |
       Select-String "^discord_radieu_password=").ToString().Split('=',2)[1].Trim()

2. Use Playwright to navigate to https://discord.com/login
   - Email: radieu@gmail.com
   - Password: $env:DISCORD_PASS (pass as fill value, never log it)
   - If 2FA is prompted, check .env.local for DISCORD_TOTP_SECRET or ask user for OTP
   - Navigate to https://discord.com/developers/applications
   - Find the p24-infra bot application (look for "p24" or "infra" in app names)
   - Click the application → Bot (left sidebar)
   - Click "Reset Token" → confirm → copy new token into $env:NEW_TOKEN (never print)
   - Close the browser
   $env:DISCORD_PASS = ""

3. Update secrets/monitoring.env.sops:
   $plain = sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops
   $temp = "$PWD\secrets\monitoring-edit.env.sops"
   $updated = $plain -replace "^DISCORD_BOT_TOKEN=.*", "DISCORD_BOT_TOKEN=$env:NEW_TOKEN"
   [System.IO.File]::WriteAllText($temp, ($updated -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
   $enc = sops --encrypt --input-type dotenv --output-type dotenv $temp
   [System.IO.File]::WriteAllText("$PWD\secrets\monitoring.env.sops", ($enc -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
   sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops | Out-Null
   if ($LASTEXITCODE -ne 0) { throw "SOPS corrupt — do NOT commit" }
   [System.IO.File]::Delete($temp); $env:NEW_TOKEN = ""; $env:SOPS_AGE_KEY_FILE = ""

4. Commit and PR targeting main:
   git checkout -b fix/rotate-discord-bot-token origin/main
   git add secrets/monitoring.env.sops
   git commit -m "chore: rotate DISCORD_BOT_TOKEN (scheduled 90d rotation)"
   git push -u origin fix/rotate-discord-bot-token
   gh pr create --base main --title "chore: rotate DISCORD_BOT_TOKEN"
   gh pr merge --merge --delete-branch

5. Append to docs/secrets-rotation-log.md:
   | <YYYY-MM-DD HH:MM UTC> | — | DISCORD_BOT_TOKEN | scheduled 90d rotation | AI-agent | SOPS (monitoring) |

Option B — Manual (fallback)

Step 1 — Log in to Discord Developer Portal
  URL: https://discord.com/developers/applications
  Email: radieu@gmail.com
  Password: see discord_radieu_password in secrets/monitoring.env.sops
  Complete 2FA if prompted

Step 2 — Reset bot token
  → Select the p24-infra bot application
  → Bot → Reset Token → confirm
  → Copy the new token (shown once; starts with long string containing two periods)

Step 3 — Store in .env.local then hand off to Claude:
  Add to d:\code_2026\p24-infra\.env.local:
    DISCORD_BOT_TOKEN_NEW=<value>
  Tell Claude "Discord bot token updated in .env.local" — Claude handles SOPS + PR.

Verification

$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$line = sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops |
    Select-String "^DISCORD_BOT_TOKEN="
$val = $line.ToString().Split('=',2)[1]
Write-Host "DISCORD_BOT_TOKEN length: $($val.Length)"   # expected: > 50
 
# Quick API check (bot identity — no messages sent)
$env:T = $val
$r = Invoke-RestMethod "https://discord.com/api/v10/users/@me" `
    -Headers @{ Authorization = "Bot $env:T" }
Write-Host "Discord bot: $($r.username)#$($r.discriminator)"
$env:T = ""; $env:SOPS_AGE_KEY_FILE = ""

Escalation

SymptomAction
Discord login requires 2FA codeCheck .env.local for DISCORD_TOTP_SECRET; if absent, user must provide OTP (Option B)
Developer Portal shows no applicationsConfirm you are logged in as radieu@gmail.com, not a different account
”Reset Token” button is greyed outBot may require privileged intents to be confirmed — check Bot settings
vps-i1 automation still fails after mergeCheck secrets-sync.yml ran; or SSH to vps-i1 and restart affected container

Prevention

  • Discord bot tokens do not expire but can be compromised; rotate on schedule.
  • The credential-rotation.yml GH Actions workflow (Monday 06:00 UTC) checks next_due and opens a human-action issue.
  • After rotation, update Last rotated and Next due at the top of this playbook.

Audit Log — Log to infra_operations

After this operation completes, log it to the infra_operations audit table.

Python (Linux server — bms-4, vps-i1, vps-h1, or similar):

import sys
sys.path.insert(0, '/opt/p24-infra')
from scripts.lib.log_op import log_op
 
log_op(
    actor="claude",  # "radieu" for manual human ops, "claude" for agent
    op_type="credential_rotation",
    resource="DISCORD_BOT_TOKEN",
    result="success",  # "success" | "failed" | "skipped"
    detail="Scheduled rotation — new Discord bot token regenerated and SOPS updated",
    env="bms-4",
    gh_issue=2730,
)

PowerShell (Windows dev machine):

$env:SUPABASE_URL = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_URL=").ToString().Split("=",2)[1].Trim()
$env:SUPABASE_SERVICE_KEY = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_SERVICE_KEY=").ToString().Split("=",2)[1].Trim()
python -c "
import os, sys
sys.path.insert(0, 'C:/code_2026/p24-infra')
from scripts.lib.log_op import log_op
log_op('claude', 'credential_rotation', 'DISCORD_BOT_TOKEN', 'success', 'Scheduled rotation — new Discord bot token regenerated and SOPS updated', 'bms-4')
"
$env:SUPABASE_URL = ''; $env:SUPABASE_SERVICE_KEY = ''