File security pipeline: security model
This page describes the security guarantees of the pipeline that scans every file before Docling, OCR, media storage, transcription and RAG: quarantine, ClamAV, YARA-X, verdicts, blocking, locked rules, roles and evidence. Engine installation, settings and signature updates are in the File security guide. Code: services/control-plane/src/eai/domains/file_security, migration sql/postgres/V199, screen portal/src/screens/file-security.tsx.
Version française : ../file-security-pipeline.md
Mandatory chain
upload -> quarantine -> clamav -> yara_x -> validation -> docling -> rag
- Quarantine. The
quarantinedrecord is committed in its own transaction before any analysis: a crash leaves the file quarantined, never released. - Builtin checks. Type detected from the content (
eai.domains.file_security.checks), allowed types, archives (depth 3 at most, 10,000 members at most), Office macros, mismatch between declared and real type. - ClamAV then YARA-X. Each engine is called over HTTPS (
EAI_CLAMAV_URLS,EAI_YARAX_URLS, Bearer keys, CAEAI_FILE_SCAN_CA_FILE) through the egress guard. Several endpoints are tried in turn; a failed endpoint goes last forEAI_FILE_SCAN_ENDPOINT_DOWN_SECONDS. - Decision. The most severe verdict wins; only
cleanreleases the file (released), any other blocking verdict blocks it (blocked). - Scan clearance. Only a released scan mints a
ScanClearancesealed with a process secret and bound to the exact digest of the bytes. Docling, OCR,store_media, the media adapters and transcription callclearance.requirebefore reading the bytes: an unscanned file cannot reach them, even through a code mistake.
No transaction is open during a scan (a scan inside an open db.tx raises NestedScan). Not files, so outside the pipeline: realtime PCM16 audio frames, POST /v1/encryption/encrypt (bytes encrypted and returned, never parsed) and connector tests.
Verdicts
| Verdict | Meaning | Status |
|---|---|---|
malware | ClamAV signature or YARA-X malware rule | blocked (always) |
suspicious | Suspicious YARA-X rule or builtin check | blocked when the rule blocks |
unscannable | Encrypted file, limits exceeded, broken archive, size above the ceiling | blocked (always) |
policy_violation | Type not allowed, archive refused, macro, type mismatch | blocked |
scan_error | Every endpoint of an engine failed | blocked (fail closed) |
clean | No blocking finding | released |
Precedence (PRECEDENCE): malware, suspicious, unscannable, policy_violation, scan_error. A YARA-X rule with action = "report" is logged without blocking.
Locked rules and tenant policy
The locked rules are constants of the code (LOCKED in eai.domains.file_security.policy), never stored nor writable: scan before any ingestion, both engines (clamav, yara_x) required, fail closed, malware and uninspectable files blocked. An attempt to change them answers 422 file_security_rule_locked.
A tenant can only tighten the other settings (GET /v1/file-security/policy, PUT /v1/file-security/policy):
| Setting | Default |
|---|---|
max_file_bytes | EAI_FILE_SCAN_MAX_BYTES (never above) |
allowed_types | every known type except executable |
allow_archives | true |
block_office_macros | true |
block_type_mismatch | true |
retain_samples | true |
quarantine_retention_days | 30 |
max_signature_age_hours | 48 |
stale_signatures | warn (block refuses every scan while the signatures are too old) |
Enforcement mode
EAI_FILE_SCAN_MODE=enforce: both engines required; none reachable gives 503 (fail closed). Mandatory in production: the startup guard refuses another mode, an engine URL outsidehttps://and a missing Bearer key.dev(refused in production): an unconfigured engine is notednot_configured; a configured engine applies exactly as inenforce.
Blocked files
- The sample of a blocked file is kept encrypted (tenant key
file-security-quarantine,eaienc1envelope) whenretain_samplesis true; a released file never leaves a sample. POST /v1/file-security/scans/{scan_id}/rescanscans the retained sample again in a new record (rescan_of); it never releases the original upload (409file_sample_unavailablewithout a sample).DELETE /v1/file-security/scans/{scan_id}/sampledeletes the sample at once (audited). Thefile_scan_samples_purgeworker job deletes samples pastquarantine_retention_days.
Roles
| Operation | Route | Roles |
|---|---|---|
| Engine status | GET /v1/file-security/status | tenant_admin, security_officer, auditor |
| Read the policy | GET /v1/file-security/policy | tenant_admin, security_officer, auditor |
| Update the policy | PUT /v1/file-security/policy | security_officer, tenant_admin |
| Scan without ingesting | POST /v1/file-security/scans | any tenant member |
| List, read scans | GET /v1/file-security/scans, GET /v1/file-security/scans/{scan_id} | readers: all; others: their own scans |
| Rescan, purge the sample | POST /v1/file-security/scans/{scan_id}/rescan, DELETE /v1/file-security/scans/{scan_id}/sample | security_officer, tenant_admin |
| Statistics | GET /v1/file-security/stats | readers: all; others: their own scans |
Evidence and detection
- Every scan is audited (
file_security.scan) with the source (knowledge,media,multimodal,transcription,connector,channel,api), the digest, the verdict and the reasons. - A blocked file raises the security event
security.file.blocked(orsecurity.file.scan_unavailablewhen the engines did not answer); the SOC audit scanner turns it into a detection for themalwareandsuspiciousverdicts (soc-runbook.md). - The
file_scan_probejob (EAI_WORKER_FILE_SCAN_PROBE_SECONDS) measures engine status and signature age.
Components
| Component | Manifest | Pinned version | Licence |
|---|---|---|---|
| ClamAV (clamd, freshclam, HTTPS gateway) | catalog/components/clamav.yaml | 1.4.6 | GPL-2.0-only |
| YARA-X (rule worker) | catalog/components/yara-x.yaml | 1.22.1 | BSD-3-Clause |
Both are installed by the Navin Manager; see the File security guide.