Playbook: HSTGR_N8N_MCP_TOKEN Rotation
Service: n8n on bms-4 (https://n8n.bms-4.infra.zintegrowana.online)
Secret: HSTGR_N8N_MCP_TOKEN
SOPS file: secrets/n8n-bms4.env.sops (must be added here after first rotation — currently GH Secret only)
Rotation frequency: 90 days (or immediately on suspected exposure)
Last rotated: Unknown (pre-SOPS era)
Next due: Rotate ASAP then set 90d schedule
Name note: The
HSTGR_prefix is historical (“Hostinger”) from when n8n was on vps-h1. n8n migrated to bms-4 (OVH). The token and name remain the same.
What uses this credential
| Consumer | How | Effect if missing |
|---|---|---|
| n8n MCP integration on bms-4 | MCP token for external tool access to n8n API | MCP-connected tools cannot call n8n workflows |
| CI/CD via GH Secret | HSTGR_N8N_MCP_TOKEN used in secrets-sync for env injection | If not in SOPS, env file on bms-4 won’t have it |
Current state (2026-06-27): Token exists only as a GH Secret — it is NOT in
n8n-bms4.env.sops. After the first rotation using this playbook, add it to the SOPS file so future rotations and secrets-sync can manage it properly.
Where stored
| Location | Key name | How to update |
|---|---|---|
| GH Secret (only current location) | HSTGR_N8N_MCP_TOKEN | gh secret set HSTGR_N8N_MCP_TOKEN --repo radieu/p24-infra |
secrets/n8n-bms4.env.sops | HSTGR_N8N_MCP_TOKEN | SOPS write pattern (add this on first rotation) |
| bms-4 deployed env | /opt/p24-infra/bms-4/.env | Auto-synced by secrets-sync.yml after SOPS entry exists |
Login credentials
- n8n UI:
https://n8n.bms-4.infra.zintegrowana.online - Admin credentials: from
N8N_ADMIN_EMAIL+N8N_ADMIN_PASSWORDinsecrets/n8n-bms4.env.sops- Safe extraction:
(sops --decrypt ... secrets\n8n-bms4.env.sops | Select-String "^N8N_ADMIN_PASSWORD=").ToString().Split('=',2)[1]
- Safe extraction:
Automation status
Playwright-automatable — n8n Settings → API page is accessible without additional MFA.
Rotation steps
Option A — Playwright agent (preferred)
You are rotating HSTGR_N8N_MCP_TOKEN — the n8n MCP API token on bms-4.
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:N8N_PASS = (sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops |
Select-String "^N8N_ADMIN_PASSWORD=").ToString().Split('=',2)[1].Trim()
$env:N8N_EMAIL = (sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops |
Select-String "^N8N_ADMIN_EMAIL=").ToString().Split('=',2)[1].Trim()
2. Use Playwright to log in to n8n:
- Navigate to https://n8n.bms-4.infra.zintegrowana.online
- Fill email: $env:N8N_EMAIL, password: $env:N8N_PASS (pass as fill value, never log)
- Navigate to Settings → API (left sidebar)
- If an existing API key is shown, delete it (click trash icon → confirm)
- Click "Create an API key" or "Generate API key"
- Copy the new token into $env:NEW_TOKEN (never print)
$env:N8N_PASS = ""; $env:N8N_EMAIL = ""
3. Update GH Secret immediately:
Write-Output $env:NEW_TOKEN | gh secret set HSTGR_N8N_MCP_TOKEN --repo radieu/p24-infra
4. Add to secrets/n8n-bms4.env.sops (first-time addition):
$plain = sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops
$temp = "$PWD\secrets\n8n-bms4-edit.env.sops"
# Check if key already exists
if ($plain | Select-String "^HSTGR_N8N_MCP_TOKEN=") {
$updated = $plain -replace "^HSTGR_N8N_MCP_TOKEN=.*", "HSTGR_N8N_MCP_TOKEN=$env:NEW_TOKEN"
} else {
$updated = $plain + "HSTGR_N8N_MCP_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\n8n-bms4.env.sops", ($enc -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops | Out-Null
if ($LASTEXITCODE -ne 0) { throw "SOPS corrupt — do NOT commit" }
[System.IO.File]::Delete($temp)
$env:NEW_TOKEN = ""
5. Commit and PR:
git checkout -b fix/rotate-hstgr-n8n-mcp-token origin/main
git add secrets/n8n-bms4.env.sops
git commit -m "chore: rotate HSTGR_N8N_MCP_TOKEN + add to SOPS (issue #1654)"
git push -u origin fix/rotate-hstgr-n8n-mcp-token
gh pr create --base main --title "chore: rotate HSTGR_N8N_MCP_TOKEN"
gh pr merge --merge --delete-branch
6. Append to docs/secrets-rotation-log.md:
| <YYYY-MM-DD> | HSTGR_N8N_MCP_TOKEN | scheduled rotation + SOPS migration | AI-agent | GH Secret + SOPS (n8n-bms4) |
Option B — Manual (fallback)
Step 1 — Log in to n8n on bms-4
URL: https://n8n.bms-4.infra.zintegrowana.online
Credentials: N8N_ADMIN_EMAIL + N8N_ADMIN_PASSWORD from secrets/n8n-bms4.env.sops
Step 2 — Rotate API key
→ Settings (gear icon, bottom-left) → API
→ Delete existing API key → Create a new one
→ Copy the new token (shown once)
Step 3 — Store in .env.local then hand off to Claude:
Add to d:\code_2026\p24-infra\.env.local:
HSTGR_N8N_MCP_TOKEN_NEW=<value>
Tell Claude "n8n MCP token updated in .env.local" — Claude handles SOPS + GH Secret + PR.
Verification
# Check GH Secret is set
gh secret list --repo radieu/p24-infra | Select-String "HSTGR_N8N_MCP_TOKEN"
# Expected output: HSTGR_N8N_MCP_TOKEN Updated <date>
# Verify SOPS entry exists (after first rotation adds it)
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$line = sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops |
Select-String "^HSTGR_N8N_MCP_TOKEN="
Write-Host "SOPS entry present: $($line -ne $null)"
$env:SOPS_AGE_KEY_FILE = ""
# Test token against n8n API
$env:T = "..." # from SOPS — never hardcode
$r = Invoke-RestMethod "https://n8n.bms-4.infra.zintegrowana.online/api/v1/workflows" `
-Headers @{ "X-N8N-API-KEY" = $env:T } -StatusCodeVariable sc
Write-Host "n8n API status: $sc — workflows returned: $($r.data.Count)"
$env:T = ""Escalation
| Symptom | Action |
|---|---|
| n8n Settings → API page not found | Check n8n version — API feature requires n8n 0.214+. Check /settings/api path |
| Login fails | Verify N8N_ADMIN_EMAIL + N8N_ADMIN_PASSWORD in secrets/n8n-bms4.env.sops are current |
| Token still rejected after rotation | secrets-sync may not have deployed yet; re-run workflow or manually update /opt/p24-infra/bms-4/.env on bms-4 |
| MCP tools lose connection after rotation | Reconnect the MCP client (Claude Desktop or other) — provide new token in MCP server config |
Prevention
- After this rotation,
HSTGR_N8N_MCP_TOKENwill be in SOPS and secrets-sync will keep bms-4 in sync automatically. - The
credential-rotation.ymlGH Actions workflow (Monday 06:00 UTC) checksnext_dueand opens ahuman-actionissue. - 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="HSTGR_N8N_MCP_TOKEN",
result="success", # "success" | "failed" | "skipped"
detail="Scheduled rotation — Hostinger n8n MCP 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', 'HSTGR_N8N_MCP_TOKEN', 'success', 'Scheduled rotation — Hostinger n8n MCP token regenerated and SOPS updated', 'bms-4')
"
$env:SUPABASE_URL = ''; $env:SUPABASE_SERVICE_KEY = ''