fix(checkpoint): require the push destination to confirm checkpoint_remote ownership - #1934
Open
Soph wants to merge 2 commits into
Open
fix(checkpoint): require the push destination to confirm checkpoint_remote ownership#1934Soph wants to merge 2 commits into
Soph wants to merge 2 commits into
Conversation
…emote ownership checkpoint_remote is committed in .entire/settings.json, so cloning or forking a project inherits it, and a guard exists so a contributor's session transcripts are not pushed into the upstream project's checkpoint repo. #1898 keyed that guard on origin's owner, which assumes origin is yours. It is not, in one of the two ways to contribute to someone else's project: fork first, clone the fork -> origin contributor/app -> mismatch, ignored clone the base, add a fork -> origin acme/app -> MATCHES, honored In the second the setting reads as ours, and a contributor pushing to their own fork aims their transcripts at acme/checkpoints. The pre-push gate cannot catch it because a configured checkpoint_remote is exempt from the gate by design, so a wrong ownership verdict goes straight through. Ownership now requires every remote that identifies the repo to be owned by the checkpoint repo's owner: origin says which project this is, the push destination says which repo we are writing to. One mismatch means inherited. The settings.local.json provenance short-circuit and the no-origin fallback are unchanged. The trade: a repo of our own with a differently-owned secondary remote no longer routes checkpoints to our checkpoint repo on pushes to that remote. The two topologies are indistinguishable from local git config — both are origin=<owner>/app plus a push destination owned by somebody else — so one of them has to lose. Pushes to the elected sync remote are unaffected, ENT-1451's gate already blocks checkpoint sync on pushes to a non-elected remote, and erring toward "not ours" never writes transcripts to a repository we cannot confirm we own. On git-refs it strictly helps: the push queue records only a ref, so a delivery to the wrong destination drains the queue and the checkpoint never reaches the right repo at all. Not addressed: FetchURL applies no ownership check, so a repo whose writes were diverted still reads from the configured checkpoint repo. Pre-existing, affects both fork topologies, wants its own change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Tightens the security gate for honoring a configured checkpoint_remote (typically committed in .entire/settings.json) so contributors don’t accidentally sync session transcripts into an upstream project’s checkpoint repository when they cloned upstream and push to their own fork.
Changes:
- Updates
checkpoint_remoteownership detection to require bothoriginand the push destination owner to match the checkpoint repo owner before enabling the dedicated checkpoint URL. - Adds an end-to-end integration test that reproduces the “clone upstream + add fork + push to fork” topology and asserts no checkpoint data reaches upstream repos.
- Updates docs and unit tests to reflect the new ownership decision and trade-offs.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates user-facing description of when checkpoint_remote is considered “yours”. |
| docs/testing/git-remote-test-plan.md | Updates test plan B5 to reflect the new ownership rule and new integration coverage. |
| cmd/entire/cli/integration_test/checkpoint_sync_remote_test.go | Adds an integration guard test for the upstream-clone + fork-push contributor topology. |
| cmd/entire/cli/checkpoint/remote/util.go | Implements the new ownership logic requiring both origin and push destination owners to match. |
| cmd/entire/cli/checkpoint/remote/util_test.go | Updates and adds unit cases covering the new ownership behavior and settings.local.json override. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+374
to
+377
| // On a remote with several push URLs only the FIRST is checked, matching the | ||
| // destination resolveRefsPushDestination sends checkpoint refs to. The | ||
| // git-branch backend still fans out to every push URL, so a multi-URL remote | ||
| // whose later URLs are owned by someone else is not covered here. |
| - Fetch the checkpoint branch locally if it exists on the remote but not locally (one-time) | ||
| - Push `entire/checkpoints/v1` to the checkpoint repo instead of your default push remote | ||
| - Ignore the setting if it looks inherited rather than yours, and push checkpoints to your own push remote instead. `checkpoint_remote` is normally committed in `.entire/settings.json`, so forking a project inherits it — without this, a contributor's session data would be pushed into the upstream project's checkpoint repo. A setting is treated as yours when it lives in the gitignored `.entire/settings.local.json`, or when your `origin` remote is owned by the same account or org as the checkpoint repo | ||
| - Ignore the setting if it looks inherited rather than yours, and push checkpoints to your own push remote instead. `checkpoint_remote` is normally committed in `.entire/settings.json`, so cloning or forking a project inherits it — without this, a contributor's session data would be pushed into the upstream project's checkpoint repo. A setting is treated as yours when it lives in the gitignored `.entire/settings.local.json`, or when **both** your `origin` and the remote you are pushing to are owned by the same account or org as the checkpoint repo. Requiring both covers contributors who cloned the upstream repo and added their own fork, where `origin` belongs to the upstream project rather than to them |
… first Addresses Copilot's review on #1934. The previous commit checked only the first push URL and documented the gap; on the git-branch backend git fans out to ALL push URLs, so a differently-owned mirror in second position read as ours and would receive session transcripts. Ownership now requires origin and every push URL of the push remote to be owned by the checkpoint repo's owner. The first push URL still drives transport derivation, matching the single destination resolveRefsPushDestination sends checkpoint refs to. GetPushURL had exactly one caller and is removed in favour of the plural form. Test note: a genuine multi-push-URL remote needs TWO `set-url --push --add` calls. The first pushurl REPLACES the remote's url as a push destination (git's push_url_of_remote), so one --add produces a one-URL remote — the first version of this test was not multi-URL at all and passed against both implementations. With the same-owner URL first and the differently-owned one second, reverting to first-URL-only checking fails with `enabled = true, want false`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
https://entire.io/gh/entireio/cli/trails/996
The topology that slips through
checkpoint_remoteis normally committed in.entire/settings.json, so anyone who clones or forks a project inherits it.e8b589835added a guard so a contributor's session transcripts don't get pushed into the upstream project's checkpoint repo. #1898 rebased that guard onto origin's owner: "am I working in a repo owned by whoever owns the checkpoint repo?"That reading assumes origin is yours. There are two ways to contribute to someone else's project, and it only holds for one:
contributor/appacme/appIn the second row origin belongs to upstream, so an inherited setting reads as ours and a contributor pushing to their own fork aims their transcripts at
acme/checkpoints. Probed againstmain:The usual outcome is an auth failure and checkpoints that never sync — noisy and broken rather than a leak — but where the contributor does have write access, their session data lands in the upstream project's repo. This is the same class as the regression #1898's second commit already fixed: keying on origin assumes origin is yours.
Why the gate doesn't already stop it
ENT-1451's pre-push gate exempts a configured
checkpoint_remote:A dedicated store is addressed directly rather than selected by the push, so it deliberately skips the gate. That exemption is correct — but it means a wrong ownership verdict sails straight past the gate. The gate is doing its job; it's being handed the wrong answer.
The fix
Ownership now requires every remote that identifies this repo to be owned by the checkpoint repo's owner — origin (which project is this) and every push URL of the push destination (which repos am I writing to). One mismatch means inherited. The
settings.local.jsonprovenance short-circuit and the no-origin fallback are unchanged.With that, the fork case produces
enabled=false,hasCheckpointURL()is false, and the gate drops the sync. Nothing reaches upstream.The trade, made deliberately
This reverts #1898's headline case: your own repo with a differently-owned secondary remote (a backup in another org) no longer routes checkpoints to your checkpoint repo on pushes to that remote.
The two topologies are genuinely indistinguishable from local git config — both are
origin=<owner>/appplus a push destination owned by someone else, and only the network could say which repo is actually yours. Something has to lose.Choosing this direction because:
git push originstill routes checkpoints to the checkpoint repo exactly as before.settings.local.jsonremains the escape hatch, and it's what the warning points at.Behavior changes
checkpoint_remoteinsettings.local.jsonA contributor who wants checkpoints in their own fork sets
checkpoint_push_remote: myfork; election then picks the fork, the gate allows the push, and ownership routes the checkpoints there. Verified:Test plan
TestCheckpointSyncRemote_InheritedCheckpointRemoteDoesNotBypassGate: HTTPS fixture withacme/appas origin,contributor/appas the fork, committedacme/checkpoints; asserts nothing reaches any of the three after a pre-push to the fork. Negative control verified — reverting justutil.gofails it withupstream's checkpoint repo must not receive a contributor's session data from an inherited setting.TestPushURLcases: the fork topology, andsettings.local.jsonstill winning inside it.differently owned push remote …), which is the trade above — it was the only case in the 25-case table that changed.TestPushURLcases for multi-push-URL remotes (second commit, from Copilot's review): a differently-owned URL in second position reads as inherited, and an all-same-owner set still resolves. Negative control: reverting to first-URL-only checking fails the first withenabled = true, want false. Note a real multi-URL remote needs twoset-url --push --addcalls — the firstpushurlreplaces the remote'surlas a push destination, so one--addyields a one-URL remote (the first version of this test wasn't multi-URL and passed either way).mise run test:cigreen (unit + integration-race+ canary),mise run lint0 issues.Review follow-up (second commit)
Copilot flagged that checking only the first push URL leaves mirror-style remotes exposed, since git-branch fans out to all of them. Fixed rather than documented: ownership now holds across every push URL, and
GetPushURL(single caller) gave way to the plural form. The first URL still drives transport derivation, matching whereresolveRefsPushDestinationsends checkpoint refs.Known limitation, not addressed here
FetchURLapplies no ownership check at all —checkpointRemoteIsInheritedis called only fromPushURL. So a repo whose writes were correctly diverted still reads from the configured checkpoint repo, looking where nothing was written. Pre-existing and affecting both fork topologies; it wants its own change, and probably belongs with the wider read-path work in #1634.Relatedly,
entire statusreports the destination for a push to the elected remote, so in the fork topology it still showsdedicated checkpoint remote (acme/checkpoints)untilcheckpoint_push_remoteis set. That's the accepted status-vs-push divergence noted in ENT-1451's own comments.🤖 Generated with Claude Code