Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: add GitHub Copilot support to guard - #468

Open
phsommer wants to merge 6 commits into
mainfrom
feat/github-copilot-support
Open

feat: add GitHub Copilot support to guard#468
phsommer wants to merge 6 commits into
mainfrom
feat/github-copilot-support

Conversation

@phsommer

@phsommer phsommer commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Adds github-copilot as a client for guard install, guard uninstall and guard discover. User-level hooks are written to ~/.copilot/hooks/agent-guard.json, which every Copilot surface reads — the CLI, the desktop app, and Copilot inside VS Code.

Adds managed hooks, which Copilot calls policy hooks: /etc/github-copilot/ policy.d/agent-guard.json, or C:\ProgramData\GitHub\Copilot\policy.d on Windows, installed with --managed and reported by guard status.

Hook events carry an X-Agent-Surface header (copilot, or copilot-vscode inside VS Code) resolved from AI_AGENT and COPILOT_CLI.

Notes for review

  • Merge order: the installed hook posts to /hidden/agent-monitor/hooks/github-copilot, which only exists once snyk/agent-monitor#367 ships. That PR must merge first.
  • CHANGELOG: release-time in this repo, so this commit leaves it alone — it needs a line at the next version bump.

Note

Medium Risk
Installs and modifies agent hook configs (including managed policy files) and depends on a new agent-monitor hook route shipping separately; telemetry uses existing push-key auth with added surface attribution logic.

Overview
Adds GitHub Copilot as a first-class Agent Guard client alongside Claude, Cursor, and Codex. The CLI accepts github-copilot for guard install, uninstall, and discover, and guard install all now includes Copilot when ~/.copilot exists.

Install writes user hooks to ~/.copilot/hooks/agent-guard.json (Copilot’s flat hook schema with type: command and platform bash/powershell keys). --managed targets OS-specific policy.d/agent-guard.json paths; on POSIX, writes under policy.d strip group/world write bits so Copilot won’t silently ignore the policy. Hook scripts are co-located without a nested hooks/hooks directory for user-level configs.

Hook forwarding and guard discover post to /hidden/agent-monitor/hooks/github-copilot. For Copilot only, events may include X-Agent-Surface (copilot-vscode vs copilot) derived from AI_AGENT / COPILOT_CLI, including in the shell/PowerShell forwarders and Python send_hook_event—other clients never send that header even if Copilot env vars are inherited.

Uninstall/detect/diff paths recognize guard entries under bash, powershell, or command. Unit and integration tests cover prepare/uninstall, policy hardening, surface scoping, and the new endpoint.

Reviewed by Cursor Bugbot for commit 4c2a154. Bugbot is set up for automated code reviews on this repo. Configure here.

@phsommer
phsommer force-pushed the feat/github-copilot-support branch from f00a617 to cb92e34 Compare September 9, 2026 14:53
@phsommer
phsommer marked this pull request as ready for review September 9, 2026 14:57
@phsommer
phsommer requested a review from a team as a code owner September 9, 2026 14:57
@qodo-merge-etso

Copy link
Copy Markdown

PR Summary by Qodo

Add GitHub Copilot hook support to Agent Guard

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Adds GitHub Copilot user and managed hooks across install, uninstall, discovery, and status.
• Routes Copilot events to Agent Monitor with surface-specific attribution headers.
• Covers hook configuration, policy permissions, forwarding, detection, and cleanup with tests.
Diagram

sequenceDiagram
    actor User
    participant CLI as Guard CLI
    participant Config as Copilot Config
    participant Surface as Copilot Surface
    participant Forwarder as Hook Forwarder
    participant Monitor as Agent Monitor
    User->>CLI: Install hooks
    CLI->>Config: Write user or policy hooks
    Surface->>Config: Load shared hooks
    Surface->>Forwarder: Emit hook event
    Forwarder->>Forwarder: Resolve agent surface
    Forwarder->>Monitor: POST event and header
    Monitor-->>Forwarder: Return policy response
    Forwarder-->>Surface: Forward response
Loading
High-Level Assessment

The approach fits the existing client integration model while reusing the compatible flat-hook filtering and detection logic. A generalized client-adapter framework would add unnecessary abstraction for one new client, while separate configurations per Copilot surface are not viable because Copilot intentionally shares one hook configuration. Merge after the Agent Monitor Copilot endpoint is available.

