Files
housecarl/AGENTS.md
T
abrown38andClaude Opus 5.5 de075edd5d Shadow mode: Housecarl beside the firewall in force, changing nothing
shadow.enabled in housecarl.yaml loads Housecarl's ruleset so that it
blocks, rejects and translates nothing: drops and rejects count and
accept (an accept only ends Housecarl's chain), policies accept and log
what they would drop, NAT only counts, MSS clamping and the flowtable
are left out, and raw rules that do more than accept, drop or reject are
left out with a warning. Comments mark what would happen ([would drop]).
Filter chains run after the other firewall's (+10) or before (-10); NAT
chains always first. Every change is checked by nftables in both forms.
No probation; takeovers and closing connections are refused. The boot
fallback filters nothing. internal/tablewatch puts the table back after
a flush ruleset. Mode in status, healthz, whoami, the web UI, the log
and the tracer; hc ruleset --enforcing. docs/shadow.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:11:01 +01:00

5.5 KiB

Working on Housecarl

Notes for coding agents (and people) changing this repository. Read this first, then docs/design.md (how it fits together and what 0.1 leaves out) and docs/development.md (layout, tests, releasing).

Housecarl is a firewall manager for Linux: housecarld keeps the machine's nftables ruleset and serves a REST API and a web UI; hc is the CLI; SQLite holds everything. It is the third of a set with Jarl (the BIND manager, ../jarl) and Steward (the DHCP server, ../steward) and copies their ways: the same auth, socket and web UI helpers, the same release process, the same look. When in doubt, do it the way Jarl and Steward do. Go module housecarl, CGO_ENABLED=0, Linux only for housecarld.

Ground rules

  • The database is the source of truth. The ruleset is rendered from it; never read nftables' state back as configuration (an importer would be the only exception).
  • Every change goes through one engine: edit a copy of the configuration, validate the whole result, render, check with nft -c -f, apply with nft -f, and save the objects, the history entry and the audit record together. Never write to the store from a handler.
  • Housecarl's own table only. Write inet housecarl and nothing else; never flush the whole ruleset (Docker, libvirt and others keep theirs).
  • Don't lock the user out. Changes made over the network are applied on probation and rolled back unless confirmed (docs/design.md). An empty configuration applies nothing. But never force a rule that keeps SSH or the web UI open: the maintainer doesn't want one.
  • Shadow mode changes no packet (docs/shadow.md): anything new the renderer writes that drops, rejects, translates or alters packets needs its shadow form (counted, marked [would …]) or must be left out there. TestShadow fails on what slips through.
  • Stay within nftables 1.1.3 (Debian 13). The dev machine's newer nft accepts things 1.1.3 refuses; the tests with HOUSECARL_NFT and the e2e test use 1.1.3.
  • Every user-visible change updates CHANGELOG.md under ## [Unreleased], and the matching docs/ page in the same change. New validation codes get a case in internal/validate/validate_test.go and a row in docs/validation.md.
  • Errors say what is wrong and how to fix it, naming the object.
  • Schema changes append a migration; never edit a shipped one.
  • make test runs go vet and a gofmt -l check. goimports isn't installed, so fix imports by hand.

The logo and wordmark

  • The masters are artwork/logo.svg (the carved doorway) and artwork/logo-small.svg (without its carvings, for under about 64 px). Each is one evenodd path in a shared viewBox; their color comes from CSS (--logo, lighter in the dark theme).
  • make logo (scripts/logo.py) copies them into the web UI (assets/logo.js, and assets/logo.svg, the favicon, from the small one) and runs scripts/banner.py, which rebuilds artwork/banner.svg. Never edit those outputs by hand.
  • The wordmark's letters are paths in assets/brand.js, in the angular style of Jarl's and Steward's (A, R and L are Jarl's, S and E Steward's). banner.py reads them from there.

Testing

make test      # vet + gofmt check + unit tests
make e2e       # real traffic through a router container (needs Docker)
  • Docker on this machine needs DOCKER_HOST=unix:///var/run/docker.sock. docker build can't reach the network here: images are made with docker run + docker commit (scripts/e2e.sh makes housecarl-nft). The e2e test prints RESULT: N passed, 0 failed; run it after any change to render, nft, engine, server, importer or the CLI.
  • Real nft in unit tests: HOUSECARL_NFT="docker run --rm -i --cap-add NET_ADMIN housecarl-nft nft" and HOUSECARL_NFT_IMAGE=housecarl-nft; without them those tests skip. Unprivileged namespaces (unshare -rn) are blocked here by AppArmor.
  • The systemd sandbox is only tested by a package test: make deb, installed in a debian:trixie container running systemd (--privileged --cgroupns=host /sbin/init).
  • The web UI has no JS tests. Bundle assets/app.js with esbuild to catch syntax and import errors, and look at it in headless Chrome against the e2e lab (KEEP=1, web UI on 10.88.10.1:8070).

Web UI rules

As in Jarl and Steward:

  • Build DOM only with h() from ui.js, never innerHTML.
  • The CSP is self only: no inline styles or scripts. Use a class, or set el.style.prop.
  • Colors are the CSS custom properties in style.css, with light and dark values; reuse them.
  • Rebuild housecarld after editing, since the files are embedded.
  • Function over polish. Leave pick-list checkboxes unticked by default.

Git and releases

The maintainer's rules:

  • Commit only when asked. Never push. The maintainer pushes, to Gitea at gitea.adbraun.lol.
  • Branches are dev-X.Y.Z, never named like a tag.
  • A release is a commit "Release X": it sets VERSION, updates the README status line, and (for a final release) moves [Unreleased] to ## [X] - YYYY-MM-DD. The annotated tag is vX with message "Housecarl X".

Shell hygiene

  • Never kill processes by pattern (pkill -f). The pattern matches the shell running it. Find the PID with ps and kill that.
  • Run long-lived test servers in the background mode of your tool, not with & in a shell that exits.
  • Never apply a test ruleset to this machine's own nftables. Test in a network namespace or a container.