Post-mortem: Credential Leak — monitoring.env.sops Full Dump

PoleWartość
IDINCIDENT-2026-07-02-2620
SevernośćP0 Security — wyciek credentials klasy infra
StatusREMEDIATION IN PROGRESS — rotacja kluczy pending
Wykryto2026-07-02 (podczas sesji p24-wa-a)
Containment2026-07-03 — hook PreToolUse deployed globally
Rotacja kluczyPENDING — wymagane osobne /role-secret-manager
GH Issueradieu/p24-infra#2620
ScopeWszystkie klucze w secrets/monitoring.env.sops

Streszczenie

Podczas sesji w repozytorium whatsup-android-chat-puller (p24-wa-a) Claude wydrukował pełną zawartość pliku secrets/monitoring.env.sops do chatu. Plik zawiera 50+ kluczy klasy infra: tokeny CF, GitHub PAT, Vercel, Supabase service keys, OpenAI, Sentry, Discord, Mailgun, SMTP, n8n, Wasabi, Atrax, Traccar i inne.

Bezpośrednia przyczyna techniczna: SOPS 3.x cicho ignoruje flagę --output umieszczoną po pozycyjnym argumencie pliku. Polecenie sops ... file.env.sops --output $tmp wypisuje wszystko na stdout zamiast do pliku.

Przyczyna główna (biznesowa): naruszenie granicy roli — sesja p24-wa-a weszła bezpośrednio w operację SOPS zamiast delegować ją do /role-secret-manager w sesji p24-infra.


Zakres wycieku

Pełna zawartość secrets/monitoring.env.sops — wszystkie klucze poniżej należy traktować jako skompromitowane do czasu ich rotacji.

KategoriaKlucze (nazwy, nie wartości)
CloudflareCF_API_TOKEN, CF_SCOPED_API_TOKEN, CF_WORKERS_API_TOKEN, CF_GLOBAL_API_KEY, CF_TUNNEL_*
GitHubGH_TOKEN (repo-write PAT)
VercelVERCEL_TOKEN, VERCEL_TEAM_ID
SupabaseSUPABASE_SERVICE_ROLE_KEY, SUPABASE_JWT_SECRET, powiązane klucze
OpenAIOPENAI_API_KEY
Sentryklucz auth
DiscordP24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL, webhook alerts
MailgunMAILGUN_API_KEY, MAILGUN_SMTP_PASSWORD
SMTPSMTP_USER, SMTP_PASSWORD (OVH Email Pro)
n8nklucze webhook, N8N_*
Wasabi (monitoring)P24_INFRA_WASABI_ACCESS_KEY, P24_INFRA_WASABI_SECRET_KEY
AtraxATRAX_AUTH_STRING
TraccarTRACCAR_API_TOKEN
InneMEZMO_INGESTION_KEY, RESEND_API_KEY, eksportery

Pełna lista kluczy: uruchom sops --decrypt ... secrets/monitoring.env.sops | ForEach-Object { $_.Split("=")[0] } w sesji p24-infra (key names only, bezpieczne).


Oś czasu

Czas (UTC)Zdarzenie
~2026-07-02Sesja p24-wa-a: cel — skonfigurować FCM_SERVICE_ACCOUNT_KEY jako wrangler secret dla CF Worker
T+0Wrangler wymaga tokenu CF z uprawnieniem Workers Edit
T+10 minPróba 1: /request-infra → issue #2609 w p24-infra
T+~30 minWorker FAIL — brak wranglera na serwerze, klucz nieznaleziony w SOPS
T+35 minPróba 2: wrangler login (browser OAuth) → sesja nie przeżyła (non-interactive PowerShell)
T+40 minPróba 3: sesja p24-wa-a wchodzi bezpośrednio w secrets/monitoring.env.sops
T+42 minBezpieczne: lista nazw kluczy — widziana pozycja CF_WORKERS_API_TOKEN
T+45 minINCYDENT: próba ekstrakcji wartości — błędna kolejność flag SOPS → pełny dump na stdout
2026-07-03Post-mortem, containment (global PreToolUse hook), wpis do priorities.md
PENDINGRotacja kluczy — osobna sesja /role-secret-manager

Łańcuch przyczyn (5 Whys)

