Bobsled's trusted boundary is the local control process, filesystem ledger, and registered Git repositories. The local browser is an operator interface. Agent output, prompts, repository instructions, and vendor-native hooks are untrusted inputs and have no direct workflow authority.
The service binds to loopback, accepts only typed endpoints, limits request and
output sizes, validates registered Git paths, and uses structured child-process
arguments with shell: false. There is intentionally no arbitrary shell API.
| Control | Status | Meaning |
|---|---|---|
| Hash-chained event ledger | Enforced | Startup fails on sequence, link, digest, payload, domain, historical-reference, or ownership corruption before publishing state |
| Single local ledger writer | Enforced | A process lock prevents concurrent control planes |
| Guarded state transitions | Enforced | Only the server can advance a change |
| Artifact approval freshness | Enforced | Current bytes are re-digested before protected transitions |
| Repository admission | Enforced | Invocation working directories must be registered Git repositories |
| Invocation time/output bounds | Enforced | Fixed timeout and five-megabyte retained output ceiling |
| Runner permission flags | Advisory | Vendor CLI restrictions are not an independent OS sandbox |
| Human role authorization | Advisory | Local operator identity is asserted, not authenticated |
| Mutating runner admission | Enforced | Only adapters implementing isolated-worktree may build; currently Codex only |
| Diff path and commit admission | Enforced | Bobsled rejects protected/sensitive paths and creates the governed commit |
| Verification filesystem isolation | Enforced on macOS | Configured command vectors run through Seatbelt with writes limited to the worktree and isolated home |
| Author/reviewer independence | Enforced | Review denies same-runner reuse and higher-risk same-family reuse |
| Blocking review disposition | Enforced | Schema-valid review results return blocking work to rework; prose alone cannot open PR approval |
| Merge-candidate freshness | Enforced | Final configured checks bind target, branch, and synthetic merge commits; target movement invalidates PR progression |
| Pull-request target | External | Explicit operator action may push only to an existing GitHub origin |
| Brokered GitHub merge reads | Enforced | One fixed gh pr view action is stage-scoped, parameter-validated, bounded, timed, and unable to express a mutation |
| Broker credential environment | Enforced | BOBSLED_GITHUB_TOKEN is mapped only into the broker child and inherited GitHub token variables are excluded |
| Runner credential filesystem isolation | Advisory | Stored GitHub CLI credentials in the user's home remain visible without an independent OS boundary |
| Local deployment identity | Enforced | A receipt binds process boot commit, clean default-branch HEAD, and ancestry of GitHub's reported merge commit |
| Governed change-detail scope | Enforced | Read routes accept recorded change and record IDs only; caller-selected paths, directory discovery, and cross-change records are unavailable |
| Record containment and integrity | Enforced | The server uses canonical parent containment, no-follow opens, opened-file identity checks, regular-file and size gates, and raw artifact digest verification before serving |
| Served-content redaction | Enforced | Complete UTF-8 text is redacted for configured secrets, credential patterns, assignments, and host paths before line truncation; unredactable bytes are refused |
| Inert governed rendering | Enforced | Markdown, diffs, and evidence become a closed React element set without governed URLs, resources, handlers, styles, or raw HTML |
| Detail-page operator identity | Advisory | Loopback origin checks and record scope are enforced, but the local operator role is not authenticated |
Agent adapters receive a small shared environment plus only the selected provider's
credential namespace. The control plane never returns credentials to the UI or
stores them in the ledger. CLIs may still use their normal credential files in
the user's home directory. The merge broker accepts a dedicated
BOBSLED_GITHUB_TOKEN, maps it to GH_TOKEN only inside its fixed child
process, and filters inherited GH_TOKEN, GITHUB_TOKEN,
GH_ENTERPRISE_TOKEN, and GITHUB_ENTERPRISE_TOKEN. Before any broker output
is retained, returned, or digested, the resolved credential and recognized
GitHub token patterns are replaced with [redacted].
Configured verification receives a synthetic home, no provider credential namespace, loopback-only worker networking, and worktree-only writes on macOS. Sandbox policy self-tests run in the primary suite; macOS does not permit applying a second Seatbelt profile from inside the already-sandboxed configured-verification process, so those tests are marked as nested skips there rather than treating exit 71 as a security assertion.
Raw model output may itself contain sensitive repository data. Runtime logs are
created mode 0600, ignored by Git, and must be handled as confidential.
The change-detail surface redacts eligible text at its serving boundary; it
does not rewrite or normalize retained logs. Direct filesystem access to those
mode-0600 records remains inside the local trust boundary.
The application CSP is defense in depth around inert rendering. The delivered
policy intentionally permits 'unsafe-inline' for Next.js bootstrap scripts
and generated application styles; the production build currently depends on
those exceptions. It still confines scripts, styles, images, and connections to
the local application, disables objects and form actions, and denies framing
and base-URL changes. Governed content does not rely on those exceptions: it is
rendered as a closed set of React text elements with no content-derived URL,
style, script, or event-handler properties.
- A malicious local process can call the loopback API or impersonate an actor.
- A compromised runner executable can ignore its CLI permission flags; worktree diff admission limits what Bobsled accepts but is not a general process jail.
- Read-only agents may observe more local data than intended without an OS sandbox.
- Invalid ledger history causes a fail-closed startup outage until an operator diagnoses and resolves it outside Bobsled; there is intentionally no automatic repair or valid-prefix recovery path.
- The approval inbox does not authenticate the asserted local role. Operators must inspect the linked read-only change detail before deciding; opening that page has no judgment or transition authority.
- Verification fails closed on platforms where Bobsled has no supported OS sandbox profile.
- Git commit checks do not prove installed dependency state for a local deployment.
- Squash/rebase content equivalence is not independently reconstructed beyond the approved pull-request head and GitHub-reported merge commit binding.
- Artifact recording still performs lexical containment rather than write-side realpath enforcement. Change-detail independently applies canonical containment and no-follow reads, but does not repair that existing admission boundary.
- Evidence
outputDigestis receipt metadata, not necessarily the retained log file digest. Change detail labels a log's separately observed digest as a read observation rather than approval-grade verification. - Binary, invalid-UTF-8, oversized, and digest-mismatched governed bytes are deliberately unavailable through the browser. The UI exposes the refusal state and identity without serving a plausible substitute.
npm run dev:controluses hot reload and narrows the boot-commit claim; governed deployment verification requires a process started withnpm run controlafter the checkout is upgraded.
Use mutating execution only with providers you trust, inspect retained diffs and evidence before approval, and do not expose port 4242 to a network interface.
Record security findings as a governed Bobsled change. Include the affected control, evidence, expected invariant, and whether the issue can cross the loopback or repository boundary. Do not place credentials or sensitive raw runner logs in Git.