collin/anvil
Misc TODO
-
should show subject line of latest commit in repo page view
- we should mirror certain things like this from github ui
- don't expose a users email on their profile page
- don't include anvil before the breadcrumbs in the repo name i.e. anvil/collin/repo just do collin/repo
-
implement github action style CI feature (done, including the (c) sandboxed broker — see "Session notes")
- I want to get this to a sufficient state that we could bootstrap this app using this CI and automate deployments if we wished
- probably might want to have other isolated anvil workers or something running for CI jobs
-
implement github pages style hosting feature (serves from a repo's
pagesbranch; each top-level dir is its own site, so rustdoc + others coexist)- should support multiple generated build pages such as one for rustdoc, and one for other rhings
-
security audit (threat model + recommendations recorded in
docs/untrusted-mode.md; CI sandbox landed. The remaining hardening it lists — quotas, separate pages origin, open-registration anti-abuse — only matters for untrusted tenants, which stay unsupported.)
Error in git push (FIXED 2026-06-10, uncommitted)
Root cause: every push after the first sends a thin pack — deltas whose
base objects aren't in the pack (the server already has them), referenced by
object id (REF_DELTA). vendor/gitserver-core/src/receive_pack.rs::write_pack
passed None as thin_pack_base_object_lookup to
gix_pack::Bundle::write_to_directory, so gix couldn't resolve the bases and
aborted. First-push-to-empty-repo worked because that pack is self-contained.
Fix: pass the already-open gix::Repository as the lookup (it implements
gix_object::Find). Regression test
receive_thin_pack_with_ref_deltas builds a real thin pack via
git pack-objects --thin, asserts it contains ref-deltas, and pushes it
through receive_pack. Verified the test fails without the fix.
Original report:
collin@mini ~/C/anvil (main)> git push
Enter passphrase for key '/Users/collin/.ssh/id_ed25519':
Enumerating objects: 117, done.
Counting objects: 100% (117/117), done.
Delta compression using up to 8 threads
Compressing objects: 100% (62/62), done.
Writing objects: 100% (66/66), 27.57 KiB | 3.94 MiB/s, done.
Total 66 (delta 39), reused 0 (delta 0), pack-reused 0 (from 0)
send-pack: unexpected disconnect while reading sideband packet
fatal: the remote end hung up unexpectedly
collin@mini ~/C/anvil (main) [128]>
server logs:
26-06-09T21:53:46.466577Z INFO anvil_ssh: ssh auth: accepted key SHA256:Rg41caN7vw2WYYxiJN6lrIlX0DTXYF0rC2QzKZW1tB0 (user 1)
2026-06-09T21:53:46.635946Z INFO anvil_ssh: ssh git-receive-pack on collin/anvil.git (user Some(1))
2026-06-09T21:53:46.805146Z ERROR anvil_ssh: git ssh git-receive-pack: protocol error: failed to write incoming pack: Ref delta objects are not supported as there is no way to look them up. Resolve them beforehand.
2026-06-09T21:54:28.571834Z INFO anvil_ssh: ssh auth: rejected unknown key SHA256:ZlWZyHqspqFeUQV84qaXtDQq4gcA33dR7y8dYbeg9u8
2026-06-09T21:54:35.565597Z INFO anvil_ssh: ssh auth: accepted key SHA256:Rg41caN7vw2WYYxiJN6lrIlX0DTXYF0rC2QzKZW1tB0 (user 1)
2026-06-09T21:54:35.676113Z INFO anvil_ssh: ssh git-receive-pack on collin/anvil.git (user Some(1))
2026-06-09T21:54:36.268520Z ERROR anvil_ssh: git ssh git-receive-pack: protocol error: failed to write incoming pack: Ref delta objects are not supported as there is no way to look them up. Resolve them beforehand.
2026-06-09T21:55:54.223033Z INFO anvil_ssh: ssh auth: rejected unknown key SHA256:ZlWZyHqspqFeUQV84qaXtDQq4gcA33dR7y8dYbeg9u8
2026-06-09T21:56:00.758384Z INFO anvil_ssh: ssh auth: accepted key SHA256:Rg41caN7vw2WYYxiJN6lrIlX0DTXYF0rC2QzKZW1tB0 (user 1)
2026-06-09T21:56:00.906553Z INFO anvil_ssh: ssh git-receive-pack on collin/anvil.git (user Some(1))
2026-06-09T21:56:01.067903Z ERROR anvil_ssh: git ssh git-receive-pack: protocol error: failed to write incoming pack: Ref delta objects are not supported as there is no way to look them up. Resolve them beforehand.
Session notes / resume point
Last updated: 2026-06-10 (second session). Working state is clean: cargo build, cargo clippy --workspace, cargo fmt --all, and cargo test --workspace all pass. Everything below is UNCOMMITTED (repo convention:
commit only when asked). All top-of-file TODO items are done.
Done this session (2026-06-10, second session)
- UI quick wins (
crates/anvil-web/src/ui.rs) — latest-commit bar on the repo page (sha + subject + author/time, attached above the file box, links to the commit); profile page no longer shows the email; repo header readsowner / repowithout the leadinganvil /. - (c) sandboxed CI broker — job containers now run with
cap_drop=ALL+no-new-privilegesunconditionally, plus config-drivenpids_limit(512),memory_mb+swap (2048),cpus(2), wall-clocktimeout_secs(1800, force-removed on expiry), optionalnetwork = false,run_as, and anallowed_imagesallowlist (empty = any; tagless entry allows all tags).CiConfigincrates/anvil-core/src/config.rs(withimage_allowedtest);execute()incrates/anvil-ci/src/lib.rs. Read-only rootfs deliberately skipped (workspace lives in the container fs; no volumes ever attached). Docs:DEPLOY.md§7,anvil.example.toml[ci]. - (b) threat model —
docs/untrusted-mode.md: severity-ranked analysis (CI containment, pages/stored-XSS origin, git resource exhaustion, registration anti-abuse, authz granularity, webhook SSRF), the already-right list, and the stance: single-tenant supported, untrusted gated on items 1–4. - Pages hosting —
crates/anvil-web/src/pages.rs: serves a repo'spagesbranch at/{owner}/{repo}/pages/...; top-level dirs are separate sites (rustdoc, book, …);index.htmlresolution with trailing-slash redirect so relative links work; extension→content-type map +nosniff; listing page with publish hint; "Pages" button on the repo header. Visibility follows the repo (private → 404). Publish withgit push origin <built-branch>:pages.
Done earlier (same day, first session)
- CI UI — runs list
/{owner}/{repo}/ci, run-detail (status/timing/log), per-commit status badges, "CI" nav link. (crates/anvil-web/src/ui.rs) - CD redeploy webhook — on a green run of
[ci] deploy_branchin the single[ci] deploy_repo, POST to[ci] deploy_webhook(X-Anvil-Deploy-Secretheader). Scoped to ONE repo.CiConfigincrates/anvil-core/src/config.rs;deploy()incrates/anvil-ci/src/lib.rs. Docs:DEPLOY.md§7,deploy/anvil.toml.reqwestadded with NO TLS feature (keeps musl cross-compile aws-lc-free). - Docker socket on hagrid —
deploy/run.shmounts it +--group-adds the gid for the non-root user; caveat inDEPLOY.md§4. - Toasty ORM cleanup —
ci.rslist_by_repo/latest_for_commit/queued_idsnow sort/limit/filter in SQL, not in memory. Verified by the newordering_and_limit_run_in_the_databasetest. (Sweep: these were the only real instances;repos::list_all_with_ownersorts by a joined username and needs all rows — intentionally left.) - (a) CSRF + cookie hardening —
- Cookie:
HttpOnly+SameSite=Lax+Secure(auto viaConfig::secure_cookies()when base_url is https). - Synchronizer token
HMAC-SHA256(server_secret, session); secret persisted atdata_dir/csrf_secret(App::csrf_tokenincrates/anvil-core/src/lib.rs). Depshmac,sha2. Csrfextractor + constant-timeverify_csrf(crates/anvil-web/src/auth.rs). Hiddencsrffield + verification on add/delete SSH key, new repo, repo settings. Login exempt; logout relies on SameSite.- htmx insurance:
auth::csrf_contextmiddleware → request-scoped task-local;layoutsends the token viahx-headerson every htmx request.
- Cookie:
The a/b/c plan — ALL DONE
(a) CSRF + cookie hardening, (b) docs/untrusted-mode.md threat model,
(c) sandboxed CI broker. Stronger isolation tiers (gVisor/Kata/Firecracker,
egress filtering, CI-minute quotas) are recorded in the threat model as the
gate for untrusted tenants, not planned work.
Loose ends
- CSRF header consumption:
hx-headerssends the token as acsrfheader, butverify_csrfonly reads the form field. When we add a tokenless htmx action (rawhx-post/hx-delete, no<form>), also read thecsrfheader. - Toasty migrations: schema only pushed on a fresh DB (
db::connect); new columns won't apply to an existing DB until migrations land. (Thedata_dir/csrf_secretfile is created automatically — no DB change.) - Suggested commits when ready: (1) CI UI, (2) CD webhook + deploy wiring,
(3) ci.rs ORM cleanup + test, (4) CSRF + cookies, (5) UI quick wins
(latest-commit bar, profile email, breadcrumb), (6) CI sandbox + threat-model
doc, (7) pages hosting. Trailer:
Co-Authored-By: Claude .... - Pages caveats (single-tenant-acceptable): served from the forge origin — move to a separate origin before untrusted users (threat model §2); whole blobs load into memory per request (fine at our scale).
Remaining roadmap (plan milestones beyond a/b/c)
- Issues · 9. Pull requests (gix merge) · 10. Webhooks (mind the SSRF item).