docs: carlos store scan, and what a clean verdict does not mean
The whole scan family under the store section, plus the three console environment variables. The part that matters most to an app author reading only this page: a scanner cannot read ciphertext. Client-side encryption is the default posture across this platform family, so someone could reasonably turn scanning on, see it succeed, and believe their objects had been examined. The docs say plainly that a clean result on an encrypted store means nothing, and that the attestation exists because neither answer is safe to guess. Also documented: the coverage line names two numbers and they are not complements; enabled is not enforcing. Deviation from the plan, recorded here rather than silently: the plan said to add the environment variables to README.md too. README carries no store environment variables at all — no store section — so a scanning block there would be the only store content in the file. They are documented in the CLI reference beside CARLOS_STORE_OPERATORS, which is where the store's other operator variable already lives.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJkJkxZZLgW8X8A5tk69Sq
1 file changed,
+62
−0
docs/site/cli.md+62 −0
diff --git a/docs/site/cli.md b/docs/site/cli.md| index 22561c166..d38f49c5d 100644 |
| --- a/docs/site/cli.md |
| +++ b/docs/site/cli.md |
| @@ -510,6 +510,68 @@ deployment's own cloud account, so it also wants an address on |
| stranger still meets the same 404 as everyone else rather than a 403 that |
| would confirm the app exists. |
| +#### carlos store scan |
| + |
| +Virus scanning over the objects in an app's store. **Off by default and |
| +opt-in**: a deployment that sets nothing has no engine, runs no sweep, and |
| +these verbs refuse with a sentence naming the variable that would wire one up. |
| + |
| +```sh |
| +carlos store scan request --app hello --plaintext |
| +carlos store scan status --app hello |
| +``` |
| + |
| +`request` is the member's verb, and it takes an attestation you cannot skip: |
| +either `--plaintext` or `--client-encrypted`. **A scanner cannot read |
| +ciphertext.** If your app encrypts objects before uploading them — which is |
| +the default posture across this family of platforms — then no engine can say |
| +anything about their contents, and a result claiming they are clean would be a |
| +true-looking sentence about bytes nobody examined. Attesting |
| +`--client-encrypted` records that honestly: the store is marked not |
| +applicable, no sweep runs, and `status` says so instead of showing coverage. |
| +There is no default answer because neither one is safe to guess. |
| + |
| +The remaining verbs belong to a deployment operator on |
| +`CARLOS_STORE_OPERATORS`, the same list `carlos store grant` uses: |
| + |
| +| verb | what it does | |
| +| --- | --- | |
| +| `grant --app <a>` | turn the engine on for this store | |
| +| `revoke --app <a>` | turn it off, and clear enforcement | |
| +| `sweep --app <a>` | run one budget of scanning now, instead of waiting for the hourly pass | |
| +| `enforce --app <a> on` | deny the app's own credentials read access to any object the engine tagged infected | |
| +| `enforce --app <a> off` | lift that denial | |
| + |
| +`revoke` and `enforce off` work even when no engine is configured. That is |
| +deliberate: unsetting the engine on a deployment with an enforcing store would |
| +otherwise leave the denial in place with no way to clear it. |
| + |
| +**What `status` will and will not claim.** The coverage line always names two |
| +numbers — how many objects were scanned, and how many were not. They are not |
| +complements. An object too large to stream past the engine was never judged at |
| +all, and a bucket full of them could report zero infections while nothing had |
| +actually been looked at. A sweep still in progress says so and says which |
| +numbers are stale; a first sweep that has not finished claims no coverage at |
| +all. Signature-database age is printed, and called out when it is old, because |
| +a clean verdict from three-month-old definitions is a weaker claim than a |
| +fresh one. |
| + |
| +**Enabled is not enforcing.** With scanning on but enforcement off, infected |
| +objects are recorded and still readable. `carlos store status` says which of |
| +the two you have. |
| + |
| +The operator side is three environment variables on the console: |
| + |
| +| variable | default | meaning | |
| +| --- | --- | --- | |
| +| `CARLOS_STORE_SCANNER` | unset | `clamav` is the only engine this build accepts. Unset means no scanning at all. | |
| +| `CARLOS_CLAMD_SOCKET` | `/run/clamav/clamd.ctl` | the unix socket to reach `clamd` on | |
| +| `CARLOS_CLAMD_MAX_BYTES` | 25 MB | objects larger than this are recorded as skipped without being fetched; mirror `clamd`'s own `StreamMaxLength` | |
| + |
| +Setting `CARLOS_STORE_SCANNER` without `CARLOS_STORE_BACKEND` is refused at |
| +boot — a scanner with no object stores to scan is a misconfiguration, and one |
| +loud failure beats a pass that silently finds nothing forever. |
| + |
| ### carlos email |
| Tenant email sending. `carlos email enable` is the whole path in one |