secnull,
a CMS that signs its own work.
A Go single-binary CMS for publishing Ed25519-signed security audits. Strict CSP nonces, Argon2id+TOTP login, SHA-256-chained audit log.
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-bootstrapsubcommand. __Host--prefixed cookies enforced at config-validation time; admin allowlist + origin check + CSRF live in the middleware chain.- Static-export mode:
make exportrenders 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.
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.