ndaal CSAF-CRUD
Secure, single-binary Rust CRUD application for managing
CSAF 2.0 and 2.1 security advisories. Provides both a
server-side rendered web UI (Hyper + Bootstrap 5 + HTMX) and a
RESTful HATEOAS JSON API under /api/v1/.
Features
- CSAF 2.0 / 2.1 — Full serde types, strict validation, dual CVSS v3.1 + v4.0 scoring.
- Classification support — TLP 2.0 colour-coded selector
(
CLEAR/GREEN/AMBER/AMBER+STRICT/RED), German Verschlusssache (VS-NfD / VS-Vertr. / Geh. / Str. Geh.), and NATO (NR / NC / NS / CTS). Written into the exported CSAF either todocument.distribution.text, todocument.notes[], or to both — configurable per-deployment. - Dual embedded database —
redbfor CSAF documents,rusqlite(bundled) for user management and audit logging. - Zero external dependencies — Single binary deployment.
- Protocols — HTTP/1.1, HTTP/2, and HTTP/3 (QUIC) over TLS 1.3 via rustls, ring crypto, and quinn.
- Self-signed certificates — Auto-generated via rcgen with 45-day validity (Let's Encrypt short-lived cert model).
- Sidecar hashes — SHA-256 (
.sha-256), SHA-512 (.sha-512), and SHA3-512 (.sha3-512) generated for every export and database dump, each algorithm individually toggleable via settings. The hyphenated extension form matchesCLAUDE.md§"Cryptographic hashes (release + CSAF)". - Rolling file logging —
tracing-appenderwrites a dailycsaf-crud.<date>.logunder the configurable Log Directory (default./data_log); stderr output is preserved.RUST_LOGgoverns both sinks. - Audit Log export — The
/admin/exportpage grows an Export Audit Log card that emits theaudit_logtable as Markdown, CSV, JSON, and SARIF 2.1.0 — each payload ships the three hash sidecars above. SARIF output is self-validated before write (version, schema, tool.driver.name, every result has a ruleId / message.text / recognised level). - Reset to defaults — The
/settingspage carries a confirmation- guarded Reset to default settings button that restores every field toSettings::default()and records asettings_resetrow in the audit log. - HATEOAS API — HAL-like
_linksin every response, RFC 9457 Problem Details error format. - Menu structure — CSAF (CRUD), Administration (Import/Export), Settings, Info (About/License/System/Privacy/ Security).
- Audit logging — Every create/update/delete/import/export recorded with ISO 8601 timestamps.
- Argon2id password hashing per RFC 9106.
Quick start
# Build
# Start the server (TLS on 8180 for TCP, 8181 for QUIC)
# Use the CLI (published to crates.io as `ndaal-csaf-cli`)
Workspace layout
crates/
├── csaf-models/ # CSAF 2.0/2.1 serde types, SQLite pool,
│ # User, AuditLog, Settings models
├── csaf-core/ # redb storage, validation, sidecar generation,
│ # import/export, configuration
├── csaf-crud/ # Hyper server, HATEOAS API, HTML routes,
│ # router, static files, TLS 1.3 setup
└── csaf-cli/ # clap-based CLI: import, export, validate,
# stats (published as `ndaal-csaf-cli`)
Testing
# All tests (unit + integration + crate integration)
# Just integration tests
# Load test (oha, HTTP/2 keep-alive, --insecure for dev cert)
OHA_REQUESTS=1000 OHA_CONCURRENCY=20
Load-test results — port 8180, HTTP/2 over TLS 1.3
1000 requests per endpoint × 20 concurrent connections,
oha --http2 --insecure against the locally-built release
binary on Apple Silicon (MacBook). All seven endpoints hit
100 % success:
| Endpoint | req/s | p95 latency |
|---|---|---|
/api/v1 |
12,037 | 0.74 ms |
/api/v1/settings |
11,844 | 0.77 ms |
/api/v1/system/health |
9,070 | 0.96 ms |
/api/v1/csaf?page=1&per_page=5 |
8,781 | 1.64 ms |
/ (HTML dashboard) |
7,918 | 2.19 ms |
/api/v1/audit-log?page=1&per_page=5 |
7,572 | 2.24 ms |
/static/img/logo.png |
3,076 | 1.50 ms |
Port 8181 (HTTP/3 over QUIC) is not exercised by this
load test — oha, h2load, and Homebrew curl on this
platform do not include an HTTP/3 / QUIC client. A dedicated
QUIC-capable tool (e.g. h2load built with nghttp3 +
ngtcp2, or neqo-client) is needed to benchmark 8181.
Test coverage includes:
- Unit tests per crate (193 tests; +15 for SHA-512, log directory, reset-to-defaults, and the four audit-log export formats)
- Integration tests — 10 end-to-end CRUD cycle tests,
extended with a post-export assertion that all three
hyphenated hash sidecars (
.sha-256,.sha-512,.sha3-512) land on disk and no unhyphenated legacy form leaks. - Crate integration tests — 129 tests reusing patterns from redb, rusqlite, sha2/sha3, argon2, serde_json, chrono, regex, uuid, matchit, rustls/rcgen, sysinfo, and tracing-appender.
- Fuzzing — 19
cargo-fuzztargets including three new 0.3.0 targets:fuzz_sidecar_hash_triplet,fuzz_sidecar_write_ext, andfuzz_audit_sarif. Parser changes are gated on 300 s per-target in CI.
Total: 302 tests passing.
Quality toolchain
Linters applied: cargo clippy (pedantic + nursery),
yamllint, ryl, markdownlint, rumdl, hadolint,
htmlhint, oxlint, ruff, bandit.
rust-doctor — workspace health score
┌────────────────────────────────────────────────────────┐
│ rust-doctor │
│ │
│ 100 / 100 Great │
│ │
│ ████████████████████████████████████████ │
│ │
│ Security: 100 │
│ Reliability: 100 │
│ Maintainability: 100 │
│ Performance: 100 │
│ Dependencies: 100 │
│ │
│ ✓ 0 error(s) ✓ 0 warning(s) ℹ 1 info(s) 38 files │
└────────────────────────────────────────────────────────┘
Run rust-doctor locally (or rust-doctor --json for machine
output) to reproduce. The session-level cache lives in each
crate's .rust-doctor-cache.json; any regression greater than
5 points vs the previous run blocks the merge. Configuration
is in rust-doctor.toml; every ignore rule
carries a justification comment referencing the equivalent
[workspace.lints.clippy] entry in
Cargo.toml.
Security
- TLS 1.3 only (no TLS 1.2 fallback)
- rustls with ring crypto provider (no OpenSSL)
- Argon2id for password hashing (RFC 9106)
- Parameterized SQL queries throughout
- No unsafe code (
#![deny(unsafe_code)]) - SHA-256, SHA-512, and SHA3-512 integrity sidecars — three orthogonal hash families for defence in depth
- SARIF 2.1.0 audit-log export with built-in schema validation
Source code
Canonical repository (used by crates.io and cargo install):
Please open issues and merge requests there. No GitHub mirror is maintained; treat any third-party GitHub copy as untrusted.
License
Apache-2.0. See SPDX headers in each source file.
Author
Pierre Gronau · ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG · Cologne