Skip to content

Test guard - #455

Closed
iamcristi wants to merge 60 commits into
mainfrom
test-guard
Closed

Test guard#455
iamcristi wants to merge 60 commits into
mainfrom
test-guard

Conversation

@iamcristi

Copy link
Copy Markdown
Contributor

No description provided.

_run_install tests that mock _install_hooks were running the real
post-install serversDiscovered send: actual machine discovery plus a
hook-script invocation on a mock path (~1.2s per test, environment-
dependent). Patch _send_servers_discovered_event autouse in the three
affected classes, and pin that clients with only unparseable configs
still emit an empty-servers entry.
_copy_hook_script returned was_updated=False whenever the forwarding
script was already current, so an install that only restored or updated
snyk-agent-guard-discover.sh printed 'hook integration up to date'.
Track the discovery-script write and include it in the returned flag.
- send hook events through the shared platform session (certifi plus the
  CA certs load_extra_ca_certs picks up from the environment), so Agent
  Guard delivery has the same trust posture as the analysis path; retries
  stay opt-in so session-start discovery keeps a single attempt
- request only servers for the install-time discovery event, and honor
  discovery_scope in the well-known-client path so a servers-only request
  no longer walks every skills dir
- let the Windows discovery trampoline hand its stdin to the child like
  the POSIX one does, instead of a blocking ReadToEnd outside the
  try/catch, and cover the script with executing tests for the first time
- catch ValueError in safe_resolve: Path.resolve raises it for a NUL byte,
  and target folders arrive from untrusted hook payloads
- resolve the active subparser from the parsed namespace instead of
  re-walking argv, dropping the hand-rolled option-width heuristic
- drop the two path helpers left with no callers, and repoint the tests
  and docstrings at _discovery_paths_with_ancestors
safe_resolve was extracted in 4cbf8f3 from two try/excepts that were already
written inline, and its single "return the literal path on failure" contract
does not fit all three of its callers. Inline the handling at each site instead
and delete the helper.

_scans_own_home goes back to failing closed, as it did before the extraction:
routing it through safe_resolve let each side degrade to its literal path and
keep comparing, so it could report own-home where it previously did not. That
gate gets to decide whether this process's CLAUDE_CONFIG_DIR / VSCODE_PORTABLE
apply to the home being scanned, so an unresolvable path must not count as
proof. It still catches more than OSError, since everything now maps to the
same safe answer and letting anything escape would abort discovery outright.

The alias filter in _all_discovery_folders and the target-folder dedup in
discover_clients_to_inspect keep the literal-path fallback unchanged, so
unresolvable paths stay distinct rather than collapsing into one another. The
ValueError arm stays earned on the pipelines side, where the paths come
straight from hook payloads and a NUL byte raises it.
guard install accepted --control-identifier as an alias for --machine-id,
which made that option string mean control_identifier on scan/inspect/evo
and machine_id here. explicitly_provided_dests keys a flat option-string to
dest map, so the collision resolved to whichever action the walk visited
last -- scan --config-file c.yaml --control-server URL --control-identifier
ID stopped registering control_identifier as explicit and let the config
file's control_servers override the command line.

Keep --machine-id only. --control-identifier is already deprecated in
favour of it, and guard install could never warn about the spelling because
its dest is machine_id. explicitly_provided_dests goes back to walking
every action, and a test over the real parser now asserts no option string
maps to two dests.
The `--` break and its test are unrelated to guard server discovery. The
argv scan mistaking a post-`--` positional for a flag predates this branch,
so the fix belongs on its own change against main rather than riding along
here.
Reverts wording churn and an invariant note that duplicated the enforcing
test's own docstring. The function now matches main verbatim.
The install failure path snapshotted each hook script's bytes and mode
before the copy and wrote them back when the test event failed, covering
scripts that merely got overwritten. main only deletes a script the install
had just created, so this widened the abort contract well past the PR's
scope.

