Post-mortem: Credential Leak — monitoring.env.sops Full Dump
| Pole | Wartość |
|---|---|
| ID | INCIDENT-2026-07-02-2620 |
| Severność | P0 Security — wyciek credentials klasy infra |
| Status | REMEDIATION IN PROGRESS — rotacja kluczy pending |
| Wykryto | 2026-07-02 (podczas sesji p24-wa-a) |
| Containment | 2026-07-03 — hook PreToolUse deployed globally |
| Rotacja kluczy | PENDING — wymagane osobne /role-secret-manager |
| GH Issue | radieu/p24-infra#2620 |
| Scope | Wszystkie 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.
| Kategoria | Klucze (nazwy, nie wartości) |
|---|---|
| Cloudflare | CF_API_TOKEN, CF_SCOPED_API_TOKEN, CF_WORKERS_API_TOKEN, CF_GLOBAL_API_KEY, CF_TUNNEL_* |
| GitHub | GH_TOKEN (repo-write PAT) |
| Vercel | VERCEL_TOKEN, VERCEL_TEAM_ID |
| Supabase | SUPABASE_SERVICE_ROLE_KEY, SUPABASE_JWT_SECRET, powiązane klucze |
| OpenAI | OPENAI_API_KEY |
| Sentry | klucz auth |
| Discord | P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL, webhook alerts |
| Mailgun | MAILGUN_API_KEY, MAILGUN_SMTP_PASSWORD |
| SMTP | SMTP_USER, SMTP_PASSWORD (OVH Email Pro) |
| n8n | klucze webhook, N8N_* |
| Wasabi (monitoring) | P24_INFRA_WASABI_ACCESS_KEY, P24_INFRA_WASABI_SECRET_KEY |
| Atrax | ATRAX_AUTH_STRING |
| Traccar | TRACCAR_API_TOKEN |
| Inne | MEZMO_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-02 | Sesja p24-wa-a: cel — skonfigurować FCM_SERVICE_ACCOUNT_KEY jako wrangler secret dla CF Worker |
| T+0 | Wrangler wymaga tokenu CF z uprawnieniem Workers Edit |
| T+10 min | Próba 1: /request-infra → issue #2609 w p24-infra |
| T+~30 min | Worker FAIL — brak wranglera na serwerze, klucz nieznaleziony w SOPS |
| T+35 min | Próba 2: wrangler login (browser OAuth) → sesja nie przeżyła (non-interactive PowerShell) |
| T+40 min | Próba 3: sesja p24-wa-a wchodzi bezpośrednio w secrets/monitoring.env.sops |
| T+42 min | Bezpieczne: lista nazw kluczy — widziana pozycja CF_WORKERS_API_TOKEN |
| T+45 min | INCYDENT: próba ekstrakcji wartości — błędna kolejność flag SOPS → pełny dump na stdout |
| 2026-07-03 | Post-mortem, containment (global PreToolUse hook), wpis do priorities.md |
| PENDING | Rotacja 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
| Warstwa | Opis | Dlaczego 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 hooks | pre-bash-safety-windows.ps1 blokuje unsafe SOPS | Scoped do sesji p24-infra — w sesji p24-wa-a nie było żadnych hooks |
redact-credentials.py PostToolUse | Ostrzega Claude żeby nie powtarzał wartości | Działa po wydruku — output był już w UI i context (architektoniczne ograniczenie) |
Globalne ~/.claude/settings.json | PreToolUse — nie istniało | Nie 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łanie | Status | Opis |
|---|---|---|
| Global PreToolUse hook | ✅ Done | ~/.claude/hooks/global-sops-safety.py — blokuje SOPS decrypt bez safe sink we WSZYSTKICH sesjach |
~/.claude/settings.json | ✅ Done | Dodano PreToolUse[PowerShell] → global-sops-safety.py |
redact-credentials.py | ✅ Done | Rozszerzono 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 | ✅ Done | Dodano PreToolUse hook (repo incydentu — poprzednio zero hooks) |
Art-Agency .claude/settings.json | ✅ Done | Dodano PreToolUse hook (brak hooks przed tym) |
| Playbook hook-security | ✅ Done | docs/playbooks/claude-code-hook-security.md — architektura, ograniczenia, matrix pokrycia |
| priorities.md P0 | ✅ Done | Wpis #2620 z pending rotacją kluczy |
| Rotacja kluczy | 🔴 PENDING | Wymagana 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_PASSWORDSMTP_PASSWORD(OVH Email Pro)MEZMO_INGESTION_KEYRESEND_API_KEYTRACCAR_API_TOKEN
Tier 3 — UI-only (wymaga human action):
ATRAX_AUTH_STRING(Atrax portal)- Sentry auth token (sentry.io dashboard)
Procedura: /role-secret-manager → docs/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
- GH Issue: #2620
- Hook architecture: claude-code-hook-security.md
- Rotation playbook: static-api-key-incident-rotation.md
- SOPS CRLF recovery: sops-windows-crlf.md
- Secret manager ops: secret-manager.md