v0.2.0 — security/quality hardening + first CI pipeline
A security/quality hardening pass driven by a full audit (summarized in
docs/findings/REMEDIATION.md), plus the project's first CI pipeline. Run
make rebuild to pick up the build-time and firewall changes on an existing
container.
Added
- Continuous integration (
.github/workflows/ci.yaml): shellcheck, hadolint,
and yamllint static gates plus asmokejob that builds the image, boots it, and
asserts the wiring (Claude CLI, the uv 3.14python3shim, the toolbelt, the seed
doc).make lint/make smokerun the same checks locally;mainis now
branch-protected on these checks, and the README carries a CI status badge. - Boot-event observability (
log-event.sh): the entrypoint, firewall, seed, and
cron phases emit a JSONL lifecycle trail under~/.claude, correlated by a per-boot
BOOT_ID.make boot-checkasserts the expected events fired in order. - IPv6 egress filtering:
ip6tablesis configured fail-closed in strict mode
(withnet.ipv6.conf.*.disable_ipv6sysctls as a backstop), so a dual-stack host no
longer bypasses the allowlist over IPv6. - Container healthcheck asserting the effective firewall mode, the Claude CLI, the
cron daemon, and the seededENVIRONMENT.md. .dockerignore(default-deny) so the generated.env(the Postgres password)
never enters the build context.
Changed
- Firewall fails closed. The window where egress is opened to fetch the GitHub/AWS
IP feeds is now guarded by anEXITtrap that re-clampsOUTPUT/INPUTtoDROPon
any mid-apply abort — previously an abort there could leave egress wide open.
Concurrent firewall runs serialize on aflock. - Reproducible, tamper-evident build. The base image is digest-pinned and every
fetched tool (yq, lazygit, AWS CLI, cargo-binstall, pnpm, the npm globals, uv) is
version-pinned and verified by SHA-256 or GPG signature before use; the two
third-party apt keys (GitHub CLI, PGDG) are fingerprint-verified. ASHELL [… -o pipefail …]directive makescurl | shbuild layers fail closed. - Lifecycle Make targets are db-profile-aware.
stop/down/nukenow tear
down the opt-indbsidecar and its volumes, somake nukeactually destroys the
database data as documented. claude updateat boot is time-bounded (and remains non-fatal), so a slow
network can't hang startup.- Dropped the
NET_RAWcapability — the firewall needs onlyNET_ADMIN.
Fixed
- DNS / SSH exfiltration channels closed. Strict mode no longer allows blanket
udp/53ortcp/22to any host: DNS is scoped to the resolver(s) in
/etc/resolv.conf, and git-over-SSH reaches GitHub via theallowed-domainsipset. @aws-ip-ranges <region>narrowing loaded zero CIDRs (a jq bug) and could break
AWS login; it now filters correctly and always retains the GLOBAL/CloudFront prefixes.- Cron jobs couldn't reach Postgres —
cron.envnow includes thePG*/
DATABASE_URLclient vars. - VS Code
postStartCommandswallowed firewall failures (it always exited 0); a
firewall error now propagates while update/cron stay non-fatal. make db-dumpwrites atomically (temp file + rename);gen-env.shre-secures.env
to0600; the zshcompinitfast-path guard (which was always-true) is fixed.
Documentation
- Security/quality audit under
docs/findings/, with a remediation summary
(docs/findings/REMEDIATION.md). - Split
CLAUDE.mdinto a lean root plus.devcontainer/CLAUDE.md(per-script
invariants), reconciled with the hardening above. - README: CI badge,
--force-recreatefor the permissive-mode switch, and a DB
quickstart precondition.
Full changelog: v0.1.5...v0.2.0