Back to main's rule, applied to both scripts: a newly created forwarder or
discovery trampoline is unlinked on abort, a pre-existing one is left alone.
Removes _HookScriptBackup, _snapshot_hook_script, _restore_hook_script and
_hook_script_path, whose only purpose was the snapshot.
The Python delivery path claimed to be snyk-agent-guard.sh/.ps1, so backend
telemetry could not tell shell-script traffic apart from in-process sends.
Identify the actual sender instead.
_agent_scan_bin guessed the CLI location from sys.frozen, argv[0] and the
interpreter's sibling console script when AGENT_SCAN_BIN was unset. The
caller supplies the path instead, so the guessing is gone: the value is read
from the environment at install time and baked into the hook command as
before, and an unset variable leaves it out so the trampolines fall back to
PATH.
The four command builders were a platform x variant cross-product, each
re-implementing the same platform logic, and _invoke_hook_script described the
same contract a fifth time as an argv list plus env dict with no shared code.
Adding a field meant four coordinated edits and a fifth in a subprocess path;
tenant_id had already drifted, accepted by all four builders but emitted by one.

A single _HookInvocation now holds the raw values and three renderers turn it
into a POSIX shell string, a PowerShell string, or an argv/env pair. Skipping
empty optional fields is what lets one code path reproduce both variants, so the
strings written into agent config files are unchanged: verified byte-identical
across 5832 input combinations covering both platforms and embedded apostrophes.
That matters because the emitted command is parsed back by _DETECTION_RE,
_extract_env_from_cmd and the push-key redaction patterns, all of which depend on
argument order and single-quoting.

Two per-variant quirks stay encoded in the spec rather than normalised: the main
forwarder leaves --client unquoted where discovery quotes it, and TENANT_ID is
emitted only on the POSIX main path, where _parse_command_info reads it back as
install-time metadata.

_build_discover_hook_command_powershell is gone. Routing the Windows discovery
path straight at the shared renderer left it unreferenced, and its output is
reproduced exactly by the surviving path. _build_hook_command_powershell stays,
since the live subprocess round-trip calls it directly on any platform.

Tests pin the exact output of every builder per platform, so a reordering that
_DETECTION_RE still happens to match can no longer slip through, and cover the
discovery-shaped argv on both platforms.
An unset variable leaves the value out of the hook command so the trampolines
fall back to PATH, and a bare executable name resolves through PATH even when
the variable is set, so the trampoline does not bypass the lookup.
_uninstall_hooks took a missing_label argument used only in the
"Nothing to uninstall" message. path.name yields the same string for
every caller and is accurate for managed and --file paths, where the
hardcoded label named a file that was never checked.
The posix _render_argv tests force IS_WINDOWS=False but built the script
path with Path(), whose flavour follows the host. On the Windows runner
that stringifies with backslashes, so the hardcoded POSIX expectation
failed. Compare against str(script_path) instead, matching the sibling
Windows test.
@iamcristi
iamcristi requested a review from a team as a code owner August 28, 2026 07:27
@iamcristi iamcristi closed this Aug 28, 2026
@qodo-merge-etso

Copy link
Copy Markdown

PR Summary by Qodo

Report MCP server discovery through Agent Guard hooks

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Report discovered MCP servers after Guard installation and at agent session start.
• Discover session project folders with scoped, timeout-bounded cross-client scanning.
• Require machine identity and harden hook commands, payload delivery, and configuration handling.
Diagram

graph TD
  Install["Guard install"] --> Config["Agent hooks"] --> Session["Session start"] --> Discover["Guard discover"] --> Pipeline["Discovery pipeline"] --> Sender["Hook event sender"] --> Monitor["Agent Monitor"]
  Discover --> Targets["Project folders"] --> Pipeline
Loading
High-Level Assessment

The approach is appropriate: lightweight platform-specific trampolines delegate discovery to the existing Python pipeline, while direct delivery reuses the backend HTTP/TLS session. Invoking discovery through the forwarding script or running a full scan was considered implicitly, but would add argument-size, latency, and unnecessary skills-scanning costs.

Files changed (27) +5819 / -532

Enhancement (13) +1178 / -386
__init__.pyPass explicit targets into discoverers +8/-7

Pass explicit targets into discoverers

• Exports discovery scopes and threads request-specific target folders into every registered agent discoverer.

src/agent_scan/agents/init.py

