Engineering
The dead drop: how we run a coding agent that can't leak credentials
CleverCrow points a coding agent at your repository, lets it read every file, run your
build, and write a fix, and the agent has no way to push code, post comments, or leak a
single credential. Not "we ask it nicely not to." It structurally cannot: the process the
agent runs in holds no git binary, no .git directory, no GitHub
token, and no AWS credentials beyond a scoped read/write policy on one S3
prefix.
This post is the architecture that makes that true.
The threat model
A coding agent is a program that executes instructions it finds in the wild. It reads issue bodies, code comments, CI logs, and reviewer feedback, all authored by people you have no reason to trust, and it has a shell. Prompt injection isn't an edge case for this design; it's the expected weather. So the question we designed around wasn't "how do we stop the agent misbehaving?" but:
Assume the agent is fully compromised. What can the attacker actually do?
The answer needs to be: write files into a directory that a human will review as a diff. Nothing else.
Four services, one of which is in a padded room
GitHub ──webhook──▶ webhook-lambda
│
▼
orchestrator-lambda
(state machine, GitHub API,
NO git credentials)
│ │
┌────────┘ └────────┐
▼ ▼
git-lambda agent-lambda
(the ONLY component (no .git, no creds,
with git push creds; runs the coder on a
clone, commit, push) bare workspace)
│ │
└──────────────┬────────────────┘
▼
S3 (the dead drop)
Each service holds exactly one kind of power, and the dangerous powers never share a process:
- The orchestrator is the state machine. It can talk to the GitHub API (comments, PRs, labels) but holds no git credentials and never sees repository contents.
- The git service is the only component that can clone or push. It runs no agent, interprets no model output as instructions, and does a fixed, small set of operations: prepare a workspace, apply a patch, push a branch.
- The agent sandbox runs the model loop with shell access, and nothing worth stealing.
The dead drop
The agent never talks to the git service, and neither talks to the other directly. Everything moves through S3, like a cold-war dead drop:
-
The git service clones the repo, strips it (no
.git, no credentials, a bare file tree) and drops the tarball in S3. - The orchestrator drops the prompt next to it.
- The agent picks both up, works entirely on the local tree (read, grep, edit, run tests), and drops the modified workspace and its artifacts (plan, PR title, PR body) back in S3.
-
The git service picks up the result, computes the diff itself, applies
it (excluding workflow files; the agent cannot edit CI), commits with a clean
identity, and pushes to an
agent/*branch. Never to a default branch.
Walk the compromise scenario through it. The attacker who owns the agent process can:
corrupt the output files. That's the list. There is no token to exfiltrate, no remote to
push to, no .git to rewrite history in, and the blast radius of corrupted
output is a bad diff, which lands in front of a maintainer as a draft PR, where bad
diffs go to die.
Defense in depth, because the boundary will be probed
The hard boundary is the credential isolation above. Inside it there are softer layers:
- Untrusted-content tagging. Every user-controlled string (issue bodies, comments, CI logs, review feedback) passes through a sanitizer that wraps it in tagged blocks and neutralizes markup before it reaches a prompt. A non-negotiable system suffix on every prompt tells the agent to treat tagged content as data, never instructions, and to flag injection attempts.
- Secret scrubbing on the way out. Agent-authored free text (PR titles, bodies) is scanned for token-shaped strings before publication, in case the repo itself contains a committed secret the agent might echo.
- Bounded progress. Three replan rounds, three CI-fix rounds, five review rounds. There is no loop an attacker can spin forever on someone else's money.
- A human at every gate. The maintainer approves the plan before any code is written and merges by hand. CleverCrow never merges automatically; there is deliberately no code path for it.
What this costs us
Honesty section: the dead drop isn't free. Every phase round-trips a workspace tarball
through S3, which adds seconds of latency per phase. The agent can't run
git log for archaeology; the history isn't there, by design. And operating
four services with disjoint IAM roles is more infrastructure than one process with a
token in its environment.
We think it's the right trade. An agent platform's worst day is "our sandbox leaked a tenant's push credentials." We removed that day from the calendar by making the sandbox too poor to rob.