Known limitations
SBOMFlow is a deterministic, offline-by-default evidence engine. It is honest about its edges on purpose: the measurable limits below are generated from the product's own resource budgets and recognized-input catalog, so this page cannot drift ahead of the code. The rest states plainly what the engine does not do. None of this is softened — a limitation you can read is worth more than a claim you cannot check.
It is not legal advice or a conformity assessment#
SBOMFlow produces engineering evidence and gap assessments to support CRA-oriented work. It is not a certification, an audit, or legal advice, and it never states or implies conformity. Regulatory conclusions require qualified human judgement that this tool does not provide.
Observed evidence is not a human review#
Everything the engine detects is an observation. It never becomes an accepted decision on its own: VEX status, evidence acceptance, release approvals, and waivers come only from human reviewer decision files. A green row means "observed", never "reviewed and cleared".
A report never states an input it could not read#
Reporting surfaces re-read artifacts a run wrote earlier. When one of those documents is present but not the shape it should be, the number or verdict it would have produced is not published — the surface says which document could not be read, and how, instead of substituting a value.
This matters because the substituted value always looked plausible. Measuring a field that holds a string counts its characters; measuring one that holds an object counts its keys; a gate record that did not parse read as no policy was enforced. None of those is a parse error anybody sees.
- Notifications (
sbomflow notify) drop the count lines and name the field when the evidence pack's counted lists are not lists, and report arecord_stateofabsentorunreadable— with no gate verdict and no severity — whenrelease-gate.jsonis missing or is not a gate record. A gate record that parsed and enforced nothing is a different fact and keeps itsinformationalreading.
release-index.jsonrecords0and names the field in the index's ownwarningswhen a counted pack field, or the gate'sviolations, is not a list. These numbers are read back bysbomflow store query(--has-kev,--min-gaps), so a fabricated one changed which releases a query returned.
finding-regressions.jsonskips a stored release whose recorded findings are not a list of records, naming it underprovenance.prior_releases_skippedasunreadableand declaring its regression count a lower bound. Read as empty instead, such a release supplied a baseline nothing was ever read from — which is how a finding present now was reported as a regression no stored release had ever shown resolved.
Absence is treated separately from an unreadable document throughout: they send you to different repairs, and neither is a statement that a check passed.
Resource limits on untrusted input#
Inputs may be hostile, so parsing is bounded by secure-by-default budgets. Exceeding one is never a silent truncation — it is stopped and surfaced as a cataloged warning naming the input. The defaults:
| Limit | Default | What it bounds |
|---|---|---|
| Single file read | 256 MiB | The largest individual file the scanner will read into memory. |
| Total decompressed size | 8 GiB | The cumulative uncompressed size an archive may expand to before the read is stopped and warned. |
| Decompression ratio | 200:1 | The uncompressed-to-compressed ratio that trips a decompression-bomb guard. |
| Archive members / records | 5,000,000 | The number of members or records processed from a single container before the input is capped. |
| Structure nesting depth | 200 | The maximum nesting depth parsed from untrusted JSON or structured input. |
| Single field / string | 8 MiB | The largest single string or field value read from a structured input. |
| Unknown-input content sample | 64 KiB | The total bytes the Unknown Input Sentinel samples (head + tail) from a single file no parser recognised, however large that file is. It is the only read the sentinel makes, and it is taken only from files nothing else classified. The sampled bytes are used to derive signals and are never recorded. |
| Directory walk depth | 100 | The deepest directory level a walk over an untrusted product tree will descend into, counting the scan root as level 0. The entry budget above bounds how MANY entries a walk may pull; this bounds its SHAPE, because one chain of nested directories costs a single entry per level and would otherwise stay far inside that budget. Meeting it prunes that branch and is always recorded — the affected inventory is reported as partial, never returned as though the whole tree had been examined. |
| General text scan | 5 MB | The largest file the general text pass (evidence signals, unrecognized text) reads; larger files are hashed and recorded, not text-scanned. |
| Recognized component inputs | 50 MB | Recognized lockfiles, component manifests, and SBOM artifacts still parse up to this size (a 22 MB, 50,000-component monorepo lockfile scans in ~11 s using ~760 MB peak memory). Above it the input is hashed and a resource_limit_exceeded warning names it — generate an SBOM for that workspace and supply it with --import-sbom. |
Input coverage is finite and stated#
A default scan parses 63 input types (each with a regression corpus — "supported" means corpus-tested, not a guarantee for every real variant of a format) and recognizes 64 more that it hashes and flags with a warning but does not parse yet — recognized inputs are never silently ignored. Anything outside this set is not treated as inventory. The exact, generated matrix is on the supported inputs page.
It does not execute, sandbox, or scan for malware#
All parsing is read-only and static. Binaries are never executed, and SBOMFlow is not an antivirus, a sandbox, or a dynamic analyzer. It records what an input declares or hashes to — not what it does at runtime.
The AI inventory is bounded, and an empty one means nothing was recognised#
Every default run writes ai-evidence.json (experimental), an inventory of the AI model artifacts in the scanned tree. Its limits are deliberate and each one is stated in the artifact itself, not only here.
- Nothing is loaded, deserialised, or executed. Model files are identified from a small byte prefix — a documented magic number, a documented structural invariant, or, when the bytes prove nothing, the filename alone, which is recorded as such. Formats whose only faithful reader runs code are recognised, hashed, and deliberately never opened, so for those the inventory knows only that the artifact exists, how big it is, and its digest.
- An empty inventory is not "no AI". It means the scan looked and recognised nothing. Formats SBOMFlow does not recognise, models fetched at runtime, and models behind an external service are outside what any filesystem walk can observe.
- A bounded walk says so. If the entry-count or directory-depth budget above stops or prunes the walk, the inventory is reported as partial and names the budget that fired. It is never returned as though the whole tree had been examined.
- Declared evidence needs a declaration.
--ai-manifestsupplies one local JSON document toanalyze/audit; a run without it is observed-only and the report says plainly that nothing was compared. Zero conflicts never means the evidence agrees. A declared agent still cannot be compared against a discovered one:environmentdecides an agent's identity, a declaration must state it, and static configuration never does. - No model or agent is observed at runtime. SBOMFlow assesses no model's behaviour, quality, accuracy, bias, robustness, or safety, and reaches no verdict about any of them. Recording what an agent is declared to be allowed to do does not constrain that agent and does not mitigate runtime prompt injection.
It does not guess component identity#
A component is identified from its declared coordinates or its content hash. SBOMFlow performs no fuzzy or name-based matching: an unmatched binary blob is hashed and recorded honestly as content-identified, never guessed against a registry. A scanned "version" that is actually a range or constraint is kept verbatim and neutralized — never invented into a concrete version — and the ambiguity is warned.
Some version ranges resolve as unknown, on purpose#
Advisory matching evaluates version ranges with per-ecosystem ordering schemes. Fourteen ecosystems ship a native, conformance-tested ordering (semver-style, Python, Debian, RPM, Alpine, Maven, NuGet, RubyGems, Composer, Hex, Pub, CRAN, Swift and Conan), and ranges in those ecosystems decide affected / unaffected outright. unknown still arises deliberately: for an ecosystem without a validated ordering scheme, for a malformed or unorderable range, and for version pairs an ecosystem's own rules declare incomparable. In every such case the matched advisory is surfaced as unknown with low confidence and a needs-verification flag: the finding is shown to a human, the unresolved basis is recorded, and SBOMFlow never silently asserts "affected" and never silently discards the advisory. Equivalent Maven coordinates written as group:artifact and group/artifact are matched consistently.
Freshness and determinism are scoped#
Baseline operation is offline: network vulnerability sources are opt-in and only as current as the snapshot you supply, so an offline run reflects the pinned data, not live advisories. Byte-for-byte reproducibility holds for the same inputs under a pinned --as-of timestamp; it is not a claim about results across different data snapshots.