Changelog

<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2026 ndaal Informationstechnik GmbH & Co KG, Cologne
SPDX-FileCopyrightText: Author: Pierre Gronau <Pierre.Gronau@ndaal.eu>
-->

# Changelog

All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/) and this project adheres to
[Semantic Versioning](https://semver.org/).

## [1.1.12] - 2026-09-16

### Security

- **`rustls` 0.23.42 → 0.23.45 — fixes
  [RUSTSEC-2026-0285](https://rustsec.org/advisories/RUSTSEC-2026-0285)**
  (`GHSA-2mjx-qc3c-rqvc`, CVSS v3.1 5.3 MEDIUM). rustls accepted a TLS 1.3
  handshake message sent at the wrong encryption level when it followed a
  key-changing message inside the same record — for example a plaintext
  `EncryptedExtensions` packed into the `ServerHello` record — where RFC 8446
  section 5.1 requires the connection to be terminated with an
  `unexpected_message` alert. The handshake transcript stays authenticated, so
  a network-position attacker can neither alter nor complete a handshake
  through this flaw; the practical effect is that a peer could send handshake
  messages in plaintext that should have been encrypted without rustls
  rejecting the connection. This is on the primary trust boundary: rustls
  terminates TLS for every request to `serve`. Advisory
  `ndaal-sa-2026-629`.
- **`hyper` 1.10.1 → 1.11.1, raising `h2` 0.4.15 → 0.4.19 — fixes
  [RUSTSEC-2026-0258](https://rustsec.org/advisories/RUSTSEC-2026-0258)**
  (`GHSA-q83h-524g-xf6h`, CVSS v3.1 3.7 LOW, ndaal assessment — upstream
  publishes no vector and rates it "Low severity"). h2 accepted and queued
  empty HTTP/2 DATA frames without limit, so a peer whose streams are not
  actively drained could drive unbounded memory growth, or a panic on length
  overflow — a denial of service against the listener. Reachable in this
  product rather than merely compiled in: `src/server.rs` builds
  `hyper_util::server::conn::auto::Builder` with `http2()` configured, so
  HTTP/2 is negotiated on the network-reachable surface. Advisory
  `ndaal-sa-2026-630`.
- `cargo audit` now reports **0 vulnerabilities** on the resolved lockfile,
  down from 2 before this release. The only remaining output is the allowed
  `ttf-parser` 0.25.1 unmaintained warning (RUSTSEC-2026-0192), which this
  release does not change.

### Changed

- **Full dependency relock** (`cargo update`): 125 crates change, 10 are added
  and 2 removed, all within the existing manifest requirements — no
  `Cargo.toml` dependency edits were needed. Direct dependencies advanced:
  `anyhow` 1.0.104, `blake3` 1.8.7, `cap-std` 4.0.3, `clap` 4.6.7, `http`
  1.5.0, `http-body-util` 0.1.5, `hyper` 1.11.1, `jsonschema` 0.48.5, `quinn`
  0.11.12, `rcgen` 0.14.10, `rustls` 0.23.45, `self_update` 1.3.0, `serde`
  1.0.229, `serde_json` 1.0.151, `time` 0.3.55, `tokio` 1.53.1,
  `tokio-rustls` 0.26.5.
- **`self_update` leaves its release candidate: 1.0.0-rc.6 → 1.3.0.** The
  manifest requirement string is a caret requirement, so the lockfile resolved
  onto the stable 1.3.0 line with no manifest change. The shipped
  `--self-update` path no longer runs a pre-release dependency.
- Notable transitive advances folded into the above: the `aws-lc-rs`
  cryptographic provider 1.17.1 → 1.18.1 with `aws-lc-sys` 0.42.0 → 0.45.0,
  `pem` 3.0.6 → 4.0.0 (semver-major, beneath `rcgen`), `ureq` 3.3.0 → 3.4.2
  beneath `self_update`, `quinn-proto` 0.11.15 → 0.11.18 moving onto the
  `rand` 0.10 line, and `num-bigint` 0.4.7 → 0.4.8, clearing a yanked-crate
  warning `cargo audit` raised against the previous lockfile.
- CSAF advisories `ndaal-sa-2026-629` through `645` record this sweep, one
  document per direct dependency, with machine-readable CVSS v3.1 and v4.0
  metrics on each. Transitive advances are folded into the advisory for the
  direct dependency that pulls them.
- Version bump to 1.1.12.

### Known issues

- The `self_update` requirement in `Cargo.toml` still literally reads
  `"1.0.0-rc.6"` even though it resolves to 1.3.0. It should be tightened to
  `"1.3"` so the manifest reads as the line it actually resolves to.
- `jsonschema` stays on the 0.48 line (0.56 is available), and `lo_core` /
  `lo_writer` on 0.4 (0.5 available), `sha3` on 0.11 (0.12 available). All are
  semver-incompatible jumps deliberately left out of a currency sweep.

## [1.1.11] - 2026-07-18

### Fixed

- **`SarifStore::unique_id` / `insert` race (lost update).** Computing a
  collision-free id and inserting the log were two separate lock
  acquisitions (`import::import_file`), so two concurrent `/import/run`
  requests importing the same file name could both observe no existing id,
  both compute the same one, and the second insert would silently overwrite
  the first — one imported log vanishing without any error. Fixed by
  `SarifStore::insert_unique`, which computes the id and pushes under a
  single write-lock acquisition; `import_file` now uses it instead of the
  separate `unique_id()` + `insert()` calls. Proven race-free under every
  thread interleaving by a new Loom model (see Added).

### Added

- **`loom-harness/`** — a standalone [Loom](https://github.com/tokio-rs/loom)
  permutation-testing crate (own empty `[workspace]`, per
  `skills/rust-loom/SKILL.md`) modelling `SarifStore`'s `RwLock`-guarded
  state: `insert_unique` never produces duplicate ids under any
  interleaving (the property that was FALSE before the fix above — the
  harness's `README.md` documents reproducing the pre-fix bug by swapping in
  the kept-as-reference buggy shape), the `RwLock` serializes writers with
  no lost update or torn read, and a smoke test for the `OnceLock`-style
  settle-once pattern (`router::ALT_SVC`, `schema`'s validator caches). Gate:
  `bash tests/scripts/test_loom.sh` (self-skips if the toolchain or crate is
  absent).
- 3 more `proptest` properties, backfilling the "remaining proptest" gaps
  found auditing every pure function against `tests/proptest_invariants.rs`
  (per `skills/rust-testing`'s definition-of-done): `path_security::is_safe_segment`
  now has its own direct property (previously only checked indirectly via
  `slugify`'s output), `html::level_bg` (total, closed over its four known
  Bootstrap classes), and `html::alert`/`card` (total, deterministic, never
  re-escape or truncate their already-built inner HTML).
- Bruno coverage for the three new Info-menu pages
  (`test/bruno/collections/info/08-readme.bru`,
  `09-administrator-guide.bru`, `10-user-guide.bru`), and `07-changelog.bru`'s
  assertion updated to match the now-live embedded `CHANGELOG.md` (it
  previously asserted against the old hand-written stub's "initial release"
  text).
- CSAF advisories `ndaal-sa-2026-330` through `337` for the dependency
  currency sweep below (all CVSS v3.1/v4.0 `0.0`/NONE — `cargo audit`
  reported zero findings for any of the eight crates before or after).

### Changed

- Version bump to 1.1.11.
- Dependency currency sweep (`cargo outdated` / `cargo update`): `bytes`
  1.12.0 → 1.12.1, `clap` 4.6.1 → 4.6.2, `http-body-util` 0.1.3 → 0.1.4,
  `rustls` 0.23.41 → 0.23.42, `tokio` 1.52.3 → 1.53.0, `lo_core` 0.4.8 →
  0.4.9, `lo_writer` 0.4.8 → 0.4.10, `jsonschema` 0.46.9 → 0.48.1 (the one
  requiring a `Cargo.toml` version-requirement raise from `"0.46"` to
  `"0.48"`; no source change — `src/schema.rs`/`src/validation.rs`'s API
  surface is unaffected and the full test suite passes unchanged). `sha3`
  stays pinned at `"0.11"` — 0.12 dropped the `Shake256` XOF the
  `.shake256-512` sidecar needs.

## [1.1.10] - 2026-07-18

### Added

- The Info-menu dropdown gains three new pages, matching the
  `grundschutz-oscal-viewer` pattern: `/readme` (the embedded `README.md`),
  `/administrator-guide` (`documentation/Administrator_Guide.md`) and
  `/user-guide` (`documentation/User_Guide.md`), each rendered as
  escaped, scrollable preformatted text (`routes::info::doc_page_html`) —
  never parsed as Markdown, so the raw document can never inject live markup.
  `/changelog` now renders the real embedded `CHANGELOG.md` the same way,
  replacing the previous hand-written, version-1.1-stale summary.
- The navbar brand now shows the ndaal logo (`src/static/img/logo.png`,
  embedded at compile time) instead of a Bootstrap-icon glyph, matching
  `grundschutz-oscal-viewer`'s navbar.
- One new `proptest` property (`doc_page_html_is_total_and_escapes_everything`)
  and one new `cargo-fuzz` target (`fuzz_doc_page`) covering the new
  doc-page renderer's escaping and determinism invariants. Three new unit
  tests assert the four embedded documents match their on-disk source, are
  non-trivial, and render their full (untruncated) content.

### Changed

- Version bump to 1.1.10.
- `Cargo.toml`'s package `include` list now also ships `CHANGELOG.md`,
  `documentation/Administrator_Guide.md` and `documentation/User_Guide.md`
  — required for the new embeds to resolve when building from the
  published crate rather than the full git checkout.

### Fixed

- All 228 `htmlhint` findings across every page (attribute ordering,
  `<meta name="description">` and `<link rel="canonical">` added to the
  page shell, the theme switcher moved from `<head>` to the top of
  `<body>` — same before-first-paint behaviour, no more disallowed
  `<script>`-in-`<head>`, the system-info table's inline `style` replaced
  with a `.info-key` CSS class, and a `<label>` added to each of the three
  path-input forms). All 8 `oxlint` findings in `theme.js` (`no-var`,
  unused `catch` bindings). No behavioural change; verified by the full
  test suite plus a manual re-crawl of all 14 pages showing zero findings.

## [1.1.9] - 2026-07-17

### Added

- Offline-first self-update: `--check-update` (read-only) and `--self-update`
  (download, checksum-verify, atomically replace the running binary),
  `src/updater.rs`. Fetches the target-triple release asset over the same
  aws-lc-rs / TLS-1.3-only transport the server uses, verifies its SHA-256
  against the release's committed `release/SHA256SUMS`, and pins the exact
  GitLab release tag it checksummed so the installed binary can never diverge
  from the one verified. `--no-self-update` / `SARIF_NO_SELF_UPDATE` opts a
  package-managed or locked-down install out of `--self-update`;
  `--check-update` stays available either way.
- 7 new `proptest` properties and 4 new `cargo-fuzz` targets
  (`fuzz_updater_asset_name`, `fuzz_updater_is_newer`,
  `fuzz_updater_parse_sha256sums`, `fuzz_updater_resolve_redirect`) covering
  the updater's pure surfaces: asset naming, the semver no-downgrade gate, the
  `SHA256SUMS` manifest parser, and the redirect resolver's scheme-downgrade
  guard.

### Changed

- Version bump to 1.1.9.
- New dependencies: `self_update` (`default-features = false`, `gitlab` +
  `ureq` + `rustls` + `checksums` only — no reqwest/openssl), `hyper-rustls`,
  `rustls-native-certs`. `deny.toml` allows `CDLA-Permissive-2.0`
  (`webpki-roots`).

## [1.1.8] - 2026-07-04

### Added

- Two more `proptest` properties (12 total): `static_serve_is_total` and
  `query_pairs_accessors_are_total`, restating invariants that already had
  `cargo-fuzz` coverage (`fuzz_static_serve`, `fuzz_query_pairs`) but no
  shrinkable counterpart for stable-toolchain CI runs.

### Changed

- Version bump to 1.1.8.
- The sidecar-digest property now checks determinism across all five checksum
  algorithms (previously only SHA-256).
- `fuzz/`'s `bytes` dependency bumped 1.11.1 → 1.12.0.

### Fixed

- Cleared 35 pre-existing
  `cargo clippy --workspace --all-targets --all-features -- -D warnings` errors
  across 12 files — the project's own documented gate was failing on `main`
  before this release, unrelated to any functional change. No behaviour change
  to the shipped binary.

## [0.1.7] - 2026-06-20

### Security

- Build `jsonschema` with `default-features = false` (keeping only
  `resolve-file`), dropping its `resolve-http` feature. This removes the
  compiled-in HTTP client (`reqwest`) and its CA-roots bundle
  (`webpki-root-certs`, `rustls-platform-verifier`) — 34 transitive crates —
  eliminating a latent outbound-HTTP / SSRF surface that has no place in an
  offline, schema-embedded validator. Validation is unaffected (the bundled
  OASIS schemas are self-contained; all known-answer and proptest checks pass).
  Advisory `ndaal-sa-2026-106` (CVSS 3.1 = 0.0, not reachable in shipped code).

### Changed

- `deny.toml`: allow the `MIT-0` license (MIT No Attribution), used by
  `borrow-or-share` in the `jsonschema` / `referencing` chain, so
  `cargo deny check` passes cleanly. `CDLA-Permissive-2.0` (`webpki-root-certs`)
  is no longer present after the HTTP-client removal above. Advisory
  `ndaal-sa-2026-107` (CVSS 3.1 = 0.0, supply-chain policy, no security impact).

## [0.1.6] - 2026-06-20

### Security

- `html::escape` now neutralizes Unicode bidirectional and format control
  characters (`U+200E`/`U+200F`/`U+061C`, `U+202A`–`U+202E`, `U+2066`–`U+2069`)
  in all SARIF-derived text rendered into the web UI, rendering each as a
  visible inert marker (`[U+202E]`) instead of passing the raw codepoint into
  the DOM. This closes a **Trojan Source** display vector (CVE-2021-42574): a
  crafted SARIF document could previously use bidirectional overrides to
  visually reorder a path or message and mislead an analyst. The viewer is
  local, single-user and read-only, so the impact is rated CVSS 3.1 = 0.0
  (NONE); this is defence-in-depth. See advisory `ndaal-sa-2026-105`.
- Coverage: a known-answer test plus an added invariant in the `proptest` suite
  and the `fuzz_html_escape` target ("no bidirectional control codepoint
  survives into the escaped output"). The `unicode-security` crate was evaluated
  and not adopted — it targets UTS #39 identifier confusables / mixed-script,
  which does not fit free-text SARIF display and does not cover bidirectional
  controls.

## [0.1.5] - 2026-06-20

### Changed

- Dependency bumps: `bytes` 1.11 → 1.12; `sha2` 0.10 → **0.11** and `sha3` 0.10
  → **0.11** (both now on the RustCrypto `digest` 0.11 generation). The
  checksum-sidecar code is unchanged and the NIST known-answer vectors still
  pass, so every digest is byte-identical to 0.1.4.

### Notes

- `sha3` is held at **0.11**, not the newer 0.12: `sha3` 0.12 removed the
  SHAKE/XOF variants, and the `.shake256-512` sidecar needs `Shake256`.

## [0.1.4] - 2026-06-20

### Added

- A `proptest` property-based test suite (`tests/proptest_invariants.rs`, 10
  properties) restating the security-critical invariants — `slugify` /
  `unique_id` segment safety, `safe_join` containment, the `escape` XSS
  boundary, the Bootstrap builders, the checksum sidecars, the URL/query and
  export-token parsers, and the model's severity-tally invariant — with
  structured strategies and shrinking. Unlike the nightly-only fuzz targets,
  these run on stable under `cargo test` (suite now 92 tests).
- Three more `cargo-fuzz` targets (17 total): `fuzz_html_render` (the Bootstrap
  badge/alert/card/page builders and the page-title escaping),
  `fuzz_report_html` (the validation-report renderer over arbitrary documents)
  and `fuzz_log_render` (the full log-detail view rendered from a hostile SARIF
  document, exercising `truncate`, `escape`, the severity badges and the page
  shell).
- An `Install` chapter in the three README mirrors (`README.md`, `LIESMICH.md`,
  `LISEZMOI.md`) and the Administrator Guide, covering `cargo binstall`
  (prebuilt GitLab release asset), `cargo install` (crates.io) and direct binary
  download with checksum verification.

### Changed

- Documentation updated to list the 17 fuzz targets and the property suite. No
  functional change to the shipped binary (the property suite, fuzz targets and
  docs are excluded from the published crate).

## [0.1.3] - 2026-06-16

### Added

- Six more `cargo-fuzz` targets (14 total): `fuzz_import_load`,
  `fuzz_export_render`, `fuzz_export_pdf`, `fuzz_token_parsers`,
  `fuzz_static_serve`, `fuzz_html_escape` — covering the import pipeline, all
  export renderers, the static-asset path guard and the HTML-escaping boundary.

### Changed

- Documentation updated to list all 14 fuzz targets. No functional code change
  since 0.1.2.

## [0.1.2] - 2026-06-16

### Changed

- Release cut from the source committed on `main`; `v0.1.2` tag matches the
  published crate and the GitLab release binaries (`cargo binstall` aligned).

### Added (since 0.1.0)

- `[package.metadata.binstall]` so `cargo binstall sarif-viewer` fetches the
  prebuilt GitLab release binary instead of compiling from source.

## [0.1.0] - 2026-06-16

### Added

- SARIF 2.1.0 + SARIF 2.2 (draft) viewer, validator and multi-format exporter in
  a single binary.
- Schema-validation gate enforced on every import and export (structural floor
  plus the embedded OASIS 2.1.0 and 2.2 JSON Schemas, with an advisory dual
  cross-check).
- Multi-format export: SARIF (JSON), Markdown, ODT and PDF, each with up to five
  checksum sidecars (SHA-256, SHA-512, SHA3-512, BLAKE3-512, SHAKE256-512).
- Bootstrap 5.3 web UI matching the ndaal house style: navbar, cards, severity
  badges, a light/dark `data-bs-theme` switcher, and an Info menu (About, System
  info, Privacy, Security, License, Imprint, Changelog).
- CLI subcommands `serve` (default), `validate` and `export`.
- Exhaustive integration test sequence (`tests/test_routes.rs`), a
  path-traversal regression corpus (`tests/test_path_security.rs`), and eight
  `cargo-fuzz` targets under `fuzz/`.
- Bruno API test collection under `test/bruno/`.

### Security

- HTTPS only on TLS 1.3, built on rustls with the **aws-lc-rs** provider and
  `prefer-post-quantum`, so the **X25519MLKEM768** post-quantum hybrid
  key-exchange group is offered first. Self-signed localhost certificate
  regenerated at every start-up.
- HTTP/3 over QUIC (UDP), advertised via `Alt-Svc`.
- Defence-in-depth response headers on every response (CSP `script-src 'self'`,
  HSTS, `X-Frame-Options: DENY`, `nosniff`, `Referrer-Policy: no-referrer`,
  Permissions-Policy deny list, COOP/COEP/CORP).
- `#![forbid(unsafe_code)]`; lint policy denies `unwrap`/`expect`/`panic`,
  unchecked arithmetic and unchecked indexing in production code; `cargo-deny`
  and `cargo-audit` configured.
- Exports written through capability-scoped `cap-std` directory handles with
  create-new semantics (path-traversal / TOCTOU defence).