base.pyAdd scoped, target-aware agent discovery +75/-23

Add scoped, target-aware agent discovery

• Introduces server, skills, and combined discovery scopes. Merges explicit targets with persisted projects and their ancestors using resilient, cached path handling.

src/agent_scan/agents/base.py

claude_code.pyScan Claude explicit target folders +3/-3

Scan Claude explicit target folders

• Uses the unified discovery-root list for project MCP servers and skills, including session-provided targets.

src/agent_scan/agents/claude_code.py

codex.pyScan Codex explicit target folders +2/-2

Scan Codex explicit target folders

• Extends project MCP and skills discovery to request-specific targets and their ancestors.

src/agent_scan/agents/codex.py

opencode.pyScan OpenCode explicit target folders +5/-5

Scan OpenCode explicit target folders

• Applies unified discovery roots to project configs, MCP servers, and skills. Relative configured skill paths can now anchor at explicit targets.

src/agent_scan/agents/opencode.py

base.pyScan VS Code family target folders +6/-6

Scan VS Code family target folders

• Routes workspace MCP, skills, settings, agent config, and devcontainer discovery through unified project and target roots.

src/agent_scan/agents/vscode/base.py

cli.pyAdd Guard discover CLI options +38/-0

Add Guard discover CLI options

• Adds 'guard discover' with client, URL, and scope options, plus machine identity for installation. Standard scans now select server-only or combined discovery scope appropriately.

src/agent_scan/cli.py

guard.pyOrchestrate Guard server discovery events +807/-316

Orchestrate Guard server discovery events

• Installs cross-platform session-start discovery hooks, parses client payloads, runs bounded discovery, and sends redacted server events. Adds machine identity propagation, robust command/TOML escaping, shared hook configuration handling, and accurate discovery-script update reporting.

src/agent_scan/guard.py

hook_events.pyDeliver hook events directly to Agent Monitor +100/-0

Deliver hook events directly to Agent Monitor

• Adds client-specific endpoints and payload fields, machine identity headers, base64 wire encoding, shared TLS configuration, timeouts, and transport-only retries.

src/agent_scan/hook_events.py

snyk-agent-guard-discover.ps1Add Windows session discovery trampoline +54/-0

Add Windows session discovery trampoline

• Invokes 'guard discover' with inherited hook input and configured environment on Windows. Discovery failures remain fire-and-forget and do not disrupt agent sessions.

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

snyk-agent-guard-discover.shAdd POSIX session discovery trampoline +10/-0

Add POSIX session discovery trampoline

• Invokes the configured Agent Scan command for best-effort session discovery while preserving hook arguments and standard input.

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

inspect.pyGate inspection work by discovery scope +39/-19

Gate inspection work by discovery scope

• Skips MCP or skills filesystem work when excluded by the requested scope while preserving client detection.

src/agent_scan/inspect.py

pipelines.pyThread scopes and targets through discovery +31/-5

Thread scopes and targets through discovery

• Adds discovery scope and target folders to inspection arguments. Validates, deduplicates, and forwards accessible targets without allowing malformed paths to abort discovery.

src/agent_scan/pipelines.py

Bug fix (3) +103 / -10
snyk-agent-guard.ps1Send explicit Windows machine identifiers +11/-2

Send explicit Windows machine identifiers

• Requires a machine identifier and places it in the PowerShell hook event's 'X-User' header.

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

snyk-agent-guard.shHarden POSIX hook event submission +4/-3

Harden POSIX hook event submission

• Requires explicit machine identity and streams encoded payloads to curl through standard input, avoiding command-line size limits.

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

utils.pyAdd TOML escaping and safer path shortening +88/-5

Add TOML escaping and safer path shortening

• Adds reversible TOML basic-string escaping for generated Codex configuration. Corrects home-relative path detection across Windows casing, separators, Unicode, and similarly prefixed paths.

src/agent_scan/utils.py

Refactor (1) +14 / -9
verify_api.pyShare backend HTTP session configuration +14/-9

Share backend HTTP session configuration

• Exposes the backend session factory and retryable transport exceptions so analysis and hook delivery share TLS, proxy, and CA behavior.