Files changed (7) +570 / -19

Enhancement (5) +210 / -14
cli.pyExpose GitHub Copilot in Guard commands +3/-3

Expose GitHub Copilot in Guard commands

• Adds GitHub Copilot to the supported client choices for hook installation, discovery, and uninstallation.

src/agent_scan/cli.py

guard.pyImplement Copilot hook lifecycle management +128/-6

Implement Copilot hook lifecycle management

• Adds Copilot user and managed paths, event configuration, installation detection, status reporting, discovery, and uninstallation. Managed policy files and scripts are hardened against group or world writes on POSIX systems.

src/agent_scan/guard.py

hook_events.pyForward Copilot events with surface attribution +25/-0

Forward Copilot events with surface attribution

• Defines Copilot payload fields and its Agent Monitor endpoint. Adds environment-based detection for Copilot and Copilot-in-VS-Code surfaces and emits the X-Agent-Surface header.

src/agent_scan/hook_events.py

snyk-agent-guard.ps1Support Copilot forwarding on Windows +22/-2

Support Copilot forwarding on Windows

• Accepts GitHub Copilot as a PowerShell hook client, routes events to its endpoint, and adds surface attribution derived from Copilot environment variables.

src/agent_scan/hooks/snyk-agent-guard.ps1

snyk-agent-guard.shSupport Copilot forwarding on POSIX systems +32/-3

Support Copilot forwarding on POSIX systems

• Adds GitHub Copilot endpoint routing and validates it as a supported client. Detects the invoking Copilot surface and conditionally includes X-Agent-Surface in forwarded requests.

src/agent_scan/hooks/snyk-agent-guard.sh

Tests (2) +360 / -5
test_guard.pyCover Copilot hook management and forwarding +327/-5

Cover Copilot hook management and forwarding

• Tests Copilot configuration generation, discovery idempotency, detection, uninstallation, path mapping, policy permissions, status output, endpoint forwarding, and all-client installation behavior.

tests/unit/test_guard.py

test_hook_events.pyTest Copilot surface header resolution +33/-0

Test Copilot surface header resolution

• Verifies VS Code, desktop, CLI, and future Copilot environments map to the expected surface header while unrelated environments omit it.

tests/unit/test_hook_events.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread src/agent_scan/guard.py Outdated
Comment thread src/agent_scan/hooks/snyk-agent-guard.sh
@qodo-merge-etso

qodo-merge-etso Bot commented Sep 9, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Copilot users receive hooks that never run 🐞 Bug ≡ Correctness
Description
_prepare_copilot_config writes Claude-style PascalCase event keys from COPILOT_HOOK_EVENTS,
including placing discovery under SessionStart. Copilot dispatches its hook lifecycle events with
its own lower-camel-case names, so the installed forwarding and session-discovery entries are not
invoked despite status detection scanning the same invalid keys.
Code

src/agent_scan/guard.py[R136-145]

+COPILOT_HOOK_EVENTS = [
+    "SessionStart",
+    "UserPromptSubmit",
+    "PreToolUse",
+    "PostToolUse",
+    "PreCompact",
+    "SubagentStart",
+    "SubagentStop",
+    "Stop",
+]
Relevance

●●● Strong

Clear lifecycle-key mismatch prevents installed hooks from running; historical hook-preservation
regressions were accepted.

PR-#365

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The newly added list supplies every key emitted by the Copilot configuration builder, and the
discovery entry is explicitly appended to SessionStart; status detection then treats those same
keys as evidence of an installed integration.

src/agent_scan/guard.py[136-145]
src/agent_scan/guard.py[760-796]
src/agent_scan/guard.py[1208-1242]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The generated GitHub Copilot hook configuration uses Claude-style event names, so Copilot does not dispatch the installed hook commands or the session-start discovery command.

## Issue Context
`_prepare_copilot_config` directly serializes `COPILOT_HOOK_EVENTS` as JSON keys and adds discovery to `SessionStart`; detection also depends on this list. Replace these with Copilot's supported event names and update the discovery event key consistently.

