About · License · System Info · Privacy · Security · Changelog · README · Administrator Guide · User Guide

Changelog

# Changelog

All notable changes to the csaf-crud workspace are documented here.
The format follows [Keep a Changelog](https://keepachangelog.com)
and the workspace uses [Semantic Versioning](https://semver.org).

## [Unreleased]

## [1.4.12] – 2026-08-16

A small release: one user-visible addition, one dependency patch, and a
lint sweep over the advisory generators. No behaviour of the server, the
CLI or the storage layer changes.

### Added

- **`/info/about` now carries a Source code section**, mirroring the card
  the Grundschutz++ Viewer shows so both ndaal products present provenance
  the same way. It links the repository, the releases page and all four
  published crates, and gives both `cargo install csaf-crud` and
  `cargo binstall csaf-crud`. The version shown comes from
  `env!("CARGO_PKG_VERSION")`, so the page cannot drift from the build
  serving it.

  The section states what `SHA256SUMS` actually proves — **integrity, not
  authenticity**. The artefacts are not signed, so whoever can rewrite a
  release archive at source can rewrite the manifest beside it. This is the
  same wording the code and the release notes use; the UI deliberately does
  not imply more than the pipeline delivers.

### Changed

- **`uuid` 1.24.0 → 1.24.1** (`ndaal-sa-2026-418`). Lockfile-only; it was
  the only direct workspace dependency behind its latest release. Scored
  CVSS 0.0 / NONE, grounded rather than assumed: `cargo audit` with all four
  deny classes exits clean across 433 crates against 1216 RUSTSEC
  advisories, and no advisory matches `uuid` at either version — so the bump
  fixes nothing and the prior state was not exposed.

  `crypto-common` and `syn` were deliberately **not** bumped. The workspace
  pins `generic-array = "0.14.9"` specifically to stop the resolver
  selecting `crypto-common 0.1.7`, which hard-pins a stale `generic-array`
  and regresses both `cargo install` and `cargo publish`'s verify step.

- **Ruff autofix over the 50 advisory generators**: 1500 → 1362 findings,
  175 fixes. Generator output was compared byte-for-byte against the
  advisories already committed and is identical — these scripts emit
  documents that ship with five checksum sidecars, so a changed string
  literal would silently invalidate published digests.

  `T201` was excluded from the unsafe pass. Ruff "fixes" it by **deleting**
  the `print()` statement, which would have removed all 70 of them —
  including the only line reporting what a generator wrote and at what
  score, making a successful run indistinguishable from a silent no-op.

### Known limitation

- **`aarch64-pc-windows-msvc` is not shipped in this release**, unchanged
  from 1.4.11. cargo-xwin feeds clang-cl syntax to plain clang and `ring`'s
  build script fails. Environmental, not a code defect: it reproduces with
  `.cargo/config.toml` deleted, on toolchains 1.96.1 and 1.97.1, with a
  freshly downloaded sysroot, and with an unchanged `cc` crate.
  `cargo binstall` fails rather than degrading for Windows ARM64 users.

## [1.4.11] – 2026-08-07

Note: 1.4.10 was never published. The version jumps from 1.4.9 to 1.4.11 so
that no released artefact is ever ambiguous about which number it carries.

### Fixed

- **`updater`: the transport never reached the integrity check.**
  `perform_with` moved its transport into `fetch_release_list` and then
  called `install` with none, so `install` built a real-network client. The
  `SHA256SUMS` digest fetch — the only integrity check that runs on the
  GitLab backend, since GitLab publishes no per-asset digest — was therefore
  unreachable from the offline test suite, and any offline test that served
  a genuinely newer release silently stopped being offline and hung on a
  real download to gitlab.com. `install` now takes the transport (grouped
  into an `InstallRequest` to stay within the four-parameter limit) and the
  single-call-site `fetch_digest` wrapper is gone — supplying `None` was its
  only behaviour and the cause of the bug. Found by mutation testing, which
  reported it three separate ways: one surviving mutant plus two timeouts.

- **`tests/scripts/test_cargo_inspect.sh`: unbounded run could stall every
  gate.** The script shelled out to a nightly
  `cargo rustc -- -Zunpretty=hir` with no time limit. One such process was
  observed wedged for 17.5 hours while holding the cargo build lock,
  blocking every other cargo command in the workspace — unrelated
  `cargo test` runs included. Each per-crate invocation is now bounded
  (`INSPECT_TIMEOUT`, default 300s, `0` disables), escalates to `SIGKILL`
  via `--kill-after` when cargo declines `SIGTERM`, resolves both
  `timeout(1)` and `gtimeout(1)`, and reports a timeout distinctly from a
  compile failure instead of folding both into one counter.

### Testing

- New offline test drives `perform_with` all the way to
  `ChecksumMissing`, which returns before any download is attempted. Its
  kill condition is exact: an unthreaded transport means the manifest
  request never lands in the recorded URLs.
- Seven new bats assertions on the inspect timeout, including a
  behavioural one that proves the host's `timeout(1)` actually honours
  `--kill-after` — busybox ships a `timeout` that rejects it, which would
  silently drop the bound while looking identical to a working one.
- The `split_sums_line` `||`→`&&` mutant is recorded as **equivalent**
  (unkillable, not a test gap) with the proof in `.cargo/mutants.toml`:
  verified over 5630 inputs with zero divergence.

## [1.4.9] – 2026-08-07

Supersedes 1.4.8. Content is identical; the version was rolled forward so
that all four crates carry the same number.

## [1.4.8] – 2026-08-06 — partial publish, superseded by 1.4.9

Published to crates.io for `csaf-models`, `csaf-core` and `csaf-crud` only;
`ndaal-csaf-cli` never shipped at this version, leaving the CLI a version
behind the libraries. Rather than close the gap with a CLI-only 1.4.8, every
crate was rolled forward to 1.4.9 so a single number identifies the whole
workspace. 1.4.8 is left published (crates.io versions cannot be withdrawn,
only yanked) and is functionally equivalent to 1.4.9 for the three crates
that carry it. The full change list is under 1.4.9 below.

### Added

- **In-place self-update for both binaries** (`skills/rust-self-update`).
  `csaf-crud` and `ndaal-csaf-cli` now expose the same three flags:
  `--check-update`, `--self-update`, and the policy opt-out
  `--no-self-update` (env fallback `CSAF_NO_SELF_UPDATE`, accepting
  `1`/`true`/`yes`/`on`). Both actions are terminal — they act and exit
  rather than continuing into the program.
  - `self_update = "1.0.0-rc.6"` is a **mandatory, un-gated** dependency:
    no `optional = true`, no cargo feature, no `#[cfg(feature)]` around
    the updater. There is exactly one binary shape, and opting out is a
    runtime policy decision, never a build-time one.
    `crates/csaf-core/tests/self_update_is_mandatory.rs` fails to
    **compile** if that ever regresses.
  - Feature set `gitlab` + `ureq` + `rustls` + `archive-tar` +
    `compression-tar-gz` + `checksums`, with `default-features = false`.
    `ureq`/`rustls` rather than `reqwest` keeps the tree free of a second
    HTTP client and of any native-tls/OpenSSL; `reqwest` and
    `zipsign-api` appear in `Cargo.lock` only as unactivated optional
    dependencies and never enter the build graph.
  - Exit codes are a documented, script-facing API: `0` up to
    date/updated/unreachable, `1` install failure, `2` argument error,
    `3` refused by policy, `10` a newer release exists. `--check-update`
    **never** fails on a network error — an unreachable host or a
    rate-limit 403 is an `Unreachable` outcome with exit `0`, so checking
    cannot break a boot path or a cron job.
  - Shared logic lives in `csaf_core::updater` and
    `csaf_core::update_cli`, so the hand-rolled parser in `csaf-crud` and
    the clap parser in `ndaal-csaf-cli` cannot drift apart.
- **`SHA256SUMS` in the release bundle**: `release/create_release.sh` now
  emits a sixth, plainly-named manifest alongside the five ndaal
  five-family manifests. This is the file the updater fetches from the
  same release tag to verify a download before it replaces the running
  executable. The release fails if it disagrees with `SHA-256SUMS.txt`.
- **Compile-time invariants**
  (`crates/csaf-core/tests/static_assertions_invariants.rs`, per
  `skills/rust-static-assertions`): `Send`/`Sync` on `CsafStorage`,
  `AppConfig` and `DbPool`; the `Error + Debug + Display + Send + Sync`
  contract on `CsafError` and `UpdateError`; object-safety of
  `HttpClient`/`HttpResponse` (the injection seam the offline updater
  suite depends on); and the `UpdateFlags` field names that *are* the CLI
  contract. These fail the build, not a test run.
- **Mutation testing config** (`skills/rust-cargo-mutants`):
  `.cargo/mutants.toml` plus the required `[profile.mutants]` block.
- **Tests for the updater**, covering the full pyramid in the same change:
  - `updater_pure.rs` — 39 tests over the IO-free surface (unit +
    proptest), including an anti-drift test that renders each binary's real
    `[package.metadata.binstall]` templates and asserts they reproduce
    `asset_name` / `bin_path_in_archive` character for character for every
    shipped triple. Without it, `cargo binstall` (the first install) and
    the updater (every install after) can silently disagree.
  - `update_cli_props.rs` — 7 properties over the shared flag parser:
    exact partition, set-iff-present, order-independence, idempotence.
  - `updater_transport.rs` — 20 offline tests through `self_update`'s
    `HttpClient` seam with a canned GitLab API (scaffolding adapted from
    upstream's MIT-licensed `tests/custom_transport.rs`), covering
    up-to-date / newer / older / empty / malformed / transport-failure /
    rate-limited, every `perform()` branch that decides *whether* to
    install, and the `SHA256SUMS` resolution itself — including the case
    where the manifest lists only the `.sha-256` **sidecar** of the target
    archive, which must be refused rather than verified against another
    file's digest.
  - `prepare_install` was extracted from the download path so its
    `UnsafeAssetName` screen — a security guard on a name derived from a
    remote release tag — is directly testable. Previously it could only be
    reached by performing a real download, so nothing exercised it.
  - Three fuzz targets — `fuzz_sha256sums_parse`,
    `fuzz_updater_asset_name`, `fuzz_cli_parse` — with corpora seeded from
    the real v1.3.7 release manifests and argument vectors. Seeding raised
    `fuzz_sha256sums_parse` coverage from 194 to 230 features.
  - Mutation testing found two genuine gaps that all of the above had
    missed, both now closed: nothing asserted `is_opted_out()` returns
    *false* (self-update could have been permanently disabled in every
    deployment unnoticed), and nothing asserted `--self-update` is actually
    *parsed* (a dropped match arm would make the flag fall through and
    start the server normally — a failure that looks like success).

### Fixed

- **`tests/scripts/test_cargo_tarpaulin.sh` had no way to fail.** It captured
  `cargo tarpaulin`'s exit code, logged a warning and wrote "FAILED" into its
  report — then discarded the status, so `quality_gates.sh --strict` could
  never abort on coverage and the reported number was decorative. It also
  never passed `--fail-under`. Now follows the same contract as the eight
  bash-tool gates in `skills/bash/SKILL.md`: report-only by default (the
  canonical `exit 0` footer is preserved), with `TARPAULIN_STRICT=1` to fail
  the gate and `TARPAULIN_FAIL_UNDER` (default 85) for the threshold. A
  self-skip when cargo-tarpaulin is absent stays rc=0 in both modes.
- **`tests/scripts/test_fuzzing_targets.sh` wrote its reports outside the
  repository.** `OUT_DIR` used `REPO_ROOT`, which in that script is
  deliberately the PARENT of the workspace (it holds the shared CLAUDE.md),
  so every fuzzing artefact landed in `<parent>/documentation/rust/fuzzing/`
  — never committed, and polluting a directory that may hold sibling
  repositories. Now uses `${QG_WORKSPACE}` like every other gate script.
- **The same script misreported every fuzz target as TIMEOUT.** The outer
  wall-clock allowance was 180s on top of the fuzz duration, far below a cold
  `cargo fuzz` build, so unless `--build` was passed the run was killed
  mid-build and bucketed as a fuzzing timeout — making `ok=0` the normal
  outcome and a genuine timeout indistinguishable from a cold cache. The
  allowance is now `FUZZ_BUILD_ALLOWANCE` (default 900s).
- **`skills/rust-self-update/references/self-update-1.0-api.md` documented
  three names that do not exist in 1.0.0-rc.6**, each of which fails to
  compile: `compression-flate2` (really `compression-tar-gz`),
  `identifier(..)` (really `asset_identifier(..)`), and
  `get_latest_releases()` (really `get_latest_release()`, which returns a
  plural `Releases`). The Backends section also still listed `gitee` and
  `manifest`, contradicting the corrected feature list earlier in the same
  file; rc.6 ships exactly `custom`, `gitea`, `github`, `gitlab`, `s3`.
  Corrected against the vendored crate source, and annotated with the fact
  that only `UpdateBuilder` — not `ReleaseListBuilder` — carries the
  `http_client` setter, which is what the offline test suite depends on.

### Changed

- **`cap-std` 3.4.5 → 4.0.2** (workspace-wide). Required by
  `skills/rust-self-update` Rule 5 for the self-update path. No API
  changes were needed in `csaf-core`'s `fs.rs`, `path_security.rs` or
  `dump.rs`; the full suite passed unchanged.
- **Dependency currency** (lockfile only; the caret requirements in
  `[workspace.dependencies]` already permitted all five):
  `hyper` 1.10.1 → 1.11.0, `serde` 1.0.228 → 1.0.229, `serde_json`
  1.0.150 → 1.0.151, `time` 0.3.53 → 0.3.55, `tokio` 1.52.3 → 1.53.1.
  The `tokio` bump removed eleven now-unreferenced Windows shim crates;
  the `serde` bump added `syn` 3.0.3 beside the existing 2.0.119
  (build-time only, so it does not reach the shipped binaries). Recorded
  as CSAF advisories `ndaal-sa-2026-383` … `-387`.

### Security

- The self-updater verifies every download against the `SHA256SUMS`
  published **in the same release**. Stated plainly because it is easy to
  overclaim: this is **integrity, not authenticity**. Anyone able to
  rewrite the release archive at the source can rewrite the manifest
  alongside it. The artifacts are **not signed** — detached signing
  (`self_update`'s `signatures` feature, via zipsign) is deliberately not
  enabled, because this project's release pipeline does not sign today.
  GitLab's releases API publishes no per-asset digest, so the crate's
  automatic release-digest check is a silent no-op on this backend and
  the `SHA256SUMS` manifest is the only integrity check that actually
  runs.

## [1.3.7] – 2026-07-16

### Added

- **Info navbar dropdown + 4 documentation pages**: `/changelog`,
  `/readme`, `/administrator`, `/user` now render the repository's
  `CHANGELOG.md`, `README.md`, `Administrator_Guide.md`, and
  `User_Guide.md` (embedded via `include_str!`) as HTML pages, reachable
  from a new `<details>`/`<summary>` dropdown under the navbar's "Info"
  entry (no JS framework), with a divider separating the existing About/
  License/System Info/Privacy/Security links from the four new doc
  links. Covered by Rust unit tests (embed-matches-on-disk, XSS-escaping,
  dropdown-link presence) and 4 new Bruno API tests
  (`test/bruno/collections/docs/`).
- **Property tests for `SidecarHashes`**: `struct_excessive_bools` allow
  documented with a `WHY:` comment (five independent on/off toggles map
  1:1 to the five mandated hash families and to `Settings`' five
  `sidecar_*` fields).
- **`clippy.toml` for `csaf-cli` and `csaf-core`**: extends the
  test-relaxed-lints policy already in place for `csaf-crud` to the
  other two publishable crates (`allow-*-in-tests` for unwrap/expect/
  indexing/panic/print/dbg/useless_vec).

### Fixed

- **`crates/csaf-core/tests/test_oasis_gating.rs`**: integration test
  files are a separate compilation unit from `lib.rs`, so the
  `#![cfg_attr(test, allow(clippy::expect_used, ...))]` relaxation there
  never covered this file. Added the matching `#![allow(...)]` block
  directly to the file. The full workspace clippy gate
  (`--workspace --all-targets --all-features -- -D warnings`) is now
  clean. (`ndaal-sa-2026-310`)
- **Bruno API test harness**: `test/bruno/run.sh` was missing the `-r`
  (recursive) flag, so `bru run collections` silently discovered 0
  requests and reported a false PASS on every run — possibly since the
  suite's creation. Fixing it exposed 4 stale assertions (sidecar
  extensions still checking the pre-0.3.0 non-hyphenated forms, a
  missing `settings_reset` audit action, and two response bodies
  asserting fields the handlers no longer return), all corrected here,
  plus a new `admin/00-reset-settings.bru` step so the suite no longer
  depends on leftover state from a prior manual session. 107/107 Bruno
  tests now pass. (`ndaal-sa-2026-311`)
- **bats-core sister test suite**: 72 of 85 `tests/scripts/*.bats` files
  had an identical `"mkdir invocations use -p -v"` test block
  copy-pasted twice (once with its explanatory comment, once without).
  Removed the duplicate, comment-free copy after verifying both bodies
  were byte-identical. (`ndaal-sa-2026-312`)
- **`csaf-crud --help` / `-h` / `--version` / `-V`**: the binary treated
  the first positional argument unconditionally as a config-file path,
  so these flags silently started the full TLS server instead of
  printing usage — discovered when it hung a release smoke test for 46
  minutes. Added early handling that prints and exits before any
  config/network setup. Regression-tested via a subprocess-spawning
  integration test bounded by a 5s timeout.
  (`ndaal-sa-2026-313`)

## [0.3.6] – 2026-07-16

### Added

- **Documentation set** (per `skills/documentation`): root `REFERENCE.md`
  (Diátaxis reference — CLI, config, runtime settings, API and web
  routes, errors, exit codes) and `structure.md` (canonical layout),
  plus the `documentation/` guides — `User_Guide`, `Administrator_Guide`,
  `Troubleshooting_Guide` — and a generated `Linkcheck_Report`. A
  Documentation index now links these from the three README mirrors.
- **`documentation/wiki/`**: a self-updating codebase wiki (per
  `skills/documentation-codewiki`) mapping entities, concepts, and
  patterns across the workspace for contributors and coding agents.
- **Fuzz + property-test coverage**: two new cargo-fuzz targets,
  `fuzz_import_file` (the import-directory parser, closing the last gap
  in CLAUDE.md's canonical six-target list) and `fuzz_oasis_validate`
  (the OASIS `csaf-rs` validator wrapper, a new untrusted-JSON surface
  with no prior coverage). `proptest` is now wired into `csaf-core` as a
  dev-dependency for the first time, pairing each new fuzz target with a
  never-panics property test (`import::tests::prop_never_panics_on_arbitrary_file_content`,
  `oasis::tests::prop_never_panics_on_arbitrary_utf8`).
- **CSAF dependency-currency advisories** `ndaal-sa-2026-300`..`308`:
  one advisory per crate bumped in the 2026-07-15 `cargo outdated`
  sweep (`csaf-rs`, `uuid`, `time`, `log`, `memchr`, `quote`,
  `regex-automata`, `syn`, `getrandom`), each carrying the canonical
  0.0/NONE dual CVSS (`cargo audit` reported zero findings against any
  of them) and tri-tool-verified five-family sidecars.

### Changed

- **`csaf-rs` 0.4.1 → 0.5.1** (feature `oasis-validator`, default-on).
  `{csaf2_0,csaf2_1}::loader::load_document_from_str` was renamed
  upstream to `load_document(source: impl JsonSource)`; adapted the two
  call sites in `crates/csaf-core/src/oasis.rs`. No behaviour change —
  `oasis::validate_oasis_json` / `is_oasis_valid` keep their existing
  signatures and the OASIS `"basic"` preset dispatch is unchanged.
- **`uuid` 1.23.3 → 1.24.0**, **`time`/`time-macros`** 0.3.49/0.2.29 →
  0.3.53/0.2.31, and five transitive crates (`log`, `memchr`, `quote`,
  `regex-automata`, `syn`, `getrandom`) bumped to their latest in-range
  versions via `cargo update`; `crates/workspace-hack/Cargo.toml`
  resynced with `cargo hakari generate`. `crypto-common`, `digest`, and
  `hashbrown` were left unbumped — the newer majors are blocked by
  `sha2`/`blake2`/`hashlink`'s own transitive requirements (no stable
  `blake2`/`argon2` release supports `digest` 0.11 yet); see
  `ndaal-sa-2026-300`..`308` for the per-crate detail.

### Fixed

- **ESLint config** (`eslint.config.js`) was non-functional: shell-style
  `#` SPDX comments (invalid JavaScript), a non-existent `security/*`
  rule, type-aware `@typescript-eslint` rules without a tsconfig, and a
  wrong `globals` environment for the browser script. It now loads and
  lints clean. Added a dev-only `package.json` pinning the ESLint
  toolchain (`node_modules` / `package-lock.json` stay gitignored).
- **Windows-ARM64 release build** (`aarch64-pc-windows-msvc`): blake3's
  NEON C intrinsics fail to compile under cargo-xwin/clang (the MSVC SDK
  stubs `vreinterpretq_u32_u8`), so `csaf-core` now selects blake3's
  pure-Rust backend on that target only — native targets keep the SIMD
  path. Restores the sixth release target (absent since v0.3.2).

### Security

- **`quinn-proto` 0.11.14 → 0.11.16** — fixes RUSTSEC-2026-0185 (HIGH,
  7.5), a remote memory-exhaustion vulnerability from unbounded
  out-of-order QUIC stream reassembly.
- **`anyhow` 1.0.102 → 1.0.103** — fixes RUSTSEC-2026-0190, an
  unsoundness in `Error::downcast_mut()`.
  `cargo audit --deny warnings --deny unsound --deny unmaintained --deny yanked`
  is clean after both bumps.
- **`release/create_release.sh`** now emits all five mandated hash
  sidecar families (`.sha-256`, `.sha-512`, `.sha3-512`, `.blake3-512`,
  `.shake256-512`) per CLAUDE.md's cryptographic-hashes convention; it
  previously emitted only three.

## [0.3.5] – 2026-06-15

Dependency-maintenance release. No source changes — in-range
(semver-compatible, non-breaking) updates only.

### Changed

- **Dependency refresh (`cargo update`).** Moved 50 direct and
  transitive crates to their latest in-range versions, including the
  runtime / web / TLS stack: `tokio` 1.52.1 → 1.52.3, `hyper`
  1.9.0 → 1.10.1, `h2` 0.4.13 → 0.4.15, `http` 1.4.0 → 1.4.2,
  `rustls` 0.23.39 → 0.23.40, `rustls-native-certs` 0.8.3 → 0.8.4,
  `rustls-pki-types` 1.14.0 → 1.14.1, `rcgen` 0.14.7 → 0.14.8,
  `serde_json` 1.0.149 → 1.0.150, `time` 0.3.47 → 0.3.49,
  `chrono` 0.4.44 → 0.4.45, `uuid` 1.23.1 → 1.23.3,
  `regex` 1.12.3 → 1.12.4, `zeroize` 1.8.2 → 1.9.0,
  `wasm-bindgen` 0.2.118 → 0.2.125, plus the usual `libc` / `cc` /
  `memchr` / `smallvec` / `hashbrown` / `windows-sys` transitive
  churn. `csaf-rs` deliberately held at 0.4.1 — 0.5.0 is a breaking
  change and is deferred to a dedicated release.
- `workspace-hack` regenerated (`cargo hakari generate`) for the
  refreshed dependency set; `cargo hakari verify` clean.

### Verified

- `cargo check --workspace --all-features` — clean.
- `cargo test --workspace --all-features` — 30 tests pass, no
  failures; the separate `--doc` pass is clean.

## [0.3.4] – 2026-05-25

OASIS conformance + crypto-provider + integrity-sidecar release.

### Added

- **OASIS validator (csaf-rs) — on by default.** New `oasis-validator`
  feature (default) wires the official CSAF 2.1 suite into export and
  import via `csaf_core::oasis`. **Export is a hard gate** — a
  non-conformant advisory is refused before any bytes are written.
  **Import is a soft gate** — findings are logged but the document is
  still admitted (third-party input is not ours to reject). CLI
  `validate` reports OASIS findings and fails on hard errors.
  (+72 transitive crates behind the default feature; disable with
  `--no-default-features`.)
- **Two further hash sidecar families.** `sidecar_blake3_512` and
  `sidecar_shake256_512` settings (default on, restored by
  reset-to-defaults), with `.blake3-512` / `.shake256-512` emitted
  alongside the SHA-2/SHA-3 sidecars — five orthogonal families per
  artefact. New settings-page toggles; new `blake3` dependency;
  `fuzz_blake3_512` / `fuzz_shake256_512` fuzz targets.
- CSAF advisories `ndaal-sa-2026-071` (0.3.3 release note),
  `-072` (test-fixture schema defect), `-073` (cvss_v4 model fix).
- `cargo-vet` supply-chain store (`supply-chain/`).

### Fixed

- **CVSS v4 model serialization.** `CvssV4` emitted the vulnerable-system
  impacts with CVSS v3 names (`confidentialityImpact`/…); renamed to the
  v4 names (`vulnConfidentialityImpact`/…), so exported `cvss_v4` objects
  conform to the CSAF 2.1 schema (advisory `ndaal-sa-2026-073`).
- **Crypto provider — ring only (no aws-lc / OpenSSL-family C).**
  `tokio-rustls`'s default features pulled `rustls/aws_lc_rs`, unifying
  `aws-lc-sys` (BoringSSL-derived) into the whole workspace contrary to
  the documented ring-only posture; pinned to
  `default-features = false, features = ["logging", "ring"]`. Drops
  `aws-lc-sys`/`aws-lc-rs` entirely and unblocks the
  `aarch64-pc-windows-msvc` cross-compile.
- **cargo-deny gate** migrated to the 0.18+ schema.
- CSAF 2.1 test fixtures: corrected `cvss_v4` keys, `cwe` → `cwes[]`,
  and added the required `cwes[].version`.

### Changed

- `csaf_mode = "2.0"` now refuses to export a structurally-2.1 advisory
  (the model emits only 2.1 structures): the export OASIS gate will not
  write a mislabelled, non-conformant document.
- Dropped the legacy non-hyphenated `.sha512` sidecar form.
- Condensed `CLAUDE.md`; `python-testing-patterns` is now a folder skill.

### Verified

- `cargo test -p csaf-core -p csaf-models` — all pass (incl. the new
  `test_oasis_gating` suite); `cargo check --workspace` clean.
- `oxlint` / `fta` clean on the web UI assets.

## [0.3.3] – 2026-05-20

Security release: closes a path-traversal write primitive in CSAF
export, confines all advisory filesystem access with capability
handles, and restricts TLS to 1.3 only.

### Security

- **Path traversal (CWE-22) in CSAF export — fixed.** The export
  path was built from the imported document's attacker-controllable
  `document.tracking.id`: validation only checked the ID was
  non-empty and `parse_tracking_id` did not reject `..` in the
  sequence component, so a crafted tracking ID such as
  `ndaal-sa-2026-../../escaped` resolved outside the configured
  export directory — an arbitrary file write as the service
  account. Documented as
  [`ndaal-sa-2026-047`](csaf/2026/047/ndaal-sa-2026-047.json).
- **Capability-confined filesystem access (defence in depth).**
  New `csaf_core::fs::DataDir` wraps the `cap-std` capability API;
  advisory import, export, dump, and audit-log writes now flow
  through `Dir`-scoped handles, so `..`, absolute paths, drive /
  UNC prefixes, and symlink / TOCTOU escapes are refused at the
  syscall layer (Linux `openat2(RESOLVE_BENEATH)`). A lexical
  guard, `csaf_core::path_security::{safe_join, is_safe_relative_path}`,
  backs every externally-derived path with a clear error before any
  filesystem operation runs.
- **Static-file serving hardened.** Replaced the naive
  `contains("..")` check with the component-based
  `is_safe_relative_path`, which also rejects absolute paths,
  drive / UNC prefixes, and NUL bytes.
- **TLS 1.3 only.** Dropped the `tls12` feature from the rustls
  dependency so the TLS 1.2 implementation is no longer compiled
  in (the server already pinned `TLS13` at runtime).

### Added

- 57-vector path-traversal regression suite
  (`crates/csaf-core/tests/test_path_security.rs`) and a
  `fuzz_safe_join` cargo-fuzz target seeded from the same corpus.
- Quality-gate harness: `scripts/quality_gates.sh` orchestrator
  (89 gates; `--list` / `--strict` / `--fast` / `--only` /
  `--skip`) plus 85 `tests/scripts/test_*.sh` runners with `.bats`
  sisters, and the `scripts/quality_gates.{uml,svg,png}` workflow
  diagram.
- `cargo-hakari` `workspace-hack` crate for build-time
  dependency-feature unification (publish members via
  `cargo hakari publish`).

### Changed

- Sidecar writers (`write_sidecar_files` /
  `write_sidecar_files_for`) take a `SidecarHashes` options object
  instead of three boolean flags.
- Removed the unused `hyper` dev-dependency from `csaf-core`;
  added doc-comment backticks and `Eq` derives across the model
  crates.

### Verified

- `cargo test --workspace --all-features` — 369 passed.
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` — clean.
- `cargo hakari verify` — workspace-hack consistent.

## [0.3.2] – 2026-04-23

### Changed

- `[workspace.dependencies]` gains a direct pin
  `generic-array = "0.14.9"`, consumed from `csaf-core` via a
  hidden `pub use generic_array::typenum::Unsigned as …` shim
  in `sidecar.rs`. 0.3.1 bumped the committed lockfiles but
  `cargo install csaf-crud` / the `cargo publish` verify step
  ignore our lockfile and re-resolve fresh, picking
  `crypto-common 0.1.7` — which hard-pins
  `generic-array = "=0.14.7"`. The direct pin on
  `generic-array 0.14.9+` forces the resolver to pick
  `crypto-common 0.1.6` instead (its `generic-array` requirement
  is the looser `^0.14.4`), so fresh resolves now converge on
  the same versions the lockfiles already use. Ref
  [`ndaal-sa-2026-040`](csaf/2026/040/ndaal-sa-2026-040.json)
  revision 1.3.0.

### Verified

- `cargo test --workspace --all-features` — 302 passed.
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` — clean.
- `cargo update` (both workspaces) — both lockfiles remain on
  `generic-array 0.14.9` + `crypto-common 0.1.6`.
- `cargo publish --dry-run -p csaf-models` — packages cleanly.

## [0.3.1] – 2026-04-23

### Changed

- **generic-array** `0.14.7` → `0.14.9` in the fuzz workspace
  (`fuzz/Cargo.lock`). The main workspace was already on
  `0.14.9`; the fuzz lockfile was held back by
  `crypto-common 0.1.7`, which hard-pins
  `generic-array = "=0.14.7"`.
  `cargo update -p crypto-common --precise 0.1.6` followed by
  `cargo update -p generic-array --precise 0.14.9` brings both
  lockfiles into alignment on the latest patch release (ref
  [`ndaal-sa-2026-040`](csaf/2026/040/ndaal-sa-2026-040.json)).

### Verified

- `cargo test --workspace --all-features` — 302 passed.
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` — clean.
- No source-level edits required.

## [0.3.0] – 2026-04-23

### Added

- **SHA-512 sidecar toggle** — new `Settings.sidecar_sha512`
  (default `true`) alongside the existing SHA-256 and SHA3-512
  toggles. Exports and database dumps now emit the three-way hash
  set (`.sha-256`, `.sha-512`, `.sha3-512`) per payload.
- **Log Directory setting** — new `Settings.log_directory`
  (default `./data_log`). The server installs a
  `tracing-appender` daily rolling file layer under this path
  (filename `csaf-crud.<date>.log`) in addition to the stderr
  subscriber; both sinks honour `RUST_LOG`.
- **Reset to default settings** — `POST /settings/reset`, fronted
  by a confirmation-guarded button at the bottom of `/settings`,
  restores every field to `Settings::default()` and records a
  `settings_reset` audit row. The CHECK constraint on
  `audit_log.action` gains `settings_reset`.
- **Audit Log export** — `/admin/export` grows an **Export Audit
  Log** card (`POST /admin/export-audit`) producing
  `audit-<UTC>.md/.csv/.json/.sarif` with matching hash sidecars
  per payload. SARIF 2.1.0 output is self-validated before write
  (version, schema, tool.driver.name, every result has
  ruleId / message.text / recognised level).
- Three new libfuzzer targets:
  `fuzz_sidecar_hash_triplet`, `fuzz_sidecar_write_ext`,
  `fuzz_audit_sarif`.
- Four new Bruno `.bru` files:
  `settings/03-reset-settings.bru`,
  `settings/04-sidecar-sha512-roundtrip.bru`,
  `settings/05-log-directory.bru`,
  `admin/03-export-audit.bru`.

### Changed

- Workspace version `0.2.1` → `0.3.0`.
- `sidecar::write_sidecar_files` and
  `sidecar::write_sidecar_files_for` gain a `write_sha512`
  parameter between `write_sha256` and `write_sha3_512`. The
  "for" helper now returns a 3-tuple of `Option<PathBuf>`.
- New helper
  `sidecar::generate_all_hashes(data) -> (sha256, sha512, sha3_512)`.
  `generate_hashes` retained as a two-tuple shim for 0.2.x callers.
- `/admin/export` and `/admin/dump` status cards now show the
  SHA-512 sidecar toggle state.

### Fixed

- **SHA-256 sidecar extension** (`.sha256` → `.sha-256`).
  Both sidecar writers were emitting the unhyphenated form,
  violating the `CLAUDE.md` §"Cryptographic hashes" mandate.
  A regression-guard test in `sidecar.rs` now fails the build
  if the legacy form ever reappears.
- `init_tracing` — the daily rolling file layer had no
  `EnvFilter`, so it captured every rustls / quinn / h2 TRACE
  internal (550 KB log file after a handful of curl requests in
  smoke testing). Both sinks now construct their own filter from
  `RUST_LOG`.
- Stale `metadata_version == "2.0"` assertions in
  `csaf_crud_cycle::test_provider_metadata_cycle` and
  `provider_meta::test_deserialize_provider_metadata` updated to
  `"2.1"` to match the in-tree `test/csaf/provider-metadata.json`
  fixture.
- Pre-existing `needless_borrow` lint in
  `fuzz_classification_writeback.rs`.
- `rust-doctor.toml` — `[ignore].rules` entries must NOT carry
  the `clippy::` prefix; four lints not on rust-doctor's
  whitelist were dropped (they remain silenced workspace-wide
  via `[workspace.lints.clippy]` in `Cargo.toml`).
- Unused `chrono` dependency removed from `crates/csaf-crud`.

### Upgrade notes

- Sidecars are derived artefacts — re-run `/admin/export` and
  `/admin/dump` once and the hyphenated extensions land
  alongside the payloads.
- Fresh databases on 0.3.0 accept `settings_reset` as an
  `audit_log.action` value; 0.2.x databases keep the narrower
  CHECK constraint and silently drop those rows. A future
  release will add an online migration if this becomes a
  blocker.

## [0.2.1] – 2026-04-23

### Changed

- **matchit** `0.8.6` → `0.9.2` (ref
  [`ndaal-sa-2026-034`](csaf/2026/034/ndaal-sa-2026-034.json)).
- **rcgen** `0.13.2` → `0.14.7` (ref
  [`ndaal-sa-2026-035`](csaf/2026/035/ndaal-sa-2026-035.json)).
- **sysinfo** `0.32.1` → `0.38.4` (ref
  [`ndaal-sa-2026-038`](csaf/2026/038/ndaal-sa-2026-038.json)).
- **generic-array** `0.14.7` → `0.14.9` — transitive patch
  bump via `cargo update -p generic-array --precise 0.14.9`
  (ref [`ndaal-sa-2026-040`](csaf/2026/040/ndaal-sa-2026-040.json)).

### Verified

- `cargo test --workspace --all-features` — 280 passed,
  0 failed, 0 ignored.
- `cargo check --workspace --all-targets` — clean.
- No source-level edits required for any of the three
  breaking-semver bumps.

## [0.2.0] – 2026-04-23

### Added

- 19 new crate-integration tests covering `askama`,
  `hyper-util`, `http-body-util`, `quinn`, `h3` / `h3-quinn`,
  and `tracing-subscriber`
  (`crates/csaf-core/tests/test_crate_integrations.rs`
  sections 26–30). Total: 129 integration tests.
- Informational advisories
  [`ndaal-sa-2026-032`](csaf/2026/032/ndaal-sa-2026-032.json)
  through
  [`ndaal-sa-2026-039`](csaf/2026/039/ndaal-sa-2026-039.json)
  documenting the planned dependency bumps and the 0.2.0
  release event.

### Changed

- Workspace version bumped from `0.1.0` to `0.2.0`; all four
  member crates publish together via `version.workspace = true`.
- **CLI crate renamed**: `csaf-cli` → `ndaal-csaf-cli`. The
  `csaf-cli` name on crates.io is owned by the unrelated
  `scm-rs/csaf-walker` project at 0.15.1. Users install the
  renamed crate with
  `cargo install ndaal-csaf-cli` and invoke the
  `ndaal-csaf-cli` binary instead of `csaf-cli`.
  See
  [`ndaal-sa-2026-039`](csaf/2026/039/ndaal-sa-2026-039.json).

### Published to crates.io

- [`csaf-models 0.2.0`](https://crates.io/crates/csaf-models/0.2.0)
- [`csaf-core 0.2.0`](https://crates.io/crates/csaf-core/0.2.0)
- [`csaf-crud 0.2.0`](https://crates.io/crates/csaf-crud/0.2.0)
- [`ndaal-csaf-cli 0.2.0`](https://crates.io/crates/ndaal-csaf-cli/0.2.0)

## [0.1.0] – 2026-04

### Added

- Initial workspace layout with four crates: `csaf-models`,
  `csaf-core`, `csaf-crud`, and `csaf-cli`.
- HATEOAS JSON API on TCP `127.0.0.1:8180` (TLS 1.3,
  HTTP/1.1 + HTTP/2) and HTTP/3 on UDP `127.0.0.1:8181`.
- CSAF 2.0 / 2.1 document storage via `redb`, with a SQLite
  sidecar for users, audit log, and settings.
- Sidecar hashes (`.sha-256`, `.sha-512`, `.sha3-512`) for
  every CSAF advisory and release artefact.