
Keep a file unavailable until its scan finishes
Accepting a file and allowing people to use it are different steps. Wildo can place an uploaded file into a scanning state before making it available, then release or quarantine it according to the result.
The field chooses whether scanning is required or skipped; configured scanner infrastructure supplies the checks. Standard forms show that processing is still underway and wait before adding the new file to the record. A rejected file stays out of the record, with an error the person can act on.
Example — A document waits for a clean result
A contract finishes uploading while its scan is still running. The standard form keeps the new attachment pending and blocks saving until processing resolves. A clean result lets the person save the record with its attachment. An infection or scanner error leaves the file unavailable; the person can discard the rejected selection and choose another file.
For engineers
Declare policy and provision its scanner
Use the file’s scanPolicy for an explicit requirement or exception. An unspecified field inherits the application’s configured scanner posture. The backend reads this decision in file-scanning.backend.service.ts; the separate global predicate is included, with intervening commentary omitted.
shouldScan(fileMetadata?: FileSchemaMetadata): boolean {
const scanPolicy = fileMetadata?.scanPolicy;
if (scanPolicy === 'required') return true;
if (scanPolicy === 'skip') return false;
return this.isGlobalScanningEnabled();
}
isGlobalScanningEnabled(): boolean {
if (!this.appConfigService.isInitialized()) return false;
return this.clamavScanner.isConfigured() || this.yaraScanner.isEnabled();
}
Move into scanning before dispatching work
The upload handler awaits scan initiation before returning upload acceptance and file metadata. Upload acceptance does not mean the scan has finished: file.readiness reports whether the returned file can be attached. Inside the scan service, the status transition occurs before asynchronous scanner execution. This excerpt omits validation above and the error-logging callback below.
await this.filesService.updateFileStatus(
fileId,
FileStatus.SCANNING,
executionContext,
);
if (onClean) {
this.onCleanCallbacks.set(fileId, onClean);
}
this.logDebug('File transitioned to SCANNING, dispatching scan', { fileId });
Distinguish a clean result from a failed check
Configured ClamAV signature scanning runs first; enabled YARA pattern scanning follows if the earlier layer has not refused the file. The pipeline requires at least one configured scanner when scanning is requested. Startup validation reports incompatible required-field and scanner configurations, and the runtime also refuses to certify a file clean when no scanner is available.
A clean verdict transitions the file to CLEAN and releases configured follow-on work. An infection moves it through INFECTED toward QUARANTINED; quarantine keeps the stored bytes but blocks normal download. A scanner or storage error moves it to FAILED, requiring recovery or re-scan. The same state checks stop linking while scanning. Standard file controls refresh pending metadata and publish the new reference only after it becomes ready; the form also preserves the processing error through submit validation. Scanning reduces malware risk; it does not validate the business meaning or confidentiality of a document.
Read readiness before attaching a file
Upload responses include file.readiness; the authorized metadata endpoint returns the same FileMetadata contract. The standard single- and multiple-file controls handle this for local files and provider selections. Custom upload interfaces must inspect readiness rather than treat HTTP success as permission to attach.
file.readiness value | Meaning for the form |
|---|---|
ready | The file may be submitted as a new attachment, subject to server authorization |
processing | Refresh metadata; do not submit the new reference yet |
attached | Retain an existing attachment; this does not authorize attaching it to another record |
unavailable | Do not submit the new reference; show the failure and allow another selection |
Readiness describes attachment eligibility, not download authorization. The backend still checks access and lifecycle state when linking or serving a file. Polling observes a scan; it does not start a new scan or automatically retry a failed one. With scanning skipped or inactive, an ordinary completed upload can already report ready.
Match field policy to the startup contract
| Field policy | Required setup | Result |
|---|---|---|
required | Configured ClamAV; YARA alone does not satisfy startup validation | The upload must pass scanning |
| Unspecified / inherited | Application scanner posture; configured ClamAV or enabled YARA activates global scanning | Inherits that posture |
skip | Explicit field exception | Skips this scanning path |
| Scan outcome | File state and evidence |
|---|---|
| Clean | CLEAN, with configured follow-on work released; this path does not emit a separate clean audit event |
| Malware | Infection/quarantine handling and a malware audit event |
| Scanner or storage failure | FAILED and failure evidence; an error is not a clean verdict |
Use status for current download eligibility and the emitted failure/malware events for investigation. The absence of a clean-event row must not be interpreted as proof that no scan ran.
Connect a required field to the deployed scanner
This illustrative attachment field requires scanning, independently of the inherited application posture:
import { z_file } from '@wildo-ai/zod-decorators';
const attachment = z_file({
multiple: false,
allowedMimeTypes: ['application/pdf'],
scanPolicy: 'required',
});
Provision a ClamAV daemon reachable from the backend, then supply its address through the application’s environment setup. For example, these values assume the backend can resolve a service named clamav; setting them does not install or start the daemon:
CLAMAV_HOST=clamav
CLAMAV_PORT=3310
The configuration loader seeds fileScanning.antivirus; the scanner reads that resolved configuration. Startup validation checks required fields against ClamAV configuration. A configured address is not proof that the scanner is healthy: the upload path still needs the daemon to accept and complete a scan.
For the additional YARA layer, provision the YARA scanning service and its rules, then configure both the enable flag and its reachable address:
YARA_ENABLED=true
YARA_HOST=yara
YARA_PORT=8080
Those names are illustrative deployment addresses. The YARA service consumes fileScanning.yara.enabled and fileScanning.yara.connection; enabling it without a host is a configuration problem. YARA alone does not satisfy the current startup requirement for a field marked required.
| What you configure | What to verify |
|---|---|
| Required field plus ClamAV | Startup accepts the configuration; an uploaded file moves through processing to a clean or refused outcome |
| Additional YARA service | Enabled flag, reachable service, and the intended rules; a clean ClamAV result does not bypass an enabled second layer |
| Custom upload interface | Poll authorized metadata while readiness is processing; submit the reference only when ready |
| Scanner outage | The file must not be treated as clean merely because the upload request succeeded |
Retain the uploaded file ID while observing processing. Repeated metadata reads observe the existing scan; uploading again creates another upload rather than repairing the first scan.