Skip to content
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@
- The deprecated, ignored `--banner` flag and `banner=` argument are removed.
Needs an API release that serves `POST /banner/preview` and the `text` field.

### Email Security — customer sample submission

- New `Mailsec.submit_sample`, `withdraw_sample`, `list_submissions`,
`get_submission` and `withdraw_submission`, and the CLI commands
`mailsec message submit-sample <msg_uuid> --category missed_threat|false_positive|other --reason "..."`,
`mailsec message withdraw-sample <msg_uuid>`, and
`mailsec submission list|get|withdraw`. An organization that has opted in
(a `mailsec_policy` record of type `sample_sharing`) can copy one message
at a time to LimaCharlie to help improve detection; the copy is deleted after
400 days or as soon as it is withdrawn, and `submission get` shows when
the stored copy was accessed. Category and reason are checked locally, a
refused submission exits non-zero, and `Mailsec.act_on_message` refuses
`submit_sample` so the category and reason cannot be skipped. Needs an API
release that serves the new routes.

### Cloud Security — code-scan pushes retry when the service is busy

- `CloudSec.ingest_code_results` (and so `cloudsec code ingest` and
Expand Down
2 changes: 1 addition & 1 deletion doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
| [Hive & Data Stores](cli/hive-data.md) | hive, secret, lookup, playbook, note, sop, adapter, cloud-sensor, extension |
| [Infrastructure](cli/infrastructure.md) | sync, output, artifact, payload, yara, integrity, logging, exfil |
| [Cloud Security & Code Security](cli/cloud-security.md) | cloudsec (findings, inventory, graph, compliance, CAASM, code lane, container images, fleet, exports) |
| [Email Security](cli/email-security.md) | mailsec (onboarding, coverage, triage, EML, remediation, campaigns, reports, rules, tenant purge) |
| [Email Security](cli/email-security.md) | mailsec (onboarding, coverage, triage, EML, remediation, campaigns, reports, sample submission, rules, tenant purge) |
| [Other Commands](cli/other-commands.md) | api, arl, usp, spotcheck, job, schema, completion, help/discover |

## SDK Reference
Expand Down
2 changes: 1 addition & 1 deletion doc/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ limacharlie schema dr create
| [Hive & Data Stores](hive-data.md) | hive, secret, lookup, playbook, note, sop, adapter, cloud-sensor, extension |
| [Infrastructure](infrastructure.md) | sync, output, artifact, payload, yara, integrity, logging, exfil |
| [Cloud Security & Code Security](cloud-security.md) | cloudsec (findings, inventory, graph, compliance, CAASM, code lane, container images, fleet, exports) |
| [Email Security](email-security.md) | mailsec (onboarding, coverage, triage, EML, remediation, campaigns, reports, rules, tenant purge) |
| [Email Security](email-security.md) | mailsec (onboarding, coverage, triage, EML, remediation, campaigns, reports, sample submission, rules, tenant purge) |
| [Other Commands](other-commands.md) | api, arl, usp, spotcheck, job, schema, completion, help/discover, case |

## See Also
Expand Down
34 changes: 32 additions & 2 deletions doc/cli/email-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

Install or upgrade with `python -m pip install --upgrade limacharlie`. See [installation](../getting-started.md#installation) for setup.

Commands for the LimaCharlie Email Security surface: mailbox coverage, the message triage queue and its drawer, the justified raw-EML download, analyst verdict revision, per-message and bulk remediation at the provider, campaigns, sender profiles, the action audit trail, the abuse-mailbox report queue, standalone EML analysis, custom-rule validation and backtest, the connection preflight, and the tenant purge.
Commands for the LimaCharlie Email Security surface: mailbox coverage, the message triage queue and its drawer, the justified raw-EML download, analyst verdict revision, per-message and bulk remediation at the provider, campaigns, sender profiles, the action audit trail, the abuse-mailbox report queue, customer sample submission, standalone EML analysis, custom-rule validation and backtest, the connection preflight, and the tenant purge.

Four permissions rather than the usual get/set pair, because the product asks to be trusted with four different things:

| Permission | Grants |
|---|---|
| `mailsec.get` | Read the product's own view: the queue, the drawer, campaigns, senders, the audit trail |
| `mailsec.set` | Change detection behaviour and triage state |
| `mailsec.act` | Remediate live mail, revise verdicts and test provider connections |
| `mailsec.act` | Remediate live mail, revise verdicts, submit and withdraw samples, and test provider connections |
| `mailsec.get.eml` | Download original message bytes; also requires `mailsec.get` and a logged justification |

Connection testing and verdict revision require `mailsec.act`. Connection records
Expand Down Expand Up @@ -197,6 +197,36 @@ limacharlie mailsec action get <ACTION_ID>

A sweep's `--reason` lands on the sweep's own record and on every member's audit row. Repeating a sweep is idempotent per member, so a double run collapses onto the rows it already wrote; `--attempt` is how you ask for a deliberate second run — a retry after a provider outage recorded *beside* what failed rather than over it. It is an opaque handle, at most 128 characters, refused rather than truncated. Neither field is part of the confirmation token, so adding either one after previewing does not invalidate it.

## Sample submission

An organization can opt in to let its analysts copy **one message at a time** to LimaCharlie so detection can improve. It is off by default, never automatic, and only a person can do it: D&R rules, automations and the AI agent are refused.

**Submitting sends the message to LimaCharlie.** The original message (attachments included) is stored, compressed and encrypted, in a LimaCharlie-owned bucket in the same datacenter as your Email Security data, with a metadata row: the message id, your category and reason, your identity, the time, the verdict, score and matched rule ids at that time, the sender, subject, mailbox address and size. It is deleted automatically after 400 days. Submitted messages are used by LimaCharlie to improve detection. Access is restricted and every access is recorded; `submission get` shows you how many times and when. **Withdraw at any time**: the stored copy and its metadata are deleted.

The organization Owner can opt in with a `mailsec_policy` record of type
`sample_sharing` (`mailsec.set`, `billing.ctrl` and `user.ctrl` for the organization).
Anyone with `mailsec.set` can opt out by writing `enabled: false` on an active
record without expiry. Deleting, disabling or expiring a sharing record requires
Owner authority because an earlier enabled record can become effective.

```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
```

Submitting and withdrawing need `mailsec.act`; listing and reading need `mailsec.get`. `--category` and `--reason` are both required (reason: 1 to 1024 characters, kept with the submission). The categories are `missed_threat` (we called it benign or unknown and it is a threat), `false_positive` (we flagged it and it is legitimate) and `other`.

```bash
limacharlie mailsec message submit-sample <MSG_UUID> --category missed_threat --reason "credential phish we did not flag"
limacharlie mailsec message withdraw-sample <MSG_UUID>
limacharlie mailsec submission list
limacharlie mailsec submission list --category false_positive --since 2026-09-01T00:00:00Z --limit 100
limacharlie mailsec submission get <SUBMISSION_ID> # includes when LimaCharlie accessed it
limacharlie mailsec submission withdraw <SUBMISSION_ID>
```

A refused submission (the organization has not opted in, the datacenter has no submissions store, or the message's raw copy is no longer stored) is reported like any other failed action, with the reason in `error`; the command prints the reason and exits non-zero. Submitting a message that already has an active submission returns `result: skipped` with the existing `submission_id`. `submission list` always returns `enabled` (the organization opted in) and `available` (the datacenter has a store), so an empty list can be told apart from a feature that is off. It is paginated: pass `next_cursor` back as `--cursor`, verbatim, until it is empty; `--limit` is 1 to 200. `reviews` in `submission get` is one timestamp per recorded access, never the accessing identity. Access is recorded before decryption and can include failed attempts. At most 200 timestamps are returned, with `reviews_truncated` indicating a partial history; counts and latest time remain complete. An unknown id is not an error: `submission get` returns `submission: null` and `submission withdraw` (or withdrawing an already-deleted submission) returns `withdrawn: false`, and the command says so on stderr. Withdrawal remains available after opt-out, provider disconnect or message-index expiry. An expired submission is still cleaned up if metadata remains.

## Reports, analysis & rules

```bash
Expand Down
2 changes: 2 additions & 0 deletions doc/sdk/security-products.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ print(mail.analyze(eml_b64=encoded, org_domains=["corp.example"]))

`mailsec.get` permits structured reads, `mailsec.set` changes triage and rules, and `mailsec.act` remediates provider mail, revises verdicts, and tests connections. Original-byte downloads require both `mailsec.get` and `mailsec.get.eml`. Provider records use `mailsec_provider.*` and credentials use `secret.*`; policy and `dr-mail` Hives reuse `mailsec.get/set`.

An organization that has opted in (a `mailsec_policy` record of type `sample_sharing`) can copy one message at a time to LimaCharlie to improve detection. `submit_sample(msg_uuid, category, reason)` needs `mailsec.act`, sends the original message to LimaCharlie (deleted after 400 days, or on `withdraw_sample(msg_uuid)` / `withdraw_submission(submission_id)`), and raises `ValueError` for an unknown category (`missed_threat`, `false_positive`, `other`) or a reason that is blank or over 1024 characters. A refusal comes back as `result: "failed"` with `error`, not as an exception. `list_submissions()` (paginated, always returns `enabled` and `available`) and `get_submission()` (includes `reviews`, one timestamp per recorded access to the copy) need `mailsec.get`.

Start with `alert_only`. Manual provider actions need an explicit `force=True` override in that mode; inspect the action and audit outcome. Bulk and campaign actions use preview and confirmation so the executed selection matches what was reviewed. See the [Email Security CLI reference](../cli/email-security.md) for these workflows, verdict revisions, campaigns, user reports, and offboarding.

## Pagination and filters
Expand Down
Loading
Loading