# Conversation scan (`conv_scan`) cleartext retention

This note documents how media moderation scanning is enabled, plus retention and admin expectations for conversation scan cleartext (conv_scan hash-check pipeline).

## How scanning is enabled (opt-in)

Moderation scanning is governed by two independent layers. Both must permit scanning for a given upload to be hash-checked.

### 1. Operator opt-in (the whole subsystem is opt-in)

CSAM hash-check scanning **does not run at all unless the operator explicitly turns it on**. There is no scanning on a default/unconfigured deployment.

- **Deploy the media stack:** `enable_media_stack = true` (Terraform) provisions the NCMEC hash DynamoDB table, the `media-processor` Lambda, and the `media-db-writer` Lambda. See `infra/aws/terraform/media.tf` and `infra/aws/terraform/terraform.tfvars.example`.
- **Configure hash providers:** the `media-processor` Lambda needs at least one provider:
  - **NCMEC** — `NCMEC_HASH_TABLE` env (provisioned automatically with the media stack).
  - **Arachnid Shield** — optional; requires `ARACHNID_USERNAME` / `ARACHNID_PASSWORD` in the media Secrets Manager secret (`ARACHNID_USERNAME_KEY` / `ARACHNID_PASSWORD_KEY`).
- **Fail-closed:** if a moderated upload is processed with **no** providers configured, the processor treats it as a fatal error (`csam_hash_check_fatal`, `NO_CSAM_PROVIDERS_ERROR`) and marks the upload `failed` rather than silently passing it. See `infra/aws/lambda/media-processor/src/run-csam-hash-checks.ts`.
- **Which matches enforce:** the platform setting `platform-csam-hash-services` (`CSAM_HASH_SERVICES`, Mongo `platform_settings`, default `["arachnid_shield"]`) selects which providers' matches trigger reports/bans in `media-db-writer`. This filters enforcement after checks run; it does not by itself start or stop scanning.

If the media stack is not deployed, no `conv_scan` objects are ever produced or processed.

### 2. Sender / conversation control (default ON, opt-out is gated)

For deployments where scanning is enabled, visual attachments (images and MP4 video) are scanned **by default**. Senders cannot silently skip it:

- The composer defaults `moderationEnabled = true` (`packages/ui/src/components/composer/useComposerAttachments.ts`).
- A sender may opt a send out **only** when the conversation has `allowSkipModeration: true` (`apps/api/src/models/conversation.ts`). That flag is **absent/false by default** and must be enabled by a group admin (or either DM participant) before the opt-out toggle is even shown.
- When a send is opted out, the client uploads no scan copy, the E2E media is marked `available` / moderation `skipped`, and the message is tagged `moderationEnabled: false`.
- **Non-visual files** (PDFs, etc.) are never scanned — this is automatic, not a user toggle.

Net effect: operators opt **in** to the capability; end users are scanned by **default** and can only opt out where a conversation has explicitly permitted it.

## Trust model and known limitations (inherent to client-side E2EE)

Because conversation attachments are end-to-end encrypted, the server never holds the plaintext of the blob that recipients decrypt. The scan operates on **client-produced scan material** (JPEG frames/thumbnails the client uploads to `uploads/conv_scan/{scanHash}/`), not on the ciphertext that is actually delivered. This makes moderation scanning **best-effort / advisory by design**, and the limitations below are direct, expected consequences of the E2EE guarantee (not considered bugs or defects, with no intention to address beyond potential perceptual size estimation later).

A modified or non-cooperating client can, in principle:

- **Skip scanning** by not generating or uploading scan copies (or by sending with `moderationEnabled: false` where a conversation permits opt-out).
- **Submit non-corresponding scan material** — e.g. hash-check a benign image while the encrypted blob delivered to recipients is something else. The server cannot detect the mismatch without the plaintext.
- **Evade perceptual hashing** by altering, cropping, or heavily recompressing frames.
- **Under-sample** content so that offending frames are not represented in the sparse grid.

These are not bugs and should not be tracked as security "issues." The server cannot verify that scan material corresponds to the encrypted payload without decrypting the payload, which would break E2EE (and isn't possible in today's model where the server never has the keys). The `scanHash` derivation binds a scan copy to a specific identity + media reference, but it deliberately does **not** and **cannot** prove that the scanned bytes match the encrypted bytes.

What the pipeline does provide, for cooperating/first-party clients: hash-based detection of known CSAM before delivery-time policy actions, evidence retention for confirmed matches, and an audited moderation trail. Threats that require server-side plaintext inspection (verifying scan/ciphertext correspondence, scanning for unknown/novel content) are **out of scope** by definition under E2EE.

## What is stored

- **E2E ciphertext:** The encrypted attachment blob delivered to recipients. The server does not decrypt it for routine operation.
- **Scan material:** Cleartext JPEG frames (grids of sampled frames for video, thumbnails for images) under `uploads/conv_scan/{scanHash}/`. Used only for CSAM hash checks and, when applicable, moderator review. Full-video scan copies are not accepted; clients upload JPEG frame grids. These are not typically stored beyond the time required to scan them (see lifecycle); they are retained and isolated for law enforcement when tagged and confirmed as CSAM.

## Lifecycle

- **Pass:** Scan objects under the prefix are deleted after hash checks pass (see `media-processor` batch completion path).
- **Reject:** Scan objects may be retained for human review until associated moderation reports are resolved and purged via API (see `purgeConvScanEvidenceForTerminalReport` and related guards for open reports).
- **Abandon / cancel:** Clients should call abandon APIs; operators may run prefix purges aligned with product policy.

## Operational knobs

- **Media stack + providers:** see "Operator opt-in" above — the primary switch for whether scanning happens at all.
- `**platform-csam-hash-services`:** selects which providers' matches enforce (default `["arachnid_shield"]`).
- **Video in scan batches:** Sealed batches that contain any video object fail fast (`conv_scan_video_rejected`, reason `conv_scan_video_not_supported`) and their objects are purged. All clients must upload frame JPEG grids; the API accepts image content types only for `conv_scan` uploads.

## Trust & safety

- Frame sampling is sparse; policy owners should document acceptable coverage and escalation procedures.
- Access to scan evidence in staff tools should follow least-privilege and audit expectations already applied to moderation queues.

