Skip to content

Guard coverage: add pin_issue/unpin_issue write ops; unconditionally block transfer_repository#2750

Merged
lpcox merged 3 commits intomainfrom
copilot/guard-coverage-add-cli-write-tools
Mar 29, 2026
Merged

Guard coverage: add pin_issue/unpin_issue write ops; unconditionally block transfer_repository#2750
lpcox merged 3 commits intomainfrom
copilot/guard-coverage-add-cli-write-tools

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 29, 2026

Three GitHub CLI write operations had no guard coverage. pin_issue and unpin_issue needed standard write-op labeling; transfer_repository (irreversible ownership transfer) must never be agent-executable and is unconditionally blocked.

Changes

tools.rs

  • Added pin_issue, unpin_issue, transfer_repository to WRITE_OPERATIONS
  • New is_blocked_tool() function — currently gates transfer_repository

tool_rules.rsapply_tool_labels

  • pin_issue / unpin_issue: repo-visibility secrecy + writer_integrity (repo-level cosmetic write)
  • transfer_repository: repo-visibility secrecy only; integrity enforcement delegated to label_resource

lib.rslabel_resource

Added a post-ensure_integrity_baseline override: if is_blocked_tool() is true, integrity is forced to blocked_integrity. This is necessary because ensure_integrity_baseline would otherwise promote blocked: tags up to none:.

let final_integrity = if tools::is_blocked_tool(&input.tool_name) {
    let scope = if repo_id.is_empty() { "global" } else { &repo_id };
    blocked_integrity(scope, &ctx)  // no agent ever holds a "blocked:" tag → always denied
} else {
    final_integrity
};