## Fix Focus Areas
- src/agent_scan/guard.py[136-145]
- src/agent_scan/guard.py[782-788]
- src/agent_scan/guard.py[1208-1209]
- tests/unit/test_guard.py[814-856]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Windows sessions skip server discovery ✓ Resolved 🐞 Bug ≡ Correctness
Description
_prepare_copilot_config installs a discovery command, and _build_discover_hook_command passes
github-copilot to the Windows discovery trampoline whose unchanged ValidateSet allows only the
three existing clients. On Windows, every installed Copilot SessionStart command is rejected
during PowerShell parameter binding before guard discover can forward a
sessionStartServerDiscovery event.
Code

src/agent_scan/guard.py[R787-788]

+    if discover_command:
+        hooks["SessionStart"].append({"type": "command", "command": discover_command})
Relevance

●●● Strong

Windows parameter validation deterministically rejects the newly supported client before discovery
executes.

PR-#320

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The CLI accepts github-copilot, and the installation flow maps that exact value into the discovery
invocation serialized in the generated Copilot configuration. The PowerShell trampoline’s Client
parameter permits only the three pre-existing values and invokes guard discover only after
parameter binding succeeds, proving that Copilot discovery events cannot be produced through this
path on Windows.

src/agent_scan/guard.py[607-624]
src/agent_scan/guard.py[782-788]
src/agent_scan/hooks/snyk-agent-guard-discover.ps1[6-9]
src/agent_scan/hooks/snyk-agent-guard-discover.ps1[36-53]
src/agent_scan/cli.py[987-993]
src/agent_scan/guard.py[1603-1608]
src/agent_scan/guard.py[1812-1834]
src/agent_scan/hooks/snyk-agent-guard-discover.ps1[39-50]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Windows session-start discovery script rejects the newly supported `github-copilot` client argument during PowerShell parameter binding, preventing session-scoped MCP server discovery from reaching `guard discover`.

## Issue Context
The CLI and installation flow support `github-copilot`, build a discovery command with `-Client github-copilot`, and serialize it into the Copilot `SessionStart` hook. The existing PowerShell trampoline still validates `Client` against its older three-value allowlist, so execution stops before it can invoke `guard discover` and forward the `sessionStartServerDiscovery` event.

## Fix Focus Areas
- src/agent_scan/guard.py[607-618]
- src/agent_scan/guard.py[787-788]
- src/agent_scan/hooks/snyk-agent-guard-discover.ps1[6-9]
- tests/unit/test_guard.py[814-826]
- tests/unit/test_guard.py[3691-3729]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Copilot users receive invalid command hooks ✓ Resolved 🐞 Bug ≡ Correctness
Description
_prepare_copilot_config emits generic command fields and reuses Cursor filtering, detection, and
diff helpers that only read that field. Copilot command hooks require the platform-specific
executable property, so installed entries are not executable and pre-existing Copilot bash or
powershell Agent Guard hooks cannot be detected, updated, or removed.
Code

src/agent_scan/guard.py[R778-785]

+    filtered = _filter_cursor_hooks(old_hooks)
+    preserved = sum(len(filtered.get(event, [])) for event in COPILOT_HOOK_EVENTS)
+    hooks = {}
+
+    for event in COPILOT_HOOK_EVENTS:
+        existing = list(filtered.get(event, []))
+        existing.append({"type": "command", "command": command})
+        hooks[event] = existing
Relevance

●●● Strong

Platform-specific hook schema mismatch breaks execution and lifecycle management; correctness fixes
for hook handling were accepted.

PR-#365

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added builder writes only command, while the selected shared helpers inspect only
entry["command"]; those helpers are also wired into Copilot uninstall and detection, making the
incompatible representation affect the full hook lifecycle.

src/agent_scan/guard.py[760-796]
src/agent_scan/guard.py[1061-1066]
src/agent_scan/guard.py[1208-1217]
src/agent_scan/guard.py[1473-1544]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
GitHub Copilot command hooks are emitted and managed using Cursor's generic `command` representation. Copilot requires platform-specific command properties, so generated entries are invalid and lifecycle operations miss existing valid Copilot entries.

## Issue Context
The same generic helpers are used for install filtering, status detection, hook diffs, and uninstall. Add Copilot-specific serialization and command extraction for both POSIX and Windows, then use it consistently across these paths.

