Supported inputs

This matrix is generated from the recognized-input catalog, so it is the exact truth of what a default sbomflow analyze/audit run reads — not a hand-written promise. "Supported" means corpus-tested: the parser has a regression corpus, nothing more. A recognized-but-unparsed input is detected, hashed into the artifact manifest, and surfaced with a stable warning — it is never silently ignored. Anything not listed here is not recognized as inventory in the default scan.

Parsed by default (63 tested)#

InputFamilyKindNotes
*.spdx.index.json (glob)yoctoBuild evidenceThe create-spdx document index; used to detect a partial doc set.
*.spdx.json (glob)yoctoBuild evidenceParsed for components only when the document carries the Yocto create-spdx creator marker; every other SPDX document is hashed and imported via --import-sbom. The optional first-party meta-sbomflow layer collects the document set without promoting build-host recipes into shipped components.
opkg/statusopkgInstalled stateThe opkg/ipkg installed-package database of OpenWrt (through the 24.10 series) and Yocto ipk images — only under usr/lib/opkg/status, var/lib/opkg/status or usr/lib/ipkg/status (the bare name status claims nothing, and var/lib/dpkg/status stays the container parser's dpkg database). Installed stanzas become components with the stated exact version and architecture, and Depends yields dependency edges. A stanza whose Status names any other state (not-installed, half-installed, config-files) is a complete record of absence and is skipped silently; a stanza whose Status is absent or is not the three fields <want> <flag> <state> is skipped AND warned about, because a truncated database that told us nothing must not read the same as one that told us the package is not there. Neither is ever invented into a component. A stanza whose STRUCTURE is ambiguous is warned about too, and never repaired: a field stated twice with different values (last-wins discards the earlier one — where the field is Status, the line order alone decided whether that package is listed as shipping), and a stanza stating Package: more than once, which is the signature of a lost blank-line separator left by an interrupted write. Such a stanza is NOT split: where the separator belonged is not readable and guessing it would invent inventory, so a package the file names is absent from the component list, and the surviving record's Auto-Installed/Depends marks are kept verbatim but not attributed to it. purl-spec defines NO opkg/openwrt type and OSV serves NO OpenWrt ecosystem (both verified 2026-07-17), so components are honest pkg:generic/<name>@<version> with match_scope: none — never pkg:deb, and never a guessed distro. OpenWrt 25.12+ switched to apk; its installed database (lib/apk/db/installed, an FDB text file — verified against apk-tools source) is read by the existing container apk parser.
statusvcpkgInstalled stateonly under vcpkg_installed/**/vcpkg/status. Same Status discipline as the opkg row: a state naming absence is skipped silently, an absent or non-three-field Status is skipped AND warned about. vcpkg's grammar is <want> ok <state> — vcpkg-tool writes that middle field as a string literal and its own reader requires it — so a middle field that is not ok is a document vcpkg cannot produce and is reported as unreadable rather than read as a record of absence. hold is a want value (the FIRST field), so a held but installed package is hold ok installed and ships. The opkg row is deliberately more permissive in that position because libopkg writes ok/reinstreq/hold/replace/noprune/prefer/obsolete/user there, comma-separated when combined: each parser follows its own writer. Structural ambiguity is reported and never repaired, on the same terms as the opkg row: a field stated twice with different values (last-wins discards the earlier one), and a stanza stating Package: more than once, which is the signature of a lost blank-line separator left by an interrupted write. Such a stanza is NOT split — guessing where the separator belonged would invent inventory — so a port the file names is absent from the component list, and the surviving record's Port-Version/Depends marks are kept verbatim but not attributed to it. Port-Version matters most there because it is a purl QUALIFIER: pinning the vanished port's revision onto the survivor would mint an identity the file never claimed for it.
bun.lockbunLockfileBun's text lockfile (JSONC, Bun >= 1.2), read with a deterministic comment/trailing-comma strip into the same bounded JSON loader as every other lockfile. Registry resolutions become pkg:npm/<name>@<version> (immediately OSV-matchable) with the stated SRI integrity as checksum provenance; the root workspace's dependency sections state the direct set and kinds. A git/tarball/file locator is recorded WITHOUT a version (the locator is kept as provenance) as an un-matchable component — an npm identity is never invented from a locator — and workspace: entries are the project's own code, never fabricated into packages. Declared overrides and patchedDependencies are recorded as EVIDENCE (bun-override/bun-patch provenance on the affected components plus an informational warning): a patched dependency keeps its registry identity — the patch is a recorded fact for human review, never a new component. trustedDependencies (the lifecycle-script allowlist — reviewer-relevant supply-chain evidence) and catalog/catalogs (workspace version catalogs; stated SPECIFIERS, never resolved-version claims) are recorded the same way, as bun-trusted/bun-catalog provenance plus informational warnings. The binary bun.lockb stays recognized-unsupported.
Cargo.lockcargoLockfile
composer.lockcomposerLockfile
conan.lockconanLockfile
deno.lockdenoLockfileDeno's lockfile, versions 4-5 (older shapes are warned about by version, never guessed at). npm entries become pkg:npm/<name>@<version> (OSV-matchable, SRI integrity kept); jsr entries are real registry coordinates but JSR has no purl type or OSV ecosystem yet, so they are recorded with the stated name/version as un-matchable generic components — a WATCHLIST row: they promote to an ecosystem-native, advisory-matchable identity the day purl-spec or OSV adds JSR, and nothing is speculated until then; clean remote module URLs are recorded as un-matchable URL components without a version (URL + stated sha256 kept as provenance). The workspace block states the direct set where present.
dependencies.lockesp-idfLockfileESP-IDF managed components (ESP Component Manager), with their component_hash as checksum provenance. Package URL defines NO type for ESP-IDF, so these are recorded honestly as pkg:generic/<namespace>/<name> — SBOMFlow will not invent a purl type or file them under another ecosystem. They are full inventory (name, exact version, checksum, direct/transitive scope) but no advisory database is keyed by these coordinates: they carry match_scope: none and are counted as not-matchable in observation-coverage.json.
go.modgoLockfile
go.sumgoLockfile
mix.lockhexLockfileElixir Hex resolved dependencies (Nerves/embedded Elixir). :hex entries become pkg:hex/<name>@<version> with the stated archive sha256 as checksum provenance and dependency edges from each entry's deps list. A :git entry is recorded WITHOUT a version (the revision is kept as provenance) as an un-matchable component — a Hex identity is never invented from a commit. Supersedes the declared mix.exs (recognized, not parsed).
rebar.lockhexLockfileErlang rebar3's resolved lock (BEAM / embedded Erlang). It is an Erlang-terms document, read with anchored patterns — rebar3 is never evaluated. {pkg,<name>,<version>} entries are Hex registry coordinates → pkg:hex/<name>@<version> (the same served chain as mix.lock: the official hex purl type and OSV's Hex ecosystem), keyed by the pkg tuple's registry name, with the pkg_hash_ext (outer, hex.pm-canonical; pkg_hash is the inner fallback) checksum kept as provenance and a Level-0 pin recorded as a direct dependency edge. A git/hg entry ships from a repository, so it is recorded WITHOUT a version (the revision is kept as provenance) as an un-matchable generic component — a Hex identity is never invented from a commit. The declared rebar.config is not parsed.
gradle.lockfilemavenLockfile
project.assets.jsonmsbuildLockfileNuGet restore's resolved-assets graph for an MSBuild project (written by every dotnet restore under obj/ — it complements packages.lock.json, which most projects do not commit). Resolved package libraries become pkg:nuget/<name>@<version> with the stated sha512 as checksum provenance; project references are the workspace's own code and are never fabricated into packages. Direct-vs-transitive is recorded only when the project section states it.
flake.locknixLockfileNix flake locked inputs. Only a github/gitlab input with a pinned rev has an official purl-typed coordinate (pkg:github/<owner>/<repo>@<rev>); every other locked type (git URL, tarball, path) is recorded as an un-matchable component named by the flake's own input label, without a version — an identity is never guessed from a URL. The stated narHash is recorded verbatim as integrity provenance. All flake components carry match_scope: none: no advisory database is keyed by these coordinates.
npm-shrinkwrap.jsonnpmLockfile
package-lock.jsonnpmLockfile
pnpm-lock.yamlnpmLockfilepnpm's resolved graph (v9 maps; earlier package-map shapes are read where recognizable). Locked packages become pkg:npm/<name>@<version> with importer-stated direct/dev axes. Root overrides and patchedDependencies declarations are recorded as EVIDENCE (pnpm-override/pnpm-patch provenance on the affected components plus an informational warning), exactly the bun.lock pattern: an exact name@version patch attaches to that version, a bare-name or name@* patch attaches to every resolution of the name (pnpm's own application rule), and a name@<range> patch is stated verbatim in the warning but never guessed onto components. An empty-range convergence override (name@, pnpm's all-versions form) is recorded with its verbatim selector and that documented meaning. Root catalogs (v9 workspace version catalogs) are recorded the same way, as pnpm-catalog provenance: pnpm names both the shared specifier and the version it resolved to, so each declaration attaches to that exact resolved component. Identity is never altered by a declaration.
yarn.locknpmLockfileYarn Classic v1 resolved graph (Yarn Berry lockfiles are recognized and honestly refused). HONEST LIMIT — resolutions overrides: Yarn's override mechanism lives in package.json, not in yarn.lock, so the lockfile alone never states the override declaration. SBOMFlow records the resolved graph the lockfile DOES state and does not half-parse the manifest to guess at override evidence — unlike bun.lock and pnpm-lock.yaml v9, which write their override/patch declarations into the lockfile itself and get override/patch evidence rows.
packages.confignugetLockfileLegacy NuGet packages.config — the flat XML installed set of a .NET Framework project (the long-lived industrial/OT tail). Each <package id version targetFramework/> states an EXACT installed version, so it is resolved lockfile-class (its versions supersede a declared source for the same identity) and yields pkg:nuget/<id>@<version> — the same served chain as packages.lock.json. targetFramework and allowedVersions (a range, never the point version) are recorded as provenance; developmentDependency="true" records the dev kind. New-style PackageReference projects use project.assets.json (also parsed); the two are different project styles, so neither supersedes the other.
packages.lock.jsonnugetLockfile
cpanfile.snapshotperlLockfileCarton's resolved Perl snapshot. Each distribution stanza becomes pkg:cpan/<Dist>@<version> (the official cpan purl type, keyed by the case-sensitive distribution name; the CPAN pathname — author id included — is kept as provenance). CPAN is NOT an OSV ecosystem (live-checked: the osv.dev API rejects it), so these components carry an honest match_scope: none — full inventory, no advisory-match claim. provides/requirements joins yield dependency edges. Supersedes the declared cpanfile (recognized, not parsed).
pubspec.lockpubLockfileDart pub's resolved set. Only hosted packages are registry coordinates, so only they get pkg:pub/<name>@<version> identity (with the stated sha256 as checksum provenance and the dependency field's direct/dev/transitive axes recorded). A git/path/sdk dependency is recorded with the version and provenance the lockfile states, but as an un-matchable generic component — a pub.dev identity is never invented for it. Supersedes the declared pubspec.yaml (recognized, not parsed).
Pipfile.lockpythonLockfile
poetry.lockpythonLockfile
uv.lockpythonLockfile
renv.lockrenvLockfileR renv's resolved environment. Only a package the lockfile itself places on CRAN (Source: Repository + Repository: CRAN) gets pkg:cran/<name>@<version> — CRAN is a published OSV ecosystem, so those are advisory-matchable. A Source: Bioconductor package (the literal renv's own writer emits) keeps a pkg:generic purl — purl-spec defines NO bioconductor type and one is never invented — but carries the bioconductor ecosystem label: Bioconductor is an osv-schema DEFINED ecosystem, so these match by ecosystem + name in the offline snapshot matcher (match_scope: registry; the online purl client honestly counts them as skipped). osv.dev does not currently SERVE a Bioconductor ecosystem, so no advisory is found in practice today; the path is fail-safe and retained deliberately (issue #482). A completely pinned GitHub/GitLab/Bitbucket remote on the PUBLIC forge becomes `pkg:githubgitlabbitbucket/<owner>/<repo>@<sha> (official purl types, honest match_scope: none, the flake.lock precedent); every other source (Local, URL, non-CRAN repositories, incomplete pins, self-hosted forge hosts) is recorded with the stated name/version as an un-matchable generic component — a CRAN, Bioconductor or public-forge identity is never invented. Requirements yields dependency edges; renv's own Hash` is kept verbatim as integrity provenance.
Gemfile.lockrubygemsLockfile
Package.resolvedswiftpmLockfileSwiftPM resolved pins, schema v1/v2/v3. Components carry the official swift purl type (pkg:swift/github.com/Alamofire/Alamofire@5.4.3). A pin resolved to a branch/commit has no release version, so it is recorded WITHOUT a version (the commit is kept as provenance) rather than having one invented; it is reported by the lockfile_pin_without_release_version warning.
library.propertiesarduinoManifestAn Arduino library's own metadata (name=/version= are mandatory in the Arduino library specification): exact evidence that a copy of that library is vendored in the tree (the source-header precedent). Arduino has no purl type, so the component is pkg:generic/<name> with match_scope: none; depends= declarations are provenance, never fabricated into components.
host-manifest.csvbuildrootManifestBuildroot legal-info host-tool manifest; source scope stays host and never enters the shipped set by name. The optional first-party external-tree target preserves it. Structural ambiguity is reported and never repaired, on the same terms as the opkg, vcpkg and Yocto rows. Two shapes are named: a data row carrying MORE cells than the header declares, which is the signature of a lost row separator (legal-manifest in package/pkg-utils.mk writes one double-quoted field per header column, so a row is never legitimately longer); and a header naming one column this reader USES twice, where the reader takes the first index and a row states different values across the two positions. Cells are compared as CSV cells, never raw commas: a real LICENSE or DEPENDENCIES WITH LICENSES value routinely contains quoted commas. Extra and reordered columns stay legitimate — columns are located by header name on purpose, and the header itself gained DEPENDENCIES WITH LICENSES in Buildroot 2018.11. A merged row is NOT split, so a package the file names is absent from the component list, and the extra cells are recorded verbatim in the warning, attributed to no package.
manifest.csvbuildrootManifestBuildroot legal-info target-package manifest. The optional first-party integrations/buildroot external-tree target collects it without analysis. Structural ambiguity is reported and never repaired, on the same terms as the opkg, vcpkg and Yocto rows. Two shapes are named: a data row carrying MORE cells than the header declares, which is the signature of a lost row separator (legal-manifest in package/pkg-utils.mk writes one double-quoted field per header column, so a row is never legitimately longer); and a header naming one column this reader USES twice, where the reader takes the first index and a row states different values across the two positions. Cells are compared as CSV cells, never raw commas: a real LICENSE or DEPENDENCIES WITH LICENSES value routinely contains quoted commas. Extra and reordered columns stay legitimate — columns are located by header name on purpose, and the header itself gained DEPENDENCIES WITH LICENSES in Buildroot 2018.11. A merged row is NOT split, so a package the file names is absent from the component list, and the extra cells are recorded verbatim in the warning, attributed to no package.
Cargo.tomlcargoManifest
*.cmake (glob)cmakeManifest
CMakeLists.txtcmakeManifestFetchContent_Declare / ExternalProject_Add declared sources, read statically — CMake is never run and ${...} is never expanded. At most 2000 declarations are READ per file, whether or not any of them parse; past that the file is truncated and a warning says so, so a hostile or generated file cannot make the scan unbounded. A declaration that cannot be read is warned about individually and never guessed at.
firmware-manifest.txtgenericManifestSBOMFlow's own three-column product manifest, name,version,ecosystem. Structural ambiguity is reported and never repaired, on the same terms as the opkg, vcpkg and Yocto rows: a row carrying a fourth POPULATED column is the signature of a lost newline fusing two rows into one. Such a row is NOT split — guessing where the newline belonged would invent inventory — so a component the file names is absent from the component list, and the extra columns are recorded verbatim in the warning, attributed to no component. An EMPTY extra column discards nothing and is not reported.
product-manifest.txtgenericManifestSBOMFlow's own three-column product manifest, name,version,ecosystem. Structural ambiguity is reported and never repaired, on the same terms as the opkg, vcpkg and Yocto rows: a row carrying a fourth POPULATED column is the signature of a lost newline fusing two rows into one. Such a row is NOT split — guessing where the newline belonged would invent inventory — so a component the file names is absent from the component list, and the extra columns are recorded verbatim in the warning, attributed to no component. An EMPTY extra column discards nothing and is not reported.
manifest.tomlgleamManifestgleam's resolved dependency lock (BEAM; gleam publishes to Hex). Content-sniffed to distinguish it from Julia's Manifest.toml (capital M) — a packages array stating source = "hex" is gleam and is parsed, never claimed by the bare name. source = "hex" packages become pkg:hex/<name>@<version> (the served Hex chain, mix.lock's identity) with the stated outer_checksum as sha256 provenance; a git/local package is recorded with its stated name/version as an un-matchable pkg:generic component — a Hex identity is never invented. The top-level [requirements] marks the direct set and each package's requirements list yields dependency edges. Julia's Manifest.toml stays a recognized-unparsed row.
go.modgoManifest
verification-metadata.xmlmavenManifestGradle's dependency-verification list (item 331): the artifacts a build verifies, with their checksums — not the resolved dependency graph. Every row it produces says so; pair it with gradle.lockfile for the resolved set.
package.jsonnpmManifest
DockerfileociManifest
compose.yamlociManifestDocker/Podman Compose file (the Compose Specification's preferred name). services.*.image references become pkg:docker components, mirroring the Dockerfile FROM rows; build:-only services and ${VAR} interpolations are recorded as no image (never guessed).
compose.ymlociManifest
docker-compose.yamlociManifest
docker-compose.ymlociManifest
platformio.iniplatformioManifestPlatformIO lib_deps library declarations, read statically (the CMake-declares precedent): interpolations are never expanded and the registry is never queried. PlatformIO has no purl type, so declared libraries are recorded as pkg:generic/<owner>/<name> with match_scope: none; version ranges are neutralized into raw_version, never presented as point versions.
requirements.txtpythonManifest
vcpkg.jsonvcpkgManifest
*.manifest (glob)yoctoManifestYocto image package manifest; the optional first-party meta-sbomflow layer preserves its exact bytes for later offline ingestion.
license.manifestyoctoManifestYocto image licence manifest. The optional first-party meta-sbomflow layer collects it from kirkstone/scarthgap LTS builds without analysing it in BitBake. Structural ambiguity is reported and never repaired, on the same terms as the opkg and vcpkg rows: a field stated twice with different values (last-wins discards the earlier one), and a record stating its package identity field more than once, which is the signature of a lost blank-line separator left by an interrupted write. Such a record is NOT split — guessing where the separator belonged would invent inventory — so a package the file names is absent from the component list, and the surviving record's LICENSE: value is kept verbatim in the warning but not attributed to it. The identity is whichever field named the package (PACKAGE NAME:, or RECIPE NAME: in the image-manifest variant), counted by its own LINES: every healthy record states both spellings, so treating them as one counter would report a merged record on every real Yocto build. The repeated-field report is likewise scoped to the fields this reader consumes and license_image.bbclass writes once per record.
west.yamlzephyrManifestZephyr west manifest. Structural ambiguity is reported and never repaired, on the same terms as the opkg, vcpkg and Yocto rows: a key stated twice inside ONE YAML mapping is folded last-wins by PyYAML and by the bundled loader before this parser sees the document, and west itself cannot report it either because its schema validation runs on the already-folded mapping. Both backends answer with the same detail — the value the repeat discarded, the value that survived it, and the mapping the repeat happened in — so no part of the report depends on whether the optional PyYAML extra is installed. The record-bearing keys (manifest, projects, name) are counted by LINES; the value keys (defaults, revision) are reported only when the repeat actually CHANGED the value, because a restatement of the identical value discards nothing. Reporting is scoped to the mapping each key is READ in, never to the key's spelling: name: legitimately recurs once per remote and once per project — 82 times in Zephyr's own manifest — revision: recurs inside every submodules: entry, and userdata: is typed any by west's schema, so a per-file occurrence count would fire on every healthy workspace. The document is NOT split, so a project the file names is absent from the component list, and the discarded statement is quoted verbatim from the source line, attributed to no project. A record that states its name: twice is two projects fused by a lost separator, so its own revision: is not attributed to the surviving name either — that project is recorded with the manifest default, or unpinned — while every other project in the file keeps the revision it states.
west.ymlzephyrManifestZephyr west manifest. Structural ambiguity is reported and never repaired, on the same terms as the opkg, vcpkg and Yocto rows: a key stated twice inside ONE YAML mapping is folded last-wins by PyYAML and by the bundled loader before this parser sees the document, and west itself cannot report it either because its schema validation runs on the already-folded mapping. Both backends answer with the same detail — the value the repeat discarded, the value that survived it, and the mapping the repeat happened in — so no part of the report depends on whether the optional PyYAML extra is installed. The record-bearing keys (manifest, projects, name) are counted by LINES; the value keys (defaults, revision) are reported only when the repeat actually CHANGED the value, because a restatement of the identical value discards nothing. Reporting is scoped to the mapping each key is READ in, never to the key's spelling: name: legitimately recurs once per remote and once per project — 82 times in Zephyr's own manifest — revision: recurs inside every submodules: entry, and userdata: is typed any by west's schema, so a per-file occurrence count would fire on every healthy workspace. The document is NOT split, so a project the file names is absent from the component list, and the discarded statement is quoted verbatim from the source line, attributed to no project. A record that states its name: twice is two projects fused by a lost separator, so its own revision: is not attributed to the surviving name either — that project is recorded with the manifest default, or unpinned — while every other project in the file keeps the revision it states.
curlver.hvendoredSource header
init.hvendoredSource header
opensslv.hvendoredSource header
png.hvendoredSource header
sqlite3.hvendoredSource header
task.hvendoredSource header
version.hvendoredSource header
zlib.hvendoredSource header

Dependency scope and kind#

Every observed component carries two orthogonal scope axes, and SBOMFlow only fills in what the lockfile actually states. Neither is ever guessed.

  • dependency_scope — the relationship: direct, transitive, or unknown.
  • dependency_kind — how the dependency is used: runtime, dev, or optional. Empty means the lockfile does not record it, not "runtime".
Lockfiledependency_scopedependency_kindWhere the kind comes from
package-lock.json, npm-shrinkwrap.jsondirect / transitiveruntime / dev / optionalper-package dev, optional, devOptional flags
pnpm-lock.yamldirect / transitivedirect deps onlythe importer section (dependencies / devDependencies / optionalDependencies); v5 per-package dev/optional flags
Pipfile.lockunknownruntime / devthe default vs develop category
composer.lockunknownruntime / devthe packages vs packages-dev array
poetry.lockunknownruntime / devcategory (Poetry &lt; 1.5) or groups (&ge; 1.5)
go.moddirect / transitivenot recordedGo has no dev-dependency concept
gradle.lockfileunknownnot recordedthe lock states Gradle configurations (runtimeClasspath, testCompileClasspath, …), not a runtime/dev axis — mapping one onto the other would be a guess
Gemfile.lockdirect / transitivenot recordedgroups live in the Gemfile, not the lock
packages.lock.json (NuGet)direct / transitivenot recorded
packages.config (legacy NuGet)unknowndev onlydevelopmentDependency="true"; the flat installed list marks no direct/transitive axis
Cargo.lockunknownnot recordedthe lock does not distinguish dev-dependencies
yarn.lock (classic)unknownnot recorded
conan.lock, uv.lockunknownnot recorded
pubspec.lockdirect / transitiveruntime / devpub's own dependency field (direct main / direct dev / transitive); direct overridden states the scope but no kind
mix.lockunknownnot recordedthe lock states resolved coordinates, not a runtime/dev axis (that lives in mix.exs, which is not parsed)
flake.lockdirect / transitivenot recordedthe root node's inputs positively state the direct set; Nix has no dev-dependency axis here
project.assets.jsondirect / transitive / unknownnot recordeddirect only when project.frameworks.<tfm>.dependencies states it; otherwise unknown, never guessed
renv.lockunknownnot recordedrenv states no direct/dev axis (the DESCRIPTION does; it is not parsed)
cpanfile.snapshotunknownnot recordedthe snapshot states resolved distributions, not a runtime/develop axis (that lives in the cpanfile, which is not parsed)
bun.lockdirect / transitivedirect deps onlythe workspace sections (dependencies / devDependencies / optionalDependencies); transitive packages record no kind
deno.lockdirect / transitive / unknownnot recordeddirect only when the workspace block states its dependency set; otherwise unknown, never guessed

Notes on the honest edges:

  • A pnpm transitive package in a v6/v9 lockfile records no kind. Propagating a parent's kind down the graph would be a guess, so it stays empty.
  • npm marks a package devOptional when it is reachable through both a dev and an optional edge. npm cannot say which edge installed it, so SBOMFlow records the weaker shipping claim, optional.
  • Poetry: a package in main and dev ships, so main wins. An unrecognized group is left empty rather than collapsed into dev.
  • When several sources describe one component, runtime wins, then optional; dev is the answer only when every source that stated a kind said dev.

Kind informs policy only. A dev dependency is still observed, still in the SBOM, and still matched against advisories — it is never dropped from the composition. In CycloneDX the kind maps to the standard scope field (runtimerequired, optionaloptional, devexcluded); when the kind is unknown, no scope is asserted at all.

Opt-in scope-aware gating#

By default the release gate blocks on every scope, exactly as before. A gate policy may set:

yaml
policy:
  exclude_dev_dependency_findings: true

With that set, a finding whose component was positively observed as a dev dependency no longer blocks the release. It is still reported in evidence-pack.json and issues.json, and the exemption is recorded in release-gate.json under dev_dependency_exempt_ids and shown by sbomflow explain <dir> --gate. A component whose kind is unknown always still blocks — the policy can only waive what the lockfile positively identified.

Recognized but not parsed yet (64 warn, never silent)#

Each surfaces an actionable warning naming the exact file; use the matching opt-in importer where one exists, or review the recorded file manually.

InputFamilyKindNotes
.configkconfigBuild evidenceRetained and hashed; use the matching build-evidence importer.
*.map.summary.json (glob)linker-mapBuild evidence
colcon-events.logros2Build evidence
zephyr.meta.jsonzephyrBuild evidence
index.jsonociContainer
oci-layoutociContainer
*.dts (glob)device-treeDevice tree
*.dtsi (glob)device-treeDevice tree
bun.lockbbunLockfileBun's binary lockfile. Recognized by name so it warns instead of falling to hash-only — a binary format is still a named format. The text bun.lock IS parsed; Bun >= 1.2 writes it by default and bun install --save-text-lockfile migrates.
conda-lock.ymlcondaLockfileconda-lock's fully-resolved lockfile. Bare environment.yml is deliberately NOT claimed: that name is a generic convention far beyond conda, so a name-only claim would mislabel unrelated YAML.
shard.lockcrystalLockfileA Crystal shards lockfile. Resolved, with no purl type or advisory ecosystem to match it against.
cabal.project.freezehaskellLockfile
stack.yaml.lockhaskellLockfile
Chart.lockhelmLockfileA Helm chart's resolved subchart dependencies (name/repository/version + digest) — a real resolved lockfile. Parse-promotion waits for demand plus an identity decision: purl-spec has NO helm type and OSV has NO Helm ecosystem (both verified 2026-07-17), so the only honest options at that point are un-matchable generic inventory or refusal (the JSR watchlist precedent) — neither is speculated here.
Manifest.tomljuliaLockfileJulia Pkg's resolved environment (the exact capitalized name is the Pkg convention).
pom.xmlmavenLockfileUnsupported by design (item 209): a pom declares dependencies whose versions come from parent poms, BOM imports and properties, so resolving the real coordinate set requires the network. SBOMFlow refuses to guess. Lock your build with gradle.lockfile (parsed), or generate a CycloneDX/SPDX SBOM from your build (e.g. the CycloneDX Maven plugin) and import it with --import-sbom.
nimble.locknimLockfileA Nim nimble lockfile. Genuinely resolved, but purl-spec defines no nim type and OSV serves no Nim ecosystem.
requirements.lockpythonLockfile
default.xmlaospManifestRecognized only when the content carries repo-tool manifest markers (<manifest> with <remote> and <project> elements) — 'default.xml' alone is far too generic a name to claim.
BUILD.bazelbazelManifest
MODULE.bazelbazelManifest
WORKSPACEbazelManifest
WORKSPACE.bazelbazelManifest
deps.ednclojureManifestA Clojure CLI dependency map. Declared coordinates only: the resolved set lives in the classpath cache, not in this file.
project.cljclojureManifestA Leiningen project file. It is executable Clojure, so it is recognised and never parsed.
shard.ymlcrystalManifestA Crystal shards manifest. The declared set; shard.lock is its resolved sibling.
libs.versions.tomlgradleManifestA Gradle version catalog (conventionally gradle/libs.versions.toml). It declares Maven coordinates; the resolved set is gradle.lockfile (parsed) — declarations are never treated as inventory.
Chart.yamlhelmManifestA Helm chart's metadata (the exact capitalized name is Helm's convention).
mix.exshexManifest
Project.tomljuliaManifestJulia Pkg's project manifest; Manifest.toml is the resolved set.
meson.buildmesonManifest
default.nixnixManifest
flake.nixnixManifest
distfeeds.confopenwrtManifestThe on-device opkg feed list the OpenWrt build system writes. Recognized only under etc/opkg/ (the path context is the OpenWrt statement); the bare name elsewhere claims nothing.
feeds.confopenwrtManifestOpenWrt buildroot feed declarations; the resolved installed set is the opkg status database (parsed).
feeds.conf.defaultopenwrtManifestThe default feed list shipped in the OpenWrt source tree (feeds.conf overrides it when present).
APKBUILDos-recipeManifestAn Alpine package recipe. It BUILDS a package rather than declaring an installed set, so parsing it would report inventory the device may never have carried.
PKGBUILDos-recipeManifestAn Arch package recipe. An executable shell script that builds a package; recognised, never parsed, for the same reason as APKBUILD.
cpanfileperlManifestThe declared set. The resolved cpanfile.snapshot IS parsed — a declaration is a wish, the snapshot is the fact.
pubspec.yamlpubManifest
pyproject.tomlpythonManifestWarned only when it declares dependencies; otherwise it is not treated as inventory.
build.sbtsbtManifestAn sbt build definition. It is executable Scala — the pom.xml/setup.py class — so it is recognised and never parsed. Generate a CycloneDX from the build and supply it with --import-sbom.
build.zig.zonzigManifestA Zig package manifest (ZON). It declares dependencies with content hashes, but purl-spec defines no zig type and no advisory ecosystem serves Zig, so a parse would be un-matchable inventory.
*.mlmodel (glob)coremlmodel-artifactCore ML model (Protocol Buffers). A .mlpackage bundle is a directory whose inner .mlmodel/weights are recognized individually; hashed only, never parsed.
*.gguf (glob)ggufmodel-artifactGGUF (llama.cpp) model; parsing it is a known memory-safety surface (the GGUF integer-overflow CVE class), so it is hashed only, never parsed.
*.onnx (glob)onnxmodel-artifactONNX model (Protocol Buffers); recognized by extension, hashed only, never parsed.
*.xml (glob)openvino-irmodel-artifactOpenVINO IR model topology, the .xml half of an .xml+.bin pair. .xml is far too generic to claim by name, so it is recognized ONLY when the content is an IR graph (<net> with <layers>/<edges>), content-sniffed; a generic XML is never claimed. The paired .bin weights stay an opaque binary_or_firmware artifact.
*.ckpt (glob)pickle-checkpointmodel-artifactModel checkpoint (PyTorch Lightning / TF). Claimed ONLY when content-sniffed as a pickle/torch-zip. Unsafe to deserialize; hashed only.
*.joblib (glob)pickle-checkpointmodel-artifactjoblib dump (scikit-learn and friends), commonly a pickle stream. Claimed ONLY when content-sniffed as a pickle/torch-zip; unsafe to deserialize; hashed only.
*.pickle (glob)pickle-checkpointmodel-artifactPython pickle. Claimed ONLY when content-sniffed as a pickle stream. Unsafe to deserialize; hashed only.
*.pkl (glob)pickle-checkpointmodel-artifactPython pickle. Claimed ONLY when the head bytes are a pickle stream — pickle deserialization executes arbitrary code (labelled unsafe_to_deserialize); hashed only, never deserialized.
*.pt (glob)pickle-checkpointmodel-artifactPyTorch checkpoint. Claimed ONLY when the head bytes are a pickle stream or a torch.save zip — a text file named model.pt (Chameleon page template) is refused. Pickle deserialization executes arbitrary code (labelled unsafe_to_deserialize); hashed only, never deserialized.
*.pth (glob)pickle-checkpointmodel-artifactPyTorch checkpoint. Claimed ONLY when content-sniffed as a pickle/torch-zip — a site-packages .pth path-configuration text file is refused. Unsafe to deserialize; hashed only.
*.safetensors (glob)safetensorsmodel-artifactHugging Face safetensors weights; recognized by extension, hashed only, never parsed.
*.engine (glob)tensorrtmodel-artifactTensorRT serialized engine (a hardware/version-specific plan). .plan is deliberately not claimed (generic-name collision); .engine/.trt are the TensorRT-specific surface. Hashed only, never deserialized.
*.trt (glob)tensorrtmodel-artifactTensorRT serialized engine (the .trt extension); hashed only, never deserialized.
*.tflite (glob)tflitemodel-artifactTensorFlow Lite model (FlatBuffer); recognized by extension, hashed only, never parsed.
*.mender (glob)menderupdate-artifactMender Artifact (a tar archive); use mender-evidence (inspected in memory, never extracted).
*.raucb (glob)raucupdate-artifactRAUC update bundle (a signed SquashFS/CMS container); use rauc-evidence on its manifest.raucm.
manifest.raucmraucupdate-artifactRAUC bundle manifest (the exact name / .raucm extension is RAUC's convention).
*.swu (glob)swupdateupdate-artifactSWUpdate update container (a newc/crc cpio archive); use swupdate-evidence.
sw-descriptionswupdateupdate-artifactSWUpdate's mandatory container member (the exact name is SWUpdate's convention).
*.itb (glob)uboot-fitupdate-artifactU-Boot FIT flattened image-tree blob (binary); use firmware-extract to carve its images.
*.its (glob)uboot-fitupdate-artifactU-Boot FIT image-tree source (device-tree syntax). Recognized only when the content is a FIT source (a /dts-v1/ document with an images or configurations node) — .its alone is not claimed. Its verified-boot signature nodes are separately surfaced as review signals; the recognition row names the file as a boot artifact.

Imported by explicit flag (2)#

These are not auto-discovered by the scan — SBOMFlow reads them only when you pass the flag, so nothing is picked up implicitly. Each is offline, local-file-only, and hashed into the artifact manifest as provenance. Imported components carry imported provenance and never overwrite a locally-observed component; a version conflict is surfaced for a reviewer.

FlagFormatSchemaNotes
--import-sbomCycloneDX 1.x JSON, SPDX 2.x JSON, SPDX 3.0.1 JSON-LD(external standards)Import a supplier-provided standards SBOM as observed component evidence. Components carry imported provenance and are merged with locally-observed ones without overwriting them.
--import-custom-manifestSBOMFlow custom manifestsbomflow-custom-manifest v1Import an explicit manufacturer-declared component + dependency handoff for evidence that cannot be reliably expressed as CycloneDX/SPDX. Every identity field is explicit; a malformed purl or missing field is warned and skipped, never guessed. See docs/CUSTOM_MANIFEST.md.

Matching capability by ecosystem#

This section is generated from three code sources and joined so a single lookup answers "for ecosystem X, is a version range advisory-range-evaluated, equality-only, or is there no advisory source at all?" — a question that was previously answerable only by reading the engine's source.

  • parsed — the recognized-input catalog above (what a default scan reads). parsed = a corpus-tested parser; recognized = detected and warned but not parsed yet.
  • advisory-matchable — the ecosystem-to-advisory (OSV) map and each component's match_scope. registry means a served advisory ecosystem exists to query; none is an honest statement that no advisory coordinate exists — never a silent miss and never a guessed or cross-ecosystem match.
  • range-ordering — the validated version-ordering schemes (currently 14). range-validated evaluates ranges under a differential-proven ordering for that ecosystem; equality-only means only exact-version equality is decidable (the generic fallback never fabricates an order).

The vocabulary is fixed — (parsed / recognized / import-by-flag) crossed with (range-validated / equality-only / match_scope: none). There are no support tiers and no scores. Components brought in with an explicit import flag (see Imported by explicit flag above) carry whatever ecosystem the imported record states and are classified by the same two rows below.

Advisory-matchable ecosystems (15)#

A served advisory (OSV) ecosystem exists for these, so a matched component carries match_scope: registry. Every one is also range-validated: the version-ordering work closed the equality-only gap for every matchable ecosystem, so no matchable ecosystem is currently equality-only.

Internal ecosystem → OSVPackage identityRecognitionmatch_scopeVersion-range ordering
apkAlpinepkg:apkparsedregistryrange-validated (apk)
bioconductorBioconductorpkg:generic (name-matched)parsedregistryrange-validated (cran)
cranCRANpkg:cranparsedregistryrange-validated (cran)
cargo, crates.io, rustcrates.iopkg:cargoparsedregistryrange-validated (semver)
deb, debianDebianpkg:debparsedregistryrange-validated (deb)
go, golangGopkg:golangparsedregistryrange-validated (semver)
hexHexpkg:hexparsedregistryrange-validated (hex)
mavenMavenpkg:mavenparsedregistryrange-validated (maven)
node, npmnpmpkg:npmparsedregistryrange-validated (semver)
nugetNuGetpkg:nugetparsedregistryrange-validated (nuget)
composerPackagistpkg:composerparsedregistryrange-validated (composer)
pubPubpkg:pubparsedregistryrange-validated (pub)
pypi, pythonPyPIpkg:pypiparsedregistryrange-validated (pep440)
gemRubyGemspkg:gemparsedregistryrange-validated (gem)
swiftSwiftURLpkg:swiftparsedregistryrange-validated (swift)

No advisory source — match_scope: none (17)#

The package identity may still be exact (an official purl type), but no served advisory ecosystem is keyed by these coordinates, so matching is honestly none. Where the version ordering is nonetheless validated (a coordinate gap, not a scheme gap), that readiness is shown — it would take effect only if an advisory source is ever served, via the same coordinated change that enabled the others.

Internal ecosystemPackage identityRecognitionmatch_scopeVersion-range ordering
arduinopkg:genericparsednoneequality-only
bitbucketpkg:bitbucketparsednoneequality-only
cmakepkg:genericparsednoneequality-only
conanpkg:conanparsednonerange-validated (conan) — readiness only, no advisory source
containerpkg:dockerparsednoneequality-only
cpanpkg:cpanparsednoneequality-only
dockerpkg:dockerparsednoneequality-only
esp-idfpkg:genericparsednoneequality-only
genericpkg:genericparsednoneequality-only
githubpkg:githubparsednoneequality-only
gitlabpkg:gitlabparsednoneequality-only
opkgpkg:genericparsednoneequality-only
platformiopkg:genericparsednoneequality-only
rpmpkg:rpmparsednonerange-validated (rpm) — readiness only, no advisory source
systempkg:genericnoneequality-only
vcpkgpkg:vcpkgparsednoneequality-only
yoctopkg:yoctoparsednoneequality-only