Skip to content

Security

Security model and current limitations

Caretaker’s current implementation controls agent-proposed changes to GitHub-hosted repositories. This page distinguishes controls verified in the running system from active engineering work and longer-term research.

The vocabulary is fixed and we hold to it: tamper-evident is correct today, tamper-proof is never correct, and independently verifiable is not a phrase we use before external anchoring exists.

Implemented and verified

Controls exercised in the running system.

Policy classification and hard denial

Each project carries its own path rules. A proposed change touching a denied path is refused outright, checked after diagnosis produces its file list and before any commit, test run, or pull request. The refusal gets its own incident status and its own decision record, distinct from an escalation.

EvidenceVerified live against a real repository and the production database: a real bug in a denied module was correctly refused, the incident reached blocked status with a deny decision, and no branch or pull request was ever created on the target.

Named-human approval, bound to one change

Consequential changes route to a person, with a link carrying a 32-byte nonce that authorizes one specific fix. Preview and consumption are separate call paths so a link prefetch cannot spend an approval.

EvidenceA 25-way concurrent stampede against one token yields exactly one winner.

Binding human approval to an exact proposed change

Atomic single-use token consumption

Validation and consumption are one statement, with expiry in the same predicate. Correctness rests on the update's own predicate rather than an advisory lock: concurrent updates to the row serialize, and the loser matches nothing.

EvidenceMutation-checked — restoring the previous two-statement form fails three tests.

Signed, hash-linked attestation ledger

Proposal, policy decision, approval, and execution are recorded as DSSE envelopes wrapping in-toto Statements, signed with Ed25519, one keypair per role. Each entry's signed parent hash must equal the previous envelope's own hash, so insertion, deletion, reordering, or edit breaks a signature rather than a foreign key.

EvidenceVerification re-runs fresh on every read of an incident's ledger rather than being cached, so a tampered row shows as broken the moment anyone looks.

What a signed, hash-linked ledger does and does not prove

Provenance-tagged quarantined reader

CI job log output — the one place genuinely third-party-influenced text enters a live incident — is read only by a separate model with no tool access, restricted to a schema-validated structured output. The raw excerpt is deleted before the privileged planner's input is assembled, on the failure path as well as the success path.

EvidenceVerified against a real prompt injection planted inside a real CI log: the reader extracted only genuine technical facts and the resulting diagnosis made no reference to the injected instructions.

Containing untrusted CI output with a quarantined reader

Merge-boundary diff reconciliation

The approved change is content-addressed at commit time. At merge time the diff is recomputed from the provider's live pull-request files API and compared. A mismatch hard-blocks the merge and is recorded as an escalation, never silently proceeded past.

EvidenceVerified against a real pull request, after a first run that failed on the honest path and exposed a defect in our own hashing.

Reconciliation at the merge boundary

Credential handling

Provider tokens are encrypted at rest, never reach the browser, and never appear in a URL. Errors redact credential-bearing strings before they are thrown, and credentials are never passed through process arguments.

EvidenceSession tokens are stored only as a SHA-256 hash, asserted by test, so a database dump is not replayable. Session expiry is enforced in SQL because nothing re-checks it in the function body.

Current trust boundaries and limitations

Where the guarantee is weaker than it sounds.

Key custody is a configuration boundary

Signing keys are split across two trust-domain secrets, not one shared secret, and separation is enforced at runtime: each service refuses to load the other domain’s role, and — tested directly, bypassing that guard — decrypting the approver key with the agent secret fails authenticated decryption. That is a real boundary.

It is still a configuration boundary, not a credential boundary. All keys live in one table, and possession of an environment string is the entire authorization model with no external check. Nothing structurally prevents both secrets reaching one process; that happened once, briefly, during initial setup.

Implemented
The accurate sentence is: the agent process does not currently hold that credential. Never: the agent process cannot obtain it.

The ledger is tamper-evident, not independently verifiable

Verification passing proves the rows carry valid signatures from the trusted keys and form an intact hash chain — nobody edited a row without holding the relevant key. It does not prove the chain was never wholesale replaced by someone who also holds the keys or controls the trust map, because the rows and the trust policy currently live in the same database.

Approval identity is a string, not an identity

There is one keypair per role, not per individual. The recorded approver identity is whatever the caller supplies. There is no SSO, SAML, or OIDC integration, and the membership model distinguishes only whether someone belongs to an organization, not what they may do within it.

Merge is not deployment

Approval merges the pull request. There is no automated production deployment step in this system, and no rollback loop: deploying a merged change is a separate, manual process. Anything claiming a deployment or a one-click undo as a current capability would be describing design, not code.

The connector’s permissions are not minimal

The current connector authenticates with a classic personal access token carrying full repository scope. Narrowing it is real work that has not been done. See Tokens and permissions for the exact scope and why it is what it is.

What is recorded, and what is not

Token counts and per-call cost are metered per fix. There is no raw prompt or response transcript store anywhere in the system, so a claim that every model request and response is recorded would be false. There is no runtime command log as a general control either.

Active research and engineering work

Designed, sequenced, and not yet built.

These are listed as open because they are open. None of them is a current protection, and the order matters: revocation and certification both depend on key custody being fixed first, because a kill switch built on shared custody is decorative — and decorative is worse than absent once it has been claimed in a security review.

Open engineering and research work
WorkStatusWhat it would establish
Credential-bound key separationPlannedThat the agent's decrypt call against the approval-side key is refused by the identity provider itself, regardless of what code runs or which environment variables exist.
External transparency-log anchoringPlannedThat the chain existed at a point in time, witnessed outside the database that stores it. Without it, the independence claim is not one we can make.
Runtime process and network guardrailsPlannedA locked container, a build-toolchain process allowlist, and an egress allowlist enforced at the container network-policy level — blocked, not merely logged. Not built; no sandbox or resource-limited worker exists today.
Per-approver identity bindingPlannedThat a specific named person authorized a change, rather than that the approver role's key signed it.
General change-stream reconciliationOpen questionComparing everything that changed in a target system against everything that was approved — including changes with no corresponding proposal.
Orphan execution detectionOpen questionDetecting an execution that never had a proposal. Absence of a proposal is not observable from the proposal record.
Connector-specific enforcement and responseOpen questionWhether a target platform can itself refuse an unattested change, rather than the control layer only observing and proposing.
Planned
Runtime isolation is the item most often assumed to exist. It does not. Repository operations do not currently run in an ephemeral, resource-limited, network-restricted worker, and we do not list that as a protection.

Invariants

Enforced by architecture, not interface wording.

Each of these is checked server-side. None of them depends on which button a browser renders.

  • A merge requires a valid, unexpired, single-use authorization bound to the reviewed change.
  • A change whose applied diff differs from the approved diff is blocked, not merged.
  • A proposal touching a denied path never reaches a commit, a push, or a pull request.
  • An unsignable approval blocks the merge rather than letting it through unattested.
  • Untrusted CI text reaches the planner only as a schema-validated extraction, or not at all.
  • Signing keys must be provisioned per environment; an unprovisioned key throws rather than processing unsigned.
  • Caretaker is never recorded as the approver.