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
| 1 | 1 | # Misc TODO | |
| 2 | 2 | ||
| 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 | |
| 4 | 4 | - 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")_ | |
| 8 | 8 | - I want to get this to a sufficient state that we could bootstrap this app using this CI and automate deployments if we wished | |
| 9 | 9 | - 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.)_ | |
| 12 | 13 | ||
| 13 | 14 | --- | |
| 14 | 15 | ||
| ⋯ 48 unchanged lines | |||
| 63 | 64 | ||
| 64 | 65 | # Session notes / resume point | |
| 65 | 66 | ||
| 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.**_ | |
| 69 | 71 | ||
| 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) | |
| 75 | 73 | ||
| 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) | |
| 77 | 99 | ||
| 78 | 100 | - **CI UI** — runs list `/{owner}/{repo}/ci`, run-detail (status/timing/log), | |
| 79 | 101 | per-commit status badges, "CI" nav link. (`crates/anvil-web/src/ui.rs`) | |
| ⋯ 22 unchanged lines | |||
| 102 | 124 | - htmx insurance: `auth::csrf_context` middleware → request-scoped task-local; | |
| 103 | 125 | `layout` sends the token via `hx-headers` on every htmx request. | |
| 104 | 126 | ||
| 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 | |
| 121 | 128 | ||
| 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. | |
| 129 | 133 | ||
| 130 | 134 | ## Loose ends | |
| 131 | 135 | ||
| ⋯ 4 unchanged lines | |||
| 136 | 140 | columns won't apply to an existing DB until migrations land. (The | |
| 137 | 141 | `data_dir/csrf_secret` file is created automatically — no DB change.) | |
| 138 | 142 | - **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). | |
| 141 | 149 | ||
| 142 | 150 | ## Remaining roadmap (plan milestones beyond a/b/c) | |
| 143 | 151 | ||
| 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). | |