Clean Framework · DOCUMENTATION
Definition qualified scanning and quarantine hardening
Definition qualified scanning and quarantine hardening
Bounded candidate reference — 2026-10-20: 0.2.0-rc.1 is committed locally through 84f415b3098af5653577b2c65eea31fca21df79c, unpublished and not production-approved; stable PHP >=8.5.11 <8.6.0 is required. This is documentation, not adoption: this knowledge base retains its installed 0.1.0-rc.1 dependency and PHP 8.2.11 local profile. Dated PHP 8.2 evidence is historical; earlier dirty-source reports retain their original test provenance, not retroactive clean-commit proof. Candidate examples are illustrative: API signatures were reviewed, but candidate code was not executed by this editorial update. See current checkpoint, open criteria and adoption boundary.
CF-SEC-Q03 is bounded local security work, not a production release. Current results and remaining requirements are in BUILD_LOG.md, the issue register and initial state. The original 148 PASS / 15 TO VERIFY / four N/A matrix is not reclassified.
Additive definition qualification
ClamAvMalwareScanner retains its constructor and operator-owned freshness contract. Existing integrations do not silently gain feed qualification. Applications may explicitly bind QualifiedClamAvMalwareScanner instead. It creates the existing adapter using the exact database path from a ClamAvDefinitionProfile; an unrelated delegate/database cannot be substituted. Neither class starts services, downloads definitions or enables a module.
An operator first verifies the selected engine/feed's provenance, integrity, current security/distribution status and update procedure. The operator then supplies an exact definition path, explicit SHA256 pins and qualification start and expiry instants. The application must receive these from trusted deployment configuration, never HTTP input or unreviewed hashes computed at startup.
// These values come from an operator-reviewed immutable manifest. This example
// does not generate pins from whichever definitions happen to be installed.
$profile = new \Clean\Framework\Storage\ClamAvDefinitionProfile(
databasePath: $approvedDefinitionDirectory,
fileSha256: $approvedDefinitionHashes,
qualifiedAt: $approvedQualificationInstant,
expiresAt: $approvedExpiryInstant,
clock: new \Clean\Framework\Support\SystemClock(),
maximumDefinitionBytes: $approvedDefinitionByteBudget
);
// Only compose inside an already qualified isolated worker. Binding the
// scanner grants no route access, module activation or publication authority.
$scanner = new \Clean\Framework\Storage\QualifiedClamAvMalwareScanner(
runner: new \Clean\Framework\Media\ProcessGroupMediaToolRunner(['/usr/bin/clamscan']),
profile: $profile
);The profile supports a single file or a flat set of at most 32 pinned files, with an explicit total read budget no greater than 512 MiB. No unexpected entries, symlinks, hardlinked files, noncanonical paths, untrusted ownership or group/other-writable entries are accepted. The immediate parent is checked as well. Root or the scanner UID may own the trusted entries. Each admitted file is streamed at most its observed size plus one byte and compared with the pin, with metadata checked again afterward. There is no cached-clean allowance.
The window must be currently valid before and after both hashing and scanning. Changed or expired qualification returns clean() === false and fixed reason scan.definitions_unqualified. Failed native results retain their safe denied codes. Missing/corrupt definitions, unknown output, exceptions and timeouts never imply clean. Profile checks stream all pins twice per scan; choose a worker/resource budget accordingly. No request-time optimization may renew or cache trust beyond the selected immutable profile/window.
File mtime is not proof of feed age. Touching a file cannot extend approval. Matching a pin does not prove vendor signatures, scanner effectiveness, a successful updater or current advisories. The operator must stop intake before expiry/update failure, atomically install a separately verified set, issue a new bounded profile and explicitly restart/recompose the intended workers. The framework does not automate those host operations. Independent review and selected-host qualification remain open; no production feed was used locally.
Quarantine and private publication
The existing state machine remains HELD → SCANNING → RELEASING → RELEASED. Required policy and audit checks are unchanged. Private file metadata and captured size/digest are revalidated after inspection and again after the release-audit callback. Index/lock files must be private regular owned files with one link; unsafe existing modes/aliases are refused, not repaired.
Private local storage now adds the complete file with a same-directory no-replace hardlink, then removes its private staging name. Random keys alone do not prevent check-then-rename races. The filesystem must support this primitive; there is no replacing-rename fallback. An exception after linking can mean an object exists without acknowledgement. Quarantine therefore keeps RELEASING and requires independent storage/audit reconciliation; do not reset, retry, delete the source or claim the effect absent automatically.
A scanner interruption can leave SCANNING; interrupted deletion can leave DELETING. Such states require a separately authorized operator procedure, not blind replay. These are deliberate fences, not automated restoration features. Roots remain non-public and dedicated; no held-byte delivery API was added.
Private directory/file checks are not protection against arbitrary code with the same UID. Keep untrusted jobs separate from quarantine-index authority, use read-only inputs, a finite no-network non-root container, quotas, deadlines and an init reaper. Same-session cancellation is locally tested; an escaped session still requires separately approved whole-container lifecycle proof.
Local commands and authority
Use the existing explicit local socket and cached reviewed native image:
php bin/isolated-acceptance --socket=/Users/dan/.docker/run/docker.sock --image=sha256:45cc8e8e1e906cc768adeaee6b65f8e426b67de034c902daf85a75ffe51d5bd0 --gate=scanner
php bin/isolated-acceptance --socket=/Users/dan/.docker/run/docker.sock --image=sha256:45cc8e8e1e906cc768adeaee6b65f8e426b67de034c902daf85a75ffe51d5bd0 --gate=scanner-quotaThe first gate runs scanner-only deterministic/native/HTTP upload checks, no video tools. The second uses the owner's separately approved dedicated 8 MiB private noexec tmpfs and strict kernel guard; it never fills a host disk or starts SQL. Missing fixture authority or quota means BLOCKED, never a fallback. Actual ClamAV uses harmless authored signatures only, not a production feed, malware or EICAR. General verification also runs deterministic Q03 regressions. No commit, deployment, live integration, reviewer contact or release is implied.
Editorial provenance
Source: docs/security-q03/README.md. Source SHA-256: 17bfa85a53a792d47729c44b455ad7cf92f0cfc301598d6a27cd52291bba60ca.
Framework candidate reference; editorial checkpoint 2026-10-20. See candidate status and adoption boundary.