# Scan policies

Configure immutable upload size, detected-type and ZIP-member policies.

Each project has one versioned policy. Change it in **Dashboard → Settings**.
Every new upload captures a complete snapshot. Editing the project does not
change an existing upload, its transfer target, or the rules used when it is scanned.
An idempotent replay returns the original upload and snapshot, even if the project
has since become stricter. Create a new logical upload to use the new policy.

## Size and file types

`max_size_bytes` is inclusive, from 1 through 52,428,800 bytes (50 MiB).
New admission above the project maximum returns `413 policy_size_limit_exceeded`
without issuing a target or reserving quota. Transfer and inspection independently
enforce the stored snapshot; the actual transferred size must also equal the
declared size. Inspection size violations use `size_limit_exceeded`.

Both the final filename extension and the detected content type must be allowed.
Allowing `.zip` does not implicitly allow every type inside it. The API accepts
only the supported catalog, with unique lowercase extensions and canonical MIME
values. An empty allowlist denies all files in that scope.

`application/octet-stream` is a neutral **declaration**, not a detected-type
allowlist value. It never overrides detection or policy checks. An incompatible
extension or MIME declaration produces `type_extension_mismatch`; a disallowed
detected type produces `type_not_allowed`. Detection identifies supported formats;
it is not full document/schema validation or a guarantee of harmless content.

## ZIP member rules

`archive.allowed_extensions` and `archive.allowed_types` apply to each standalone
file in a ZIP, including nested ZIPs. Detection uses extracted bytes, not just the
member name. Members are read to completion, with size and CRC validation, through
bounded temporary storage. Corrupt or truncated archives fail inspection.

Nested ZIPs are disabled by default. When enabled, they must also be permitted by
both member allowlists. Limits apply across the whole archive tree:

| Setting                 | Platform maximum/default               |
| ----------------------- | -------------------------------------- |
| `max_entries`           | 1,000 entries, including directories   |
| `max_expanded_bytes`    | 209,715,200 bytes (200 MiB), combined  |
| `max_entry_bytes`       | 52,428,800 bytes (50 MiB)              |
| `max_depth`             | 3, with the outer container at depth 1 |
| `max_compression_ratio` | 100:1, per entry and combined          |

Projects can lower these ceilings but cannot raise them. Nested container bytes
and their expanded members both count toward combined expansion. Unsafe paths,
symlinks, encryption and limit violations are never treated as clean.

Office OOXML files are ZIP containers, but their internal XML, relationships and
VBA parts are not standalone uploads. Member allowlists do not apply to those
parts; archive integrity and resource limits still do. An Office file inside a ZIP
must itself pass the ZIP-member type rules, encryption and macro checks. Embedded
objects under OOXML `embeddings/` parts are currently unsupported and rejected
with `type_not_allowed`. These rules do not promise content-type classification
of every embedded stream in legacy Office or PDF documents.

## Mandatory security checks

Malware scanning and encrypted-content rejection cannot be disabled. The macro
policy is `reject` by default; `allow` permits macros but never skips malware
scanning. RAR, 7z, TAR and gzip are not in the configurable catalog.

## Update through the dashboard API

`PUT /v1/projects/:projectId/scan-policy` requires the owner's Auth0 access token,
not a project API key. Cross-account projects return `404 project_not_found`.
The v2 request is a complete replacement; include every field:

```json
{
  "schema_version": 2,
  "allowed_extensions": ["pdf", "png", "zip"],
  "macro_policy": "reject",
  "max_size_bytes": 10485760,
  "allowed_types": ["application/pdf", "image/png", "application/zip"],
  "archive": {
    "allowed_extensions": ["pdf", "png"],
    "allowed_types": ["application/pdf", "image/png"],
    "allow_nested": false,
    "max_entries": 100,
    "max_expanded_bytes": 52428800,
    "max_entry_bytes": 10485760,
    "max_depth": 3,
    "max_compression_ratio": 100
  }
}
```

The response is `{ "scan_policy": { "version": 2, ... } }`, with the current
project version rather than a fixed value. Only a semantic change increments
`version`; reordering an allowlist does not. `schema_version` identifies the policy
format and is independent of the project's revision number. Unknown settings,
duplicates and limits outside the platform ceilings return `400`.

Legacy two-field updates (`allowed_extensions`, `macro_policy`) remain supported
and preserve existing extended rules. Legacy upload snapshots remain unchanged;
they do not acquire ZIP-member allowlists retroactively. Rules are captured when
creation reads the project policy, so an edit racing with creation may result in
either complete revision, never a mixture of individual fields.
