Skip to main content

File security: ClamAV and YARA-X before any ingestion

French version: file-security. Decision: ADR-0166. API reference: control-plane-v1.

No file uploaded by a user, an agent or a connector reaches Docling, OCR, media storage, transcription, RAG or a model before it has been scanned by ClamAV (antivirus) and YARA-X (detection rules). Everything is installed and updated locally: no signature and no rule is read from the Internet.

1. Mandatory pipeline​

Upload → Quarantine → ClamAV + YARA-X → Validation → Docling → RAG / AI

StageWhat happens
UploadAn ingestion route receives the bytes of the file (base64 or multipart).
QuarantineA scan record is created in the quarantined state before the content is read; a crash leaves it in quarantine, never released.
ClamAV + YARA-XBuiltin checks (size, real type read from the bytes, encrypted file, archive, nesting), then both engines, both mandatory.
ValidationThe verdict and the tenant policy decide: released only when everything is clean, otherwise blocked. A release yields a scan clearance bound to the sha256 of the bytes.
DoclingConversion, OCR, media storage and transcription require this clearance: without it, or for other bytes, they refuse to run.
RAG / AIThe extracted text still goes through the existing screening (prompt injection markers, hidden text, RAG quarantine): the antivirus does not protect against prompt injection.

Routes covered: POST /v1/knowledge-bases/{kb_id}/documents, POST /v1/media, POST /v1/multimodal/vision, POST /v1/multimodal/stt, POST /v1/multimodal/diarize, POST /v1/multimodal/dlp, POST /v1/multimodal/images/edit, POST /v1/multimodal/video/process and POST /v1/gateway/audio/transcriptions. A blocked file gives 422 file_blocked (details.scan_id, details.verdict, details.reasons); no reachable engine gives 503 file_scan_unavailable.

2. Installation by the Navin Manager​

Both services are in the Security and policies category of the catalog: navin-manager/catalog/services/clamav.yaml and navin-manager/catalog/services/yara-x.yaml.

ServiceRoleLicenceModesOfficial documentation
ClamAV 1.4.6 LTSAntivirus: viruses, trojans, malicious macros, infected files, archivesGPL-2.0-only (separate process, exception LEX-022)standalone, replicated (2 hosts or more)https://docs.clamav.net/
YARA-X 1.22.1Detection by rules, signatures and suspicious patternsBSD-3-Clause (exception LEX-023)standalone, replicated (2 workers or more)https://virustotal.github.io/yara-x/

On every ClamAV host, clamd only listens on the loopback and a local socket: its protocol has neither TLS nor authentication. An HTTPS gateway authenticated by key only forwards INSTREAM, PING and VERSION. freshclam only reads the internal mirror declared in the mirror_url parameter (https required, no default value). Plan about 3 GB of memory per host for the full database. The YARA-X worker serves its compiled rules over HTTPS, with the same certificate authority as the Manager console.

ClamAV is installed by the podman method with the official image docker.io/clamav/clamav:1.4.6 (and a pinned Python image for the gateway), served by the Manager mirror: the distribution packages do not pin version 1.4.6 and their units point to the public update servers. YARA-X is installed by the python method (wheel yara-x==1.22.1 from the mirror). The offline bundle of the Manager accepts the signature databases (--clamav-databases) and the rules (--yara-rules), checked by verify-bundle.

The navin-enterprise service requires both bindings: the Manager renders for the platform the addresses of every instance, the keys and the certificate authority, and imposes EAI_FILE_SCAN_MODE=enforce.

3. Platform settings​

SettingRole
EAI_FILE_SCAN_MODEenforce (mandatory in production, required by the startup guard) or dev (development: an engine that is not configured is recorded not_configured, a configured engine applies as in enforce).
EAI_CLAMAV_URLSClamAV gateways separated by commas, all https; a load balancer address is accepted too.
EAI_CLAMAV_API_KEYKey of the ClamAV gateways.
EAI_YARAX_URLSYARA-X workers separated by commas, all https.
EAI_YARAX_API_KEYKey of the YARA-X workers.
EAI_FILE_SCAN_CA_FILECertificate authority (PEM) of the gateways and workers.
EAI_FILE_SCAN_TIMEOUT_SECONDSTimeout of one scan per engine (60 s by default).
EAI_FILE_SCAN_MAX_BYTESSize ceiling of the platform; a tenant policy can only lower it.

