OS Analyzer Evidence Semantics (Non-Language Scanners)
This document defines the evidence contract produced by OS/non-language analyzers (apk/dpkg/rpm/pacman + Windows/macOS OS analyzers) so downstream SBOM/attestation logic can rely on stable, deterministic semantics.
Evidence Paths
OSPackageFileEvidence.Pathis rootfs-relative and normalized:- No leading slash (
/). - Forward slashes only (
/), even on Windows inputs. - Never a host path.
- No leading slash (
- Any analyzer-specific absolute path must be converted to rootfs-relative before emission.
- Helper:
StellaOps.Scanner.Analyzers.OS.Helpers.OsPath.TryGetRootfsRelative(...).
- Helper:
Examples:
- Good:
usr/bin/bash - Bad:
/usr/bin/bash - Bad:
C:\scans\rootfs\usr\bin\bash
Layer Attribution
OSPackageFileEvidence.LayerDigestis best-effort attribution derived from scan metadata:ScanMetadataKeys.LayerDirectories(optional mapping of layer digest → extracted directory)ScanMetadataKeys.CurrentLayerDigest(fallback/default)
- Helper:
StellaOps.Scanner.Analyzers.OS.Helpers.OsFileEvidenceFactory.
Digest & Hashing Strategy
Default posture is avoid unbounded hashing:
- Prefer package-manager-provided digests when present (
OSPackageFileEvidence.Digests/OSPackageFileEvidence.Sha256). - Compute
sha256only when:- No digests are present, and
- File exists, and
- File size is ≤ 16 MiB (
OsFileEvidenceFactorysafeguard).
- Primary digest selection for file evidence metadata prefers strongest available:
sha512→sha384→sha256→sha1→md5
Analyzer Warnings
OS analyzers may emit AnalyzerWarning entries (Code, Message) for partial/edge conditions (missing db, parse errors, unexpected layout).
Normalization rules (in OsPackageAnalyzerBase):
- Deduplicate by
(Code, Message). - Stable sort by
CodethenMessage(ordinal). - Cap at 50 warnings.
OS Analyzer Caching (Surface Cache)
Linux OS analyzers (apk/dpkg/rpm/pacman) support safe, deterministic reuse via ISurfaceCache:
- Cache key:
(tenant, analyzerId, rootfsFingerprint)under namespacescanner/os/analyzers. - Fingerprint inputs are intentionally narrow: a single analyzer-specific “DB fingerprint file”:
apk:lib/apk/db/installeddpkg:var/lib/dpkg/statusrpm:var/lib/rpm/rpmdb.sqlite(preferred) or legacyPackagesfallbackpacman:var/lib/pacman/local/ALPM_DB_VERSION(preferred) or the lexicographically lowestdescfile as a fallback
- Fingerprint payload includes:
- Root path + analyzerId
- Relative fingerprint file path
- File length +
LastWriteTimeUtc(ms) - Optional file-content sha256 when the file is ≤ 8 MiB
Worker wiring:
StellaOps.Scanner.Worker.Processing.CompositeScanAnalyzerDispatcherrecords cache hit/miss counters per analyzer.
RPM sqlite Reader Notes
When rpmdb.sqlite is present, the reader avoids SELECT * and column scanning:
- Uses
PRAGMA table_info(Packages)to select a likely RPM header blob column (prefershdr/header, excludespkgIdwhen possible). - Queries only
pkgKey+ header blob column for parsing.
Pacman (ALPM) Analyzer Notes
Pacman packages are emitted by StellaOps.Scanner.Analyzers.OS.Pacman.PacmanPackageAnalyzer.
Database layout
- The analyzer walks
var/lib/pacman/local/<name>-<ver>/and parses two files per package:desc(always required) — newline-separated%KEY%\nvalue(s)\n\nblocks.files(optional) — same%KEY%block format; carries%FILES%and%BACKUP%.
- Each block is introduced by a line of the form
%KEY%and terminated by a blank line. %BACKUP%rows are<rootfs-relative path>\t<md5sum>; the path identifies configuration files.
Parsed keys
%KEY% | Surfaced as |
|---|---|
%NAME% | OSPackageRecord.Name |
%VERSION% | OSPackageRecord.Version (raw <pkgver>-<pkgrel>) |
%ARCH% | OSPackageRecord.Architecture (falls back to any when blank, matching ALPM’s noarch convention) |
%LICENSE% | OSPackageRecord.License joined with AND after ordinal sort |
%PROVIDES% | OSPackageRecord.Provides (deduplicated, ordinal-sorted) |
%DEPENDS% | OSPackageRecord.Depends (deduplicated, ordinal-sorted) |
%REPLACES% | vendorMetadata.replaces (comma-joined, ordinal-sorted) |
%CONFLICTS% | vendorMetadata.conflicts |
%OPTDEPENDS% | vendorMetadata.optDepends |
%GROUPS% | vendorMetadata.groups |
%BASE% | OSPackageRecord.SourcePackage and vendorMetadata.base |
%DESC%/%URL%/%PACKAGER%/%BUILDDATE%/%INSTALLDATE%/%SIZE%/%REASON%/%VALIDATION% | vendorMetadata.{description,homepage,packager,buildDate,installDate,installedSize,reason,validation} |
Unknown %KEY% | vendorMetadata.pacman:<lowercase-key> |
PURL
pkg:alpm/<distribution>/<name>@<version>?arch=<architecture><distribution>resolution order:metadata["os.distribution"]if forwarded by the worker.- The
ID=line of<rootfs>/etc/os-releasewhen present. - Literal
archfallback.
File evidence
%FILES%rows are surfaced asOSPackageFileEvidenceentries with a leading/. Directory entries (trailing/in pacman’s listing) are preserved with the same convention used by the apk analyzer.%BACKUP%paths setOSPackageFileEvidence.IsConfigFile = true.- Pacman
desc/filesdo not carry per-file digests, so digests are computed lazily byOsFileEvidenceFactoryonly when the actual file exists in the rootfs snapshot.
Determinism
- Package directories are enumerated in ordinal order before parsing, then the final record list is re-sorted by
OSPackageRecord.CompareTo(PURL, then Name/Version/Architecture). - Lists (
Provides,Depends,groups,replaces,conflicts,optDepends) are deduplicated and sorted ordinally before emission. - Timestamps from pacman metadata (
%BUILDDATE%,%INSTALLDATE%) are surfaced as-is invendorMetadataso they participate in golden snapshots; the analyzer itself does not stamp the clock.
