<!-- SPDX-License-Identifier: Apache-2.0 -->
<!-- SPDX-FileCopyrightText: 2026 ndaal GmbH & Co KG, Cologne -->

# 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
  to `document.distribution.text`, to `document.notes[]`, or to
  both — configurable per-deployment.
- **Dual embedded database** — `redb` for 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 matches `CLAUDE.md` §"Cryptographic
  hashes (release + CSAF)".
- **Rolling file logging** — `tracing-appender` writes a daily
  `csaf-crud.<date>.log` under the configurable **Log Directory**
  (default `./data_log`); stderr output is preserved. `RUST_LOG`
  governs both sinks.
- **Audit Log export** — The `/admin/export` page grows an
  **Export Audit Log** card that emits the `audit_log` table 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 `/settings` page carries a confirmation-
  guarded **Reset to default settings** button that restores every
  field to `Settings::default()` and records a `settings_reset` row
  in the audit log.
- **HATEOAS API** — HAL-like `_links` in 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

```bash
# Build
cargo build --release

# Start the server (TLS on 8180 for TCP, 8181 for QUIC)
cargo run --bin csaf-crud

# Use the CLI (published to crates.io as `ndaal-csaf-cli`)
cargo run --bin ndaal-csaf-cli -- import --directory test/csaf
cargo run --bin ndaal-csaf-cli -- validate \
  test/csaf/2026/003/ndaal-sa-2026-003.json
cargo run --bin ndaal-csaf-cli -- stats
```

## Workspace layout

```text
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

```bash
# All tests (unit + integration + crate integration)
cargo test --workspace

# Just integration tests
cargo test --workspace --test csaf_crud_cycle
cargo test --workspace --test test_crate_integrations

# Load test (oha, HTTP/2 keep-alive, --insecure for dev cert)
OHA_REQUESTS=1000 OHA_CONCURRENCY=20 test/loadtest/run.sh
```

### 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-fuzz` targets including three new
  0.3.0 targets: `fuzz_sidecar_hash_triplet`,
  `fuzz_sidecar_write_ext`, and `fuzz_audit_sarif`. Parser
  changes are gated on 300 s per-target in CI.

Total: 302 tests passing.

## Quality toolchain

```bash
cargo fmt --all
cargo clippy --workspace --all-targets --all-features -- \
  -D warnings
cargo test --workspace
cargo audit
cargo deny check
cargo machete --with-metadata
cargo llvm-cov --lcov --output-path target/coverage/lcov.info \
  --workspace --all-features
rust-doctor
cargo kani --workspace --output-format=terse   # nightly
```

Linters applied: `cargo clippy` (pedantic + nursery),
`yamllint`, `ryl`, `markdownlint`, `rumdl`, `hadolint`,
`htmlhint`, `oxlint`, `ruff`, `bandit`.

### rust-doctor — workspace health score

```text
┌────────────────────────────────────────────────────────┐
│ 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`](rust-doctor.toml); every ignore rule
carries a justification comment referencing the equivalent
`[workspace.lints.clippy]` entry in
[`Cargo.toml`](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`):

- <https://gitlab.com/vPierre/ndaal_public_csaf_crud>

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