4. Verdicts and blocks​

VerdictCauseResult
cleanNo engine and no check flags the file (findings of rules in report mode are logged)released
malwareClamAV signature or YARA-X rule with a malware verdictblocked
suspiciousOffice macro (block_office_macros), PUA, suspicious YARA-X ruleblocked
unscannableEncrypted file or archive, scan limits exceeded, broken executable, excessive nestingblocked
policy_violationSize, type not allowed, declared type different from the real type, archive forbiddenblocked
scan_errorError or timeout on every instance of an engineblocked

Precedence goes from malware to clean in the order of the table. Samples are only kept for blocked files, encrypted at rest, purged after quarantine_retention_days; a released file is never kept by this pipeline.

The navin-baseline YARA-X rules block by default: the EICAR test file, Office documents with a VBA project or an auto-executing macro, PDFs with JavaScript started by an action (fillable PDF forms with formatting scripts included), PDFs with a Launch action or an auto-run embedded file, Windows executables embedded in a document, booby-trapped OLE objects in RTF files, HTML smuggling and scripts that download then run a payload (PowerShell, Windows Script Host, shell). To only log a family of files that is legitimate at the bank, publish in the internal repository a version of the rules where that rule carries action = "report".

ClamAV only reads the start of a ZIP member bigger than MaxFileSize without reporting it: the ClamAV gateway and the platform read the central directory of every ZIP (zip, docx, xlsx, pptx, odt...) before the scan and classify a member or a total beyond the limits (EAI_FILE_SCAN_ARCHIVE_MAX_MEMBER_BYTES, EAI_FILE_SCAN_ARCHIVE_MAX_TOTAL_BYTES) as unscannable, so that no part of a file goes through unscanned.

5. Tenant policy​

GET /v1/file-security/policy gives the effective policy; PUT /v1/file-security/policy (security_officer, tenant_admin) changes it and audits the change. Locked rules cannot be changed (422 file_security_rule_locked): scan before any ingestion, both engines mandatory, fail closed, malware and uninspectable files blocked.

{"max_file_bytes": 26214400, "allow_archives": false, "block_office_macros": true,
"block_type_mismatch": true, "retain_samples": true, "quarantine_retention_days": 30,
"max_signature_age_hours": 48, "stale_signatures": "block"}

stale_signatures set to block blocks every scan while the oldest ClamAV database is older than max_signature_age_hours; set to warn, the age is only reported in the status and the alerts.

6. Controlled updates of signatures and rules​

From the service sheet in the Navin Manager console:

CommandServiceEffect
signature_statusclamavVersion and date of the databases installed on each host.
update_signaturesclamavfreshclam from the internal mirror only, then clamd reload.
import_signaturesclamavOffline import of a directory: each database is checked by sigtool before an atomic install, then clamd reloads.
rules_statusyara-xVersion, sha256 and number of loaded rules.
update_rulesyara-xDownload from the internal repository (rules_repository_url, https), check of the expected sha256, compilation in a staging area, atomic switch; any failure keeps the active rules.
rollback_rulesyara-xBack to the previous version of the rules.

The internal signature mirror is prepared as described in https://docs.clamav.net/appendix/CvdPrivateMirror.html. GET /v1/file-security/status shows for each instance the signature version, its age and the rules version.

7. Logging and alerts​

  • Every scan is a record (GET /v1/file-security/scans, GET /v1/file-security/scans/{scan_id}) with the sha256, the real type, the result of each engine and the reasons, and an audit event.
  • Every block emits the security event security.file.blocked and a SOC detection; an engine outage emits security.file.scan_unavailable.
  • Metrics: eai_file_scans_total, eai_file_scan_duration_seconds, eai_file_scan_engine_up, eai_file_scan_signature_age_hours, eai_file_scan_engine_errors_total.
  • Alert rules shipped by the Manager (navin-manager/catalog/templates/prometheus/rules.yml.j2): NavinFileMalwareBlocked, NavinFileScanEngineDown, NavinFileScanSignaturesStale, NavinFileScanBlockedSpike.
  • GET /v1/file-security/stats summarises a window (24 h by default); POST /v1/file-security/scans/{scan_id}/rescan scans a kept sample again without ever releasing the original upload; DELETE /v1/file-security/scans/{scan_id}/sample purges the sample.

