<!--
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).