<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2026 ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG, Cologne
SPDX-FileCopyrightText: Author: Pierre Gronau <Pierre.Gronau@ndaal.eu>
-->
# Changelog
All notable changes to the ndaal SBOM Auditor are documented in this
file. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.11] - 2026-08-07
First 1.x release.
### Added
- **The test harnesses now cover the 0.4.10 surfaces.** `/components` and
`/sbom/{id}/scorecard` shipped in 0.4.10 with unit and route tests but
no presence in the out-of-process harnesses, so nothing exercised them
end to end. Added across all five: two Bruno requests for the scorecard
(including the 404 path), one for `/components`, and one asserting
`/about` names all nine analyzers; two Playwright spec files — the
scorecard spec seeds its own SBOM rather than borrowing one another
file left behind, which would pass or skip depending on worker
scheduling; two Goose journey steps, where the component query is
measured separately from the empty page because it is the only read in
the mix that `LIKE`-scans `sbom_components` and averaging the two would
hide how that cost grows with the index; and four scorecard properties
(`proptest`) pinning that arbitrary bytes always yield exactly
seventeen elements in the 9/8 split, that `met()` can never exceed what
was scored met, that zero components can never score a component
element met, and that every status renders with both a label and a
colour class.
- **A fifth Loom model** —
`component_index_rebuild_is_never_observed_half_done`.
`Store::replace_components` rebuilds an SBOM's component index as
DELETE-then-INSERT while holding one mutex guard across the whole
transaction. The model proves, over every interleaving, that a
concurrent `search_components` sees the old index or the new one and
never the empty window between them: on a page that answers "which of
my SBOMs declare this component", a momentary zero reads as *not
affected* — a false negative with no error and nothing in the log.
### Fixed
- **The workspace Clippy gate was red, and had been since 0.4.10.** Rust
1.97 tightened several lints and the 0.4.10 modules tripped eighteen of
them: `derive_partial_eq_without_eq` across the CSAF model and the
scorecard types, `option_if_let_else` in `product_key` and
`csaf_artifact_name`, `missing_const_for_fn` on `Scorecard::total`,
`too_long_first_doc_paragraph` in a findings test, and
`significant_drop_tightening` on the three new store methods. The store
fix follows the pattern already established in that file — the bodies
move to free functions taking `&Connection`, so the guard is a
temporary that drops at the end of one statement. `replace_components`
keeps its guard for the whole transaction deliberately, which is
exactly what the new Loom model proves must happen. The standalone
`loadtest/` crate tripped the same tightening
(`needless_question_mark` on both scenario builders) and is clean
again.
- **The Playwright analyze specs waited on a signal 0.4.10 removed.**
Both polled for `hx-get` to *disappear* from the run-status fragment as
the "analysis finished" signal. The 0.4.10 raw-HTML-lint fix made that
attribute unconditional — a conditional wrapped around an `attr="value"`
pair is unparseable as a tag — and moved the decision to the sibling
`hx-trigger`, which flips to `none`. So the assertion could no longer
ever become true and both specs ran to their deadline whatever the
server did. They now assert `hx-trigger="none"`. The budgets are also
raised (8-minute poll inside a 10-minute per-test ceiling): the ensemble
went from five tools to nine in 0.3.8, and a cold run additionally pays
for grype's one-shot vulnerability-DB update, which alone can reach its
own five-minute deadline.
- **`/about` under-reported the analyzer ensemble.** The page hand-listed
five tools while the ensemble has been nine since 0.3.8 — bomdrift,
inspektr_cli, shieldbom and provenant were missing, so the page told a
reader the auditor runs less than it does. It now renders from the
canonical `Tool::all()`, the same source `/settings` derives from, so
the two cannot diverge by construction. A route test pins it: every
analyzer `/settings` can toggle must be described on `/about`, with
`/settings` treated as canonical because that is where an operator
enables the ensemble.
- **TLP labels lost their colours.** The CSAF publisher settings rendered
the TLP label as a plain dropdown, so RED, AMBER, GREEN and CLEAR were
visually indistinguishable — a poor affordance for a disclosure
marking, where getting it wrong over-shares. They are now colour-coded
radio badges following the FIRST TLP 2.0 convention, with the label
text rendered beside every badge so colour is never the only signal.
## [0.4.10] - 2026-08-06
### Added
- **Component-level SBOM parsing** (`src/sbom_component/`). Until now the
auditor read three things out of an SBOM — format, spec version and a
component count — and handed the bytes to subprocesses as an opaque blob.
It now lifts, per component: name, version, producer, package URLs, CPEs,
checksums and licences; the dependency edges between components; and the
document's own metadata (authors, generating tools, timestamp, serial
number, lifecycle phase, signature presence). All six supported formats
are covered — `CycloneDX` JSON and XML (recursing into nested
components), SPDX 2.x JSON and tag:value, SPDX 3.0 JSON-LD, syft JSON.
`NOASSERTION` is preserved verbatim rather than folded into "absent",
because "the producer knows it does not know" is different information.
- **CISA 2026 minimum-elements scorecard** (`src/scorecard.rs`, page at
`/sbom/{id}/scorecard`). Scores an ingested SBOM against all seventeen
elements — nine SBOM-metadata, eight component-data — answering whether
a supplier provided what the guidance asks for, which no vulnerability
scan can. A component element is *met* only when EVERY component carries
it; an SBOM that is 99% complete is not complete. Also warns on
deprecated format versions (`CycloneDX` <= 1.2, SPDX 2.1/2.2).
- **Cross-SBOM component search** (`/components`, schema v5). Answers
"which of my SBOMs ship log4j-core", which no per-document view can.
Components are indexed at ingest; the page counts SBOMs with no index
yet and offers a rebuild rather than presenting a partial index as a
complete answer — in a supply-chain search, "not found" reads as "not
affected".
- **CSAF 2.1 / 2.2 advisory export** (`src/csaf/`, two new export
formats). Turns one SBOM's analysis into an OASIS CSAF advisory, with
findings merged by CVE across tools. Skipped, failed and timed-out
analyzers become an explicit "Analysis coverage" note naming each tool
and its reason. Optional per-finding burst via the new
`csaf_split_per_finding` setting.
- **CVSS vector capture** (`src/cvss.rs`, schema v4). Six of the seven
finding parsers previously hardcoded no score at all. Score, vector and
version are now lifted from each tool's real wire shape. A CVSS 4.0
vector is never derived from a 3.1 one — they are different metric sets
— and a vector with no numeric base score (OSV) yields no CSAF metric
rather than a fabricated number.
- New settings: `csaf_publisher_name`, `csaf_publisher_namespace`,
`csaf_publisher_contact`, `csaf_tlp`, `csaf_split_per_finding`.
### Fixed
- **Migration ladder**: rung 3 stamped `PRAGMA user_version =
{SCHEMA_VERSION}` interpolated, so bumping `SCHEMA_VERSION` would have
made rung 3 stamp the new version and silently skip every later rung —
the new columns would never have been created on an existing database.
Each rung now stamps its own literal.
- **Raw-HTML lint**: three templates wrapped a conditional around an
attribute with no separating space, which htmlhint reads as one
malformed tag. Askama rendered them correctly, so only the lint was red
— but a permanently-red lint is a lint nobody reads. All 21 templates
now pass.
- Two pre-existing doc-lint errors in `benches/parsers.rs` that were
failing `clippy --workspace --all-targets` independently.
### Changed
- The export surface grows from six formats to eight. CSAF is the first
format that can emit more than one artifact, so rendering now returns a
list; the other six are unchanged.
### Documentation
- **CISA 2026 SBOM Minimum Elements — conformance assessment** at
[`CSI_2026_cisa_sbom_minimum_elements_508c.md`](documentation/compliance/CSI_2026_cisa_sbom_minimum_elements_508c.md).
Assesses `sbom-auditor` 0.3.9 against the CISA publication of
29 July 2026 (17 data fields, 6 practices and processes), co-authored
with 17 international organizations including the BSI. Result: 2 of 9
SBOM metadata fields met, 0 of 8 component data fields, and 1 met /
3 partial / 2 gap across the practices. The assessment records the
single root cause — the auditor treats an SBOM as an opaque blob to
hand to the analyzer ensemble rather than parsing it — alongside the
eight areas where the tool exceeds the guidance (false-green guard,
machine-readable non-coverage, five checksum families over four
constructions, offline operation, dual schema-validated SARIF,
SPDX 3.0 acceptance), and a ranked gap list `G1`–`G12`. A
component-level SBOM parser (`G1`) and a minimum-elements scorecard
(`G2`) are identified as the highest-leverage work: `G1` alone
converts 15 of the 17 data-field rows.
- The assessment's verdict tables are refreshed for this release: with the
parser, scorecard and CSAF export in place the auditor now reads 16 of
the 17 data fields (the seventeenth, SBOM Author Signature, is detected
but not cryptographically verified) and closes gaps `G1`, `G2`, `G3`
and `G10`.
## [0.3.9] - 2026-08-05
### Changed
- Version bump to 0.3.9; development continues on top of the 0.3.8 release.
- **Dependency currency:** `base64` 0.23.0 → 0.23.1 (semver-compatible patch;
the at-rest crypto path in `src/crypto.rs` encodes sealed TEXT-column values
with it). No source change — `Cargo.toml`'s `base64 = "0.23"` already
permitted it. Recorded as CSAF advisory `ndaal-sa-2026-379` (CVSS 0.0, NONE).
### Fixed
- **Release pipeline** (`release/create_release_on_gitlab.sh`): push the current
branch via an explicit `<branch>:<branch>` refspec instead of `HEAD:main`
(avoids the non-fast-forward rejection and this repo's `.invalid`-HEAD
resolution), and upload GitLab release assets individually — tolerating
"already attached" — so a partial upload is idempotent and self-heals on
re-run rather than aborting on the first collision.
### Documentation
- CSAF advisory `ndaal-sa-2026-380` announces the 0.3.8 release (informational,
reproducing the 0.3.8 changelog).
### Testing
- Six additional `skills/rust-testing` sequences over the pure-domain parsers,
each verified green before commit:
- **bolero + arbitrary** structured fuzzing (`tests/bolero_structured.rs`) —
three harnesses over `sbom_format::detect`, `findings::parse`,
`licensing::parse`.
- **Kani** bit-precise proofs (`kani-harness/`, standalone crate mirroring
`loom-harness/` so Kani's pinned nightly need not build `libsqlite3-sys`):
`Severity::from_cvss` matches the documented CVSS bands and is monotonic —
both `VERIFICATION:- SUCCESSFUL`.
- **Criterion** parser benchmarks (`benches/parsers.rs`).
- **cargo-insta** golden snapshots (`src/snapshots/`) pinning the Markdown and
SARIF 2.1 / 2.2 export shapes, with the `.snap.new` drift gate.
- **assert_cmd** process-contract tests (`tests/cli_contract.rs`) — exit
status + stdout/stderr for `--version` / `--help` and rejected invocations.
- **cucumber** BDD acceptance loop (`tests/bdd.rs`,
`tests/features/sbom_analysis.feature`) — four Gherkin scenarios,
`fail_on_skipped()` so no scenario passes vacuously.
## [0.3.8] - 2026-08-05
### Added
- **Four new analyzers, bringing the ensemble to nine.** Each is optional at
runtime, detected on `PATH`, overridable via `SBA_TOOL_<NAME>_BIN`, and
self-skips (never fake-green) when absent:
- **bomdrift** ([crates.io](https://crates.io/crates/bomdrift)) — supply-chain
drift / typosquat detection. Diffs the SBOM against an empty baseline the
orchestrator writes to scratch and normalises the SARIF result.
- **inspektr** ([crates.io](https://crates.io/crates/inspektr)) — SBOM
vulnerability scanner; its top-level `vulnerabilities` JSON is normalised
into CVE-shaped findings.
- **shieldbom** ([crates.io](https://crates.io/crates/shieldbom-core)) —
offline-first embedded / IoT CVE scanner; SARIF 2.1.0 output (the CVE is
the `ruleId`, the CVSS-like `security-severity` property drives severity).
- **provenant** ([crates.io](https://crates.io/crates/provenant-cli)) — a
ScanCode Toolkit port reporting licence / copyright / provenance rather than
vulnerabilities.
- **Licence & provenance findings.** provenant's output is not CVE-shaped, so it
gets its own model (`LicenseFinding`: licence / copyright / holder / author),
its own store table (additive schema v3, `license_findings`, foreign-keyed to
the run with `ON DELETE CASCADE`), a "Licence & provenance" section on the
SBOM detail page (En/De/Fr), and a per-tool section in the Markdown export —
so provenance data is surfaced instead of discarded.
- **SARIF 2.1.0 finding parser** shared by bomdrift and shieldbom: GitHub's
`security-severity` property drives severity when present, otherwise the SARIF
`level` maps (error → high, warning → medium, note → low).
### Testing
- Compile-time invariants (`static_assertions`) for the new types; the
nine-tool ensemble count is pinned.
- Property tests: both untrusted-output parsers never panic on arbitrary bytes
and only ever emit store-valid rows; SARIF and ScanCode output are bounded by
their declared item count (no amplification).
- cargo-fuzz: the findings target now covers the three new sources; a new
`fuzz_licensing_parse` target covers provenant, each seeded with a valid
sample (both smoke-ran clean).
- End-to-end integration tests (stub tools) prove each output shape is
persisted through the whole orchestration, and that provenant lands licence
findings in their own table and nothing in the vulnerability table.
## [0.3.7] - 2026-08-04
### Fixed
- **grype vulnerability-DB warm-up.** grype's scan runs offline by design
(`GRYPE_DB_AUTO_UPDATE=false`), so once its DB passed grype's built-in max
age (5 days) grype hard-failed — surfacing a routine DB refresh as a
`fail`. The analyzer now runs `grype db update` at most **once per
process**, out of band, before the first grype scan; the scan itself stays
offline against the freshened DB. The warm-up is best-effort: on a
genuinely offline host it fails fast and grype scans against whatever DB it
has. Verified: with the DB refreshed, all five analyzers (syft / grype /
trivy / osv-scanner / bomber) now complete `ok` on the CycloneDX log4j
fixture (grype: 20 findings), where grype previously recorded `fail`.
## [0.3.6] - 2026-08-04
### Fixed
- **osv-scanner invocation modernised.** The analyzer called `osv-scanner
scan source --sbom <input>`; `--sbom` is deprecated in osv-scanner 2.x and
`scan source` is a *directory* walker (it logged `Starting filesystem walk
for root: /` and exited 128 with "could not determine extractor"). The call
is now `osv-scanner scan --lockfile <input> --format json`, which reads the
single materialised file and auto-detects its format from the
server-controlled name (`input.cdx.json`, `input.spdx.json`, the converted
`scanner-input.cdx.json`, …). Verified: osv-scanner now completes `ok` with
32 findings on the CycloneDX log4j fixture where it previously failed.
## [0.3.5] - 2026-08-04
### Changed
- **SPDX 3.0 is normalised for the scanners via `syft convert`.** The
vulnerability scanners (grype / trivy / osv-scanner / bomber) cannot read
SPDX 3.0 JSON-LD. The analyzer now converts an SPDX 3.0 input to CycloneDX
**once, up front**, and every scanner reads the conversion; syft itself
still reads the original. When syft cannot produce the conversion (it is
not installed), the scanners **skip** with a clear reason instead of
failing on an input they cannot parse. This supersedes the 0.3.4 note that
the scanners fail on SPDX 3.0; with syft present, trivy now scans an SPDX
3.0 SBOM that previously failed.
### Notes
- Vulnerability matching after conversion depends on the SBOM carrying
package URLs. The upstream SPDX 3.0 examples describe source files and AI
datasets whose components have no `purl`, so purl-strict scanners
(grype / osv-scanner / bomber) still find nothing to match on those
fixtures; a package-manager SPDX 3.0 SBOM (with purls) matches normally.
This is honest tool behaviour, not a conversion failure — trivy parses the
converted CycloneDX successfully either way.
## [0.3.4] - 2026-08-04
### Added
- **SPDX 3.0 JSON-LD ingest.** SBOM files in the SPDX 3.0 JSON-LD encoding
(`@context` + `@graph`, e.g. `*.spdx3.json`) are now detected, counted and
stored instead of being rejected with `422 Unprocessable Entity`. A new
`SbomFormat::Spdx3Json` (`spdx3-json`) variant is detected from the SPDX
3.x `@context` (or a graph `specVersion` beginning `3.`); the spec version
is read from the graph's `CreationInfo` and the component count is the
number of package elements (`software_Package` and its profile subclasses
`ai_AIPackage` / `dataset_DatasetPackage`, all ending in `Package`).
### Changed
- **Store schema v2.** The `sboms.format` CHECK constraint is broadened to
allow `spdx3-json`. Because `SQLite` cannot alter a CHECK in place, the
`migrate` ladder gains a v2 rung that rebuilds the `sboms` table with
foreign keys disabled, so the rebuild does not cascade-delete existing
`analysis_runs`; an existing v1 database upgrades in place with its data
intact.
### Notes
- The external scanners handle an SPDX 3.0 input per their own capabilities:
`syft` parses it; scanner versions that do not yet support SPDX 3.0 fail
cleanly (a visible `fail`, never a false `ok`), and reports are still
produced documenting the outcome. Ingest, detection, storage and export do
not depend on the scanners.
## [0.3.3] - 2026-08-04
Test-fixture release. No change to application behaviour, and no change to
the published crate: the Cargo `include` list scopes the crates.io package
to the crate's own sources, so the example corpus is not part of the
published package.
### Added
- `example/` — a corpus of 651 public test SBOM files for exercising
ingest, format detection, the analyzer ensemble and the exporters,
fetched unmodified from four upstream collections:
anchore/sbom-examples (510 files, Apache-2.0), CycloneDX/bom-examples
(83, CC0-1.0), spdx/spdx-examples (56, GPL-3.0) and
sonatype-nexus-community/cyclonedx-sbom-examples (2, Apache-2.0). The
SBOM data files are tracked via Git LFS (~2.6 GB); `PROVENANCE.md` and a
per-file `MANIFEST.csv` ship alongside in plain git. See
`example/PROVENANCE.md`.
- End-to-end test sequences that drive the corpus through the API and the
browser UI (upload → analyze → export) and assert that reports are
produced.
## [0.2.1] - 2026-08-03
Packaging and distribution release. No changes to application behaviour;
this version makes the auditor installable as a prebuilt binary.
### Added
- `cargo binstall` support: `[package.metadata.binstall]` points at the
GitLab release assets, matching the self-updater's raw-binary naming
(`sbom-auditor-<version>-<triple>`), so `cargo binstall sbom-auditor`
fetches a prebuilt binary instead of compiling from source.
- Prebuilt release binaries for the supported target triples, each with
the five cryptographic hash sidecars (`.sha-256`, `.sha-512`,
`.sha3-512`, `.blake3-512`, `.shake256-512`).
### Changed
- Scoped the published crate with a Cargo `include` list so the package
contains only the crate itself (sources, embedded assets, build
script, licence and the info-menu docs) rather than the whole
repository. The published package is ~832 KiB.
- Updated dependencies to their latest releases: `cap-std` 3.4 → 4.0
(with `cap-primitives` 4.0 and `io-extras` 0.19 — the
capability-confined filesystem; the used `Dir` API is unchanged across
the major, so no code change), `base64` 0.22 → 0.23, `roxmltree`
0.20 → 0.21, and `time` 0.3.54 → 0.3.55. None of these updates
remediate a security advisory (`cargo audit` / RUSTSEC report no
applicable vulnerability for the previous versions); they are routine
supply-chain currency updates. The dev-only `jsonschema` is
intentionally held at 0.28.
## [0.1.0] - 2026-08-03
The initial build. A single offline binary that ingests SBOM files,
analyses them with an ensemble of external tools, normalises the
findings and exports them in six formats.
### Added
#### Secure web shell (transport and hardening)
- HTTPS-only listener: TLS 1.3 via rustls (aws-lc-rs provider with
`prefer-post-quantum`, so the X25519MLKEM768 post-quantum hybrid
key-exchange group is offered first); self-signed certificate
regenerated on every start-up, or an operator-supplied
`--tls-cert` / `--tls-key` pair.
- Loopback-only bind by default (`127.0.0.1:8680`), with an explicit
`--allow-non-loopback` opt-in and an anti-DNS-rebinding request-Host
allowlist (`--allowed-host` / `SBA_ALLOWED_HOSTS`).
- Security-header middleware on every response: HSTS, `X-Frame-Options`
DENY, a hash-pinned Content-Security-Policy, `X-Content-Type-Options`
nosniff, referrer minimisation, a Permissions-Policy deny list and
cross-origin isolation headers.
- Same-origin check on every state-changing POST and a request-body
size limit.
- Hand-rolled hyper 1 + matchit router, shared by the binary and the
integration tests so both exercise the identical route table.
- Connection admission control (a semaphore cap), TLS-handshake,
header-read and whole-request timeouts, and graceful shutdown.
- Embedded static assets (Bootstrap, Bootstrap Icons, htmx, Roboto and
Roboto Mono fonts, logo) — no CDN, no filesystem reads.
#### Persistent store, settings and storage encryption
- Bundled SQLite store (rusqlite with a statically linked, vendored
SQLite) in WAL mode, holding the SBOM inventory, the analyzer run
ledger, findings, operator settings and an append-only audit log; a
`PRAGMA user_version` migration ladder versions the schema.
- Typed operator settings persisted in the database (import and export
directories, per-run analyzer timeout, analyzer concurrency, the five
checksum-sidecar toggles, the five analyzer enable toggles and the
upload size limit) with a settings page and an audited change diff.
- Optional storage encryption (XChaCha20-Poly1305) that seals stored
SBOM bytes and raw analyzer output while they sit on disk, keyed by
`--encryption-key-file`; pre-existing plaintext rows still read back.
- Capability-confined filesystem: every data, import and export path is
opened through a cap-std handle, closing path-traversal, symlink and
TOCTOU classes at the syscall layer. Stored file names are
server-derived.
#### SBOM ingest
- Browser upload (`POST /upload`) via a hand-rolled, size-limited
single-file multipart parser, and bulk import from a configured folder
(`POST /import`) via a cap-std directory walk.
- Format detection for CycloneDX (JSON/XML), SPDX (JSON/tag-value) and
syft-json, with the declared spec version and component count captured
at ingest.
- Content-hash (SHA-256) deduplication: re-ingesting the same bytes
returns the existing entry rather than a duplicate row.
#### Analyzer orchestration
- An ensemble of five optional, `PATH`-detected tools — syft, grype,
trivy, osv-scanner and bomber — each overridable with
`SBA_TOOL_<NAME>_BIN` and individually enableable in settings.
- Each tool runs as an isolated subprocess under a per-run deadline, a
process-wide concurrency semaphore, bounded stdout/stderr capture and
`kill_on_drop` on timeout.
- A false-green guard: a run is `ok` only when the tool ran and produced
parseable JSON (a non-zero exit on findings still counts); a missing
binary is a visibly distinct `skip`, unparseable output is `fail`, and
an elapsed deadline is `timeout`. A skipped tool is never shown as
success.
- The bomber compatibility gate normalises CycloneDX newer than 1.6 via
`syft convert`, and skips with a documented reason when syft cannot
convert the input.
- The stored SBOM is decrypted into a per-analysis scratch directory
(removed on completion) so a tool is never handed the sealed on-disk
file; captured output is sealed again on disk.
- The detail page polls run status over htmx until every run is
terminal.
#### Findings normalization and detail UI
- Each `ok` run's JSON is normalised into a common finding shape per
tool, with the CVE alias resolved from the tool's GHSA/OSV identifier
and a normalised severity.
- Findings are shown per tool with severity badges, ordered most severe
first; a live `/tools` inventory page probes each tool's presence,
version, role and documented caveat.
#### Multi-format export with checksum sidecars
- Six output formats produced in-process by native Rust crates (no
pandoc, typst or LibreOffice shell-out): SARIF 2.1.0, SARIF 2.2,
Markdown, ODT, DOCX and PDF (the PDF embeds Roboto for umlaut-safe
output).
- Every export lists skipped and failed analyzers with their reasons, so
an incomplete analysis is never presented as clean.
- Up to five checksum sidecars per artifact (`.sha-256`, `.sha-512`,
`.sha3-512`, `.blake3-512`, `.shake256-512`), each toggleable in
settings, written into the capability-scoped export directory; the
download route allowlists the served name against the directory
listing.
#### Internationalization and operability
- Trilingual UI chrome (English / German / French) with a cookie-pinned
language switcher (`sba_lang`) and a dark-mode toggle (`sba-theme`).
- Info menu with dialog pages: About, System info, Privacy, Security,
License, Imprint, Changelog, README, Administrator Guide and User
Guide.
- Liveness (`/healthz`) and readiness (`/readyz`) probes.
- Structured logging (`--log-format json`) plus an append-only log file
under `--log-dir` (default `./ndaal/log`).
- Hardened self-update (`--check-update` / `--self-update`) over the
TLS 1.3 stack, verifying the download against the published checksums
and refusing on mismatch, with a `--no-self-update` policy opt-out.
#### Test harnesses
- Rust unit and integration suite (`cargo nextest run`) plus doctests.
- `loom-harness/` concurrency-permutation models of the shared state.
- `test/bruno/` API collection exercising every route against a live
server.
- `test/playwright/` browser E2E of the upload, analyze, findings and
export journeys.
- `loadtest/` Goose scripted-scenario load test.
[0.3.7]: https://gitlab.com/vPierre/ndaal_public_sbom_auditor/-/releases/v0.3.7
[0.2.1]: https://gitlab.com/vPierre/ndaal_public_sbom_auditor/-/releases/v0.2.1
[0.1.0]: https://gitlab.com/vPierre/ndaal_public_sbom_auditor/-/tags/v0.1.0