The File security screen of the portal gathers the pipeline, the engine status, the scans, the blocked files, the policy and the test of a file (POST /v1/file-security/scans).

8. High availability​

With two ClamAV gateways and two YARA-X workers or more (replicated mode), the platform client tries each instance in turn and sets a failed instance aside for a short time: losing one host does not block ingestion. Losing every instance of an engine blocks ingestion (503), as intended. Place the instances on distinct hosts and fault domains; the HA checks of the Manager verify it at install time.

9. SDKs​

from enterprise_ai import EnterpriseAI

client = EnterpriseAI()
print(client.file_security.status()["ready"])
scan = client.file_security.scan(open("contract.pdf", "rb").read(), filename="contract.pdf")
print(scan["status"], scan["verdict"], scan["reasons"])

The TypeScript (client.fileSecurity), Java (client.fileSecurity()) and Go (client.FileSecurity) SDKs expose the same methods: status, policy, scan, list, detail, rescan, sample purge and statistics.

10. Procedures​

The screen is Security and governance > File security (portal/src/screens/file-security.tsx), open to platform_admin, tenant_admin, security_officer and auditor, with the tabs Engines, Analyses, Blocked files and Rules.

  1. Check that the platform is ready to scan. Uploads are refused while the engines do not answer (fail closed), so check the engines before opening ingestion to users.

    Screen: File security > Engines. API: GET /v1/file-security/status.

    Expected result: the mode is enforce, the status is Ready, every ClamAV gateway and YARA-X worker is Up and the signatures are not stale.

    If it fails: "Not configured" means the engine is not installed or its address is missing (install it with the Navin Manager); "Down" on every instance means ingestion answers 503 file_scan_unavailable; "Stale signatures" means running update_signatures from the service sheet of the Navin Manager.

  2. Test a file. The file goes through quarantine, ClamAV, YARA-X and the rules like an upload, but it is never ingested.

    Screen: File security > Test a file, choose the file, Analyse. API: POST /v1/file-security/scans.

    Expected result: the verdict and the reasons; Open the analysis shows the result of each engine.

    If it fails: 503 file_scan_unavailable (no engine reachable); a verdict other than clean is the expected answer for a dangerous file, for example the EICAR test file is blocked.

  3. Review a blocked file. A blocked file stays in quarantine with its encrypted sample when retain_samples is on.

    Screen: File security > Blocked files, open the analysis, then Rescan or Purge the sample. API: POST /v1/file-security/scans/{scan_id}/rescan, DELETE /v1/file-security/scans/{scan_id}/sample.

    Expected result: a rescan creates a new analysis without ever releasing the original upload; a purge deletes the sample for good, the record and its verdict stay.

    If it fails: 409 file_sample_unavailable means no sample is kept (already purged or retain_samples off).

  4. Change the tenant rules. Only a security officer or a tenant administrator changes them, and every change is audited.

    Screen: File security > Rules, change the rules of the tenant, Save the rules. API: PUT /v1/file-security/policy.

    Expected result: "Rules saved."; GET /v1/file-security/policy returns the new effective policy.

    If it fails: 422 file_security_rule_locked means a locked rule (scan before ingestion, required engines, fail closed, block malware, block unscannable files); 422 invalid_request names the field refused (details.field), for example a value outside its bounds or an unknown type.

11. Troubleshooting​

StatusCodeCauseFix
422file_blockedthe file was blocked (details.verdict, details.reasons)read the analysis details.scan_id; a legitimate family is allowed only by a rules version with action = "report"
503file_scan_unavailableno instance of an engine answerscheck the engines tab, restart the instances with the Navin Manager
422file_security_rule_lockeda locked rule was changedkeep the locked rules, change only the rules of the tenant
409file_sample_unavailableno sample kept for this analysisupload the file again
403tenant_requireda call without a tenantcall with a tenant credential
404not_foundunknown analysischeck scan_id and the tenant

12. Next steps​