Two interlocking pieces shipped together:
1. Strict Ed25519 signature verification on URL-fetched apps artifacts.
Every URL the apps cascade resolves must publish a corresponding
<url>.sig (raw 64-byte Ed25519 signature). The fetcher rejects on
any failure (sig 404, transport error, wrong key, tampered body)
and the resolver falls back to the embedded copy.
The trusted public key is OPERATOR-CONFIGURED via --apps-pubkey /
ZDDC_APPS_PUBKEY (PEM file path). No baked-in default — same posture
as TLS certificates. Operators using zddc.varasys.io's canonical
channels download pubkey.pem from there and configure the local
path. Operators with their own signing infrastructure pass their
own public key.
Build pipeline (./build) gains sign_release_artifacts: walks
dist/release-output/ after promote and produces an Ed25519 .sig
alongside every real file. ZDDC_SIGNING_KEY=~/.config/zddc-signing/
key.pem (mode 0600). Symlinks skip — the .sig at the symlink
target is what counts.
Test coverage: parse-PEM round-trip, malformed/wrong-type PEM
rejection, valid-signature accept, tampered-body reject, wrong-key
reject, malformed-signature reject, end-to-end fetch+sign+verify,
fetch-rejects-tampered, fetch-rejects-missing-sig, fetch-rejects-
wrong-key. Existing fetch tests updated to use signed-fixture
helpers.
2. Dev Helm chart mounts production data READ-ONLY and layers an
OverlayFS writable scratch on top. Prod data is the lowerdir;
dev's writes (form submissions, archive index state, .zddc edits)
land in upperdir; main container sees the merged read-write view
at $ZDDC_ROOT. Setup runs in a privileged init container; main
container runs unprivileged. Solves the dev-replica-on-shared-
dataset problem at the filesystem layer with no zddc-server code
change.
Docs: env-var tables in zddc/README.md and AGENTS.md gain a
ZDDC_APPS_PUBKEY row. The Federal-readiness gap analysis "Code-signed
apps: URL fetches" subsection is rewritten as "what's currently in
place" instead of "what would need to be added," with a forward
pointer to per-entry signed_by: (multi-key) and Sigstore as the
federally-acceptable evolution.
The website "Verify your downloads" section + the embedded pubkey
gone — but the website needs separate updates landing in zddc-website
to publish pubkey.pem and add the verify section. Pending in that
repo's commit.
Production binary unchanged at 13.1 MB. All 11 Go test packages green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two charts under helm/, both compile zddc-server from source via an
init container — no container image registry, no pre-built binary.
The init container clones the repo at a configured git ref, runs
`go build`, and writes the binary into a shared emptyDir; the main
container is alpine + the freshly built static binary.
helm/zddc-server-prod/ Production-shaped:
- gitRef pinned to a stable tag in
values.yaml.example (zddc-server-v0.0.7).
- imagePullPolicy IfNotPresent.
- Slower probe cadence (30s liveness, 10s
readiness).
- ZDDC_LOG_LEVEL=info.
- replicaCount: 1 (operators raise as needed
when backed by a shared filesystem).
helm/zddc-server-dev/ Dev/soak-shaped:
- gitRef defaults to "main" (rebuilt every pod
restart). build-time annotation forces
recreate on every helm upgrade.
- imagePullPolicy Always on the build image
so the latest golang:1.24-alpine is pulled.
- Faster probe cadence (10s liveness, 5s
readiness) — fail-fast in dev.
- ZDDC_LOG_LEVEL=debug. NOTE: debug logs every
request's full header map (includes auth
tokens / cookies) — this chart is for
private dev namespaces only.
- Strategy: Recreate (single replica racing
on different SHAs would be a mess).
Both charts:
- Wire the ZDDC_* env-var contract (ZDDC_ROOT, ZDDC_ADDR,
ZDDC_TLS_CERT=none, ZDDC_INSECURE_DIRECT=1, ZDDC_EMAIL_HEADER,
ZDDC_CORS_ORIGIN, ZDDC_LOG_LEVEL, ZDDC_INDEX_PATH).
- Mount a caller-supplied PVC at ZDDC_ROOT (chart does not create the
PVC; operators provision storage themselves).
- Optional Ingress (ingress.enabled: true). TLS is expected to be
terminated upstream of the pod; the pod listens on plain HTTP.
- No secrets in values.yaml.example. ACL email lists go in .zddc files
inside the data volume; image-pull and TLS secrets are referenced by
name only.
helm/README.md documents the design rationale (why build from source
instead of using a registry image), a quick-start example, and the
explicit list of what the charts do and don't do.
Note: `helm lint` cannot be run in this dev environment (helm isn't
installed). YAML syntax of Chart.yaml and values.yaml.example
verified via `python3 -c "yaml.safe_load(...)"`. Operators should
run `helm lint` and `helm template` before installing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>