Plan #4033 — n8n Supabase-Vault runtime secrets: empirical discovery addendum to #4032

Issue: #4033[PLAN] n8n Supabase-coupled secrets → Supabase Vault runtime Type: Code-change-design (plan only) — discovery addendum, not a competing architecture Status: Draft — iteration 1 Last updated: 2026-07-12 Author: Claude Opus 4.8 worker (i1-cw-1, vps-i1) Sibling / supersedes-questions-in: #4032 (CLOSED — plan on main: docs/plans/plan-4032-n8n-supabase-vault-runtime-secrets.md) Related: #1713 (ATRAX rotation, human-action), #4021 (invalid_grant incident), #3941/#3942 (fleet-update failure alerts), #3826 (Pinbox24 CF Worker broker — parked FUTURE), #4008 (n8n singleton-lock design-pattern sibling)


0. Why this document exists (and why it is not a second full plan)

#4033 and #4032 are duplicate plan issues filed within the same window on the identical topic (n8n reads Supabase-coupled leaf-secrets from vault.decrypted_secrets at runtime). #4032 is CLOSED and its plan is merged. Re-deriving the Model-A-vs-B architecture here would be pure duplication — that decision stands as recorded in #4032.

What #4033 adds, and the only reason it earns a separate artifact, is empirical discovery against the actual repository. #4032 built its inventory from the issue-body’s live-n8n listing and left the ground truth as open questions (its §6.3 Q1: “Are the ATRAX workflow JSON files versioned in-repo?”). They are — n8n-workflows/*.json holds 68 exported workflows including every ATRAX one. Reading them directly overturns three load-bearing claims in #4032 and surfaces a security finding #4032 never saw. This document is the discovery layer; #4032 remains the architecture.

Bottom line for a reviewer: keep #4032’s target design (dedicated least-privilege Postgres role, Vault-runtime reads, remove-from-SOPS-after-cutover). Apply the four corrections in §2 before any implementation issue is spawned, and treat §3 (plaintext-in-git) as a P1 that gates the whole thing.


1. Discovery method (reproducible, read-only, no secret values)

All findings below come from static analysis of the committed exports in n8n-workflows/, not from the live n8n API. Commands (safe — they read node types and $env key names, never values):

