Date: 2026-07-25 Status: Specified. Contains one finding against code already on the M0 branch. Scope: What Sync holds, what it must never hold, the blast radius when it is compromised, and the gap between the architecture the design document claims and the code as written.
This is a security document and a sales document. The design document's promise — we never execute your code and never hold your secrets — is the strongest procurement argument Sync has, and it is only worth making if it survives inspection. This document is that inspection.
Kudelski Security published a compromise of CodeRabbit that went from a single pull request to remote code
execution and write access across approximately one million repositories.
(kudelskisecurity.com/research/how-we-exploited-coderabbit-from-a-simple-pr-to-rce-and-write-access-on-1m-repositories/)
The shape of that failure is the shape of Sync's risk exactly, and it is worth stating precisely because the lesson is not "be careful":
- A service analyzes repositories it does not control.
- It runs a tool from, or configured by, the untrusted repository.
- That process shares an environment with the credentials of a GitHub App installed across every customer.
- Therefore one attacker-controlled repository yields the whole installation base.
Steps 1 and 4 are inherent to what Sync is. Steps 2 and 3 are choices, and they are the only two places where this can be prevented.
| Asset | Where it lives | Loss means |
|---|---|---|
| GitHub App private key | Control plane | Write access to every installed repository. Total, unrecoverable. |
| Installation access tokens | Control plane, short-lived | Write access to one customer until expiry |
| The API Dependency Graph | Postgres | Discloses a customer's vendor topology and call-site map |
| Model API credentials | Patch node | Billing theft; prompt exfiltration |
| The migration corpus | Postgres | Least sensitive by construction — see 2026-07-25-sync-migration-corpus.md |
Sync deliberately holds none of: customer production credentials, vendor API keys, .env contents, CI
secrets, or any ability to deploy. This is the architecturally load-bearing decision. Competitors that execute
customer code in their own environment to run tests must hold or synthesize enough environment to make that code
run; Sync pushes a branch and lets the customer's own CI, holding the customer's own secrets, be the verifier.
src/sync/index/tsc.py on the M0 branch prefers the repository's own compiler:
local_tsc = repo_path / "node_modules" / ".bin" / ("tsc.cmd" if _on_windows() else "tsc")
if local_tsc.exists():
command = [str(local_tsc), "--noEmit"]That is a binary from the customer's dependency tree, executed by Sync, in Sync's process environment. Three separate execution paths open here, and the first is the one people miss:
- The compiler binary itself.
node_modules/.bin/tscis whatever the repository put there. tsconfig.json. Even with a trusted compiler,extendsandcompilerOptions.pluginsresolve intonode_modulesand load code into the compiler process. Typechecking a project is not a pure function of its source.- Populating
node_modulesat all. A clone does not arrive with dependencies installed. Whatever installs them runspreinstall/postinstalllifecycle scripts from the entire transitive dependency tree unless explicitly told not to.
The fallback path has a second, smaller problem: --package=typescript@latest fetches an unpinned compiler over
the network at verification time. That is both a supply-chain exposure and a source of non-reproducible
verification results — the same patch can verify differently on two days.
This is not a bug in the code as written. The function does exactly what M0 needs, against a fork we
control, and preferring the project's own compiler is the correct behavior for typecheck fidelity. It is a
mismatch between that code and a claim in CLAUDE.md that is marked non-negotiable. One of the two has to
move, and the claim is worth more than the convenience.
How it was resolved: the claim moved, and one of the three paths closed. CLAUDE.md no
longer asserts the absolute; it states that never executing customer code is the intent rather
than the invariant, and that Sync runs the customer's toolchain while never running their
application. The third path above is genuinely closed — every install command in
src/sync/index/deps.py passes --ignore-scripts, so no lifecycle script from the dependency
tree runs. The first two are open by design: src/sync/index/tsc.py:132 still prefers
node_modules/.bin/tsc, and tsconfig.json still resolves plugins into the compiler process.
The unpinned fallback is also still there (tsc.py:150, --package=typescript@latest), so the
non-reproducibility noted above stands.
The rule to adopt, stated so it can be tested rather than remembered:
No process that touches customer repository content ever shares an environment with a credential.
Concretely:
- Split the verification sandbox from the control plane. Clone, install, patch, and typecheck run in an ephemeral container with no GitHub App key, no installation token, no model API key, and no database credential in its environment or on its filesystem. The patch and its diagnostics come back over a pipe. This is the mitigation that would have contained the CodeRabbit compromise, and it is the only one on this list that is not optional.
- Install with lifecycle scripts disabled.
npm ci --ignore-scripts. Accept that a small number of packages will not build; a project that cannot typecheck without running arbitrary install scripts is a project Sync declines to verify, and declining is a supported outcome — the design already treats an unverifiable finding as abandoned rather than as a reason to lower the gate. - No network egress from the sandbox after dependency installation, and no egress to anything but the package registry during it.
- Pin the fallback compiler. Replace
typescript@latestwith a version resolved from the repository's ownpackage.json, falling back to a version pinned in Sync. Verification must be reproducible. - Run non-root, read-only root filesystem, with the clone on the only writable mount, and a hard wall-clock
kill.
_TSC_TIMEOUT_SECONDSalready provides the last of these.
The claim narrows and survives, and the narrowed version is still the strongest in the category:
Sync never holds customer secrets, never deploys, and never executes customer code anywhere a credential can be reached. Typechecking runs in a disposable, credential-free, network-isolated sandbox, and the authoritative verification is the customer's own CI, running in the customer's own environment, under the customer's own secrets.
That is a claim a security reviewer can check against an architecture diagram, which is the property that makes it useful in procurement.
Request the minimum that the remediation graph actually needs, and be able to justify each line in a security review:
| Permission | Level | Why |
|---|---|---|
| Contents | Read and write | Clone; push a branch. Never to a default branch. |
| Pull requests | Read and write | Open the pull request; read merge outcome |
| Checks / Actions | Read | Poll the CI run in await_ci |
| Metadata | Read | Mandatory |
Not requested, and worth saying so explicitly on the trust page: Administration, Secrets, Environments, Deployments, Packages, Members, Webhooks-write.
Sync writes only to branches it created, opens pull requests, and never merges. Merge authority stays with a human, which is what keeps the blast radius of a bad patch at "a pull request someone declines."
Enforce the branch constraint in code, not in policy. A guard in sync.forge that refuses to push to a
repository's default branch or to any branch Sync did not create, with a test that asserts the refusal, is worth
more than any documented intention.
The patch node reads content that an attacker can influence: vendor changelog prose, and the customer's own source and diagnostics. A changelog entry, or a comment in a repository file, can carry instructions aimed at the model.
The verification gate is the real containment, and this is the second place where "nothing unverified reaches a
pull request" pays for itself: an injected instruction that produces a malicious edit still has to pass tsc
and then the customer's CI, and it still lands as a reviewable diff on a branch rather than as a merge.
Two limits worth naming rather than glossing:
- The gate constrains what reaches a pull request, not what the model does while running. Sandbox isolation is what constrains that, which is another reason mitigation 1 is not optional.
- A patch can be malicious and still typecheck and still pass CI. The evidence bundle is the answer — the pull request carries the specification diff, the changelog entry, the affected call sites, and the CI link, so a reviewer can see whether the diff matches the justification. A diff that does something the evidence does not explain is the signal to look for, and it is visible precisely because Sync shows its reasoning rather than presenting a black box.
Written in the order a review actually goes.
- Do you store our source code? No. The ADG stores call-site locations and shapes. Clones are ephemeral and destroyed with the sandbox.
- Do you hold any of our secrets? No — not vendor keys, CI secrets, or environment files. Sync cannot run your application and does not try to.
- Do you run our code? Only a typecheck, in a disposable container with no credentials and no network. Your CI is the authoritative verifier.
- Can you merge? No. Sync opens pull requests. It cannot merge and does not request the permission that would let it.
- What can you write to? Branches Sync created. Enforced in code, with a test.
- What happens if you are breached? The App key is the crown jewel and is isolated from every process that touches customer content. Rotation revokes every installation token. No customer secret exists to leak because none was ever collected.
- Do you train on our code? No. The corpus records structural shapes, salted hashes, and outcomes — never
source. See
2026-07-25-sync-migration-corpus.md, which specifies a test asserting no source text can appear in it.
| Milestone | Security work |
|---|---|
| M0 | The branch-guard in sync.forge with its test. Pin the fallback compiler. Document the sandbox requirement. The M0 target is a fork we control, so the sandbox itself is not yet load-bearing. |
| M1 | The credential-free verification sandbox, before any repository Sync does not own is indexed. This is the gate on touching a real customer, and it should be treated as one. |
| M4 | SOC 2 Type II observation period, subprocessor list, DPA, and the public trust page built from the seven answers above. |
The honest constraint: SOC 2 Type II requires an observation window and money, and neither is available to a solo, self-funded operator inside twelve months. The architecture above is what can be offered in the meantime, and it is a stronger answer than most certified vendors can give — but it is not a substitute for the certificate, and no proposal here should pretend otherwise.