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

ConsumerHowEffect if missing
n8n MCP integration on bms-4MCP token for external tool access to n8n APIMCP-connected tools cannot call n8n workflows
CI/CD via GH SecretHSTGR_N8N_MCP_TOKEN used in secrets-sync for env injectionIf 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

LocationKey nameHow to update
GH Secret (only current location)HSTGR_N8N_MCP_TOKENgh secret set HSTGR_N8N_MCP_TOKEN --repo radieu/p24-infra
secrets/n8n-bms4.env.sopsHSTGR_N8N_MCP_TOKENSOPS write pattern (add this on first rotation)
bms-4 deployed env/opt/p24-infra/bms-4/.envAuto-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_PASSWORD in secrets/n8n-bms4.env.sops
    • Safe extraction: (sops --decrypt ... secrets\n8n-bms4.env.sops | Select-String "^N8N_ADMIN_PASSWORD=").ToString().Split('=',2)[1]

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

SymptomAction
n8n Settings → API page not foundCheck n8n version — API feature requires n8n 0.214+. Check /settings/api path
Login failsVerify N8N_ADMIN_EMAIL + N8N_ADMIN_PASSWORD in secrets/n8n-bms4.env.sops are current
Token still rejected after rotationsecrets-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 rotationReconnect the MCP client (Claude Desktop or other) — provide new token in MCP server config

Prevention

  • After this rotation, HSTGR_N8N_MCP_TOKEN will be in SOPS and secrets-sync will keep bms-4 in sync automatically.
  • 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="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 = ''