FAQ & Troubleshooting

The questions that actually come up when reading a kunnus SBOM — most of them trace back to one design decision: the scanner records what the input asserts and refuses to guess or go online.

Why is a component missing from my SBOM?

The most common causes are deliberate: declarations whose identity fields contain unresolved variables (${...} in CMake or platformio.ini) are dropped rather than guessed; feature-conditional vcpkg dependencies are not walked; local path: components are skipped as in-development code. If an entire ecosystem is missing, check that its marker file (lockfile/manifest) is present in the scanned tree, and re-run with --verbosity info to see which extractors ran.

Why does a component have no version?

Because the scanned input pins none, and kunnus refuses to resolve versions online. Examples: a vcpkg dependency without an override or version floor, a git submodule in an exported tree without .git, a west project without a revision or default. The declared state is recorded verbatim; resolving it against a registry would need network access the scanner deliberately avoids.

The scanner exited with code 1 but wrote an SBOM — is it broken?

No — that is the degraded-scan signal. The SBOM was generated, but one or more extractor plugins failed, so the result may have gaps. The error message lists the failed plugins; --verbosity info gives per-plugin detail. Exit 0 is reserved for scans where every plugin ran clean, so CI can tell the two apart.

Does kunnus-scanner send my code or data anywhere?

No. Scans are offline by default and make no network requests. Exactly three features touch the network, all explicit: --online-licenses (deps.dev licence lookups), scanning a remote container reference (registry pull), and kunnus upload. Anything else phoning home would be a bug — and per the project's security policy, a reportable vulnerability.

Why do some components have no licence?

Licences come from package data available offline: OS package databases, Debian copyright files, lockfiles that embed licences (composer.lock), and installed packages' own manifests. Ecosystems whose inputs carry no licence data (Go, Rust, .NET lockfiles, embedded manifests) stay empty offline — pass --online-licenses to fill them via deps.dev, the one opt-in online feature.

Why does the same library appear only once although several sub-projects declare it?

Deduplication is intentional: components sharing a package URL are merged, and every location they were found at is preserved in the component's evidence.occurrences list. So the SBOM stays free of duplicates without losing the answer to "where exactly is this used?".

Which SBOM format does the scanner emit — and can I get SPDX?

CycloneDX 1.6 JSON only, conformant with BSI TR-03183-2 — the technical guideline that operationalizes the CRA's SBOM requirements. SPDX output is not currently supported; if you need SPDX for a downstream consumer, convert the CycloneDX file with a tool such as cyclonedx-cli.

Does the SBOM contain a dependency graph, or just a flat list?

Both, depending on the ecosystem. Where a lockfile pins the resolved graph — Cargo.lock, composer.lock, Gemfile.lock, package-lock.json/npm-shrinkwrap.json (v2/v3), packages.lock.json, renv.lock — the real edges are mined into CycloneDX dependencies[]. Everything else contributes a root-presence claim only. The document does not pretend otherwise: compositions[] declares the graph incomplete, which is CycloneDX's native way of stating a known unknown.

What do the kunnus:unknown:* properties on a component mean?

That a CISA minimum-element field could not be determined: producer, version, hash or license. CISA requires an SBOM author to state that a value is unknown rather than silently omit it, and CycloneDX 1.6 has no field-level marker for that — so kunnus emits these properties instead. They always mean unknown to the scanner, never known-but-withheld. A component with a supplier, version, hash or licence never carries the matching marker.

A component that used to appear twice is now listed once — what changed?

You were seeing a declared constraint and a resolved pin side by side: the manifest says >=2.0, the lockfile says 2.31.0, and the differing purls kept deduplication from merging them. The phantom half carried a version that was never released and a CPE matching no advisory, so it is dropped for Cargo, NuGet and Python where a lockfile actually covers the manifest. Where a project has no lockfile, its declarations are untouched — losing a component is worse than listing one twice.

Do rescans of the same product share a serial number?

Only when you supply an identity: --component-id (plus --component-version) makes the serial deterministic, so rescans form one document series. Container scans of a registry reference get this for free from the image reference. Without identity flags every run gets a fresh random serial — see the SBOM identity guide.