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.

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 a record_state of absent or unreadable — with no gate verdict and no severity — when release-gate.json is missing or is not a gate record. A gate record that parsed and enforced nothing is a different fact and keeps its informational reading.
  • release-index.json records 0 and names the field in the index's own warnings when a counted pack field, or the gate's violations, is not a list. These numbers are read back by sbomflow store query (--has-kev, --min-gaps), so a fabricated one changed which releases a query returned.
  • finding-regressions.json skips a stored release whose recorded findings are not a list of records, naming it under provenance.prior_releases_skipped as unreadable and 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:

LimitDefaultWhat it bounds
Single file read256 MiBThe largest individual file the scanner will read into memory.
Total decompressed size8 GiBThe cumulative uncompressed size an archive may expand to before the read is stopped and warned.
Decompression ratio200:1The uncompressed-to-compressed ratio that trips a decompression-bomb guard.
Archive members / records5,000,000The number of members or records processed from a single container before the input is capped.
Structure nesting depth200The maximum nesting depth parsed from untrusted JSON or structured input.
Single field / string8 MiBThe largest single string or field value read from a structured input.
Unknown-input content sample64 KiBThe 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 depth100The 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 scan5 MBThe largest file the general text pass (evidence signals, unrecognized text) reads; larger files are hashed and recorded, not text-scanned.
Recognized component inputs50 MBRecognized 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-manifest supplies one local JSON document to analyze/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: environment decides 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.