# env-key readers
for f in n8n-workflows/*.json; do
  grep -oE '\$env\.[A-Za-z0-9_]+' "$f" | sort -u | sed "s/^/$f: /"
done
# Supabase coupling = our project ref OR a supabase/postgres node
grep -l 'mwkqmgadqnkkihjdeqsi' n8n-workflows/*.json
# hardcoded OAuth password literals (count only — never print the match)
grep -c '"name": "password"' n8n-workflows/<file>.json

Caveat — repo export vs live. These JSONs are point-in-time exports (committed under merge f49df18). The live workflow on bms-4 may have diverged. Every §2/§3 conclusion that depends on “how the node reads the secret” must be re-confirmed against the live n8n API before the implementation issue acts on it. That confirmation is a server/n8n-admin operation (bms-4), out of scope for a dev-issue worker — flagged as the first implementation-phase gate.


2. Four corrections to #4032

2.1 CORRECTION — the two failing production workflows do NOT read $env; they hard-code the OAuth grant

#4032 §2.3 lists atrax, kravag-scheduled-fleet-updates (CCx9UMdphmGficDX) as reading ATRAX_USERNAME/PASSWORD/CLIENT_ID/CLIENT_SECRET via $env. It is not so in the export: the get_token node (POST https://tronik.atrax4.com/oauth/token, form-urlencoded) carries the grant_type=password body with the username, password, client_id and client_secret as literal bodyParameters values — no $env, no n8n credential. Same for fleet-update-v2-batch (AJ1px9uHIfbsriof) and atrax-daily-stats-collector (Wi3ZJcPw0YbxLXwa).

Consequence: for these workflows, rotating ATRAX_PASSWORD in SOPS does nothing — the SOPS value is not on their code path. If the live workflows also hard-code (to be confirmed per §1 caveat), then the #4021 invalid_grant on these two cannot be fixed by any SOPS/secrets-sync action; it is fixed only by editing the literal (short-term) or by the Vault migration (target). This is the single most important operational correction in this document.

2.2 CORRECTION — fleet-update-v2-batch must be IN scope

#4032 §2.3 explicitly excludes fleet-update-v2-batch: “…do not read $env for ATRAX creds and are not in scope for the Vault rewrite.” But fleet-update-v2-batch is one of the two workflows named in the #4033 incident (the other being CCx9UMdphmGficDX). Excluding it means the plan does not fix half of the reported failure. It is Supabase-coupled (writes to mwkqmgadqnkkihjdeqsi) and therefore qualifies for Vault under #4032’s own rule. It belongs in scope. (#4032’s title even advertises “ATRAX + fleet-update” — the exclusion contradicts the title.)

2.3 ANSWER — #4032 §6.3 open-question 1: yes, the workflow JSONs are versioned in-repo

They live in n8n-workflows/ and are git-tracked. So the implementation is JSON-edit-in-repo + deploy, not “manual n8n UI change captured in an ops doc” (the fallback #4032 hedged toward). This also means the exports are a git-history artifact — see §3.

2.4 EXPANDED — full candidate inventory (supersedes #4032 §2.3’s 3-row table)

Supabase-coupled and reading an ATRAX/OAuth secret (the migration set), with how each sources the secret today (export ground-truth; key names only):

WorkflowIDSupabase-coupledSecret source (export)Notes
atrax, kravag-scheduled-fleet-updatesCCx9UMdphmGficDXyeshard-coded OAuth literalPROD, failing #4021
fleet-update-v2-batchAJ1px9uHIfbsriofyeshard-coded OAuth literalPROD, failing #4021 — #4032 wrongly excluded
atrax-daily-stats-collectorWi3ZJcPw0YbxLXwayeshard-coded OAuth literaldaily
atrax_kravag…_copyUA7WPM1fVRVcaEyAyesmixed: $env ATRAX_USERNAME/PASSWORD and a hard-coded nodebackup copy; delete or migrate, do not leave dual
atrax-drivers-daily-snapshotakgnvueDkDPbLJ49yes$env ATRAX_USERNAME, ATRAX_PASSWORDmatches #4032
atrax-ecodriving-dailyzdzyIQ8UBsDAF0wIyes$env ATRAX_USERNAME, ATRAX_PASSWORDmatches #4032
atrax-fleet-updates-devzTxZ7cIpJJ5uk4SGyes$env ATRAX_USERNAME, ATRAX_PASSWORDdev — migrate last, use as canary
Tronik_GPS_-_Kierowcy_720AEMvkxDCUsvVMFYUyes$env CLAUDE_PROXY_SECRETNOT ATRAX; CLAUDE_PROXY was removed 2026-06-24 — stale, exclude/repair separately

Secret-readers that are NOT Supabase-coupled → stay on SOPS (adding a Vault read here would introduce a new Supabase dependency — the exact anti-pattern the design forbids):

atrax-report-generator-webhook (N8N_ATRAX_REPORT_SECRET — webhook shared secret, not the OAuth creds), gh-actions-monitor (GH_TOKEN), github-auto-trigger (GITHUB_TRIGGER_SECRET), hu-sp-report-email ×2 (N8N_HU_SP_REPORT_SECRET), infra-alert-llm-classifier (CLAUDE_PROXY_SECRET, GROQ_API_KEY, WAHA_TELEGRAM_CHAT_ID), linkedin-post (BRAND_WEBHOOK_SECRET, LINKEDIN_ACCESS_TOKEN), linkedin-token-rotation-alert (DISCORD_WEBHOOK_URL), telegram-infra-alerts (WAHA_TELEGRAM_CHAT_ID), WAHA_Telegram_Handler (TELEGRAM_BOT_TOKEN, WAHA_CONTROL_TOKEN).


3. SECURITY FINDING (P1) — plaintext ATRAX credentials committed to the repo

The get_token nodes in the hard-coded workflows above embed the ATRAX password and OAuth client_secret as cleartext bodyParameters values. These files are git-tracked and the literals are present in the committed HEAD (git show HEAD:n8n-workflows/fleet-update-v2-batch_*.json | grep -c '"name": "password"' → 1) and therefore in git history. This is a plaintext-secret-in-VCS leak, independent of the SOPS story.

This finding did not exist in #4032 (it assumed all reads were clean $env). Remediation is not optional and not a dev-issue action — it requires:

  1. Rotate the ATRAX password + ATRAX_CLIENT_SECRET at the ATRAX portal so the leaked values are worthless — this is the already-open [#1713] (secret-manager / human-action). The leak makes #1713 urgent, not merely overdue.
  2. Scrub the literals from the working tree (replace with a Vault-read node per §4/#4032 target, or with $env as an interim) — coordinated with the n8n live edit so production does not break.
  3. Purge history (BFG / git filter-repo) — a force-push + coordination operation; human + secret-manager, never a background dev-issue worker.

Per the repo secret-handling rule (a value appeared in a tool result during discovery), this is tracked to the rotation path in [#1713]; follow docs/playbooks/static-api-key-incident-rotation.md. No credential value is reproduced in this document or any committed file.

Sequencing implication: rotation (step 1) must happen regardless of the Vault migration. Do it first — it independently clears 3942 the moment the workflows pick up the new value (via whichever path they actually use — confirm live per §1). The Vault migration then makes the next rotation a one-line UPDATE.


4. Impact on #4032’s migration mechanic (small, compatible)

#4032 §3.4 assumes the cutover is “repoint {{ $env.ATRAX_* }}{{ $node['Read ATRAX Vault']…}}”. For the hard-coded workflows the step is instead “replace the literal bodyParameter value → {{ $node['Read ATRAX Vault'].json.atrax_* }}”. Same target node, same Vault read, same least-privilege role — only the before-state differs. #4032’s target design (§3.1 role, §3.2 Vault rows, §3.3 bootstrap ATRAX_VAULT_PG_CONN in secrets/n8n-bms4.env.sops, §3.4 Postgres node) is adopted unchanged. The migration set expands from #4032’s 3 workflows to the 6 in-scope rows of §2.4 (exclude the dev canary until last; exclude the stale Tronik…CLAUDE_PROXY case entirely).

Proven precedent (strengthens confidence): n8n-workflows/branding/oauth-token-refresh.json already reads/writes Vault at runtime from n8n Postgres nodes — SELECT decrypted_secret FROM vault.decrypted_secrets WHERE name = '…' and SELECT vault.update_secret('…', '…'), via a postgres credential. Model B is not theoretical here; it is a copy of an existing, working workflow pattern in this repo.


5. Resolution of #4033 point 2 — Pinbox24

The #4033 body asks whether the Pinbox24 password can be migrated to Vault the same way, or stays with the #3826 CF Worker token-broker.

Discovery: the Pinbox-touching workflow in the export is invoices_registration_2_pinbox (yZGOqBR9LNtTwIKX) — it is Supabase-coupled (writes to our project) and references Pinbox, but its Pinbox interaction is a webhook / extracted-link flow, not a static grant_type=password OAuth node reading a stored secret (no $env secret, no hard-coded OAuth literal in the export). The nearest OAuth-token workflow, get-intercars-token (aobZne7lF02hgeX1), authenticates to InterCars (is.webapi.intercars.eu), is not Supabase-coupled, and is a different vendor entirely — not Pinbox24.

Recommendation: Pinbox24 stays out of this Vault migration; keep it on the #3826 CF Worker token-broker track. Reasons:

  • Qualification condition not met. The Vault-runtime pattern’s gate is “the workflow already has Supabase as a hard runtime dependency for its own secret path.” The Pinbox credential is not read from a static store on a Supabase-coupled OAuth node in the export, so migrating it would either (a) add a Supabase dependency to a non-qualifying secret path, or (b) be a no-op. Neither justifies the work.
  • #3826 is explicitly parked FUTURE — do not touch before end of July. The #4033 guard repeats this. The token-broker is the sanctioned home for the Pinbox credential; pre-empting it here would create exactly the dual-source-of-truth the whole design is trying to kill.
  • If a future audit shows a Pinbox workflow does read a stored Pinbox password on a Supabase-coupled OAuth node (confirm against live n8n, §1), it becomes a separate migration issue with its own Postgres role + RLS policy row (never a widened ATRAX policy) — filed after #3826 resolves, not folded into the ATRAX cutover.

6. What #4033 changes vs what #4032 already covers

ConcernOwner
Model A vs B decision, target role/RLS/bootstrap/node design, SOPS-removal runbook, success criteria#4032 (unchanged — on main)
Empirical inventory correction (§2), plaintext-in-git P1 (§3), Pinbox24 resolution (§5), reusable design-pattern playbook (§7)#4033 (this addendum)

No architecture is re-decided here. If #4032 and this addendum ever conflict on a fact, this addendum wins (it is grounded in the committed artifacts); if they conflict on a design choice, #4032 wins (it is the ratified plan).


7. Deliverable — reusable design-pattern playbook (#4033 point 5)

#4033 point 5 asks for the pattern to be registered as a reusable playbook with a when-to-use / when-NOT checklist, sibling to the #4008 singleton-lock design-pattern. #4032 proposed only a narrow n8n-vault-secret-rotation.md (rotation ops). This addendum ships the broader design-pattern doc: docs/playbooks/n8n-supabase-vault-runtime-secrets.md (added in this PR). The rotation ops doc from #4032 §5.5 remains a separate, complementary follow-up.


8. Follow-up issues to spawn after review (none executed by this worker)

  1. [secret-manager / human-action] Rotate ATRAX password + ATRAX_CLIENT_SECRET and scrub + history-purge the plaintext from n8n-workflows/*.json — fold into / escalate #1713 (§3). This is the P1; do it first, independent of Vault.
  2. [db-admin / secret-manager] Apply #4032 §3.1 role migration + §3.2 Vault seed (with freshly rotated values) — one step that also satisfies #1713’s rotation.
  3. [n8n-admin, server op] Confirm live-vs-export for all §2.4 workflows, then edit the 6 in-scope workflows to the Vault-read node; include fleet-update-v2-batch (§2.2). Dev canary (atrax-fleet-updates-dev) last.
  4. [secret-manager] After 3 clean runs, remove ATRAX_* from both secrets/n8n-bms4.env.sops and secrets/monitoring.env.sops (#4032 §2.1) and update dev_r_services source-of-truth.

9. Files this PR changes

  • docs/plans/plan-4033-n8n-vault-runtime-discovery-addendum.md (this document)
  • docs/playbooks/n8n-supabase-vault-runtime-secrets.md (new reusable design-pattern playbook)

No code, no SOPS edit, no schema change, no n8n edit, no repo scrub is executed by this dev-issue worker. The plan + playbook are the deliverables; implementation is queued via §8.

File-overlap signature: docs/plans/plan-4033-n8n-vault-runtime-discovery-addendum.md, docs/playbooks/n8n-supabase-vault-runtime-secrets.md

files_to_change: docs/plans/plan-4033-n8n-vault-runtime-discovery-addendum.md, docs/playbooks/n8n-supabase-vault-runtime-secrets.md


Plan-only deliverable — discovery addendum to #4032. Implementation issues per §8 spawn after review.