security: harden Time for a public repo (forged beats, stranger retries, token in browser pass, worker shell sandbox) #45

Merged
teahubxyz merged 1 commits from security/public-repo-hardening into main 2026-10-14 04:24:43 +00:00
Owner

Fixes the three critical and two of the high findings from the 2026-10-13 audit (teahub #109), after pausing the venture loop (venture.yml disabled, TIME_VENTURE=off).

Critical

  • Forged state beats. beats_of accepted <!-- time {...} --> from any comment author; on a public repo a stranger could post a pr/planned beat and the next tick would merge a chosen PR or build a chosen page. Now only comments by github-actions[bot] count (is_time).
  • Token served to model-written JS. The browser pass cloned gh-pages with the token in the URL, served the whole clone over http.server, and ran the page in a networked Chrome. Now .git is dropped after the clone, only ventures/<slug>/ is served, and Chrome runs with --host-resolver-rules that resolve nothing but localhost.
  • Worker shell next to secrets. A scrubbed env= did not stop cat /proc/<pid>/environ on the same uid. Hands.run now executes in a throwaway container (checkout mounted, nothing else) when TIME_RUN_IMAGE names an image; heartbeat.yml/venture.yml set node:22-bookworm. Unset = host shell, as before (local runs, tests).

High

  • /time retry counts only from owner/member/collaborator (was: any non-bot).
  • verdict trusts Time's own comments and trusted associations; any other bot is a stranger.
  • touches_protected paginates pulls/N/files and counts a rename under both names (was: first 100 files, renames invisible).

Tests: fake comments carry logins; new cases for a stranger's retry and a forged beat; the fake GitHub paginates files. python3 .github/time/test_time.py green.

Not in this PR (audit follow-ups): require a named CI context rather than the worker's own status before auto-merge; heartbeat.yml ticking on any Approve/Object comment; labs-aicharts.yml step-output interpolation.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CYHXFvWMCs1GUmRmpySqQ6

Fixes the three critical and two of the high findings from the 2026-10-13 audit (teahub #109), after pausing the venture loop (`venture.yml` disabled, `TIME_VENTURE=off`). **Critical** - **Forged state beats.** `beats_of` accepted `<!-- time {...} -->` from any comment author; on a public repo a stranger could post a `pr`/`planned` beat and the next tick would merge a chosen PR or build a chosen page. Now only comments by `github-actions[bot]` count (`is_time`). - **Token served to model-written JS.** The browser pass cloned `gh-pages` with the token in the URL, served the whole clone over `http.server`, and ran the page in a networked Chrome. Now `.git` is dropped after the clone, only `ventures/<slug>/` is served, and Chrome runs with `--host-resolver-rules` that resolve nothing but localhost. - **Worker shell next to secrets.** A scrubbed `env=` did not stop `cat /proc/<pid>/environ` on the same uid. `Hands.run` now executes in a throwaway container (checkout mounted, nothing else) when `TIME_RUN_IMAGE` names an image; `heartbeat.yml`/`venture.yml` set `node:22-bookworm`. Unset = host shell, as before (local runs, tests). **High** - `/time retry` counts only from owner/member/collaborator (was: any non-bot). - `verdict` trusts Time's own comments and trusted associations; any other bot is a stranger. - `touches_protected` paginates `pulls/N/files` and counts a rename under both names (was: first 100 files, renames invisible). Tests: fake comments carry logins; new cases for a stranger's retry and a forged beat; the fake GitHub paginates files. `python3 .github/time/test_time.py` green. Not in this PR (audit follow-ups): require a named CI context rather than the worker's own status before auto-merge; `heartbeat.yml` ticking on any Approve/Object comment; `labs-aicharts.yml` step-output interpolation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01CYHXFvWMCs1GUmRmpySqQ6
Sign in to join this conversation.