Summary
EXPORT_SENSITIVITY is a single shared manifest whose comment states it is "Shared verbatim by the agent-state export and the streaming group export so the two never disagree about the sensitivity contract of the data they emit." But the two exports do not emit the same data. The streaming group export additionally streams projection tables carrying decoded message plaintext, app-event content, and pubkeys — the most sensitive content in the corpus — yet the manifest's top-level contains list advertises only IDs, digests, and relay URLs. A consumer trusting the manifest to classify the download would conclude it carries no decoded content.
Code
forensics/analysis.py:62-81:
# ... Shared verbatim by the agent-state export and the streaming group export so the two
# never disagree about the sensitivity contract of the data they emit.
EXPORT_SENSITIVITY = {
"classification": "sensitive_forensic_export",
"contains": [
"engine_ids", "account_refs", "group_refs",
"message_ids", "payload_digests", "relay_urls",
],
"omits": ["raw_upload_bodies", "bearer_tokens", "source_ips", "user_agents"],
}
Consumed as the streaming export's top-level manifest at forensics/views.py:3271 ("sensitivity": EXPORT_SENSITIVITY). The streaming export (api_group_export_stream) streams delivery_artifact rows that include decoded_payload and decoded_app_event — and the per-row sensitivity helper itself confirms these are present and sensitive:
forensics/views.py:1468-1483:
if artifact.decoded_payload:
field_paths.append("decoded_payload")
if artifact.decoded_app_event:
field_paths.append("decoded_app_event")
...
if decoded_app_event.get("pubkey_hex"):
field_paths.append("decoded_app_event.pubkey_hex")
if decoded_app_event.get("content"):
field_paths.append("decoded_app_event.content")
It also streams state_delta rows (value, subject_pubkey_hex) and convergence_run branch identifiers — none reflected in the top-level contains list. The agent-state export never streams these projection tables, so the shared constant is accurate for that export but not for the streaming one.
Concrete failure scenario
An operator pulls GET /api/v1/groups/<slug>/export/, reads the top-level "sensitivity" manifest to decide handling/retention, and sees no mention of decoded content → treats the file as ID-level metadata, while it actually contains decoded message bodies (decoded_payload, decoded_app_event.content) and pubkeys. For a tool whose CLAUDE.md/AGENTS.md explicitly enumerates payload digests and decoded content as sensitive, an inaccurate sensitivity manifest is a mis-classification that can drive under-protection of exported plaintext.
Why not a duplicate
#189 is scoped to the agent-state export and enumerates device/account labels, digests, and raw_context/raw_kind passthrough — fields that export emits. It cannot cover decoded_payload / decoded_app_event.content / pubkey_hex / state_delta.value, because the agent export does not stream the projection tables that hold them. #278 is network_sensitive_field_paths omitting payload_digest on NetworkObservation. The gap here is specific to the streaming export reusing the shared top-level manifest that omits its most sensitive contents. The per-row sensitivity_payload entries flag these correctly; only the shared top-level contains summary is wrong.
Suggested fix
Either split the manifest so the streaming export declares its own contains (adding decoded_payloads, decoded_app_event_content, pubkeys, state_values), or extend EXPORT_SENSITIVITY["contains"] to cover the union and gate the extra entries on which sections a given export actually streams. Add a test asserting the streaming manifest's contains is a superset of the field categories any streamed section can emit.
Severity: MEDIUM — sensitivity manifest under-declares decoded message plaintext/pubkeys in the streaming export (more sensitive content than #189's under-declared fields).
Summary
EXPORT_SENSITIVITYis a single shared manifest whose comment states it is "Shared verbatim by the agent-state export and the streaming group export so the two never disagree about the sensitivity contract of the data they emit." But the two exports do not emit the same data. The streaming group export additionally streams projection tables carrying decoded message plaintext, app-event content, and pubkeys — the most sensitive content in the corpus — yet the manifest's top-levelcontainslist advertises only IDs, digests, and relay URLs. A consumer trusting the manifest to classify the download would conclude it carries no decoded content.Code
forensics/analysis.py:62-81:Consumed as the streaming export's top-level manifest at
forensics/views.py:3271("sensitivity": EXPORT_SENSITIVITY). The streaming export (api_group_export_stream) streamsdelivery_artifactrows that includedecoded_payloadanddecoded_app_event— and the per-row sensitivity helper itself confirms these are present and sensitive:forensics/views.py:1468-1483:It also streams
state_deltarows (value,subject_pubkey_hex) andconvergence_runbranch identifiers — none reflected in the top-levelcontainslist. The agent-state export never streams these projection tables, so the shared constant is accurate for that export but not for the streaming one.Concrete failure scenario
An operator pulls
GET /api/v1/groups/<slug>/export/, reads the top-level"sensitivity"manifest to decide handling/retention, and sees no mention of decoded content → treats the file as ID-level metadata, while it actually contains decoded message bodies (decoded_payload,decoded_app_event.content) and pubkeys. For a tool whose CLAUDE.md/AGENTS.md explicitly enumerates payload digests and decoded content as sensitive, an inaccurate sensitivity manifest is a mis-classification that can drive under-protection of exported plaintext.Why not a duplicate
#189 is scoped to the agent-state export and enumerates device/account labels, digests, and
raw_context/raw_kindpassthrough — fields that export emits. It cannot coverdecoded_payload/decoded_app_event.content/pubkey_hex/state_delta.value, because the agent export does not stream the projection tables that hold them. #278 isnetwork_sensitive_field_pathsomittingpayload_digestonNetworkObservation. The gap here is specific to the streaming export reusing the shared top-level manifest that omits its most sensitive contents. The per-rowsensitivity_payloadentries flag these correctly; only the shared top-levelcontainssummary is wrong.Suggested fix
Either split the manifest so the streaming export declares its own
contains(addingdecoded_payloads,decoded_app_event_content,pubkeys,state_values), or extendEXPORT_SENSITIVITY["contains"]to cover the union and gate the extra entries on which sections a given export actually streams. Add a test asserting the streaming manifest'scontainsis a superset of the field categories any streamed section can emit.Severity: MEDIUM — sensitivity manifest under-declares decoded message plaintext/pubkeys in the streaming export (more sensitive content than #189's under-declared fields).