Tests

  • is_blocked_tool unit tests in tools.rs
  • apply_tool_labels tests for pin_issue/unpin_issue (writer integrity) and transfer_repository (secrecy)
  • End-to-end proof in lib.rs tests that the override survives ensure_integrity_baseline

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /tmp/go-build3725800994/b330/launcher.test /tmp/go-build3725800994/b330/launcher.test -test.testlogfile=/tmp/go-build3725800994/b330/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true g_.a 0JyzvQ--L cal/bin/cc z_oxide-92023c1c/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/compile 5519 .13/x64/bin/git bash ortc�� 64/src/runtime/c-p v0Q-HlWws ache/go/1.25.8/x-lang=go1.24 k/gh-aw-mcpg/gh-/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet k/gh-aw-mcpg/gh--atomic guard-d6a03f414d-bool 01.o (dns block)
    • Triggering command: /tmp/go-build3217884824/b334/launcher.test /tmp/go-build3217884824/b334/launcher.test -test.testlogfile=/tmp/go-build3217884824/b334/testlog.txt -test.paniconexit0 -test.timeout=10m0s -W 5800994/b228/_pkdebuginfo=2 /tmp/go-build372-C ache/go/1.25.8/xdebug-assertions=on . b/gh-aw-mcpg/int-unsafeptr=false --64 ache/go/1.25.8/x--check-cfg -I 5800994/b315/_pkg_.a -I docker-buildx --gdwarf-5 --64 -o docker-buildx (dns block)
  • invalid-host-that-does-not-exist-12345.com
    • Triggering command: /tmp/go-build3725800994/b315/config.test /tmp/go-build3725800994/b315/config.test -test.testlogfile=/tmp/go-build3725800994/b315/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true -c=4 -nolocalimports -importcfg /tmp/go-build3725800994/b245/importcfg -pack /home/REDACTED/go/pkg/mod/github.com/tetratelabs/[email protected]/builder.go /home/REDACTED/go/pkg/mod/github.com/tetratelabs/[email protected]/cache.go ortc�� g_.a 64/src/internal/coverage/rtcov/r-I ache/go/1.25.8/x64/pkg/tool/linu/tmp/go-build3725800994/b165/ core-10e2da29e93/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/compile abis stup/toolchains//tmp/go-build3725800994/b196/_pkg_.a ache/go/1.25.8/x-trimpath (dns block)
    • Triggering command: /tmp/go-build3217884824/b319/config.test /tmp/go-build3217884824/b319/config.test -test.testlogfile=/tmp/go-build3217884824/b319/testlog.txt -test.paniconexit0 -test.timeout=10m0s .19a�� .19alb25.rcgu.o .19alb25.rcgu.o .19alb25.rcgu.o .19alb25.rcgu.o .19alb25.rcgu.o .19alb25.rcgu.o .19alb25.rcgu.o .19a�� .19alb25.rcgu.o .19alb25.rcgu.o .19alb25.rcgu.o .19alb25.rcgu.o .0655h1b.rcgu.o .0655h1b.rcgu.o .0655h1b.rcgu.o (dns block)
  • nonexistent.local
    • Triggering command: /tmp/go-build3725800994/b330/launcher.test /tmp/go-build3725800994/b330/launcher.test -test.testlogfile=/tmp/go-build3725800994/b330/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true g_.a 0JyzvQ--L cal/bin/cc z_oxide-92023c1c/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/compile 5519 .13/x64/bin/git bash ortc�� 64/src/runtime/c-p v0Q-HlWws ache/go/1.25.8/x-lang=go1.24 k/gh-aw-mcpg/gh-/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet k/gh-aw-mcpg/gh--atomic guard-d6a03f414d-bool 01.o (dns block)
    • Triggering command: /tmp/go-build3217884824/b334/launcher.test /tmp/go-build3217884824/b334/launcher.test -test.testlogfile=/tmp/go-build3217884824/b334/testlog.txt -test.paniconexit0 -test.timeout=10m0s -W 5800994/b228/_pkdebuginfo=2 /tmp/go-build372-C ache/go/1.25.8/xdebug-assertions=on . b/gh-aw-mcpg/int-unsafeptr=false --64 ache/go/1.25.8/x--check-cfg -I 5800994/b315/_pkg_.a -I docker-buildx --gdwarf-5 --64 -o docker-buildx (dns block)
  • slow.example.com
    • Triggering command: /tmp/go-build3725800994/b330/launcher.test /tmp/go-build3725800994/b330/launcher.test -test.testlogfile=/tmp/go-build3725800994/b330/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true g_.a 0JyzvQ--L cal/bin/cc z_oxide-92023c1c/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/compile 5519 .13/x64/bin/git bash ortc�� 64/src/runtime/c-p v0Q-HlWws ache/go/1.25.8/x-lang=go1.24 k/gh-aw-mcpg/gh-/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet k/gh-aw-mcpg/gh--atomic guard-d6a03f414d-bool 01.o (dns block)
    • Triggering command: /tmp/go-build3217884824/b334/launcher.test /tmp/go-build3217884824/b334/launcher.test -test.testlogfile=/tmp/go-build3217884824/b334/testlog.txt -test.paniconexit0 -test.timeout=10m0s -W 5800994/b228/_pkdebuginfo=2 /tmp/go-build372-C ache/go/1.25.8/xdebug-assertions=on . b/gh-aw-mcpg/int-unsafeptr=false --64 ache/go/1.25.8/x--check-cfg -I 5800994/b315/_pkg_.a -I docker-buildx --gdwarf-5 --64 -o docker-buildx (dns block)
  • this-host-does-not-exist-12345.com
    • Triggering command: /tmp/go-build3725800994/b339/mcp.test /tmp/go-build3725800994/b339/mcp.test -test.testlogfile=/tmp/go-build3725800994/b339/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/runtime/cgo erive-f8a9da973ea849b8.serde_der--64 x_amd64/vet erive-f8a9da973ebash int erive-f8a9da973e--version x_amd64/vet ache�� ternal/sysfs/adapter.go ternal/sysfs/datasync_linux.go x_amd64/compile erive-f8a9da973egrep erive-f8a9da973e-qE -Wl,-Bstatic x_amd64/compile (dns block)
    • Triggering command: /tmp/go-build3217884824/b343/mcp.test /tmp/go-build3217884824/b343/mcp.test -test.testlogfile=/tmp/go-build3217884824/b343/testlog.txt -test.paniconexit0 -test.timeout=10m0s -gua�� -guard/target/debug/deps/github_guard-d6a03f414dc506a0.2j8pqcxyyfybe7nghv8aqe5ar.158bmhb.rcgu.o -guard/target/debug/deps/github_guard-d6a03f414dc506a0.2qy5imqpflz26hi4smzxan76o.158bmhb.rcgu.o ache/go/1.25.8/x64/pkg/tool/linunow ive.291b3f11bb18/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet ive.291b3f11bb18-V=full x_amd64/compile ache/go/1.25.8/x64/pkg/tool/linu-o -uns�� ithub-guard/rust-guard/target/debug/deps/rustc7pSDMJ/symbols.o ithub-guard/rust-guard/target/debug/deps/github_guard-d6a03f414dc506a0.0bxgpkkxlbr5ndvint8fbouk8/usr/bin/runc ithub-guard/rust-guard/target/debug/deps/github_guard-d6a03f414dc506a0.0jw880073obtnc1lrfavrjkde--version ithub-guard/rustrunc ithub-guard/rust--version ithub-guard/rust-guard/target/de/tmp/go-build1931372066/b003/vet.cfg ithub-guard/rust-guard/target/debug/deps/github_guard-d6a03f414dc506a0.1b9hao6thyobgm5waptlklsec.158bmhb.rcgu.o (dns block)

