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