src/agent_scan/verify_api.py

Tests (8) +4498 / -123
conftest.pyIsolate discovery command test state +13/-0

Isolate discovery command test state

• Clears 'AGENT_SCAN_COMMAND' by default and provides the environment's Agent Scan executable for opt-in integration tests.

tests/conftest.py

test_guard_install.pyExercise Guard discovery end to end +158/-6

Exercise Guard discovery end to end

• Verifies installation and session-start discovery events, hook configuration shapes, machine identity, target payloads, and Claude, Cursor, and Codex behavior.

tests/e2e/test_guard_install.py

test_agent_discovery.pyCover scoped and explicit-target discovery +514/-16

Cover scoped and explicit-target discovery

• Tests discovery scopes, target/project merging, ancestor traversal, malformed paths, caching, and target-based MCP and skills discovery across agent families.

tests/unit/test_agent_discovery.py

test_cli_config_file.pyGuard against cross-subparser flag collisions +33/-8

Guard against cross-subparser flag collisions

• Adds parser-wide assertions that reused option strings map consistently and preserves explicit-flag detection coverage.

tests/unit/test_cli_config_file.py

test_guard.pyComprehensively test Guard discovery orchestration +3462/-93

Comprehensively test Guard discovery orchestration

• Covers hook rendering, installation, uninstallation, script lifecycle, machine identity, TOML safety, timeout behavior, event serialization, CLI parsing, and cross-platform execution. Keeps mocked installation tests hermetic from real discovery sends.

tests/unit/test_guard.py

test_hook_events.pyTest direct hook event delivery +188/-0

Test direct hook event delivery

• Validates endpoints, headers, encoding, shared sessions, URL normalization, timeout configuration, failures, and retry policy.

tests/unit/test_hook_events.py

test_inspect.pyTest discovery scope gating +82/-0

Test discovery scope gating

• Confirms each scope populates only requested data and server-only discovery avoids skills filesystem work.

tests/unit/test_inspect.py

test_utils.pyTest TOML and path utility edge cases +48/-0

Test TOML and path utility edge cases

• Covers TOML escape round trips and Windows-safe home-relative path normalization, including mixed separators and Unicode case behavior.

tests/unit/test_utils.py

Documentation (2) +26 / -4
cli-reference.mdDocument Guard discovery and machine identity +25/-3

Document Guard discovery and machine identity

• Documents the internal 'guard discover' command, session-start discovery events, machine identifiers, and 'AGENT_SCAN_COMMAND'. Updates installation examples for the new identity input.

docs/cli-reference.md

antigravity.pyAlign Antigravity discovery documentation +1/-1

Align Antigravity discovery documentation

• Updates the project-root documentation to reference the target-aware discovery path.

src/agent_scan/agents/vscode/antigravity.py

@qodo-merge-etso

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (4) 📜 Skill insights (0)

Grey Divider


Action required

1. Failed upgrade breaks hooks 🐞 Bug ☼ Reliability
Description
_install_hooks overwrites an existing forwarding script before the test event, but on test failure
only deletes newly created scripts and never restores overwritten content. During an upgrade from
the previous hook, the retained config does not supply MACHINE_ID while the newly copied script
now requires it, so a transient test-event failure leaves every existing Guard hook failing.
Code

src/agent_scan/guard.py[R530-531]

+    main_script = _copy_hook_script(_forwarder_script_path(config_path))
+    discover_script = _copy_hook_script(discover_script_path) if install_discovery else None
Relevance

●●● Strong

Close Guard precedents accept rollback and cleanup when installation fails, including preserving
state across partial upgrades.

PR-#269
PR-#388

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The install path copies both scripts before _send_test_event, and its failure cleanup only unlinks
scripts that did not previously exist; _copy_hook_script writes new bytes over an existing
destination. The new forwarding scripts reject invocations without MACHINE_ID, whereas
configuration is not updated until after the test succeeds, proving an interrupted upgrade can
strand the old command with the incompatible new script.

src/agent_scan/guard.py[527-587]
src/agent_scan/guard.py[1766-1790]
src/agent_scan/hooks/snyk-agent-guard.sh[88-91]
src/agent_scan/hooks/snyk-agent-guard.ps1[49-56]

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

