Kto zebrał dowód, kiedy, jaki hash, jaka akcja, czy była akceptacja — to cztery/pięć pól, które
muszą się zgadzać dla każdego evidence w paczce board-pack. Ta strona pokazuje format chain_of_custody
(JSONB w tabeli ip3_evidence), jak go czytać krok po kroku i jak łączy się z niezależnym
hash-chain audytu (F2). Przykład poniżej jest w całości syntetyczny — dane demonstracyjne, nie realny incydent.
Każdy rekord w ip3_evidence ma kolumnę chain_of_custody JSONB — tablicę obiektów
{by, at, action, request_id} dopisywanych przy każdej istotnej operacji na dowodzie (zebranie,
zmiana statusu, dołączenie do paczki). To jest dzisiejszy, dowodliwy mechanizm (kod + endpoint + test),
NIE deklaracja niezmienności prawnej.
| Pole | Typ | Znaczenie | Status |
|---|---|---|---|
by | string | Identyfikator aktora (req.principal.sub) — kto wykonał akcję na dowodzie. | LIVE |
at | ISO-8601 timestamp | Moment zapisu zdarzenia (serwer, UTC). | LIVE |
action | string | Nazwa akcji, np. collected, status_change, attached_to_package. | LIVE |
request_id | string | Korelacja z logiem żądania HTTP — pozwala odtworzyć kontekst wywołania. | LIVE |
approval | obiekt (kto/kiedy zatwierdził) | Formalna akceptacja zmiany statusu przez drugą osobę (four-eyes). | ROADMAP |
| podpis pola | PAdES/HMAC per-wpis | Kryptograficzne podpisanie każdego wpisu CoC osobno (dziś podpisywana jest cała paczka, nie pojedynczy wpis). | ROADMAP |
Uczciwie: approval jako osobne, wymuszone pole four-eyes nie jest dziś egzekwowane w schemacie —
status-change może dopisać każdy z rolą operator/admin. To oznaczamy jako ROADMAP, nie jako lukę ukrytą.
Poniższe dane są w całości zmyślone na potrzeby ilustracji formatu. Incydent, hashe i identyfikatory nie odnoszą się do żadnego realnego zdarzenia.
{
"evidence_id": "K0-EVD-SYN-0007",
"incident_public_id": "K0-INC-SYN-0042",
"evidence_type": "scanner_report",
"hash_sha256": "7a1c…f30e (SYMULACJA)",
"status": "COLLECTED → TRIAGED → PACKAGED",
"chain_of_custody": [
{ "by": "analyst@synthetic", "at": "2026-07-05T08:12:03Z", "action": "collected", "request_id": "req_syn_0001" },
{ "by": "reviewer@synthetic", "at": "2026-07-05T09:40:11Z", "action": "status_change:triaged","request_id": "req_syn_0002" },
{ "by": "auditor@synthetic", "at": "2026-07-05T14:02:55Z", "action": "attached_to_package", "request_id": "req_syn_0003" }
]
}
Dowód (raport skanera) wgrany do systemu, policzony sha256, pierwszy wpis CoC dopisany automatycznie przy insercie.
Zmiana statusu z COLLECTED na TRIAGED; drugi wpis dopisany do tej samej tablicy JSONB (append, nie nadpisanie).
Evidence dołączony do evidence-package/board-pack; manifest paczki referencuje evidence_id + pełną tablicę CoC.
chain_of_custody nie może być pusta dla dowodu w statusie innym niż nowo utworzony. Reguła close-with-evidence sprawdza cocOk (długość > 0) przed zamknięciem incydentu.at powinny rosnąć monotonicznie; skok wstecz lub duplikat znacznika czasu to sygnał do ręcznej weryfikacji, nie automatyczne odrzucenie (dziś brak automatycznej walidacji kolejności — ROADMAP).request_id z CoC da się odnaleźć w niezależnym logu audytowym (ip3_audit_events / hash-chain F2) — dwa źródła powinny się zgadzać.package_sha256 całości; przy ?sign=true (F2) dochodzi HMAC-SHA256 podpisu paczki, weryfikowalny przez POST /reports/verify-signature.Chain-of-custody per-dowód (opisany wyżej) i hash-chain audytu to dwa różne, uzupełniające się mechanizmy — nie mylić:
Zakres: pojedynczy dowód w ip3_evidence.
Format: JSONB tablica zdarzeń {by, at, action, request_id}.
Status: LIVE — działa od pierwszej fali evidence-package.
Zakres: cały strumień zdarzeń audytowych systemu (nie tylko evidence).
Format: każdy wpis audytowy linkowany SHA-256 do poprzedniego (prev_hash → hash); modyfikacja jednego ogniwa jest wykrywalna przez GET /api/ip3/v1/audit/chain/verify.
Status: shadow LIVE na prod za flagą IP3_AUDIT_CHAIN — pisze równolegle do audytu bazowego, dziś nie zastępuje go.
Zakres: evidence-package/board-pack jako całość, nie pojedynczy wpis CoC.
evidence-package?sign=true → HMAC-SHA256 kluczem serwera, weryfikacja POST /reports/verify-signature.
Status: LIVE. Uczciwie: HMAC integrity ≠ kwalifikowany podpis eIDAS/PAdES/TSA — to ROADMAP.
Innymi słowy: CoC odpowiada na pytanie „kto dotykał tego dowodu i kiedy", hash-chain audytu odpowiada na pytanie „czy cały log zdarzeń jest nienaruszony", a podpis paczki odpowiada na pytanie „czy ta konkretna paczka board-pack jest tym samym plikiem, który wygenerowano". Trzy różne dowody, jeden cel: żeby przed sądem lub regulatorem nie trzeba było wierzyć nam na słowo.
ip3_evidence, dopisywana przez te same endpointy co reszta cyklu evidence (import → incydent →
evidence-package → retest → close). Nie ma osobnej bazy danych CoC ani osobnego API — to jedna spójna warstwa.
Powiązane: weryfikacja hashy i podpisu paczki → /verify · chain-of-custody działań agenta AI (osobny mechanizm, prev_hash na poziomie agenta) → /agent-coc · pełny przepływ finding → board pack → /pentest-report-board-pack · ćwiczenie purple-team na własnym serwisie → /pentest-self · rejestr ograniczeń → /known-limitations.