diff --git a/docs/email-security/api-reference.md b/docs/email-security/api-reference.md index 1607583a7..ca8ea30dd 100644 --- a/docs/email-security/api-reference.md +++ b/docs/email-security/api-reference.md @@ -56,6 +56,8 @@ Shared behaviours: | `GET /reports` | The user-report queue. Params: `status[]` (`open`, `triaging`, `resolved`), `oldest_first`, `cursor`, `limit` | | `GET /reports/{report_id}` | One report: who reported it, the message they reported, the original once located across the tenant's mailboxes, and its triage state | | `GET /senders/{key}` | The accumulated profile for one correspondent. `key` is qualified (`email:someone@corp.example` or `domain:corp.example`) or a bare address or domain. A key with no profile says so explicitly rather than returning a zeroed profile | +| `GET /submissions` | `{enabled, available, submissions, next_cursor}` — the samples your organization copied to LimaCharlie. Filters: `category` (`missed_threat`, `false_positive`, `other`), `since`, `until` (RFC 3339), `limit` (1-200, default 50), `cursor`. `enabled` is whether the organization opted in and `available` is whether the datacenter has a submissions store; both are always present. See [Sample Submission](sample-submission.md). Requires `mailsec.get` | +| `GET /submissions/{submission_id}` | `{submission, reviews, reviews_truncated}` — one submission and up to 200 review-access timestamps (never who); counts and latest-review time include all accesses. An unknown id is not an error: it returns `{"submission": null, "reviews": []}`. Requires `mailsec.get` | | `GET /actions/{action_id}` | `{action}` — one audit entry expanded, **including the JSON request payload the message timeline omits**. For a raw-message download that payload carries the access justification. Gated on `mailsec.get`: reading who did what to a message is part of reading the product | | `GET /onboarding` | `{scopes, steps, script}` — the setup steps, OAuth scopes and `gcloud` commands for connecting a tenant, for rendering in a setup flow. Each step carries a `console` and, where verifiable, a `verified_by` naming the connection-test check that proves it. Params: `provider` (`gworkspace` default, or `m365`), `project_id`, `sa_email`, `topic`, `subscription` — supply them and the commands come back ready to run rather than templated | | `GET /tenant` | `{confirmation, expires_in_seconds, warning}` — the tenant-purge preview. Returns the warning describing exactly what a purge removes, and mints the single-use `confirmation` token that [`DELETE /tenant`](#delete-tenant) requires. **It changes nothing.** The token expires after `expires_in_seconds` (300). Requires Owner-level authority, not `mailsec.get` | @@ -311,6 +313,8 @@ faster, which is worth doing for its own sake. | Route | Does | |---|---| | `POST /messages/{msg_uuid}/actions` | Perform a typed action on one message. Body: `action` (`quarantine_message`, `trash_message`, `move_to_spam`, `restore_message`, `banner_message`, `unbanner_message`), optional `force` (boolean; see [alert-only overrides](#explicit-override-in-alert-only-mode)), optional `reason`, optional `attempt` (idempotency token — omit to collapse onto the existing attempt). `banner_message` uses the organization's own banner, rendered from its `mailsec_policy` record of type `banners`; its optional `text` (plain text, at most 512 characters; refused on any other action) replaces the wording for that one banner. No caller supplies HTML. Requires `mailsec.act` | +| `POST /messages/{msg_uuid}/actions` with `submit_sample` or `withdraw_sample` | Copy one message to LimaCharlie, or withdraw that copy. `submit_sample` requires `category` (`missed_threat`, `false_positive`, `other`) and a `reason` of 1-1024 characters; `withdraw_sample` takes an optional `reason`. Only a person can run either: D&R rules, automations and the AI agent are refused. Submission requires organization opt-in; withdrawal remains available after opt-out or provider disconnect. See [Sample Submission](sample-submission.md). Requires `mailsec.act` | +| `DELETE /submissions/{submission_id}` | Withdraw a submission: hard-deletes the stored copy and its metadata and returns `{withdrawn: true, submission_id, action_id}`. An unknown or already-deleted id returns `{withdrawn: false, submission_id}` with no `action_id`; an expired id is still cleaned up if metadata remains. Requires `mailsec.act` | | `POST /campaigns/{campaign_id}/actions` | Sweep a campaign. Same body plus `confirm`. **Without `confirm` this previews** and changes nothing, returning the member ids, the distinct mailboxes, the counts and a `confirm` token derived from that exact member set. With `confirm` it executes exactly that set; a campaign that grew since the preview is refused. Capped at 500 members. `reason` is recorded on **every member's** audit row and on the sweep's own row (`action_id` in the response); `attempt` (bounded at 128 characters, refused not truncated) mints a new row per member, so a deliberate retry is recorded beside what it retried instead of over it. Neither is part of the `confirm` token. Requires `mailsec.act` | | `POST /actions/bulk/execute` | Execute a previewed bulk remediation. Returns a `bulk_id` immediately and the provider work proceeds in the background. Requires `mailsec.act`. See [Bulk Remediation](remediation.md) | | `POST /reports/{report_id}/resolve` | Record a triage outcome. Body: `disposition` — one of `malicious`, `spam`, `graymail`, `benign`, `simulation`. Resolving an already-resolved report succeeds and reports `already_resolved`, so two analysts clicking at once is not an error. Requires `mailsec.set` | diff --git a/docs/email-security/cli.md b/docs/email-security/cli.md index 8dc006ddf..58ee950d7 100644 --- a/docs/email-security/cli.md +++ b/docs/email-security/cli.md @@ -6,7 +6,7 @@ The `limacharlie mailsec` command group covers the Email Security API surface: the coverage screen, the message index and drawer, the audited raw-EML download, verdict revisions, campaigns and campaign-wide sweeps, bulk remediation over a selection you name, sender profiles, the action audit trail, the abuse-mailbox -report queue, custom-rule validation and backtest, the connection preflight, the +report queue, sample submission, custom-rule validation and backtest, the connection preflight, the served onboarding guide, and the tenant purge. Commands take the global options (`--oid`, @@ -55,7 +55,7 @@ trusted with four separable things — plus one command that is not any of them: |---|---| | `mailsec.get` | Read the product's own view: queue, drawer, campaigns, senders, audit trail | | `mailsec.set` | Change triage state — resolving a user report | -| `mailsec.act` | Remediate live mail at the provider | +| `mailsec.act` | Remediate live mail at the provider; submit and withdraw samples | | `mailsec.get.eml` | Download the original bytes of a message; requires a logged justification | | `mailsec.act` **and** `billing.ctrl` **and** `user.ctrl` | `tenant purge`, in both its preview and its destructive form. Owner-level authority, the same trio deleting the organization requires — there is no separate "owner" permission | @@ -97,6 +97,13 @@ limacharlie mailsec message bulk-action --action quarantine_message --input-file limacharlie mailsec message bulk-action --action quarantine_message --input-file uuids.txt --confirm --reason "INC-4471" limacharlie mailsec message bulk-status +# Sample submission (opt-in): copy ONE message to LimaCharlie, list it, withdraw it +limacharlie mailsec message submit-sample --category missed_threat --reason "credential phish we did not flag" +limacharlie mailsec message withdraw-sample +limacharlie mailsec submission list --category false_positive --since 2026-09-01T00:00:00Z +limacharlie mailsec submission get +limacharlie mailsec submission withdraw + # Campaigns: one attack, triaged once limacharlie mailsec campaign list --min-members 3 limacharlie mailsec campaign get @@ -301,6 +308,22 @@ carries the outcome** — `0` only when the job completed and something was acte on. The full contract, including every `state`, `result` and count, is in [Bulk Remediation](remediation.md#from-the-cli). +### Submitting a sample sends the message to LimaCharlie + +`message submit-sample` copies one message to LimaCharlie, so it is opt-in, explicit and +one message per call. The organization must have opted in with a `sample_sharing` +[policy record](policy.md#sample_sharing); `--category` +(`missed_threat`, `false_positive`, `other`) and `--reason` (1 to 1024 characters) are +both required and are checked before anything is sent. A refusal (not opted in, no store +in the datacenter, raw copy no longer stored) is reported like any other failed action, with the reason in +`error`; the command prints the reason and exits non-zero. `submission list` prints the `enabled` and +`available` flags, so an empty list can be told apart from a feature that is off, and +pages with `--cursor`. `submission get` shows recorded access times for the copy, and +`submission withdraw` (or `message withdraw-sample`) deletes it. An unknown id is not an +error: `submission get` returns `submission: null` and `submission withdraw` returns +`withdrawn: false`, and the command says so on stderr. See +[Sample Submission](sample-submission.md). + ### Revising a verdict is `mailsec.act`, not `mailsec.set` `message revise` records a human disposition over the scorer's, appending to the diff --git a/docs/email-security/index.md b/docs/email-security/index.md index df38a341f..bab417a36 100644 --- a/docs/email-security/index.md +++ b/docs/email-security/index.md @@ -25,6 +25,7 @@ it at the provider. | **Remediation** | Typed, idempotent, audited actions performed at the provider: quarantine, trash, move to spam, restore, apply and remove a warning banner. Available by policy automation, from the console, from a D&R rule, from the API and from the CLI. | | **Campaigns** | Messages the engine attributed to one attack are clustered, so a campaign that hit forty mailboxes is triaged once and swept once. | | **User reports** | An abuse mailbox becomes an SLA queue: reports are joined back to the original message across the whole tenant, robots that mail the abuse address are auto-resolved out of the queue, and reporters can be sent a templated acknowledgement. | +| **Sample submission** | Opt-in, one message at a time: an analyst can send LimaCharlie a copy of a message the engine got wrong, and list and withdraw what was sent. See [Sample Submission](sample-submission.md). | | **Telemetry** | `EMAIL_MESSAGE`, `EMAIL_VERDICT` (every verdict decision — the engine's own at ingest, then each override), `EMAIL_ACTION`, `EMAIL_USER_REPORT` and `EMAIL_INGEST_ERROR` land in the same lake as your EDR, cloud and identity telemetry — so "phish delivered, then that user's endpoint ran a new binary" is one D&R rule. | | **Configuration as data** | Connections, policy and custom rules are Hive records, so everything is API-first and git-syncable from day one. | | **AI assistant access** | [MCP tools](mcp.md) for read-only coverage and triage, with separately permissioned diagnostics and responses. Requires a server version containing MailSec support. | @@ -110,6 +111,7 @@ Managing connections and policy uses the ordinary Hive permissions for the | [Bulk Remediation](remediation.md) | Acting on a set of messages you named: preview, confirm, execute, poll | | [Campaigns](campaigns.md) | Clustering and campaign-wide sweeps | | [User Reports](user-reports.md) | The abuse mailbox and the report SLA queue | +| [Sample Submission](sample-submission.md) | Opt-in: send LimaCharlie a copy of one message the engine got wrong, and withdraw it | | [Detections & Verdicts](detections.md) | How a verdict is produced, and what the rules can read | | [Custom Rules](custom-rules.md) | Writing, validating and backtesting your own mail rules | | [IOC & Reputation Feeds](ioc-feeds.md) | Mirroring a threat feed into a lookup and matching messages against it | diff --git a/docs/email-security/messages.md b/docs/email-security/messages.md index 1f817c0a4..f0244c94f 100644 --- a/docs/email-security/messages.md +++ b/docs/email-security/messages.md @@ -260,6 +260,11 @@ limacharlie mailsec message action \ Actions require `mailsec.act`. +Two more actions, `submit_sample` and `withdraw_sample`, do not touch the message's +placement: they copy it to LimaCharlie to help improve detection, or delete that copy. +They are opt-in, only a person can run them, and alert-only mode does not withhold them. See +[Sample Submission](sample-submission.md). + To act on many messages at once — a filtered page of this queue, or a selection you built elsewhere — see [Bulk Remediation](remediation.md). It is the same executor and the same audit trail, with a preview and a confirmation over the set @@ -290,8 +295,10 @@ provider outage — pass a new `attempt` token. ### Enforcement -In an alert-only organization, actions from **every source**, including analysts, -are recorded but withheld. The response reports `force_required: true`. +In an alert-only organization, actions that change a mailbox, from **every +source**, including analysts, are recorded but withheld. (`submit_sample` and +`withdraw_sample` change no mailbox and are not withheld; see +[Sample Submission](sample-submission.md#alert-only-mode-does-not-apply).) The response reports `force_required: true`. To perform that action deliberately, repeat the request with JSON `force: true`, use the console's explicit override confirmation, or pass `--force` in the CLI: diff --git a/docs/email-security/policy.md b/docs/email-security/policy.md index ac0cfcd8c..78fe18488 100644 --- a/docs/email-security/policy.md +++ b/docs/email-security/policy.md @@ -9,7 +9,7 @@ fleet-wide policy are a script, not a UI workflow. | Hive | Records | Purpose | |---|---|---| | `mailsec_provider` | one per mail connection | which tenant to protect, with which credential — see [Connecting Providers](providers.md) | -| `mailsec_policy` | many, discriminated by `policy_type` | automations, exclusions, VIPs, thresholds, banners, retention, reporter replies, hunt defaults, clustering | +| `mailsec_policy` | many, discriminated by `policy_type` | automations, exclusions, VIPs, thresholds, banners, retention, reporter replies, sample submission, hunt defaults, clustering | | `dr-mail` | one per rule | all mail rules, including installed defaults — see [Custom Rules](custom-rules.md) | @@ -39,7 +39,7 @@ How each type composes: | `exclusions` | Concatenated — a set of independent suppressions | | `vips` | Union, deduplicated and sorted | | `thresholds` | Last writer wins per field, with the ordering invariant re-checked afterwards | -| `banners`, `reporter_reply`, `hunt_defaults`, `clustering` | Last writer wins per field | +| `banners`, `reporter_reply`, `sample_sharing`, `hunt_defaults`, `clustering` | Last writer wins per field | | `retention` | **Minimum** wins — the shortest horizon for each field; see [Retention](#retention) | ### Unknown fields are refused @@ -571,6 +571,8 @@ A tenant purge permanently deletes, for one organization: - user (abuse-mailbox) reports - stored raw messages and their parsed copies - link-detonation results +- sample submissions: every message your analysts copied to LimaCharlie, and its + metadata (see [Sample Submission](sample-submission.md)) - the organization's Email Security provider connection and policy configuration It also **stops the mail connections at Microsoft 365 and Google Workspace**, so @@ -749,6 +751,32 @@ Template keys must be verdicts. Values are plain text (no `<` or `>`), capped at --- +## `sample_sharing` + +Lets your analysts copy one message at a time to LimaCharlie so detection can +improve. See [Sample Submission](sample-submission.md) for what is kept, where, +for how long and how to withdraw. + +```yaml +policy_type: sample_sharing +enabled: true +``` + +| Field | Default | | +|---|---|---| +| `enabled` | `false` | Opt-in. Submitting copies a message to LimaCharlie, so without this record (or with `enabled: false`) every submit request is refused | + +The record is closed: `enabled` is the only field, unknown fields are refused, and +a record that sets nothing is refused. A suggested record name is +`sample-sharing`. Turning it on requires `mailsec.set` and the organization Owner's +`billing.ctrl` and `user.ctrl` authority. Turning it off needs only `mailsec.set`: +write `enabled: false` on an active record without expiry. Removing, disabling or +expiring an override requires Owner authority because an earlier enabled record +could become effective. Nothing is ever submitted automatically, and D&R rules, +automations and the AI agent cannot submit even when the record is on. + +--- + ## `hunt_defaults` This legacy record type remains accepted for compatibility, but no current diff --git a/docs/email-security/sample-submission.md b/docs/email-security/sample-submission.md new file mode 100644 index 000000000..c9d5db28b --- /dev/null +++ b/docs/email-security/sample-submission.md @@ -0,0 +1,266 @@ +# Sample Submission + +--8<-- "includes/email-security-beta.md" + +Sample submission lets your analysts send LimaCharlie a copy of a message the +engine got wrong, so detection can improve. It is **off by default**, it is +**never automatic**, and it works **one message at a time**. + +!!! warning "Submitting sends the message to LimaCharlie" + When an analyst submits a message, LimaCharlie keeps a copy of the original + message, including its attachments. The sections below state exactly what is + kept, where, for how long and how access to it is recorded. You can withdraw any submission + at any time, which deletes the copy. + +## Why it exists + +Two kinds of mistakes are worth telling us about: + +- a threat the engine called benign or unknown (a missed threat), and +- a legitimate message the engine flagged (a false positive). + +Sample submission is the way to hand us one of those, with a short reason, +without turning on anything broader. It copies the message to LimaCharlie. It does +not change the verdict on your message. + +## Turning it on + +Sample submission is opt-in per organization. Only the organization Owner can +turn it on. Until you opt in, submit requests are refused and nothing is ever copied. + +Opt in with a `mailsec_policy` record of type `sample_sharing`: + +```yaml +policy_type: sample_sharing +enabled: true +``` + +```bash +echo '{"policy_type": "sample_sharing", "enabled": true}' > opt-in.json +limacharlie hive set --hive-name mailsec_policy --key sample-sharing \ + --input-file opt-in.json --enabled +``` + +| Field | Default | | +|---|---|---| +| `enabled` | `false` | Without a record the feature is off. Set it to `false` on an active record without expiry to turn it off again | + +The record has only that one field. Unknown fields are refused, and a record that +sets nothing is refused. When several records set `enabled`, the last one in +record-name order wins, as with [`reporter_reply`](policy.md#reporter_reply). +See [Policy Reference](policy.md#sample_sharing). Enabling sharing requires +`mailsec.set` and Owner authority (`billing.ctrl` and `user.ctrl`) for the organization. +Anyone with `mailsec.set` can turn it off by writing `enabled: false` on an active +record without expiry. Deleting, expiring or disabling a sample-sharing record requires +Owner authority because removing an override can reveal an earlier enabled record. +Turning sharing off prevents new submissions; existing copies remain until withdrawal +or retention expiry. + +## Who can submit + +Only a person. Submitting and withdrawing need `mailsec.act`, and the request +must come from an analyst in the console or from an API key. +**D&R rules, automations and the AI agent cannot submit or withdraw.** The backend +refuses them, records the refusal as a `failed` action, and returns: + +```text +submit_sample is an analyst action: automation, D&R rules and the AI agent may not send mail to LimaCharlie +``` + +Listing and reading submissions needs `mailsec.get`. + +## Alert-only mode does not apply + +[Alert-only mode](messages.md#enforcement) governs actions that write to a +mailbox, and submitting or withdrawing a sample touches none. So an organization in +alert-only mode can still submit and withdraw samples, and `--force` is not needed +or used. What governs these two actions is the opt-in and the rule that only a +person can run them. + +## What you choose when you submit + +Every submission needs a **category** and a **reason**. + +| Category | Use it when | +|---|---| +| `missed_threat` | We called the message benign or unknown, and it is a threat | +| `false_positive` | We flagged the message, and it is legitimate | +| `other` | Any other detection issue with this message | + +The reason is free text, 1 to 1024 characters after trimming, and is kept with the +submission. Unlike the other message actions, it is required. + +## What is kept + +For each submission, LimaCharlie keeps: + +- **The message itself**: the original raw bytes (RFC 822, including + attachments), compressed and encrypted with AES-256-GCM. The encryption key is + derived per organization from a dedicated LimaCharlie key that is separate from + the key protecting your own stored mail. +- **A metadata row**: the message id, the category, the reason your analyst typed, + your analyst's identity, the time, the verdict, score and matched detection + rule ids at that time, the sender, the subject, the mailbox address and the + size. + +Only the one message you chose is copied. + +## Where it is kept and for how long + +- **Where**: in a LimaCharlie-owned bucket in the **same datacenter and region** + as your organization's Email Security data. It is a separate bucket from the + one that holds your raw messages. +- **How long**: 400 days from the submission. After that the copy is no longer available for + access and is deleted automatically by background cleanup. Your organization's mail retention settings (`message_days` and + `flagged_days` in the [`retention`](policy.md#retention) record) do not apply to + submissions: they neither shorten nor extend that period. +- **Earlier**: any time you withdraw it, or when your organization's Email + Security data is deleted (see below). + +## Access and how you can tell + +Submitted messages are used by LimaCharlie to improve detection. Access is +restricted and every access is recorded. No other customer can see it. + +You can see that record. Each submission carries a `review_count` and a +`last_reviewed_at`, and [`GET /submissions/{submission_id}`](#routes) returns the +up to 200 access timestamps, oldest first; `reviews_truncated` says when +more exist. The total `review_count` and latest `last_reviewed_at` stay accurate. Access is recorded before decryption, +so the count can include attempts that failed to open the copy. The record is a count and +timestamps only: it never names the identity that accessed it. + +## Withdrawing + +You can withdraw at any time, from the console, the CLI or the API, even after +turning sample sharing off, disconnecting the mailbox provider, or expiry of the +message index. Withdrawing +**deletes the stored copy and its metadata** (a hard delete), then writes the +withdrawal to the audit trail. It cannot be undone; to share the message again, +submit it again. + +- Withdrawing by message: `withdraw_sample` on the message, or + `limacharlie mailsec message withdraw-sample `. +- Withdrawing by submission id: `DELETE /submissions/{submission_id}`, or + `limacharlie mailsec submission withdraw `. + +Withdrawing an unknown or already-deleted submission is harmless: the submission +routes answer `withdrawn: false` and nothing is deleted a second time. An expired +submission can still be withdrawn while background cleanup is pending; any +surviving copy and metadata are deleted and the response says `withdrawn: true`. + +## If the organization is deleted + +Deleting the organization (the Email Security tenant purge) deletes all of its +submissions too. The automatic deletion that follows an unsubscribe or a trial +lapse is the same tenant purge, so it deletes submissions as well. See +[Data retention and deletion](policy.md#data-retention-and-deletion). + +## From the command line + +```bash +# Submit one message. Both --category and --reason are required. +limacharlie mailsec message submit-sample \ + --category missed_threat --reason "credential phish we did not flag" + +# Withdraw by message, or by submission id +limacharlie mailsec message withdraw-sample +limacharlie mailsec submission withdraw + +# See what you have sent +limacharlie mailsec submission list +limacharlie mailsec submission list --category false_positive --since 2026-09-01T00:00:00Z --limit 100 + +# One submission, including when LimaCharlie accessed it +limacharlie mailsec submission get +``` + +The CLI checks the category and the reason before sending anything. A refused +submission exits non-zero and prints the reason. See +[Command Line Interface](cli.md). + +## Routes + +All routes are under `/v1/mailsec/{oid}`. See [API Reference](api-reference.md) +for the shared conventions. + +| Route | Does | +|---|---| +| `POST /messages/{msg_uuid}/actions` with `{"action": "submit_sample", "category": ..., "reason": ...}` | Submit one message. `category` and `reason` are required; `attempt` is an optional idempotency token. Requires `mailsec.act` | +| `POST /messages/{msg_uuid}/actions` with `{"action": "withdraw_sample"}` | Withdraw the submission made from this message. `reason` (up to 1024 characters) and `attempt` are optional. Requires `mailsec.act` | +| `GET /submissions` | `{enabled, available, submissions, next_cursor}`. Filters: `category`, `since`, `until` (RFC 3339), `limit` (1-200, default 50), `cursor`. Requires `mailsec.get` | +| `GET /submissions/{submission_id}` | `{submission, reviews}`, where `reviews` is `[{ts}]`, up to 200 recorded accesses. `reviews_truncated` identifies a partial history; count and latest time remain complete. An unknown id is not an error: it returns `{"submission": null, "reviews": []}`, so branch on `null`. Requires `mailsec.get` | +| `DELETE /submissions/{submission_id}` | `{withdrawn: true, submission_id, action_id}`. Hard-deletes the stored copy and its metadata. An unknown or already-deleted id returns `{withdrawn: false, submission_id}` with no `action_id`. An expired id is still cleaned up if its metadata remains. Requires `mailsec.act` | + +`GET /submissions` always returns two flags, so an empty list is never ambiguous: +`enabled` says your organization has opted in, and `available` says your +datacenter has a submissions store. Pagination works as for the other lists: pass +`next_cursor` back as `cursor`, verbatim, until it is empty. + +A submission looks like this: + +```json +{ + "submission_id": "3f1c9b7e5a2d4c8e9a0b1c2d3e4f5a6b", + "msg_uuid": "0057db2b-0000-4000-8000-000000000001", + "category": "missed_threat", + "reason": "credential phish we did not flag", + "actor": "analyst@corp.example", + "ts": "2026-09-30T12:00:00Z", + "expires_at": "2027-11-04T12:00:00Z", + "provider": "m365", + "mailbox_address": "user@corp.example", + "sender_email": "sender@example.net", + "subject": "Invoice overdue", + "verdict": "benign", + "score": 0, + "matched_rules": [], + "size_bytes": 12345, + "review_count": 0 +} +``` + +`last_reviewed_at` is present once an access has been recorded and +omitted before that. + +### Action results and refusals + +A submission goes through the same action route as other message actions and +returns the same result shape. `ok` and `skipped` carry a `submission_id`; +`skipped` means the message already has an active submission, so submitting twice +is safe. A refusal is reported like any other failed action, with one of these +texts in `error`: + +| Error | Meaning | +|---|---| +| `sample submission is not enabled for this organization` | You have not opted in | +| `sample submission is not available in this datacenter` | Your datacenter has no submissions store | +| `the message's raw copy is no longer stored, so it cannot be submitted` | The raw copy aged out or was never stored | +| `submit_sample is an analyst action: automation, D&R rules and the AI agent may not send mail to LimaCharlie` | The request came from automation, a D&R rule or the AI agent | + +A missing or unknown `category`, or a missing or over-long `reason`, is refused +with an HTTP 400 before anything is sent. + +Actions that reach execution are written to the action audit trail. While the +mailbox connection is active, an `EMAIL_ACTION` event also carries `category` and +`submission_id`. Withdrawal after disconnect remains in the action and platform +audit trails. Submitting and withdrawing also write `mailsec_sample_submitted` and `mailsec_sample_withdrawn` +platform audit events. + +## FAQ + +**Does submitting change my verdicts?** No. It copies the message to LimaCharlie; +it does not revise the verdict or move the message. + +**Is anything submitted automatically?** No. Nothing is submitted unless a person +runs the action on a message, and only after your organization has opted in. + +**Is it per message?** Yes. Each submission is one message. There is no bulk or +campaign form. + +**Can I see when the copy was accessed?** Yes: the access count and timestamps are +shown on every submission. The accessing identity is not shown. + +**Can I take it back?** Yes, at any time. Withdrawing deletes LimaCharlie's copy +and its metadata. + +**Can another customer see it?** No. diff --git a/mkdocs.yml b/mkdocs.yml index 9ce0471cb..433a113f9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -603,6 +603,7 @@ nav: - Bulk Remediation: email-security/remediation.md - Campaigns: email-security/campaigns.md - User Reports: email-security/user-reports.md + - Sample Submission: email-security/sample-submission.md - Detections & Verdicts: email-security/detections.md - Mail Rules: email-security/custom-rules.md - Rule Reference: email-security/rule-reference.md