## Issue description
A failed Guard upgrade leaves overwritten hook scripts in place while retaining the old config, which is incompatible with the new `MACHINE_ID` requirement.

## Issue Context
Capture existing script contents or defer replacement until validation succeeds. On every failure path, restore pre-existing forwarding and discovery scripts, while still removing scripts that were newly created.

## Fix Focus Areas
- src/agent_scan/guard.py[527-585]
- src/agent_scan/hooks/snyk-agent-guard.sh[88-91]
- src/agent_scan/hooks/snyk-agent-guard.ps1[49-56]

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



Remediation recommended

2. Skills scope drops results 🐞 Bug ≡ Correctness
Description
The new guard discover --scope skills option disables server discovery and discovers skills, but
_servers_discovered_entries serializes only mcp_configs and always leaves skills empty. The
command therefore reports success while sending none of the data requested by the advertised
skills or skill-bearing all scopes.
Code

src/agent_scan/cli.py[R995-999]

+    guard_discover_parser.add_argument(
+        "--scope",
+        choices=[scope.value for scope in DiscoveryScope],
+        default=DiscoveryScope.ALL.value,
+        help="Discovery data to collect (default: all)",
Relevance

●●● Strong

Accepted discovery fixes consistently preserve and report newly supported skills data; this is a
direct advertised-scope correctness gap.

PR-#304
PR-#321

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The CLI exposes every DiscoveryScope, including skills and all, and passes that scope into
discovery. AgentDiscoverer.discover fills skills_dirs for those scopes, but the event serializer
iterates only client.mcp_configs and constructs InspectedPath without skills; the normal
inspection path demonstrates that skills_dirs must be converted into InspectedSkill values to
populate the wire model.

src/agent_scan/cli.py[995-999]
src/agent_scan/agents/base.py[231-240]
src/agent_scan/guard.py[1142-1177]
src/agent_scan/guard.py[1180-1201]
src/agent_scan/inspect.py[437-479]
src/agent_scan/models/inspect.py[50-71]

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

## Issue description
`guard discover` accepts `skills` and `all` scopes but drops discovered `skills_dirs` when constructing the event payload.

## Issue Context
Use the existing skill inspection/serialization path so skill results and skill discovery errors populate each `InspectedPath.skills`, or restrict the command to the only supported `servers` scope if skill delivery is not part of the event contract.

## Fix Focus Areas
- src/agent_scan/cli.py[995-999]
- src/agent_scan/guard.py[1142-1201]
- src/agent_scan/guard.py[1304-1339]

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



Informational

3. Hook payload errors swallowed 📘 Rule violation ☼ Reliability
Description
_run_discover catches every payload parsing exception and silently continues with empty discovery
context. Malformed hook input can therefore produce an apparently valid event with missing target
folders and session identity instead of an explicit failure.
Code

src/agent_scan/guard.py[R402-403]

+    except Exception:
+        pass
Relevance

●●● Strong

Recent reviews accept explicit handling of malformed inputs and reject silently allowing parsing
failures to escape or disappear.

PR-#433
PR-#214

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 6 requires specific exception handling and prohibits swallowed errors. The changed
parser catches all exceptions with pass, after which the function sends an event using the default
empty context.

Rule 6: Handle errors explicitly; don't swallow; clean up; don't leak internals
src/agent_scan/guard.py[389-415]

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

## Issue description
Hook payload parsing failures are swallowed, so discovery continues with incomplete context.

## Issue Context
Differentiate absent optional input from malformed or unreadable hook input, and report a safe, actionable error without exposing exception internals.

## Fix Focus Areas
- src/agent_scan/guard.py[362-415]

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


4. Exception details exposed to users 📘 Rule violation ☼ Reliability
Description
Hook delivery and discovery failures are converted with str(error) and printed directly to CLI
users. Transport, subprocess, filesystem, or runtime exceptions may reveal internal paths, host
details, or other implementation information instead of a generic message.
Code

src/agent_scan/hook_events.py[R48-49]

+        except Exception as error:
+            return False, str(error)
Relevance

●● Moderate

Privacy-related logging concerns have often been rejected, but this specifically exposes arbitrary
exception details and lacks an exact precedent.

PR-#320
PR-#341

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 6 explicitly prohibits exposing str(e) in client responses or messages. The new
hook delivery code returns str(error), and changed guard call sites print the returned detail
directly; discovery also prints e directly.

Rule 6: Handle errors explicitly; don't swallow; clean up; don't leak internals
src/agent_scan/hook_events.py[44-50]
src/agent_scan/guard.py[1289-1300]
src/agent_scan/guard.py[1324-1345]

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

## Issue description
Raw exception strings flow into user-visible guard failure messages.

## Issue Context
Keep detailed exceptions in internal logs with safe context, while returning stable generic messages to CLI callers.

## Fix Focus Areas
- src/agent_scan/hook_events.py[32-50]
- src/agent_scan/guard.py[1228-1243]
- src/agent_scan/guard.py[1289-1301]
- src/agent_scan/guard.py[1324-1346]

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


5. Discovery worker never drains 📘 Rule violation ☼ Reliability
Description
_run_with_timeout launches discovery on a daemon thread and deliberately abandons it after the
deadline. Timed-out filesystem and subprocess work can continue in the background without
cancellation or draining until the process exits.
Code

src/agent_scan/guard.py[R352-356]

+    thread = threading.Thread(target=run, daemon=True)
+    thread.start()
+    thread.join(timeout)
+    if thread.is_alive():
+        raise TimeoutError(f"timed out after {timeout:g}s")
Relevance

●● Moderate

Timeout-related hanging work is accepted historically, but no close precedent establishes required
thread draining or cancellation semantics.

PR-#320
PR-#442

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 36 requires background work to finish or be cleaned up during shutdown. The
helper's documentation says the daemon worker is deliberately abandoned, and the timeout branch
raises while that worker remains alive.

Rule 36: Implement graceful shutdown & connection draining
src/agent_scan/guard.py[333-359]
src/agent_scan/guard.py[1180-1201]

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

## Issue description
Timed-out discovery workers are abandoned as live daemon threads.

## Issue Context
Use cancellable operations or managed worker processes and ensure timed-out work is terminated and joined before shutdown.

## Fix Focus Areas
- src/agent_scan/guard.py[333-359]
- src/agent_scan/guard.py[1180-1201]

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


View low (1)
6. Hook timeout remains magic 📘 Rule violation ☼ Reliability
Description
_invoke_hook_script hard-codes a 15-second subprocess timeout even though related timeout values
are named constants. This duplicates the hook request timeout value and obscures the operational
meaning of the limit.
Code

src/agent_scan/guard.py[1234]

+            timeout=15,
Relevance

● Weak

Recent, closely matching timeout-magic-number findings in this repository were explicitly rejected,
including identical named-constant rationale.

PR-#418
PR-#279

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 40 requires unexplained numeric limits to use named constants. The new subprocess
path uses timeout=15, while the same PR defines named constants for stdin, discovery, and direct
hook-request timeouts.

Rule 40: Replace magic numbers with named constants
src/agent_scan/guard.py[58-66]
src/agent_scan/guard.py[1228-1236]
src/agent_scan/hook_events.py[24-34]

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 hook subprocess timeout is an unexplained numeric literal.

## Issue Context
Introduce a domain-specific constant and use it at the subprocess call, centralizing the timeout if it intentionally matches the direct hook request timeout.

## Fix Focus Areas
- src/agent_scan/guard.py[58-66]
- src/agent_scan/guard.py[1204-1243]

ⓘ 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

Grey Divider

Tip of the day
💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/agent_scan/guard.py
Comment on lines +402 to +403
except Exception:
pass

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

1. Hook payload errors swallowed 📘 Rule violation ☼ Reliability

_run_discover catches every payload parsing exception and silently continues with empty discovery
context. Malformed hook input can therefore produce an apparently valid event with missing target
folders and session identity instead of an explicit failure.
Agent Prompt
## Issue description
Hook payload parsing failures are swallowed, so discovery continues with incomplete context.

## Issue Context
Differentiate absent optional input from malformed or unreadable hook input, and report a safe, actionable error without exposing exception internals.

## Fix Focus Areas
- src/agent_scan/guard.py[362-415]

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

Comment on lines +48 to +49
except Exception as error:
return False, str(error)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

2. Exception details exposed to users 📘 Rule violation ☼ Reliability

Hook delivery and discovery failures are converted with str(error) and printed directly to CLI
users. Transport, subprocess, filesystem, or runtime exceptions may reveal internal paths, host
details, or other implementation information instead of a generic message.
Agent Prompt
## Issue description
Raw exception strings flow into user-visible guard failure messages.

## Issue Context
Keep detailed exceptions in internal logs with safe context, while returning stable generic messages to CLI callers.

## Fix Focus Areas
- src/agent_scan/hook_events.py[32-50]
- src/agent_scan/guard.py[1228-1243]
- src/agent_scan/guard.py[1289-1301]
- src/agent_scan/guard.py[1324-1346]

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

Comment thread src/agent_scan/guard.py
Comment on lines +352 to +356
thread = threading.Thread(target=run, daemon=True)
thread.start()
thread.join(timeout)
if thread.is_alive():
raise TimeoutError(f"timed out after {timeout:g}s")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

3. Discovery worker never drains 📘 Rule violation ☼ Reliability

_run_with_timeout launches discovery on a daemon thread and deliberately abandons it after the
deadline. Timed-out filesystem and subprocess work can continue in the background without
cancellation or draining until the process exits.
Agent Prompt
## Issue description
Timed-out discovery workers are abandoned as live daemon threads.

## Issue Context
Use cancellable operations or managed worker processes and ensure timed-out work is terminated and joined before shutdown.

## Fix Focus Areas
- src/agent_scan/guard.py[333-359]
- src/agent_scan/guard.py[1180-1201]

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

Comment thread src/agent_scan/guard.py
Comment on lines +530 to +531
main_script = _copy_hook_script(_forwarder_script_path(config_path))
discover_script = _copy_hook_script(discover_script_path) if install_discovery else None

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

5. Failed upgrade breaks hooks 🐞 Bug ☼ Reliability

_install_hooks overwrites an existing forwarding script before the test event, but on test failure
only deletes newly created scripts and never restores overwritten content. During an upgrade from
the previous hook, the retained config does not supply MACHINE_ID while the newly copied script
now requires it, so a transient test-event failure leaves every existing Guard hook failing.
Agent Prompt
## Issue description
A failed Guard upgrade leaves overwritten hook scripts in place while retaining the old config, which is incompatible with the new `MACHINE_ID` requirement.

## Issue Context
Capture existing script contents or defer replacement until validation succeeds. On every failure path, restore pre-existing forwarding and discovery scripts, while still removing scripts that were newly created.

## Fix Focus Areas
- src/agent_scan/guard.py[527-585]
- src/agent_scan/hooks/snyk-agent-guard.sh[88-91]
- src/agent_scan/hooks/snyk-agent-guard.ps1[49-56]

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

Comment thread src/agent_scan/cli.py
Comment on lines +995 to +999
guard_discover_parser.add_argument(
"--scope",
choices=[scope.value for scope in DiscoveryScope],
default=DiscoveryScope.ALL.value,
help="Discovery data to collect (default: all)",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

6. Skills scope drops results 🐞 Bug ≡ Correctness

The new guard discover --scope skills option disables server discovery and discovers skills, but
_servers_discovered_entries serializes only mcp_configs and always leaves skills empty. The
command therefore reports success while sending none of the data requested by the advertised
skills or skill-bearing all scopes.
Agent Prompt
## Issue description
`guard discover` accepts `skills` and `all` scopes but drops discovered `skills_dirs` when constructing the event payload.

## Issue Context
Use the existing skill inspection/serialization path so skill results and skill discovery errors populate each `InspectedPath.skills`, or restrict the command to the only supported `servers` scope if skill delivery is not part of the event contract.

## Fix Focus Areas
- src/agent_scan/cli.py[995-999]
- src/agent_scan/guard.py[1142-1201]
- src/agent_scan/guard.py[1304-1339]

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

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