## Fix Focus Areas
- src/agent_scan/guard.py[760-796]
- src/agent_scan/guard.py[1061-1066]
- src/agent_scan/guard.py[1208-1217]
- src/agent_scan/guard.py[1473-1544]
- tests/unit/test_guard.py[1702-1800]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View high (1)
4. Managed Copilot installs are skipped ✗ Dismissed 🐞 Bug ≡ Correctness
Description
The new _CLIENT_INSTALL_PATHS entry makes _run_install require the invoking user's ~/.copilot
directory even when --managed targets the system-wide policy path. On an administrator or
deployment account without that user directory, guard install github-copilot --managed returns
without creating /etc/github-copilot/policy.d/agent-guard.json or its Windows equivalent.
Code

src/agent_scan/guard.py[1615]

+    "github-copilot": Path.home() / ".copilot",
Relevance

●●● Strong

Managed installation incorrectly depends on user-level client detection, matching prior accepted
managed-install robustness fixes.

PR-#269

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The standard managed command has no file override, so _run_install filters clients through
_is_client_installed and exits when none remain. For Copilot that predicate checks only
Path.home() / '.copilot', while _config_path(..., managed=True) independently resolves the
system policy location.

src/agent_scan/guard.py[244-262]
src/agent_scan/guard.py[1611-1627]
src/agent_scan/guard.py[1639-1656]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Managed Copilot installation is skipped when the invoking account has no user-level `~/.copilot` directory, even though the requested destination is system-wide.

## Issue Context
`_run_install` applies user-directory installation detection whenever `--file` is absent, without accounting for `--managed`. The Copilot managed path is independent of the invoking user's home directory.

## Fix Focus Areas
- src/agent_scan/guard.py[244-262]
- src/agent_scan/guard.py[1611-1627]
- tests/unit/test_guard.py[1878-1936]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. Hook surface routing lacks tests ✓ Resolved 📘 Rule violation ☼ Reliability
Description
The new get_agent_surface logic in the Bash hook and equivalent PowerShell branches have no
automated tests for their environment-to-header mappings. Existing Copilot script coverage verifies
the endpoint and response only, so changes to AI_AGENT, COPILOT_CLI, or X-Agent-Surface
handling can pass the suite without exercising the installed hooks.
Code

src/agent_scan/hooks/snyk-agent-guard.sh[R86-90]

