14.1 MB
binary size
6469
Go LOC
6
subcommands
8
middleware stages
0
CGO deps

secnull (cmd/secnullcms) is a publication whose credibility depends on the publisher's posture. The CMS itself is single-binary, pure-Go SQLite (no CGO), and refuses to serve any audit whose hash does not match the signed manifest at content/integrity.json. Every editorial action ends up in an append-only SHA-256-chained audit log.

Tech scope

  • Single binary, cross-compilable to Linux and Windows. No runtime dependencies beyond the OS.
  • Pure-Go SQLite via modernc.org/sqlite — no CGO, no glibc surprises in containers.
  • Strict CSP with per-request nonces; every inline <script> or <style> in a template must consume the request-context nonce or it is blocked at runtime and reported.
  • Ed25519 article signatures verified at startup; admin login is Argon2id + TOTP enrolment via the admin-bootstrap subcommand.
  • __Host--prefixed cookies enforced at config-validation time; admin allowlist + origin check + CSRF live in the middleware chain.
  • Static-export mode: make export renders the public site to ./dist, suitable for serving from any HTTP server.

Architecture

Subcommand dispatch happens before flag parsing — main.go switches on os.Args[1] for keygen, sign, verify, verify-audit, admin-bootstrap, and export, routing to internal/subcommands/. Adding a subcommand means editing two files in lockstep.

Content is loaded once at startup, then optionally hot-reloaded by an mtime-polling watcher (no fsnotify, deliberately, for cross-platform parity). If signature enforcement is on, any mismatch aborts startup — make sign-content is mandatory after every audit edit.

Routing uses Go 1.22 method-prefixed mux patterns. The middleware chain order is load-bearing and documented inline: requestID → recoverer → accessLog → rate-limit → securityHeaders → adminAllowlist → originCheck → csrf → mux. Public read routes intentionally bypass the rate limiter.

State surfaces

  • data/secnull.db — SQLite admin / session store: users, sessions, lockouts.
  • data/audit.log — append-only SHA-256-chained event log.
  • data/overrides.json — editorial overrides (e.g. retractions) layered over signed content.
  • content/audits/*.md + content/integrity.json — signed publication tree.
requestID recoverer accessLog rate-limit secHeaders adminAllow originCheck csrf
8 stages, public reads bypass rate-limit · as of 2026-04-26
server · 3399 content · 835 auth · 724 subcommands · 577 auditlog · 368 config · 365 cmd · 201
internal/ + cmd/ · 6,469 LOC across 24 files · as of 2026-04-26

Surface

What callers see is small: a single 14.1 MB binary on the path. secnullcms serve brings the public site up; secnullcms admin-bootstrap enrols the first operator (Argon2id + TOTP); secnullcms sign updates content/integrity.json after an audit edit; secnullcms verify and verify-audit validate the signed manifest and the SHA-256-chained log. The HTTP surface is intentionally narrow — public read routes plus an admin allowlist gated by origin check and CSRF. There is no plugin API, no embedded admin SPA, and no client-side framework.

Constraints

The constraints are deliberate, not residual. Pure-Go SQLite (modernc.org/sqlite) avoids CGO so containers behave the same way as bare-metal builds — the ‘0 CGO deps’ plate above is a contract, not a measurement. Subcommand dispatch happens before flag parsing because mixing the two created order-of-operations bugs in the previous design. The middleware order is explicit and tested — reordering rate-limit and securityHeaders, for example, would let throttled responses leak headers that should never be served. Static-export mode (make export) renders the public site to ./dist for serving from any HTTP server, but the audit-log chain is only verifiable when the binary itself is on the path.

:/ ESC