If you need me to access, download, or install something from one of these locations, you can either:


⌨️ Start Copilot coding agent tasks without leaving your editor — available in VS Code, Visual Studio, JetBrains IDEs and Eclipse.

Copilot AI changed the title [WIP] Add guard coverage for 3 uncovered CLI write operations Guard coverage: add pin_issue/unpin_issue write ops; unconditionally block transfer_repository Mar 29, 2026
Copilot AI requested a review from lpcox March 29, 2026 03:07
@lpcox lpcox marked this pull request as ready for review March 29, 2026 15:09
Copilot AI review requested due to automatic review settings March 29, 2026 15:09
@lpcox lpcox merged commit ffc3a35 into main Mar 29, 2026
17 checks passed
@lpcox lpcox deleted the copilot/guard-coverage-add-cli-write-tools branch March 29, 2026 15:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds missing guard coverage for several GitHub CLI write operations by labeling pin_issue / unpin_issue as repo-scoped writes and enforcing an unconditional deny policy for transfer_repository via a blocked-integrity override in label_resource.

Changes:

  • Extend tool classification with pin_issue, unpin_issue, and transfer_repository, plus new is_blocked_tool() gating.
  • Add apply_tool_labels rules for pin/unpin (repo visibility secrecy + writer integrity) and transfer_repository (repo visibility secrecy).
  • Enforce blocked integrity in label_resource post-baseline and add unit/integration tests to prove the override survives ensure_integrity_baseline.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
guards/github-guard/rust-guard/src/tools.rs Adds new write ops and is_blocked_tool() with unit tests.
guards/github-guard/rust-guard/src/lib.rs Overrides integrity to blocked_integrity for blocked tools after baseline enforcement; adds test.
guards/github-guard/rust-guard/src/labels/tool_rules.rs Labels pin_issue/unpin_issue as writer-integrity repo writes; applies secrecy for transfer_repository.
guards/github-guard/rust-guard/src/labels/mod.rs Adds unit tests for new apply_tool_labels behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
}
secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx);
integrity = writer_integrity(repo_id, ctx);
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

In the new pin_issue / unpin_issue arm, integrity = writer_integrity(repo_id, ctx) is applied unconditionally. If repo_id is empty (e.g., malformed/missing owner+repo in tool_args), writer_integrity("") produces unscoped base labels including "approved" (see existing test_empty_scope_integrity in labels/mod.rs), which is broader than intended for a repo-scoped operation. Please gate this so writer integrity is only assigned when repo_id is non-empty; otherwise keep/return a conservative integrity (e.g., leave as-is/empty so the baseline becomes none). Adding a regression test for the empty-scope case would help prevent accidental global approval.

Suggested change
integrity = writer_integrity(repo_id, ctx);
if !repo_id.is_empty() {
integrity = writer_integrity(repo_id, ctx);
} else {
// Malformed or missing repo scope: avoid assigning unscoped writer integrity.
// Use an empty integrity baseline so no global approval is implied.
integrity.clear();
}

Copilot uses AI. Check for mistakes.
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.

[guard-coverage] Guard coverage gap: 3 CLI write operations not covered (transfer_repository, pin_issue, unpin_issue)

3 participants