fix(sandbox): render the default branch into platform contracts - #186
Open
oskurikhin wants to merge 1 commit into
Open
fix(sandbox): render the default branch into platform contracts#186oskurikhin wants to merge 1 commit into
oskurikhin wants to merge 1 commit into
Conversation
renderRunContract substituted two of the three placeholders present in the
shared prompt templates. The third, {{DEFAULT_BRANCH}}, reached platform-lane
agents literally, including inside the CI-doctor's security_audit_gate, where
it forms the command that enumerates a pull request's changed files.
The repository lane is unaffected: it renders the same templates through
packages/core/src/render.ts, which resolves every placeholder.
The connected repository's default branch was already in scope at the call
site, so pass it through. Add a test that renders every shared prompt template
and fails on any placeholder the platform renderer leaves behind, so the next
one breaks the build instead of reaching an agent.
adrian-lorenzo
requested changes
Aug 24, 2026
adrian-lorenzo
left a comment
Member
There was a problem hiding this comment.
Thanks for the contribution!
defaultBranch comes from GitHub, and valid Git ref names may contain shell metacharacters. This substitution places it unquoted inside a command the CI doctor is told to run, so a hostile default branch can produce executable shell injection in the sandbox. Please avoid interpolating the value into shell text or quote it safely, and add a regression test using a hostile but valid Git ref.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's wrong
renderRunContract()substitutes two of the three placeholders that exist in theshared prompt templates. The third,
{{DEFAULT_BRANCH}}, reaches platform-laneagents literally.
It lands inside the CI-doctor's
<security_audit_gate>—packages/cli/templates/prompts/doctor.md:31:So the agent is instructed to run
git diff --name-only origin/{{DEFAULT_BRANCH}}...HEAD,which is not a valid ref. The gate's stop-list depends on that diff to know which
paths the pull request touches.
The repository lane is unaffected: it renders the same templates through
packages/core/src/render.ts, which resolves every placeholder.How it reaches an agent
prompts/doctoris a registered platform contract —services/api/src/routes/v1/projects-repos.ts:503packages/db/src/seed.ts:761renderRunContract(...)atservices/api/src/sandbox/orchestrator.ts:1731, inside the same function thathandles
ci_doctormodeThe comment above
renderRunContractalready states the intent — "so agents neverreceive literal
{{...}}instructions". The body implements two thirds of it.What this changes
repois already selected in full atorchestrator.ts:1691, andrepos.default_branchisnotNull, so the value was already in scope. This passesit through and adds the third substitution, falling back to
main.The test worth looking at is the third one. It reads
packages/cli/templates/prompts/*.md, renders each throughrenderRunContract, andfails if any
{{...}}survives. The platform renderer is a hand-written subset of therepo-lane engine, so this turns the next added placeholder into a red build instead of
a silent instruction. With the production change reverted it reports:
Verification
I could not run the full
pnpm verifyladder here. The database-backed tierself-skips without Postgres on this machine, so
github-platform-lane.test.tsand theother DB-gated suites were skipped rather than run — CI covers them.
biome checkalsoreports a formatter diff on both files, but it does so on untouched files too
(
services/api/src/previews.ts,executors.ts,learning.tseach report one): it isthe CRLF artifact of a Windows checkout with
core.autocrlf=true, and the committedcontent is LF.
A design question I did not decide here
This keeps the hand-written subset and guards it with a test. The alternative is to have
the platform lane reuse
packages/core/src/render.tsoutright, which removes the secondrenderer instead of testing it. That is a larger change and felt like yours to make —
happy to follow up with it if you would rather go that way.