anvilsign in

collin/anvil · 39b66ff9

docs: check off TODO items; refresh session notes

Collin Richards · 2026-06-09 22:50 UTC · 39b66ff9c1d9afa7ae19615cfade4d871866ac11 · parent 2ba8b0fa · browse files

modifiedTODO.md+49 −42
11 # Misc TODO
22
3-- [ ] should show subject line of latest commit in repo page view
3+- [x] should show subject line of latest commit in repo page view
44 - we should mirror certain things like this from github ui
5-- [ ] don't expose a users email on their profile page
6-- [ ] don't include anvil before the breadcrumbs in the repo name i.e. anvil/collin/repo just do collin/repo
7-- [x] implement github action style CI feature _(core done — see "Session notes" below; the "isolated workers" idea is the (c) sandboxed broker, still TODO)_
5+- [x] don't expose a users email on their profile page
6+- [x] don't include anvil before the breadcrumbs in the repo name i.e. anvil/collin/repo just do collin/repo
7+- [x] implement github action style CI feature _(done, including the (c) sandboxed broker — see "Session notes")_
88 - I want to get this to a sufficient state that we could bootstrap this app using this CI and automate deployments if we wished
99 - probably might want to have other isolated anvil workers or something running for CI jobs
10-- [ ] implement github pages style hosting feature
11-- [ ] security audit _(started: see (b) threat-model below)_
10+- [x] implement github pages style hosting feature _(serves from a repo's `pages` branch; each top-level dir is its own site, so rustdoc + others coexist)_
11+ - should support multiple generated build pages such as one for rustdoc, and one for other rhings
12+- [x] 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.)_
1213
1314 ---
1415
⋯ 48 unchanged lines
6364
6465 # Session notes / resume point
6566
66-_Last updated: 2026-06-10. Working state is clean: `cargo build`, `cargo clippy
67---workspace`, `cargo fmt --all`, and `cargo test --workspace` all pass.
68-Everything below is UNCOMMITTED (repo convention: commit only when asked)._
67+_Last updated: 2026-06-10 (second session). Working state is clean: `cargo
68+build`, `cargo clippy --workspace`, `cargo fmt --all`, and `cargo test
69+--workspace` all pass. Everything below is UNCOMMITTED (repo convention:
70+commit only when asked). **All top-of-file TODO items are done.**_
6971
70-Two of your top-of-file items are quick wins we noticed but did NOT do yet:
71-- **profile email** — `user_profile` in `crates/anvil-web/src/ui.rs` renders
72- `owner.email`; just drop that block.
73-- **breadcrumb `anvil/` prefix** — `repo_index` header in the same file starts
74- with `a href="/" { "anvil" } " / "`; remove the leading anvil link.
72+## Done this session (2026-06-10, second session)
7573
76-## Done this session
74+- **UI quick wins** (`crates/anvil-web/src/ui.rs`) — latest-commit bar on the
75+ repo page (sha + subject + author/time, attached above the file box, links
76+ to the commit); profile page no longer shows the email; repo header reads
77+ `owner / repo` without the leading `anvil /`.
78+- **(c) sandboxed CI broker** — job containers now run with `cap_drop=ALL` +
79+ `no-new-privileges` unconditionally, plus config-driven `pids_limit` (512),
80+ `memory_mb`+swap (2048), `cpus` (2), wall-clock `timeout_secs` (1800,
81+ force-removed on expiry), optional `network = false`, `run_as`, and an
82+ `allowed_images` allowlist (empty = any; tagless entry allows all tags).
83+ `CiConfig` in `crates/anvil-core/src/config.rs` (with `image_allowed` test);
84+ `execute()` in `crates/anvil-ci/src/lib.rs`. Read-only rootfs deliberately
85+ skipped (workspace lives in the container fs; no volumes ever attached).
86+ Docs: `DEPLOY.md` §7, `anvil.example.toml` `[ci]`.
87+- **(b) threat model** — `docs/untrusted-mode.md`: severity-ranked analysis
88+ (CI containment, pages/stored-XSS origin, git resource exhaustion,
89+ registration anti-abuse, authz granularity, webhook SSRF), the already-right
90+ list, and the stance: single-tenant supported, untrusted gated on items 1–4.
91+- **Pages hosting** — `crates/anvil-web/src/pages.rs`: serves a repo's `pages`
92+ branch at `/{owner}/{repo}/pages/...`; top-level dirs are separate sites
93+ (rustdoc, book, …); `index.html` resolution with trailing-slash redirect so
94+ relative links work; extension→content-type map + `nosniff`; listing page
95+ with publish hint; "Pages" button on the repo header. Visibility follows the
96+ repo (private → 404). Publish with `git push origin <built-branch>:pages`.
97+
98+## Done earlier (same day, first session)
7799
78100 - **CI UI** — runs list `/{owner}/{repo}/ci`, run-detail (status/timing/log),
79101 per-commit status badges, "CI" nav link. (`crates/anvil-web/src/ui.rs`)
⋯ 22 unchanged lines
102124 - htmx insurance: `auth::csrf_context` middleware → request-scoped task-local;
103125 `layout` sends the token via `hx-headers` on every htmx request.
104126
105-## Next up (the agreed a/b/c plan — (a) done)
106-
107-### (b) untrusted-mode threat-model doc ← START HERE
108-Write `docs/untrusted-mode.md` (or `SECURITY.md`). Capture the severity-ranked
109-analysis:
110-1. **CI runner = root-equiv RCE via Docker socket** — the hard blocker; fix is (c).
111-2. Stored XSS if we ever serve raw blobs → separate origin + `text/plain` + CSP.
112-3. Git resource exhaustion (pack/decompression bombs, huge ref ads) → size/
113- storage quotas + timeouts.
114-4. Open registration anti-abuse (email verify, rate limit, CAPTCHA, quotas, ban).
115-5. Authorization granularity → collaborator roles + per-repo tokens / deploy keys.
116-6. Webhook SSRF (when webhooks land) → block private IPs / metadata / localhost.
117-Already-right: private repos 404 (no leak), reserved usernames + `/-/`, CI
118-tar-upload (not bind-mount), single-repo CD gate, argon2, CSRF + Secure cookies.
119-Recommendation to record: single-tenant/owner-operated stays the supported
120-stance; untrusted is gated behind (c).
127+## The a/b/c plan — ALL DONE
121128
122-### (c) sandboxed CI broker
123-Make anvil the only Docker client; the job container gets NO socket. Forbid bind
124-mounts; `--cap-drop=ALL`, `--security-opt=no-new-privileges`, read-only rootfs,
125-non-root uid, `--pids-limit`, mem/cpu caps, wall-clock timeout, egress limits,
126-image allowlist + CI-minute quotas. Stronger tier: gVisor/Kata/Firecracker
127-microVMs (this is the "isolated workers" idea from the list above). Current
128-runner: `crates/anvil-ci/src/lib.rs::execute`.
129+(a) CSRF + cookie hardening, (b) `docs/untrusted-mode.md` threat model,
130+(c) sandboxed CI broker. Stronger isolation tiers (gVisor/Kata/Firecracker,
131+egress filtering, CI-minute quotas) are recorded in the threat model as the
132+gate for untrusted tenants, not planned work.
129133
130134 ## Loose ends
131135
⋯ 4 unchanged lines
136140 columns won't apply to an existing DB until migrations land. (The
137141 `data_dir/csrf_secret` file is created automatically — no DB change.)
138142 - **Suggested commits when ready:** (1) CI UI, (2) CD webhook + deploy wiring,
139- (3) ci.rs ORM cleanup + test, (4) CSRF + cookies. Trailer: `Co-Authored-By:
140- Claude ...`.
143+ (3) ci.rs ORM cleanup + test, (4) CSRF + cookies, (5) UI quick wins
144+ (latest-commit bar, profile email, breadcrumb), (6) CI sandbox + threat-model
145+ doc, (7) pages hosting. Trailer: `Co-Authored-By: Claude ...`.
146+- **Pages caveats (single-tenant-acceptable):** served from the forge origin —
147+ move to a separate origin before untrusted users (threat model §2); whole
148+ blobs load into memory per request (fine at our scale).
141149
142150 ## Remaining roadmap (plan milestones beyond a/b/c)
143151
144-8. Issues · 9. Pull requests (gix merge) · 10. Webhooks (mind the SSRF item) ·
145-github-pages-style static hosting (your list item).
152+8. Issues · 9. Pull requests (gix merge) · 10. Webhooks (mind the SSRF item).