Testing and trust
Trust in an evidence tool has to be earned mechanically, not claimed. This page describes how SBOMFlow is tested, what the checks actually verify, how the product behaves when inputs go wrong, and how you can re-run the verification yourself. All figures below were produced by running the listed commands against the current source tree; re-run them to check us.
| Lab area | Cases | Passed | Failed |
|---|---|---|---|
| customer-isolation | 1 | 1 | 0 |
| determinism | 1 | 1 | 0 |
| identity-accuracy | 8 | 8 | 0 |
| launch-readiness | 3 | 3 | 0 |
| license-accuracy | 1 | 1 | 0 |
| release-gate | 10 | 10 | 0 |
| resilience | 10 | 10 | 0 |
| security-redaction | 10 | 10 | 0 |
| usability-contracts | 8 | 8 | 0 |
| vulnerability-accuracy | 9 | 9 | 0 |
| Total | 61 | 61 | 0 |
Current verified figures#
| Check | Result |
|---|---|
| Offline test suite | 28,600+ tests, passing (optional-extra paths are reported as explicit skips — never hidden, and a guard test fails if any test hard-imports an optional extra, so the suite stays green on a stdlib-only interpreter); a drift test fails if this figure falls behind the real suite |
| Tests an interpreter did not run | Counted and named per optional extra, and printed beside each result. A green run states how many tests it held back and which extra would release them, so "the suite passed" cannot be read as "the suite ran everything". A missing extra is kept separate from every other kind of skip, because only the first is recoverable by installing something |
| Required runtime dependencies | 0 — standard library only; optional extras are exactly that, and a guard test fails if importing any runtime module would pull one in |
| Offline demo | Full evidence pack from a bundled example, no network |
| Warning catalog | 600+ stable codes, every one documented; the docs page is generated from the catalog and a test fails on drift |
| Generated files | 14 mandatory artifacts on every successful analysis run (analyze/audit; a run that fails early writes no output directory at all, and other commands write no evidence pack), pinned by a count-free regression test and each structurally validated or exact-content checked. The total is INPUT-DEPENDENT: a richer project adds conditional artifacts, plus the deterministic .sbomflow-output ownership marker. For reference, the bundled example writes 27 top-level files, recomputed by running the example rather than quoted; a minimal single-manifest project writes fewer, because the conditional artifacts a richer project triggers are simply absent. Counting basis is top-level files — a recursive count is one higher, because approved decisions land in an applied-decisions/ subdirectory |
| Clean-environment install | Wheel builds and installs into a fresh virtual environment with no network and no dependencies, and the installed CLI runs the demo |
What the test suite actually covers#
- Parser tests — positive fixtures for every supported manifest, lockfile, and embedded build format, plus negative and malformed fixtures for each.
- Malformed-input behaviour — broken JSON/YAML/CSV/archives must produce a cataloged warning with the exact path, never a crash and never silence.
- Resource-bound tests — oversized archive members, deep nesting, and decompression limits are enforced and surfaced as warnings.
- Memory budgets are enforced, not just measured — a dedicated lane runs a deterministic synthetic scan and fails on a gross regression against a committed budget for peak process memory and for a per-phase peak that names which phase grew. The thresholds carry large cross-machine headroom, so the lane alarms on a real regression rather than on runner variance.
- Identity tests — version epochs, vendor suffixes, prereleases, range specifiers, container digests, and same-name/different-origin components must survive unchanged; a structural-garbage guard is tested against both garbage and legitimate unusual values.
- Determinism tests — repeat runs under a fixed
--as-ofmust be byte-identical, including the evidence-bundle ZIP. Determinism is asserted across machines, not just reruns: an environment matrix re-runs a full analysis under four timezone × locale combinations (UTC through Kiritimati and Anchorage; the C locale through a comma-decimal one) and requires every produced artifact — HTML report, PDF summary,issues.csv, and the JSON surfaces — to have identical SHA-256 digests. Source guards keep it that way by construction: library code never touches the locale, the renderers use no locale-aware number formatting or locale-dependentstrftimedirectives, and they never read the wall clock. - Time-independence guard — determinism is asserted across time as well as locale: no test can expire with the wall clock, and the guard that enforces it is itself time-independent, so its own verdict cannot drift either.
- Boundary tests — reviewer status defaults, VEX justification enforcement, self-approval rejection, gate/VEX consistency, and draft watermarks are asserted directly.
- Integrity tests — the audit-log hash chain, artifact hashes, and cross-artifact references are validated, and corrupted or tampered outputs must fail validation.
- Crash-consistency tests — every consequential human-decision write is crash-tested at every interruption point in its transaction protocol (and at interruption pairs for concurrent operations), asserting after each that state is old-complete or new-complete, the audit log carries at most one event per operation, and recovery either converges or refuses visibly.
- Stateful property testing (optional dev lane) — beyond the enumerated schedules, a separate lane drives randomized operation sequences — decision writes, idempotent retries, crashes and recoveries against a real store, and the full waiver lifecycle — under a fixed, reproducible profile. It needs one optional dev-only package and is never part of the zero-dependency baseline.
- Error-contract tests — every CLI error code renders with a fix and a docs link, and every code has a matching section in the error reference.
- Every example printed by the CLI is executed, including from an installed wheel — every
sbomflow …line an operator can copy off a--helppage, asbomflow helppage, thesbomflow examplesoutput or the generated CLI reference pages is one example in the command catalog, and the suite runs every one of them in a fresh directory with the network poisoned. Each must return the exit it declares and be seen to do what it says — write the files it names (a JSON one must parse) or print the text it names — so an exit code alone never counts as proof. Every example for a command that serves any requirement regime runs a second time against a product configured withrequirement_regime: noneand must keep its exit.make cli-examples-checkthen builds the wheel, installs it into a clean offline environment and runs the same examples through the installedsbomflow, reportingN of Nwith N counted from the catalog. This is a statement about SBOMFlow's own tests: it says the documented commands work as printed, and nothing about any product they are run on. - Docs and site checks — every relative documentation link must resolve, and the public build is checked against a fail-closed allowlist so internal material cannot be published.
How CVE and advisory matching is tested#
Matching a component to a CVE is where a scanner earns or loses your trust: a false positive wastes a reviewer's afternoon, and a false negative is the one that matters. Both directions are tested, and both failure modes are handled conservatively rather than guessed.
- Against false positives — a withdrawn advisory (OSV
withdrawn) or a rejected one (NVDRejected) is excluded from active findings and recorded in the scan's provenance so the exclusion stays auditable. A disputed advisory is not dropped: it stays active and is flagged for a human. If an advisory'swithdrawnvalue is itself malformed, the finding stays active and warns (advisory_withdrawn_malformed) — bad data never silently removes a finding. - Against false negatives — pinned known-affected oracles run the full local matching path (package URL → version → advisory affected-range) against a pinned advisory snapshot, and assert that each oracle's affected version matches while its paired fixed version does not.
- Against quiet guessing — when two versions cannot be ordered under a component's ecosystem scheme, or an advisory range cannot be resolved locally, the finding is surfaced for human verification with lowered match confidence. An unresolvable advisory range also carries the
advisory_range_unresolvedwarning. It is never silently asserted as affected, and never silently dropped. - Component identity — a package-URL conformance corpus pins how versions, epochs, vendor suffixes, and qualifiers are parsed, so the coordinate a CVE is matched against does not drift between releases.
A CVE match is an observation, never a decision. Suppressing a finding — not_affected, fixed, or a waiver — comes only from human review, never from the matcher.
Synthetic stress testing#
Beyond unit tests, SBOMFlow is exercised against an in-house laboratory of ten synthetic connected-device manufacturer estates — from a small sensor company to multinational industrial estates — with realistic build systems, supplier evidence, release rooms, deliberately malformed files, planted operational inconsistencies, held-out answer keys, and positive and negative controls.
Runs against this corpus are checked by an executable acceptance contract (complete workflow coverage across all ten businesses) and a per-fact semantic evaluation that fails on any silent omission, false merge, false positive, missing provenance, or overclaim.
What the acceptance contract does not check. It runs on an interpreter with no optional packages installed at all, so it proves the standard-library-only baseline on every machine — and the surfaces that need an optional package are not exercised there. In particular, JSON-Schema validation of emitted SBOMs against the vendored official CycloneDX and SPDX schemas runs in its own conformance lane, which installs the schema validator into a throwaway environment and validates against the pinned official schemas. The acceptance run states this in its own output rather than leaving a green result to be read as wider than it is. The reason is cost, and it is measured: schema-validating the largest corpus outputs takes minutes per output, which does not belong in a per-change gate. That cost is tracked as a defect in its own right, not treated as settled.
AI surface hardening and its measured budgets#
The AI scan runs on the default analyze/audit path, so every hostile shape below reaches it on an ordinary run with no flag. Three adversarial corpus cases cover them, each with the control that keeps a refusal-only suite honest:
| Hostile input | What must happen |
|---|---|
| A SafeTensors header declaring more bytes than the file holds | never produces a bytes-based claim; it may still be inventoried, but identified_by reads extension_only |
A pickle wearing a .onnx name | recorded and hashed as unsupported, deliberately never parsed |
| Empty and truncated model files | inventoried or skipped, never guessed at, never fatal |
| An archive whose compressed size hides a large declared one | never expanded to look inside for models |
| A model symlinked to a target outside the scan root | not followed; nothing behind it reaches any artifact |
| A manifest with an unknown field, a traversal or absolute path, an embedded credential, a malformed digest, or an unknown schema version | each refused with its JSON pointer, and no refusal echoes the offending value |
| Positive control — a well-formed model in the same hostile tree | still identified from its bytes |
| Negative control — a tree with no models at all | still writes the record, still says a scan ran, and still refuses to read as "this product has no AI" |
Resource limits are measured, not assumed — the numbers below come from running the shipped code against a generated tree, not from reading the source:
| Measurement | Observed | Budget |
|---|---|---|
| Peak allocation hashing a 64 MiB artifact | 2.1 MiB | 4 MiB — a whole-file read would show up as a peak near the file size |
| Peak allocation scanning a realistic tree (12 models, 300 other files) | 1.2 MiB | 8 MiB |
| Directory entries walked for that tree | 324 for 312 files | under 2× the file count |
| Models identified in that tree | 12 of 12, walk complete | — |
Memory is measured per allocation, never as the process-wide high-water mark, which is contaminated by whatever ran before it. No budget here is a wall-clock assertion: performance budgets are expressed in work — bytes per read, entries walked — which is a property of the algorithm rather than of the machine's load, so they hold on any machine. Elapsed times are recorded for a human and asserted on by nothing.
A traversal budget that fires is never silent: the walk stops or prunes, names the budget in bounds_hit, and the inventory is marked PARTIAL. A depth bomb prunes its own branch without erasing its siblings, so planting one deep chain cannot turn into an inventory-erasure primitive.
Latest full validation (July 2026)#
The most recent end-to-end validation pass ran every tier of this laboratory and then went further than "the commands exited zero":
- Business-scale advisory controls. One estate's full scan (about 4,900 components) is fed a deterministic offline advisory snapshot using the official ecosystem naming conventions, with positive and negative controls — advisories that must match and one that must not — so "producing more findings" can never fake a better score. Unreviewed findings stay
under_investigation(the reviewer boundary is asserted, not assumed). - Every artifact re-checked from outside. All generated outputs across all ten estates were re-parsed and reconciled independently: product identity agreement, count reconciliation across SBOM/VEX/issue surfaces, every bundle and sharing-pack hash recomputed, no forbidden conformity language, and no leaked secrets or machine-specific paths on shareable surfaces.
- Hostile and offline pressure. A network tripwire fails any accidental connection attempt; mutation self-checks deliberately corrupt outputs and require the oracles to catch each corruption, proving the assertions are not vacuous.
The acceptance contract passes in full on the current release. The full offline suite total is stated once, at the top of this page, and guarded there — repeating it here is how it went stale before.
This process found real defects — which is its job. Those that matter to users are fixed with regression coverage in the current release and recorded in what's new; one example:
- Very large recognized lockfiles (over the general text-scan limit) previously produced no components and no explanation. Recognized structured inputs now parse up to a much higher dedicated cap, and anything beyond it is hashed and surfaced with a documented resource warning and a concrete remedy — never silently dropped. See known limitations for the current caps and a measured large-input example.
Be clear about what this is and is not:
- It is synthetic. Every company, product, and file is fictional; sample advisories are labelled non-real.
- It is not real-customer validation, and it is not proof of conformity or universal format compatibility.
- Its purpose is reproducible engineering stress testing — large realistic inputs, hostile file shapes, and honest scoring of what was detected, warned, or left to human review.
How SBOMFlow behaves when things go wrong#
| Situation | Behaviour |
|---|---|
| Malformed input file | Cataloged warning with the exact path; the scan continues |
| Recognized but unsupported evidence | Hashed into the artifact manifest and surfaced with a stable warning — visible, never silent |
| Warnings you cannot accept in CI | --strict[=codes] exits 5 after writing all artifacts, so evidence is never lost to a policy |
| Enforced gate blocks a release | Exit 1 with the exact blocking reason recorded; every artifact is still produced |
| Output directory corrupted or edited | sbomflow validate fails with exit 4 and names the mismatch |
| Audit log tampered | The hash chain breaks; validation fails and new decisions are refused |
| Reviewer tries to approve their own submission | Rejected — separation of duties is enforced |
| Human VEX decision | Applies only with a valid justification and only to its scope; invalid not_affected is downgraded and stays visible |
| Symlink escaping the scan root | Refused with a warning — outside content is never read into evidence |
| Optional tool or extra missing | Explicit "skipped" note; baseline operation continues. In the test lanes the skip is also counted and attributed to the extra that would release it, so a passing run cannot imply it exercised a surface it never reached |
Telling whether an audit was changed#
SBOMFlow is built to answer "was this audit changed after the fact?" honestly. Every audit is tamper-evident: the audit log is an append-only hash chain and each artifact carries a SHA-256, so if the recorded evidence was changed, sbomflow validate fails (exit 4) and new reviewer decisions are refused. To see what changed between two audits of successive releases, compare their release records — drift lists the components, findings, and gaps that changed.
Verify it yourself#
Everything below runs offline against an installed build:
- Run
sbomflow analyzetwice over the same tree with the same--as-of; the artifacts are byte-identical. - Run
sbomflow validateon an output directory, then edit one artifact and run it again: exit4, naming the mismatch. - Run
sbomflow verify-bundle --layeredon a bundle, then on a copy with one member altered: the content layer fails and the bundle is NOT TRUSTED. - Run
sbomflow doctorto see the local environment facts a run relies on.
The synthetic laboratory's acceptance contract and per-fact semantic evaluation run offline as part of the same trust gauntlet; they are available under the private-review process.
How the v0.5.5 build was verified#
v0.5.5 is available to approved private testers. The build handed to them is a certified private v0.5.5 tester build with no known release-blocking defects, subject to the documented restrictions. This section states what that sentence rests on, and — equally important — what it does not.
Release identity#
| Fact | Value |
|---|---|
| Release tag | v0.5.5 |
| Release commit | bc402ff3886351f90c8e82912300ddfc54302df0 |
| Release tree | 3f1fefac51b633445568c3a82543fe4fed13cfd8 |
| Release wheel | sbomflow-0.5.5-py3-none-any.whl |
| Wheel SHA-256 | 6744b6ab1c52d27a31b3fbbca606176a1b0b012726760bfaead4ab3651a6ae1e |
These values come from one generated source, so no two published pages can disagree about them.
What was established#
- Every mandatory release check ran and passed. None was skipped, and a skipped check would never have counted as a pass.
- Three independent builds of the same tree produced a byte-identical wheel, compared by SHA-256 — among them builds from two separate exports of the tree that had no part in producing the release artifact.
- A clean-machine install ran with the index and dependency resolution both switched off. That is why "zero required runtime dependencies" is a behaviour here rather than an assertion about a metadata field.
- Zero network markers in the install transcript. The sweep looked for five markers, and each of the five was first calibrated against a transcript of an install that did use the network, so the zero is a measurement rather than a pattern that never fires.
- 501 of 501 wheel entries were inspected. The examined count and the total are the same number, which is the only reason a finding count of zero carries information.
What was not established#
- The build is not signed. There is no signature on the wheel.
- There is no continuous-integration attestation. No hosted build service produced one for this release.
- A checksum is not a signature. It proves the bytes you received match the bytes the build produced; it does not establish who built them.
- Reproducibility here is one machine, one interpreter, one operating system. All three builds ran in the same environment. This is same-environment reproducibility and is not a cross-platform or toolchain-independent claim.
- An optional trust layer whose extra was absent is not claimed to have run. A check that could not run reports that it was skipped; it never reports a pass.
- Native Windows is unproven. The native-Windows check runs and has never passed — see the compatibility matrix. The verified platforms are Linux, macOS on Apple Silicon, and Windows through WSL.
- "No known release-blocking defects" is not "error-free." It is a statement about what was known when the build was certified.
- None of it is a verdict about your product. No conformity, certification, safety or security conclusion is expressed or implied.
Every number above is stated with the count it was measured out of, and every absence was calibrated against a case where the same check fires.
What we do not claim#
No test suite proves the absence of defects, and a synthetic corpus cannot guarantee every future customer input. SBOMFlow does not claim perfect accuracy, universal format support, or legal conformity — and any claim on this site that cannot be traced to running code and tests is a bug. Real, sanitised customer artifacts remain the standard we validate against next.
Next: capabilities · security & privacy · what's new