Skip to content

feat(git-credential): add git_credentials_read_only flag - #56

Merged
chaodu-agent merged 1 commit into
openabdev:mainfrom
rockexe0000:feature/git-credentials-read-only
Aug 14, 2026
Merged

feat(git-credential): add git_credentials_read_only flag#56
chaodu-agent merged 1 commit into
openabdev:mainfrom
rockexe0000:feature/git-credentials-read-only

Conversation

@rockexe0000

@rockexe0000 rockexe0000 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Add [mcp] git_credentials_read_only (default false, behavior unchanged).
When true, /git-credential mints a contents: read installation token
instead of contents: write: the credential can git clone / fetch a private
repo but cannot push.

Problem

token_git (src/app_token.rs) hardcodes contents: write, so any agent
granted git-over-HTTPS access can both clone and push — there is no
clone-only option. It also forces the backing GitHub App to hold
Contents: read/write; installing it with Contents: read only would make
the mint fail (GitHub 422) and break the endpoint.

Use case

Read-only AI agents that must git clone a private repo but never
commit/push. Previously the only choices were "no git credential at all"
(can't clone) or "full write credential" (can also push). This fills the
missing "clone, no push" mode, so an agent can be read-only end to end
(MCP read tools + read-only clone).

Changes

  • src/config.rs — new McpConfig::git_credentials_read_only: bool
    (#[serde(default)], defaults to false).
  • src/app_token.rstoken_git(repo, read_only) selects contents: read
    vs contents: write, under a distinct cache purpose
    (git:contents=read / git:contents=write) so a read-only credential can
    never be served a write token, or vice versa.
  • src/git_credential.rs — passes git_credentials_read_only into the mint.
  • README.md + config.example.toml — documented.
  • Tests — token-layer read-only mint asserts contents: read under its own
    cache namespace; endpoint test asserts read-only mode mints contents: read.

Relationship to upstream #49

#49 (ref-level push
policy) constrains where a push-capable token may push, which needs
git-proxying that octobroker deliberately avoids. This PR covers the other
half — agents that should never push — by removing the push capability
entirely (contents: read). It is not a duplicate and does not supersede
#49; it fills the "no push at all / clone-only" option #49's list lacks.

Testing

  • cargo test
  • cargo test read_only
  • cargo test downscopes
  • cargo clippy --all-targets -- -D warnings
  • cargo check

The /git-credential endpoint always minted contents:write tokens, so an
agent granted git-over-HTTPS access could both clone and push — there was
no way to grant clone-only access. This also meant the backing GitHub App
had to hold Contents: read/write; a read-only App would fail the mint (422).

Add [mcp] git_credentials_read_only (default false, behavior unchanged).
When true, token_git downscopes the installation-token envelope to
contents:read: git clone/fetch works, push is rejected by GitHub, and the
App only needs Contents: read. Read tokens use a separate cache namespace
so a read-only credential can never be served a write token (or vice versa).

Docs (README, config.example.toml) and tests updated: token_git read-only
mint asserts contents:read and a distinct cache namespace; git_credential
endpoint test asserts read-only mode mints contents:read.

Co-authored-by: rockexe0000 <rockexe0000@gmail.com>

@chaodu-agent chaodu-agent 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.

Note

LGTM ✅ — correct token-level capability removal with proper cache isolation; one non-blocking follow-up recommended (per-agent granularity).

What This PR Does

Adds [mcp] git_credentials_read_only (default false, behavior unchanged). When set, /git-credential mints contents: read installation tokens instead of contents: write — agents can git clone/fetch private repos but cannot push.

How It Works

The permission envelope is selected in token_git(repo, read_only) and enforced by GitHub itself — no git proxying, consistent with octobroker's existing design. Read and write git tokens live under distinct cache purposes (git:contents=read / git:contents=write), so a read-only credential can never be satisfied by a cached write token or vice versa.

Findings

# Severity Finding Location
1 🟢 Cache namespace split done correctly — the one subtle security bug this feature could introduce (cross-namespace cache hits) is explicitly tested with a 3-namespace assertion src/app_token.rs tests
2 🟢 Backward compatible: #[serde(default)], default false; docs + config.example.toml updated src/config.rs
3 🟢 Tests at both layers: token mint envelope (test_git_token_downscopes_permissions_and_isolates_cache) and endpoint (test_read_only_mode_mints_contents_read) src/app_token.rs, src/git_credential.rs
4 ℹ️ Follow-up (non-blocking): the flag is global — every agent on the broker gets read-only git creds, or none do. Mixed fleets (one push-capable agent + read-only reviewer agents) need per-agent granularity, which octobroker's existing per-agent policy structure could carry. The cache-purpose split here already future-proofs for it. Recommend opening a follow-up issue so a later per-agent override composes cleanly with this global flag before the config surface ossifies. src/config.rs
Baseline Check
  • main has no read-only git credential support (only a doc-comment mention of Contents permissions) — PR is a single commit f56d386 of net-new value on top of main.
  • Correctly complementary to #49 (ref-level push policy): #49 constrains where push-capable tokens push; this removes push capability entirely. Not a duplicate.

Verified

At PR HEAD f56d386 (fresh clone, macOS/arm64):

  • cargo clippy --all-targets -- -D warnings — clean
  • cargo test133 passed; 0 failed
  • cargo test read_onlygit_credential::tests::test_read_only_mode_mints_contents_read
  • cargo test downscopesapp_token::tests::test_git_token_downscopes_permissions_and_isolates_cache

5️⃣ Three Reasons We Might Not Need This PR

  1. "Just don't enable git credentials for read-only agents" — insufficient: the use case is agents that must clone private repos. Without this, the only options are no-clone or full-push.
  2. "#49 will cover it" — no: #49's ref-level policy requires git proxying and still leaves the token push-capable. Removing the capability at the token is strictly stronger and simpler for the never-push case.
  3. "Deploy a second octobroker with a read-only App" — works but doubles operational surface for a one-line permission difference; a config flag is the right cost. (The per-agent follow-up in F4 eventually removes even the two-instance need for mixed fleets.)

@chaodu-agent
chaodu-agent merged commit fe0811b into openabdev:main Aug 14, 2026
1 check passed
chaodu-agent added a commit that referenced this pull request Aug 14, 2026
* feat(git-credential): per-agent git_credentials_read_only override

Follow-up to #56: the global flag forces the whole fleet into one mode.
A per-agent Option<bool> on [[mcp.agents]] now overrides the global
default in either direction; None (omitted) inherits. Enables the
mixed-fleet posture: read-only global default + one explicitly
push-capable agent. Both values are operator-set config, never
agent-controlled. Issuance log now records the minted mode.

* feat(git-credential): record effective mode in durable audit + TOML roundtrip test

Self-review findings: (1) the minted mode (contents read/write) was only
in the tracing log, not the audit JSONL — forensic gap for mixed-mode
fleets; now recorded on every git_credential_result, including failure
paths. (2) add a TOML deserialization test guarding the tri-state
Option<bool> per-agent key.

---------

Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
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.

3 participants