+get_agent_surface() {
+  if [[ "${AI_AGENT:-}" == "github_copilot_vscode_agent" ]]; then
+    printf '%s' "copilot-vscode"
+  elif [[ "${AI_AGENT:-}" == github_copilot_* || -n "${COPILOT_CLI:-}" ]]; then
+    printf '%s' "copilot"
Relevance

●● Moderate

Coverage gap is plausible, but existing environment-routing tests partially cover the new behavior
and evidence is mixed.

PR-#238

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 4 requires tests that exercise changed code paths. Both installed hook scripts add
independent surface-detection and header-generation branches, while the added Bash integration tests
only assert the Copilot endpoint and response body; repository test searches find no script tests
setting AI_AGENT or COPILOT_CLI.

Rule 4: Every change must include automated tests; bug fixes add a regression test
src/agent_scan/hooks/snyk-agent-guard.sh[86-90]
src/agent_scan/hooks/snyk-agent-guard.ps1[121-138]
tests/unit/test_guard.py[3517-3553]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The installed Bash and PowerShell hooks implement Copilot surface detection without automated coverage of the resulting `X-Agent-Surface` header.

## Issue Context
Python-level tests cover similar logic in `hook_events.py`, but the independently implemented hook scripts can regress without those tests failing. Cover VS Code, another Copilot host or CLI, and a non-Copilot environment.

## Fix Focus Areas
- tests/unit/test_guard.py[3517-3553]
- src/agent_scan/hooks/snyk-agent-guard.sh[86-90]
- src/agent_scan/hooks/snyk-agent-guard.ps1[121-138]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 8 rules
Review mode: 🧠 Deep: This adds substantial cross-platform runtime behavior across installation, managed policy permissions, shell/PowerShell hooks, event attribution, and multiple client code paths, creating many independent opportunities for subtle defects.

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/agent_scan/hooks/snyk-agent-guard.sh
Comment thread src/agent_scan/guard.py
Comment thread src/agent_scan/guard.py
Comment on lines +136 to +145
COPILOT_HOOK_EVENTS = [
"SessionStart",
"UserPromptSubmit",
"PreToolUse",
"PostToolUse",
"PreCompact",
"SubagentStart",
"SubagentStop",
"Stop",
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Copilot users receive hooks that never run 🐞 Bug ≡ Correctness

_prepare_copilot_config writes Claude-style PascalCase event keys from COPILOT_HOOK_EVENTS,
including placing discovery under SessionStart. Copilot dispatches its hook lifecycle events with
its own lower-camel-case names, so the installed forwarding and session-discovery entries are not
invoked despite status detection scanning the same invalid keys.
Agent Prompt
## Issue description
The generated GitHub Copilot hook configuration uses Claude-style event names, so Copilot does not dispatch the installed hook commands or the session-start discovery command.

## Issue Context
`_prepare_copilot_config` directly serializes `COPILOT_HOOK_EVENTS` as JSON keys and adds discovery to `SessionStart`; detection also depends on this list. Replace these with Copilot's supported event names and update the discovery event key consistently.

## Fix Focus Areas
- src/agent_scan/guard.py[136-145]
- src/agent_scan/guard.py[782-788]
- src/agent_scan/guard.py[1208-1209]
- tests/unit/test_guard.py[814-856]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We register the PascalCase hook events which are supported by Github Copilot as well. Documented here: https://docs.github.com/en/copilot/reference/hooks-reference#hook-event-input-payloads

Comment thread src/agent_scan/guard.py Outdated
Comment thread src/agent_scan/guard.py Outdated
@phsommer
phsommer force-pushed the feat/github-copilot-support branch from 670261d to e7233e7 Compare September 10, 2026 08:49

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread src/agent_scan/guard.py
"SubagentStart",
"SubagentStop",
"Stop",
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong Copilot hook event keys

High Severity

COPILOT_HOOK_EVENTS uses Claude-style PascalCase names (SessionStart, PreToolUse, UserPromptSubmit, Stop, etc.), but GitHub Copilot’s hooks schema expects camelCase keys such as sessionStart, preToolUse, userPromptSubmitted, and agentStop. Installed entries land under keys Copilot never reads, so guard hooks would not run while install/detect can still look successful.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e7233e7. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We register the PascalCase hook events which are supported by Github Copilot as well. Documented here: https://docs.github.com/en/copilot/reference/hooks-reference#hook-event-input-payloads

Adds `github-copilot` as a client for `guard install`, `guard uninstall` and `guard discover`.
User-level hooks are written to ~/.copilot/hooks/agent-guard.json, which every
Copilot surface reads — the CLI, the desktop app, and Copilot inside VS Code.

Adds managed hooks, which Copilot calls policy hooks: /etc/github-copilot/
policy.d/agent-guard.json, or C:\ProgramData\GitHub\Copilot\policy.d on Windows,
installed with `--managed` and reported by `guard status`.

Hook events carry an X-Agent-Surface header (`copilot`, or `copilot-vscode`
inside VS Code) resolved from AI_AGENT and COPILOT_CLI.
A Copilot command hook names its script under `bash` (Unix), `powershell`
(Windows), or `command` (cross-platform fallback). Write the
platform-specific key and read all three, consistently across install
filtering, status detection, hook diffs and uninstall.
@phsommer
phsommer force-pushed the feat/github-copilot-support branch from e7233e7 to 4c2a154 Compare September 11, 2026 09:51

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4c2a154. Configure here.

"claude-code": HookClient("cwd", "session_id", "/hidden/agent-monitor/hooks/claude-code"),
"cursor": HookClient("workspace_roots", "conversation_id", "/hidden/agent-monitor/hooks/cursor"),
"codex": HookClient("cwd", "session_id", "/hidden/agent-monitor/hooks/codex"),
"github-copilot": HookClient("cwd", "session_id", "/hidden/agent-monitor/hooks/github-copilot"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot discovery ignores sessionId

Medium Severity

GitHub Copilot hook stdin uses camelCase sessionId, but guard discover for github-copilot looks up session_id via HOOK_CLIENTS. Real SessionStart payloads therefore never supply a session id, so discovery always falls back to the generic session-start-server-discovery marker instead of correlating with the active Copilot session.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4c2a154. Configure here.

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.

1 participant