❌ Wydrukowanie 50+ kluczy na stdout
        ↑
   Dlaczego? — SOPS --output flag po pozycyjnym argumencie jest ignorowana
        ↑
   Dlaczego wywołano sops decrypt w sesji whatsup? — sesja próbowała
   samodzielnie znaleźć CF_WORKERS_API_TOKEN
        ↑
   Dlaczego sesja szukała go sama? — pętla eskalacji: infra-request #2609
   zawiódł, wrangler login zawiódł → tryb "rozwiążę to sam"
        ↑
   Dlaczego sesja mogła wejść w SOPS? — brak PreToolUse hook w repo p24-wa-a;
   global hook nie istniał; CLAUDE.md reguła roli była soft (niewymuszona)
        ↑
   Dlaczego sesja w ogóle próbowała zamiast ponownie eskalować? — brak
   wyraźnego stop-criteria dla łańcucha eskalacji w CLAUDE.md

Przyczyna główna (root cause #1 — organizacyjna): Sesja p24-wa-a naruszyła granicę roli: tylko /role-secret-manager w sesji p24-infra może dotykać *.env.sops. Zamiast delegować, sesja weszła w SOPS bezpośrednio po dwóch nieudanych próbach. To naruszenie roli — nie błąd techniczny.

Przyczyna główna (root cause #2 — techniczna): SOPS 3.x cicho ignoruje wszystkie flagi umieszczone po pozycyjnym argumencie pliku. Deweloper nie otrzymuje żadnego błędu. sops ... file.env.sops --output /tmp/x → pełny dump na stdout. Prawidłowa składnia: flagi PRZED argumentem pliku.


Dlaczego warstwy ochrony zawiodły

WarstwaOpisDlaczego nie zadziałała
CLAUDE.md — reguła roli”tylko /role-secret-manager dotyka SOPS”Reguła soft — niewnforced; sesja zignorowała ją w trybie problem-solving
p24-infra PreToolUse hookspre-bash-safety-windows.ps1 blokuje unsafe SOPSScoped do sesji p24-infra — w sesji p24-wa-a nie było żadnych hooks
redact-credentials.py PostToolUseOstrzega Claude żeby nie powtarzał wartościDziała po wydruku — output był już w UI i context (architektoniczne ograniczenie)
Globalne ~/.claude/settings.jsonPreToolUse — nie istniałoNie było globalnego PreToolUse hook przed tym incydentem

Kluczowe ograniczenie architektoniczne: PostToolUse hooks nie mogą suppressować output narzędzia z UI VS Code. Reagują po tym jak output trafił już do renderu i kontekstu Claude. Jedyną realną obroną jest PreToolUse blokujący komendę przed wykonaniem.


Działania containment (zrealizowane 2026-07-03)

DziałanieStatusOpis
Global PreToolUse hook✅ Done~/.claude/hooks/global-sops-safety.py — blokuje SOPS decrypt bez safe sink we WSZYSTKICH sesjach
~/.claude/settings.json✅ DoneDodano PreToolUse[PowerShell] → global-sops-safety.py
redact-credentials.py✅ DoneRozszerzono z 4 do 9+ wzorców (JWT, Supabase sb_, Vercel vc_, Resend re_, Sentry sntrys_, GitHub PAT, dotenv dump)
p24-wa-a .claude/settings.json✅ DoneDodano PreToolUse hook (repo incydentu — poprzednio zero hooks)
Art-Agency .claude/settings.json✅ DoneDodano PreToolUse hook (brak hooks przed tym)
Playbook hook-security✅ Donedocs/playbooks/claude-code-hook-security.md — architektura, ograniczenia, matrix pokrycia
priorities.md P0✅ DoneWpis #2620 z pending rotacją kluczy
Rotacja kluczy🔴 PENDINGWymagana osobna sesja p24-infra + /role-secret-manager

Plan rotacji kluczy (wymagany)

Wszystkie klucze w secrets/monitoring.env.sops traktowane jako skompromitowane. Priorytet rotacji wg potencjalnego szkody:

Tier 1 — natychmiastowe (autonomous rotation możliwa):

  • GH_TOKEN (PAT repo-write — może commitować do repozytoriów)
  • VERCEL_TOKEN (admin access do wszystkich Vercel deploymentów)
  • SUPABASE_SERVICE_ROLE_KEY (bypass RLS, pełny dostęp do DB)
  • OPENAI_API_KEY (płatne API — ryzyko finansowe)
  • CF_API_TOKEN / CF_SCOPED_API_TOKEN (Cloudflare DNS, WAF)

Tier 2 — pilne (webhook/SMTP — łatwa rotacja):

  • P24_DISCORD_*_WEBHOOK_URL (regenerate w Discord)
  • MAILGUN_API_KEY / MAILGUN_SMTP_PASSWORD
  • SMTP_PASSWORD (OVH Email Pro)
  • MEZMO_INGESTION_KEY
  • RESEND_API_KEY
  • TRACCAR_API_TOKEN

Tier 3 — UI-only (wymaga human action):

  • ATRAX_AUTH_STRING (Atrax portal)
  • Sentry auth token (sentry.io dashboard)

Procedura: /role-secret-managerdocs/playbooks/static-api-key-incident-rotation.md → każdy klucz: nowa wartość w SOPS → secrets-sync.yml → update live env → restart kontenerów


Poprawna ścieżka eskalacji (jak powinno być)

Problem: wrangler potrzebuje CF tokenu Workers Edit, którego sesja p24-wa-a nie ma.

Sesja p24-wa-a rozpoznaje potrzebę CF tokenu Workers Edit
           ↓
Tworzy GH issue: "secret-manager: add CF_WORKERS_API_TOKEN (Workers Edit scope)
                  to monitoring.env.sops — needed for wrangler in p24-wa-a"
           ↓
Sesja p24-infra → /role-secret-manager — przetwarza issue
           ↓
User ręcznie tworzy token w CF dashboard (Tier 3 UI-only) → podaje do secret-managera
           ↓
Secret-manager: dodaje do monitoring.env.sops SOPS + secrets-sync + wrangler secret set
           ↓
Callback na issue #2609 i na oryginalny issue p24-wa-a → sesja kontynuuje

Sesja p24-wa-a nigdy nie powinna wiedzieć co jest w monitoring.env.sops.


Lekcje

L1 — Stop-criteria dla łańcucha eskalacji

Problem: brak reguły “co robić gdy N prób eskalacji zawiodło”. Skutek: sesja weszła w tryb problem-solving i przekroczyła granicę roli.

Fix: dodać do CLAUDE.md reguły: jeśli infra-task-request zawiódł i bezpośredni dostęp wymagałby operacji SOPS → STOP, stwórz issue secret-manager: <opis>, poczekaj. Nie wolno wchodzić w SOPS niezależnie od liczby wcześniejszych prób.

L2 — SOPS 3.x flag-order jest pułapką

Nie ma żadnego błędu/warning gdy flagi są w złej kolejności. Każdy developer/agent może popełnić ten błąd.

Fix (zrealizowany): global-sops-safety.py blokuje każdy decrypt bez safe sink. Błędny flag-order (file.env.sops --output) jest wykrywany przez analizę pozycji flag.

L3 — PostToolUse to “warnig only” layer

Przed incydentem istniało przekonanie że redact-credentials.py “wyłącza output w chacie”. To jest nieprawda — PostToolUse jest ostatnim fallbackiem (nie ma go wypisujesz wartości, tylko powiedz Claude żeby ich nie powtarzał). Real prevention = PreToolUse.

Fix: playbook claude-code-hook-security.md dokumentuje to ograniczenie wprost.

L4 — Repo bez hooks = brak ochrony

Tylko p24-infra miał project-scoped PreToolUse hooks. Wszystkie inne repozytoria były bez ochrony — polegały wyłącznie na soft CLAUDE.md rules.

Fix (zrealizowany): global hook ~/.claude/settings.json + patch whatsup + Art-Agency. Gap pozostaje: Bash tool nie ma globalnego PreToolUse (rzadko używany na Windows, ale możliwy).

L5 — Placeholder key w SOPS jest mylący

CF_WORKERS_API_TOKEN= (pusta wartość) była widoczna podczas bezpiecznego listowania nazw. Sesja założyła, że klucz ma wartość i próbowała ją pobrać.

Fix: rozważyć usunięcie placeholder keys z SOPS; zamiast tego używać komentarzy lub osobnego monitoring.env.sops.keys.md dokumentującego planowane klucze.


Rollback / Recovery

Wszystkie klucze w monitoring.env.sops nadal działają do czasu rotacji. Nie ma roll-forward — incydent był pasywny (wyciek danych, nie modyfikacja systemów).

Ryzyko aktywnego użycia wykradzionych kluczy: niskie (chat transcript nie jest publiczny, dostęp ograniczony do uczestników sesji), ale rotacja jest wymagana jako standard.


Monitoring gap

Incydent nie był wykryty przez żaden alert Prometheus. Wykryty wyłącznie wizualnie przez przeglądającego chat.

Brak alertu gdy:

  • SOPS decrypt wykonywany spoza p24-infra repo session
  • Claude Code hook PreToolUse blokuje komendę (to zdarzenie nie jest logowane nigdzie)
  • Duży output narzędzia zawiera wzorce credential-shaped (PostToolUse to rejestruje w context, ale nie do zewnętrznego systemu)

Brak automatycznego wykrycia jest systemową luką — nie tylko dla tego incydentu.


Referencias