README

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

# SARIF Viewer

An **offline, single-binary viewer, validator and multi-format exporter** for
[SARIF](https://sarifweb.azurewebsites.net/) **2.1.0** (the OASIS standard) and
**SARIF 2.2** (the OASIS working draft).

Load the static-analysis output of CodeQL, Semgrep, opengrep, ruff, clippy,
htmlhint, `ansible-lint` or any other SARIF-emitting tool, browse the runs and
results in a local web UI, **validate every document against the bundled SARIF
JSON Schemas**, and export to **SARIF / Markdown / ODT / PDF** with checksum
sidecars.

This project reuses, as much as possible, two existing ndaal Rust codebases:

- the **CSAF-CRUD** validation / import / export pattern
  (`ndaal_public_csaf_crud`) — schema validation enforced on every import and
  export; and
- the **BSI-Grundschutz OSCAL viewer**
  (`ndaal_public_bsi_grundschutz_oscal_viewer`) — its `matchit` router +
  security-header middleware, and its multi-format export engine (`lo_writer`
  for Markdown/ODT, an embedded-font renderer for PDF, checksum sidecars).

## Highlights

- **Dual-schema validation.** Both the OASIS **2.1.0 errata** schema
  (JSON-Schema draft-04) and the **2.2 draft** schema (JSON-Schema 2020-12) are
  embedded at build time. Validation runs against the version-matched schema
  plus a structural-conformity floor; a dual cross-check against the *other*
  schema is reported as advisory (per the ndaal `sarif` skill's dual-validation
  requirement).
- **Validate on every import and export.** An invalid document is **rejected on
  import** and **refused on export** — nothing un-validated is ever loaded or
  written.
- **Multi-format export.** SARIF (validated JSON), **Markdown**, **ODT**, and
  **PDF** (Unicode-faithful via an embedded Roboto font). Each artifact gets up
  to five checksum sidecars: SHA-256, SHA-512, SHA3-512, BLAKE3-512,
  SHAKE256-512.
- **Bootstrap web UI.** The same house style as the ndaal OSCAL/CSAF viewers —
  Bootstrap 5.3, Bootstrap Icons, the ndaal logo in the navbar, a light/dark
  `data-bs-theme` switcher, cards and badges. All assets (Bootstrap CSS/JS,
  icon font, Roboto, sample logs, the logo) are embedded in the binary; no CDN.
- **Docs, in the app.** The Info menu's Changelog/README/Administrator
  Guide/User Guide entries render the project's own `CHANGELOG.md`,
  `README.md` and `documentation/` guides — embedded at compile time, so the
  running binary always matches what it documents about itself.
- **HTTPS + HTTP/3.** Serves TLS 1.3 only — HTTP/1.1 + HTTP/2 over TCP and
  **HTTP/3 over QUIC/UDP** (advertised via `Alt-Svc`) — with a self-signed
  localhost certificate minted at start-up (rcgen + rustls/ring + quinn/h3,
  reused from the ndaal CSAF-CRUD listener).
- **Single self-contained binary.** No external services; binds loopback. The
  only JavaScript is the vendored `bootstrap.bundle.min.js` and an external
  theme switcher — no inline scripts.
- **Hardened web layer.** Bounded concurrency, TLS-handshake + slowloris
  header-read timeouts, and a strict response security-header set (CSP
  `script-src 'self'`, HSTS, `X-Frame-Options`, `Referrer-Policy`,
  COOP/COEP/CORP, …).

## Install

Three ways to install, fastest first. Each delivers the same self-contained
binary — it embeds the SARIF schemas, UI assets and sample logs, so copying the
single file is a complete installation.

```bash
# 1. Prebuilt binary, no compile (recommended). cargo-binstall fetches the
#    matching GitLab release asset via the crate's
#    [package.metadata.binstall] mapping.
cargo binstall sarif-viewer

# 2. Compile and install from crates.io.
cargo install sarif-viewer
```

Both resolve the latest published release from crates.io
(<https://crates.io/crates/sarif-viewer>); append `--version 0.1.4` to pin one.

Or download the binary for your platform from the project's GitLab releases page
(<https://gitlab.com/vPierre/ndaal_public_sarif_viewer/-/releases>) and verify
its checksum:

```bash
ver=0.1.4
base="https://gitlab.com/vPierre/ndaal_public_sarif_viewer/-/releases/v${ver}/downloads"
file="sarif-viewer-${ver}-x86_64-unknown-linux-musl"   # pick your platform
curl -LO "${base}/${file}"
curl -LO "${base}/SHA256SUMS"
sha256sum -c SHA256SUMS 2>&1 | grep "${file}"
chmod +x "${file}" && ./"${file}"
```

Prebuilt targets: Linux x86_64 / ARM64 (static, musl), Windows x86_64 / ARM64,
macOS Intel / Apple Silicon. On macOS, clear the quarantine flag on the unsigned
binary with `xattr -d com.apple.quarantine <file>`.

## Updating

The binary can check and update itself against the project's GitLab releases:

```bash
sarif-viewer --check-update   # read-only; reports whether a newer release exists
sarif-viewer --self-update    # downloads, checksum-verifies and installs it
```

`--self-update` fetches the release binary for your target triple, verifies its
SHA-256 against the release's committed `SHA256SUMS`, and atomically replaces
the running binary — it never overwrites anything unless the checksum matches.
Set `--no-self-update` or `SARIF_NO_SELF_UPDATE=1` to make `--self-update`
refuse (for package-managed or locked-down installs); `--check-update` stays
available either way.

## Build

For development, or a platform without a prebuilt binary, compile from source:

```bash
cargo build --release
# binary: target/release/sarif-viewer
```

Requires a recent stable Rust toolchain (developed against 1.96).

## Usage

### Web viewer (default)

```bash
sarif-viewer                       # HTTPS on 127.0.0.1:8580, HTTP/3 on :8581
sarif-viewer serve --port 8580 --quic-port 8581 \
    --import ./scan-results/ \      # pre-load a file or directory (repeatable)
    --export-dir ~/sarif-out
sarif-viewer serve --no-quic        # disable the QUIC/HTTP-3 listener
```

The QUIC (HTTP/3) port defaults to the HTTPS port + 1. The localhost certificate
is self-signed, so browsers warn once. Then open <https://127.0.0.1:8580>.
Pages:

| Path                  | Purpose                                                                  |
| --------------------- | ------------------------------------------------------------------------ |
| `/`                   | Dashboard: stats, import box, loaded-log table                           |
| `/logs/{id}`          | Run/result detail for one log                                            |
| `/logs/{id}/validate` | Dual-schema validation report for a loaded log                           |
| `/logs/{id}/download` | Download the raw SARIF                                                   |
| `/import`             | Import a `.sarif`/`.json` file or directory (validated)                  |
| `/validate`           | Validate a file on disk *without* loading it                             |
| `/export`             | Export a loaded log to SARIF / Markdown / ODT / PDF                      |
| Info menu (top-right) | About · System info · Privacy · Security · License · Imprint · Changelog |

The viewer ships two embedded, schema-valid sample logs so it is useful on first
launch.

### Command line

```bash
# Validate (exit code 1 if invalid); runs the dual 2.1.0 + 2.2 check.
sarif-viewer validate report.sarif

# Validate then export to every format with checksum sidecars.
sarif-viewer export report.sarif --out-dir ./out

# Selected formats only, overwrite, no sidecars.
sarif-viewer export report.sarif --format markdown --format pdf \
    --out-dir ./out --overwrite --no-sidecars
```

## Architecture

```text
src/
├── main.rs            CLI (serve / validate / export) + entry point
├── lib.rs             module map + embedded-sample loader
├── schema.rs          compiled JSON-Schema validators (2.1.0, 2.2) — embedded
├── validation.rs      structural floor + schema gate (import & export)
├── model.rs           typed read-only views over a SARIF Value
├── store.rs           in-memory store of loaded logs
├── import.rs          parse → validate → admit
├── export.rs          SARIF/MD/ODT/PDF artifact builder + writer (validates first)
├── export_pdf.rs      embedded-font PDF renderer        (reused from OSCAL viewer)
├── sidecar.rs         SHA-2/SHA-3/BLAKE3/SHAKE sidecars (reused from OSCAL viewer)
├── router.rs          matchit router + security headers (reused from OSCAL viewer)
├── server.rs          bounded localhost HTTP accept loop
├── static_files.rs    embedded static assets (Roboto faces)
├── html.rs            server-side HTML layout (themed, no JS)
├── app_state.rs       shared state (store, export dir, listen addr)
└── routes/            home, import, validate, export, about pages
schemas/               vendored OASIS SARIF 2.1.0 + 2.2 draft JSON Schemas
data/samples/          embedded + on-disk sample SARIF logs
```

## Tests & fuzzing

```bash
cargo test          # 138 tests: unit + route sequence + path-traversal corpus + property suite
```

- `tests/test_routes.rs` — drives the router over every page (the Info/"About"
  menu, the standard pages), the import/validate/export actions, per-log views,
  static assets, the 404 fallback and HEAD/405 handling.
- `tests/test_path_security.rs` — the path-traversal regression corpus.
- `tests/proptest_invariants.rs` — `proptest` property suite (23 properties)
  restating the slugify/`safe_join`/`is_safe_segment`/`escape`/sidecar/decoder/
  model invariants, the self-updater's semver/checksum/redirect contracts, the
  Info-menu doc-page renderer's escaping, and the `html` builders'
  (`level_bg`/`alert`/`card`) totality, with structured strategies and
  shrinking; runs on stable, complementing the byte-level fuzz targets.

22 `cargo-fuzz` targets live under `fuzz/` (run on a nightly toolchain):

```bash
cargo +nightly fuzz run fuzz_safe_join -- -max_total_time=60   # one target
fuzz/run-all.sh 60                                             # all targets
```

Targets:

- Parsers / guards: `fuzz_safe_join`, `fuzz_slugify` (export-filename safety
  invariant), `fuzz_url_decode`, `fuzz_query_pairs`, `fuzz_token_parsers`,
  `fuzz_static_serve` (asset-path guard), `fuzz_html_escape` (XSS boundary).
- SARIF pipeline: `fuzz_sarif_validate`, `fuzz_sarif_model`, `fuzz_import_load`
  (full import).
- Renderers: `fuzz_export_pdf` (embedded-font PDF layout), `fuzz_export_render`
  (SARIF/Markdown/ODT/PDF), `fuzz_sidecar_hash`, `fuzz_html_render` (Bootstrap
  badge/alert/card/page builders), `fuzz_report_html` (validation-report page),
  `fuzz_log_render` (full log-detail view from a hostile SARIF document),
  `fuzz_doc_page` (the Info-menu embedded-document renderer).
- Self-update: `fuzz_updater_asset_name`, `fuzz_updater_is_newer`,
  `fuzz_updater_parse_sha256sums`, `fuzz_updater_resolve_redirect`.
- Dispatch: `fuzz_route_match` (full request routing).

`loom-harness/` — a standalone [Loom](https://github.com/tokio-rs/loom)
permutation-testing crate (its own workspace, so `--cfg loom` never touches
this crate's tokio-dependent build) proving `SarifStore`'s `RwLock`-guarded
insert path is race-free under every thread interleaving:

```bash
bash tests/scripts/test_loom.sh   # self-skips if the toolchain/crate is absent
```

## Validation notes

The SARIF 2.1.0 schema does **not** permit `null` for absent optional properties
(e.g. `tool.driver.organization`). Some tools nonetheless emit `null`s; such
files are reported invalid. Two of the on-disk samples (`*-report.sarif`) are
deliberately non-conformant in this way so you can see the import-rejection
path; `minimal.sarif` and `nvulnlookup-clean.sarif` are schema-valid.

## Security & deployment

The server binds `127.0.0.1` and serves TLS 1.3 only (HTTP/1.1 + HTTP/2 over
TCP, HTTP/3 over QUIC/UDP) with a self-signed localhost certificate regenerated
each start-up — it is an offline, single-user desktop tool. Use a CA-issued
certificate (or a TLS-terminating reverse proxy) for any non-loopback exposure.
The CSP allows only `script-src 'self'` (the vendored Bootstrap bundle and the
external theme switcher); there are no inline scripts, so no `'unsafe-inline'`
and no per-script hash to maintain.

## Documentation

- German / French mirrors: [`LIESMICH.md`](LIESMICH.md) ·
  [`LISEZMOI.md`](LISEZMOI.md)
- [`REFERENCE.md`](REFERENCE.md) — CLI, routes and configuration reference
- [`structure.md`](structure.md) — repository layout
- [`CHANGELOG.md`](CHANGELOG.md)
- Guides under [`documentation/`](documentation/): Administrator, User,
  Troubleshooting, Developer.
- Code wiki: [`documentation/wiki/en/overview.md`](documentation/wiki/en/overview.md)
  (prose) / [`documentation/wiki/en/codemap.md`](documentation/wiki/en/codemap.md)
  (structural, "where is X") — a navigable map of the codebase for
  contributors and coding agents.
- API tests: [`test/bruno/`](test/bruno/) (run with `test/bruno/run.sh`).

## License

Apache-2.0 (code). Bundled assets carry their own licenses: the Roboto fonts
(OFL-1.1) and the vendored OASIS SARIF schemas. © 2026 ndaal GmbH & Co KG.