Skip to content

feat(daemon): admit narrow Git bundle exec policy - #2763

Open
Harbor404 wants to merge 4 commits into
agentconnect-md:mainfrom
Harbor404:feat/source-cache-git-policy
Open

Harbor404 wants to merge 4 commits into
agentconnect-md:mainfrom
Harbor404:feat/source-cache-git-policy

Conversation

@Harbor404

Copy link
Copy Markdown
Contributor

Part of #2732. This PR implements only CP1.3 (shim exec Git inventory).

Scope

  • Allows git clone --bundle-uri only after validating an https:// URL.
  • Admits only git bundle create <file> [--filter=blob:none] <ref>.
  • Requires the bundle output to be an absolute path under the shim staging root, including symlink-aware containment checks.
  • Accepts only full refs/heads/* or refs/tags/* refs and rejects option-like or injected refs.
  • Forces GIT_NO_LAZY_FETCH=1 for bundle creation, overriding caller environment.
  • Keeps unbundle, verify, list-heads, extra filters, extra flags, and all other bundle spellings refused.

Verification

  • pnpm --filter @agentconnect.md/daemon exec vitest run test/git-command-policy.test.ts test/shim-exec-handler.test.ts -> 30 passed.
  • pnpm --filter @agentconnect.md/daemon typecheck -> passed.
  • pnpm lint -> 0 errors, 9 pre-existing warnings.
  • Prettier check and git diff --check -> passed.

Deliberate limits

The policy is intentionally narrower than Git's full grammar: credential-free HTTPS URLs without fragments, absolute bundle files, and full branch/tag refs. These keep CP1.3 fail-closed until a later checkpoint needs a broader contract. CP1.5 clone wiring and CP1.6 write-back are not included.

Created by Codex . DeepSeek-V4-Flash

Allow HTTPS --bundle-uri on clone and exact bundle create\u003cfilter\u003eref forms inside the staging root. Force GIT_NO_LAZY_FETCH=1 for bundle creation and keep all other bundle verbs refused.

Part of agentconnect-md#2732
@Harbor404

Copy link
Copy Markdown
Contributor Author

@agentconnect-md-test please review CP1.3.

@agentconnect-md-test

Copy link
Copy Markdown
Contributor

This agent responds to requests from this repository's maintainers and trusted contributors. A maintainer can mention it on this thread to have it take a look.

@agentconnect-md-test agentconnect-md-test Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Architecture review — CP1.3 (shim exec Git inventory), Part of #2732

I checked this against docs/designs/source-cache.md §6.1/§13 and the CP1.3 checklist in #2732. The policy itself matches the design:

  • --bundle-uri works only on clone and only with a credential-free https:// URL. Prefix and = spellings are closed. The existing global -c/--config refusal already blocks the bundle.uri/transfer.bundleURI config route.
  • bundle is admitted only as create <file> [--filter=blob:none] <ref>, covering both the blobless and full shapes. unbundle/verify/list-heads stay refused.
  • GIT_NO_LAZY_FETCH=1 is forced by the shim, which is the spawning process. That fits git-workspace-model §11: the check runs where Git is spawned.
  • Scope is correctly limited to CP1.3. CP1.5 clone wiring and CP1.6 write-back are explicitly deferred.

Blocking: the bundle staging root is borrowed from the skill reconcile

runGit passes paths.skillStagingDir (/run/agentconnect/skills-staging) as the only place a bundle may be written. That directory belongs to ClusterSkillHandler:

  • Its per-operation handle directories feed the skill snapshot that goes to the CLI cell.
  • Per-workspace handlers live under hashed subdirectories of it.
  • gcInactive() deletes every subdirectory older than the inactivity window.

Under this PR's contract:

  1. The exec channel can write a .bundle file into an in-flight skill operation's private handle directory, for example skills-staging/<handle>/x.bundle. All segments pass SAFE_BUNDLE_PATH_SEGMENT. The shim's own stance is that every check duplicates the daemon's, so this cross-subsystem write should be refused by construction, not trusted because the daemon is the caller.
  2. CP1.6 write-back would have to stage bundles in a directory whose lifecycle another subsystem owns. Skill GC could delete a pending bundle before the writeback PUT. Also, canonicalStagingRoot refuses when the root is missing, and today only the skill handler creates it lazily.

Requested change: add a dedicated shim path for workspace bundles, e.g. ShimPaths.bundleStagingDir = ${runtimeRoot}/bundle-staging, owned by the future writeback operation, and pass that to validateGitArgs. This is a small change now, and it fixes the contract before CP1.5/CP1.6 build on it.

Non-blocking, for CP1.6

  • Bundle handle, not a free path. §13 defines writeback as taking a local bundle handle. Prefer the shim minting a fresh 0700 directory per write-back and resolving the handle itself, over a daemon-chosen absolute path. Otherwise the containment check here is only a point-in-time check against a same-UID runtime on the shared agent pod (§6.1 "Known boundary").
  • Filesystem I/O in the shared policy module. git-command-policy.ts now touches the filesystem (lstat/realpath). That is fine while the shim is the only caller. Any future transport that adopts validateGitArgs must pass a staging root on the host that actually spawns Git, never a pod path checked from the daemon.
  • Comment style. CLAUDE.md asks for one-line comments. The --bu= prefix comment in validateBundleUris is two lines.

After the staging-root change, this is approvable as CP1.3.

sent by architect (Claude Agent · default) · open in session

return null
}
if (capability === 'exec') return runGit(payload, deps, abort)
if (capability === 'exec') return runGit(payload, deps, paths.skillStagingDir, abort)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: skillStagingDir is ClusterSkillHandler's private root. It holds per-operation snapshot handles, hashed per-workspace roots, and is swept by gcInactive(). With this wiring, an exec bundle create may write into a live skill handle's directory, and CP1.6 bundles would sit under another subsystem's GC. Please give workspace bundles their own shim path, e.g. ShimPaths.bundleStagingDir under ${runtimeRoot}/bundle-staging, owned by the writeback operation.

@agentconnect-md-test agentconnect-md-test Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved for CP1.3. The new policy admits only the specified bundle create form with a staging-root output and full branch/tag ref, restricts clone bundle URIs to validated HTTPS URLs, and forces GIT_NO_LAZY_FETCH=1 for bundle creation. I inspected the verified revision, ran six focused policy cases and a real filtered-bundle creation with Git, and git diff --check passed. The package test suite was not rerun here because this checkout has no installed dependencies.

sent by review-bot (Codex · gpt-6-sol) · open in session

@spacedragon

Copy link
Copy Markdown
Contributor

Review note from a parallel P0 investigation (#2731). The --bundle-uri rule here handles unique-prefix spellings well (--bu= and --bundle-u are refused). The same Git behavior has a pre-existing gap in the global REFUSED_ARGUMENT list on main, which this file now owns.

validateGitArgs admits abbreviations of the options it means to refuse, and Git executes them:

argv validateGitArgs on main
clone --upload-pack=x r d refused
clone --upload=x r d admitted
clone --upl=x r d admitted
fetch --upload=x admitted
push --receive=x admitted

I confirmed that Git 2.54 treats clone --upload=<cmd> as --upload-pack and runs the command. The policy is a control on what the daemon may ask the shim to run, so this breaks the inventory's guarantee rather than adding a new attack path from the runtime.

Suggested fix: apply the unique-prefix test you wrote for --bundle-uri to every refused long option (--upload-pack, --receive-pack, --exec-path, --config/--config-env). An argument is refused when its option part (before =) is a prefix, longer than --, of a refused name. Add test rows for the abbreviated spellings. It could land here or as a separate fix, whichever you prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants