Skip to content

Streaming group export reuses EXPORT_SENSITIVITY, whose top-level "contains" list under-declares the decoded plaintext / pubkeys / state values it actually streams #295

Description

@erskingardner

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    MEDIUMSeverity: important bug or performance issue with bounded impactsecurity

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions