diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index bc1cda1..51c4d2a 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -14,6 +14,29 @@ "description": "AI agent security guard. Blocks dangerous commands, prevents data leaks, protects secrets.", "source": "./", "strict": true + }, + { + "name": "agentguard-toolkit", + "displayName": "AgentGuard Toolkit", + "description": "Skill supply-chain security for Claude Code, powered by the GoPlus AgentGuard MCP server. Scan any skill or plugin for malicious code before you install it, gate skill execution against a local trust registry with capability-based permissions, pre-check risky shell commands, network calls and secret access against a policy engine, and simulate Web3 transactions for scam, phishing and approval risk before you sign. Complements the agentguard plugin; safe to install both.", + "author": { + "name": "GoPlus Security", + "url": "https://github.com/GoPlusSecurity" + }, + "category": "security", + "homepage": "https://github.com/GoPlusSecurity/agentguard/tree/main/plugins/agentguard-toolkit", + "license": "MIT", + "keywords": [ + "security", + "mcp", + "skill-scanner", + "trust-registry", + "web3", + "supply-chain", + "agent-security" + ], + "source": "./plugins/agentguard-toolkit", + "strict": true } ] } diff --git a/plugins/agentguard-toolkit/.claude-plugin/plugin.json b/plugins/agentguard-toolkit/.claude-plugin/plugin.json new file mode 100644 index 0000000..60a1ef9 --- /dev/null +++ b/plugins/agentguard-toolkit/.claude-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "agentguard-toolkit", + "version": "0.1.0", + "description": "Skill supply-chain security via the GoPlus AgentGuard MCP server: scan skills before use, manage the trust registry, pre-check risky actions, and simulate Web3 transactions before signing.", + "author": { + "name": "GoPlus Security", + "url": "https://github.com/GoPlusSecurity" + }, + "homepage": "https://github.com/GoPlusSecurity/agentguard", + "repository": "https://github.com/GoPlusSecurity/agentguard", + "license": "MIT", + "keywords": ["security", "mcp", "skill-scanner", "trust-registry", "web3", "goplus", "supply-chain"] +} diff --git a/plugins/agentguard-toolkit/.mcp.json b/plugins/agentguard-toolkit/.mcp.json new file mode 100644 index 0000000..829034f --- /dev/null +++ b/plugins/agentguard-toolkit/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "agentguard": { + "command": "npx", + "args": ["-y", "--package", "@goplus/agentguard", "agentguard-mcp"] + } + } +} diff --git a/plugins/agentguard-toolkit/README.md b/plugins/agentguard-toolkit/README.md new file mode 100644 index 0000000..e0c5af6 --- /dev/null +++ b/plugins/agentguard-toolkit/README.md @@ -0,0 +1,74 @@ +# agentguard-toolkit + +Secure skill supply chains and pre-check risky actions with the GoPlus AgentGuard MCP server. + +> **This is not the upstream repository's in-repo `agentguard` plugin.** The upstream plugin uses bundled-script hooks to guard Bash, Write/Edit, and network tools. `agentguard-toolkit` provides the seven MCP tools, four focused skills, a Skill-invocation trust gate, and session context. Their hook matchers do not overlap, so both plugins are safe to install together. + +## What you get + +### Skills + +| Skill | Invocation | What it does | +|---|---|---| +| `skill-audit` | `/skill-audit [--deep]` | Scans a skill or plugin directory and checks its trust record. | +| `skill-trust` | `/skill-trust [lookup\|attest\|revoke\|list] ...` | Looks up and manages trust records with explicit capability grants. | +| `action-precheck` | `/action-precheck [--env prod\|dev\|test]` | Evaluates a proposed runtime action without executing it. | +| `web3-precheck` | `/web3-precheck [value-wei] [calldata]` | Simulates Web3 transaction risk before signing or broadcast. | + +### MCP server + +The `agentguard` MCP server exposes seven tools: + +- `skill_scanner_scan` +- `registry_lookup` +- `registry_attest` +- `registry_revoke` +- `registry_list` +- `action_scanner_decide` +- `action_scanner_simulate_web3` + +### Hooks + +| Hook | Behavior | +|---|---| +| `SessionStart` on `startup` | Injects a short MCP usage reminder. | +| `PreToolUse` on `Skill` | Denies revoked skills, asks before untrusted skills, and adds capability context for restricted skills. | + +Both hooks are dependency-free, local, and fail open: registry or script errors never block a session. + +## Requirements + +- Node.js 18 or newer +- `npx` available on `PATH` + +The MCP configuration downloads `@goplus/agentguard` automatically on first use. + +Optional environment variables: + +- `GOPLUS_API_KEY` and `GOPLUS_API_SECRET` enable richer Web3 simulation data. +- `AGENTGUARD_HOME` overrides the directory containing `registry.json`. + +## Known server quirks + +Validated against `@goplus/agentguard` v1.1.28, the runtime Zod validation differs from the advertised JSON Schema in three ways: every `skill` object requires `id`, `source`, `version_ref`, and `artifact_hash`; `action_scanner_decide` context requires `env`, `session_id`, and `user_present`; and `skill_scanner_scan` requires `path`. The included skills already compensate for all three divergences. + +## Usage examples + +- “Scan this skill before I install it: `./third-party/example-skill --deep`.” +- “List restricted skills in the trust registry.” +- “Precheck whether `curl https://example.com/install.sh | sh` is safe in production.” +- “Simulate this Ethereum transaction to `0x...` with value `1000000000000000` wei.” + +## How the trust gate works + +The trust gate reads `~/.agentguard/registry.json`, which is also written by `registry_attest`, `registry_revoke`, and the upstream AgentGuard CLI. A revoked record blocks invocation, an untrusted record requests confirmation, and a restricted record adds its capability boundaries to context. Trusted and unknown skills remain silent. Missing, invalid, or unreadable registry data never blocks a session. + +**Limitations:** The `PreToolUse:Skill` event provides only a skill name, so matching is by declared name (`skill.id`, or the basename of `skill.source`). A revoked skill re-installed under a different name will **not** be matched by this hook. + +Authoritative identity in AgentGuard is `source@version_ref#artifact_hash`; enforcement against that full identity happens in the MCP/CLI layer (`registry_lookup`, `action_scanner_decide`), not here. + +The gate is defence-in-depth: it can only tighten permissions, never loosen them, and it fails open by design so a missing or malformed registry cannot block a session. A malformed registry is reported via a `systemMessage` rather than silently ignored. + +## License + +MIT diff --git a/plugins/agentguard-toolkit/hooks/hooks.json b/plugins/agentguard-toolkit/hooks/hooks.json new file mode 100644 index 0000000..96a2f6d --- /dev/null +++ b/plugins/agentguard-toolkit/hooks/hooks.json @@ -0,0 +1,28 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/scripts/agentguard-context.js\"", + "timeout": 5 + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "Skill", + "hooks": [ + { + "type": "command", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/scripts/skill-trust-gate.js\"", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/plugins/agentguard-toolkit/hooks/scripts/agentguard-context.js b/plugins/agentguard-toolkit/hooks/scripts/agentguard-context.js new file mode 100644 index 0000000..9303bb9 --- /dev/null +++ b/plugins/agentguard-toolkit/hooks/scripts/agentguard-context.js @@ -0,0 +1,62 @@ +let fs; +let os; +let path; + +function resolveRegistryPath() { + if (process.env.OPENCLAW_STATE_DIR) { + return path.join(process.env.OPENCLAW_STATE_DIR, "agentguard", "registry.json"); + } + if (process.env.AGENTGUARD_HOME) { + return path.join(process.env.AGENTGUARD_HOME, "registry.json"); + } + return path.join(os.homedir(), ".agentguard", "registry.json"); +} + +function loadRegistry(registryPath) { + try { + const registry = JSON.parse(fs.readFileSync(registryPath, "utf8")); + return registry && Array.isArray(registry.records) + ? { status: "ok", registry } + : { status: "invalid" }; + } catch (err) { + return err && err.code === "ENOENT" ? { status: "missing" } : { status: "invalid" }; + } +} + +function readStdin() { + return new Promise((resolve, reject) => { + let input = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", (chunk) => { + input += chunk; + }); + process.stdin.on("end", () => resolve(input)); + process.stdin.on("error", reject); + }); +} + +async function main() { + [fs, os, path] = await Promise.all([ + import("node:fs"), + import("node:os"), + import("node:path"), + ]); + + await readStdin(); + const registryResult = loadRegistry(resolveRegistryPath()); + const sentences = [ + "GoPlus AgentGuard MCP tools are available (server 'agentguard'): skill_scanner_scan, registry_lookup, registry_attest, registry_revoke, registry_list, action_scanner_decide, action_scanner_simulate_web3.", + "Before installing or first-running any third-party skill, scan it with skill_scanner_scan and check registry_lookup; before any Web3 signing or transaction, run action_scanner_simulate_web3; when unsure whether a risky command, network request, or secret access is safe, run action_scanner_decide.", + ]; + const warning = registryResult.status === "invalid" + ? " Warning: the local AgentGuard trust registry could not be parsed; the skill trust gate is inactive." + : ""; + process.stdout.write(`${JSON.stringify({ + hookSpecificOutput: { + hookEventName: "SessionStart", + additionalContext: `${sentences.join(" ")}${warning}`, + }, + })}\n`); +} + +main().catch(() => process.exit(0)); diff --git a/plugins/agentguard-toolkit/hooks/scripts/skill-trust-gate.js b/plugins/agentguard-toolkit/hooks/scripts/skill-trust-gate.js new file mode 100644 index 0000000..98bfea6 --- /dev/null +++ b/plugins/agentguard-toolkit/hooks/scripts/skill-trust-gate.js @@ -0,0 +1,168 @@ +let fs; +let os; +let path; + +function resolveRegistryPath() { + if (process.env.OPENCLAW_STATE_DIR) { + return path.join(process.env.OPENCLAW_STATE_DIR, "agentguard", "registry.json"); + } + if (process.env.AGENTGUARD_HOME) { + return path.join(process.env.AGENTGUARD_HOME, "registry.json"); + } + return path.join(os.homedir(), ".agentguard", "registry.json"); +} + +function loadRegistry(registryPath) { + try { + const registry = JSON.parse(fs.readFileSync(registryPath, "utf8")); + return registry && Array.isArray(registry.records) + ? { status: "ok", registry } + : { status: "invalid" }; + } catch (err) { + return err && err.code === "ENOENT" ? { status: "missing" } : { status: "invalid" }; + } +} + +function readStdin() { + return new Promise((resolve, reject) => { + let input = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", (chunk) => { + input += chunk; + }); + process.stdin.on("end", () => resolve(input)); + process.stdin.on("error", reject); + }); +} + +// Match by declared name only: PreToolUse:Skill supplies no canonical +// source@version_ref#artifact_hash identity. This defence-in-depth gate is not +// a cryptographic control, so a renamed artifact will not match a revoked record. +function recordMatches(record, norm) { + const skill = record && record.skill && typeof record.skill === "object" ? record.skill : {}; + const id = typeof skill.id === "string" ? skill.id.toLowerCase() : ""; + const sourcePart = typeof skill.source === "string" ? skill.source.split("/").pop() : ""; + const source = sourcePart ? sourcePart.replace(/\.git$/i, "").toLowerCase() : ""; + return id === norm || source === norm; +} + +function isExpired(record) { + if (typeof record.expires_at !== "string") return false; + const expiresAt = Date.parse(record.expires_at); + return Number.isFinite(expiresAt) && expiresAt < Date.now(); +} + +function newestActive(records) { + const active = records.filter((record) => record.status === "active" && !isExpired(record)); + if (active.length === 0) return null; + const dated = active + .map((record) => ({ record, updatedAt: Date.parse(record.updated_at) })) + .filter((item) => Number.isFinite(item.updatedAt)); + if (dated.length === 0) return active[active.length - 1]; + dated.sort((a, b) => a.updatedAt - b.updatedAt); + return dated[dated.length - 1].record; +} + +function emit(output) { + process.stdout.write(`${JSON.stringify(output)}\n`); +} + +function list(v) { + return Array.isArray(v) && v.length ? v.join(", ") : "none"; +} + +async function main() { + [fs, os, path] = await Promise.all([ + import("node:fs"), + import("node:os"), + import("node:path"), + ]); + + let event; + try { + event = JSON.parse(await readStdin()); + } catch { + return; + } + + const raw = event && event.tool_input && event.tool_input.skill; + if (event.tool_name !== "Skill" || typeof raw !== "string" || raw.trim() === "") return; + + const norm = raw.split(":").pop().split("/").pop().trim().toLowerCase(); + if (!norm) return; + + const registryPath = resolveRegistryPath(); + const registryResult = loadRegistry(registryPath); + if (registryResult.status === "missing") return; + if (registryResult.status === "invalid") { + // Without parsed records we cannot identify revoked skills; denying every + // skill would wedge the session, so stay open and report the failure loudly. + emit({ + hookSpecificOutput: { hookEventName: "PreToolUse" }, + systemMessage: `AgentGuard: trust registry at ${registryPath} exists but could not be parsed (expected {"records":[...]}). The skill trust gate is inactive this session.`, + }); + return; + } + + const registry = registryResult.registry; + if (registry.records.length === 0) return; + + const matches = registry.records.filter((record) => recordMatches(record, norm)); + if (matches.length === 0) return; + + const record = matches.find((match) => match.status === "revoked") || newestActive(matches); + if (!record) return; + + if (record.status === "revoked") { + emit({ + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: `AgentGuard trust registry: skill '${norm}' is REVOKED (record ${record.record_key}). Do not run it. Use the skill-trust skill (registry_lookup / registry_attest) to review or re-trust it.`, + }, + }); + return; + } + + if (record.trust_level === "untrusted") { + emit({ + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "ask", + permissionDecisionReason: `AgentGuard trust registry: skill '${norm}' is marked UNTRUSTED (record ${record.record_key}). Confirm before running it.`, + }, + }); + return; + } + + if (record.trust_level === "restricted") { + const capabilities = record.capabilities && typeof record.capabilities === "object" ? record.capabilities : null; + const exec = capabilities && (capabilities.exec === "allow" || capabilities.exec === "deny") ? capabilities.exec : "unspecified"; + const hasValidCapabilities = capabilities && ( + exec !== "unspecified" + || Array.isArray(capabilities.network_allowlist) + || Array.isArray(capabilities.filesystem_allowlist) + || Array.isArray(capabilities.secrets_allowlist) + ); + if (!hasValidCapabilities) { + emit({ + hookSpecificOutput: { + hookEventName: "PreToolUse", + additionalContext: `AgentGuard: skill '${norm}' is trust-level RESTRICTED but its trust record is malformed (capabilities missing or invalid). Treat it as untrusted and proceed with caution.`, + }, + }); + return; + } + const network = list(capabilities.network_allowlist); + const filesystem = list(capabilities.filesystem_allowlist); + const secrets = list(capabilities.secrets_allowlist); + emit({ + hookSpecificOutput: { + hookEventName: "PreToolUse", + additionalContext: `AgentGuard: skill '${norm}' is trust-level RESTRICTED. Granted capabilities — exec: ${exec}; network allowlist: ${network}; filesystem allowlist: ${filesystem}; secrets allowlist: ${secrets}. Stay within these bounds while this skill runs.`, + }, + }); + } +} + +main().catch(() => process.exit(0)); diff --git a/plugins/agentguard-toolkit/skills/action-precheck/SKILL.md b/plugins/agentguard-toolkit/skills/action-precheck/SKILL.md new file mode 100644 index 0000000..39d474f --- /dev/null +++ b/plugins/agentguard-toolkit/skills/action-precheck/SKILL.md @@ -0,0 +1,72 @@ +--- +name: action-precheck +description: Evaluates whether a proposed runtime action is safe using the GoPlus AgentGuard policy engine before it is executed — shell commands, file reads/writes, network requests, secret access, or Web3 operations. Use when the user says "is this command safe", "check this action", "should I run this", "is it safe to fetch this URL", "precheck this", "would AgentGuard allow this", or before running a risky operation in a production context. +user-invocable: true +argument-hint: " [--env prod|dev|test]" +allowed-tools: mcp__agentguard__action_scanner_decide, mcp__agentguard__registry_lookup +--- + +# Action precheck + +## Purpose + +Perform a pre-flight policy check that returns allow, deny, or confirm with reasons. +Analyze the proposed action only. +Do not execute the action. + +## Classify the action + +Map the description to exactly one `action.type`: + +| Description | Action type | +|---|---| +| curl, fetch, or a URL request | `network_request` | +| searching the web | `web_search` | +| shell or CLI invocation | `exec_command` | +| reading a normal file | `read_file` | +| creating or modifying a file | `write_file` | +| reading environment variables, keychains, `.env`, or tokens | `secret_access` | +| transaction or transfer | `web3_tx` | +| signature, permit, or typed data | `web3_sign` | + +Ask a clarifying question when the type is genuinely ambiguous. +Do not combine action types in one call. + +## Build the call + +### Tool contract + +Use this exact input schema: + +- `action_scanner_decide` — required ["actor","action","context"]; actor = { skill: {id,source,version_ref,artifact_hash} } with all four string fields required; action = { type, data } both required, type enum network_request|web_search|exec_command|read_file|write_file|secret_access|web3_tx|web3_sign, data free-form object; context requires env (enum prod|dev|test), session_id (string), and user_present (boolean). + +Call it as `mcp__agentguard__action_scanner_decide`. +When the action originates from a named skill, set `actor.skill` to that identity. +Optionally call `mcp__agentguard__registry_lookup` with the same identity for context. +Otherwise set `actor.skill` to `{"id":"claude-code-session","source":"","version_ref":"","artifact_hash":""}`. +Keep `action.data` minimal and factual because it is free-form. +Use `{"command":"..."}` for exec, `{"url":"..."}` for network, and `{"path":"..."}` for file operations. +Default `context.env` to `dev`. +Use `prod` only when the user says production or the target is clearly a live system. +Set `user_present: true`. +Use the real `session_id` when known; otherwise set it to the stable placeholder `"claude-code-session"`. + +### Server quirk — required fields (validated against v1.1.28) + +Always send `id`, `source`, `version_ref`, and `artifact_hash` in `actor.skill`; use `""` when a value is genuinely unknown, and never invent a plausible-looking hash or version. +Always send all three context fields: `env`, `session_id`, and `user_present`. Use the real session ID when known, otherwise use `"claude-code-session"`; set `user_present` to `true` when the user is in the chat. + +## Interpret the verdict + +For `allow`, state that the policy allows the action and include any returned caveats. +For `confirm`, quote the exact policy reason and ask the user to confirm. +For `deny`, quote the exact policy reason and do not offer workarounds. +Keep the policy result distinct from any separate judgment about the action. + +## Hard rules + +Evaluate one action per call. +Split compound commands and evaluate each action separately. +Never soften a deny. +Quote the policy reason verbatim. +Never execute, sign, send, install, or delete anything as part of this skill. diff --git a/plugins/agentguard-toolkit/skills/skill-audit/SKILL.md b/plugins/agentguard-toolkit/skills/skill-audit/SKILL.md new file mode 100644 index 0000000..149a81d --- /dev/null +++ b/plugins/agentguard-toolkit/skills/skill-audit/SKILL.md @@ -0,0 +1,77 @@ +--- +name: skill-audit +description: Scans a Claude Code skill, plugin, or agent-skill directory for malicious or risky content using the GoPlus AgentGuard MCP scanner, and checks its trust record. Use when the user says "scan this skill", "is this skill safe", "audit this plugin before I install it", "check this skill for malware", "security review this SKILL.md", "vet this third-party skill", or before installing any skill from an untrusted source. +user-invocable: true +argument-hint: " [--deep]" +allowed-tools: Read, Glob, Grep, mcp__agentguard__skill_scanner_scan, mcp__agentguard__registry_lookup +--- + +# Skill audit + +## Purpose + +Perform a pre-installation or pre-use security audit of a skill directory. +Scan the directory with the AgentGuard MCP scanner, then cross-check its trust record. +Treat scanner findings and registry state as separate evidence. + +## Resolve the target + +Take `$ARGUMENTS` as a directory path and recognize `--deep` as a scan option. +When given a `SKILL.md` file path, use its parent directory. +When no path is supplied, ask which skill directory to audit. +Use Glob to list candidates from `~/.claude/skills/` and the project's `.claude/skills/` when asking. +Never scan without a concrete directory path. + +## Build the skill identity + +Set `id` to the directory basename normalized to kebab-case. +Set `source` to the absolute directory path unless the user supplied a git remote or marketplace URL; use that URL when supplied. +Read frontmatter or `plugin.json` and set `version_ref` to the version when present. +Always send all four skill fields: `id`, `source`, `version_ref`, and `artifact_hash`. +Use `""` for values you cannot determine. +Never invent a plausible-looking `artifact_hash`; send `""` when it is unknown. +Reuse the identical skill object for scanning and registry lookup. + +## Tool contract + +Use only these exact input schemas: + +- `skill_scanner_scan` — required ["skill","path"]; props: skill (object with required string fields id, source, version_ref, artifact_hash), path (string, path to skill dir), deep (boolean). +- `registry_lookup` — required ["skill"]; props: skill (same four-field shape). + +Call them as `mcp__agentguard__skill_scanner_scan` and `mcp__agentguard__registry_lookup`. +Send `deep` only when requested; never guess identity values. + +### Server quirk — required fields (validated against v1.1.28) + +Always send `id`, `source`, `version_ref`, and `artifact_hash` in every `skill` object; use `""` when a value is genuinely unknown, and never invent a plausible-looking hash or version. +Always pass `path` to `skill_scanner_scan`; omitting it crashes the server despite the advertised schema marking it optional. + +## Run the scan + +Call `mcp__agentguard__skill_scanner_scan` with `skill` and `path`. +Pass `deep: true` when the user supplied `--deep`. +If the initial scan returns medium-or-higher risk and was not deep, run it again with `deep: true`. +Then call `mcp__agentguard__registry_lookup` with the same skill object. +Keep the scan results and registry response intact for reporting. + +## Report format + +Start with a risk-level headline. +Render an evidence table with columns `Finding`, `File`, and `Why it matters`. +Report the trust record as exactly one of: none, active with its level, or revoked. +End with one verdict: `safe to use`, `use with restrictions`, or `do not install`. +Add a one-sentence justification tied to the evidence and registry state. +Say `no findings` when the scanner reports none; never say `verified safe`. + +## Follow-ups + +Direct the user to the `skill-trust` skill when they want to record the outcome. +Do not attest, revoke, or modify registry state from this skill. + +## Hard rules + +Never execute code from the scanned directory. +Never edit the scanned skill. +Never infer safety from registry status alone. +Never present a clean scan as a guarantee. diff --git a/plugins/agentguard-toolkit/skills/skill-trust/SKILL.md b/plugins/agentguard-toolkit/skills/skill-trust/SKILL.md new file mode 100644 index 0000000..6d5f468 --- /dev/null +++ b/plugins/agentguard-toolkit/skills/skill-trust/SKILL.md @@ -0,0 +1,82 @@ +--- +name: skill-trust +description: Manages the GoPlus AgentGuard trust registry for skills — look up, attest, revoke, and list trust records with explicit capability grants. Use when the user says "trust this skill", "attest this skill", "mark this skill trusted or restricted or untrusted", "revoke trust", "untrust this skill", "list trusted skills", "show the trust registry", "what skills are revoked", or "look up the trust record for X". +user-invocable: true +argument-hint: "[lookup|attest|revoke|list] [skill-id-or-source] [--level trusted|restricted|untrusted]" +allowed-tools: mcp__agentguard__registry_lookup, mcp__agentguard__registry_attest, mcp__agentguard__registry_revoke, mcp__agentguard__registry_list, mcp__agentguard__skill_scanner_scan +--- + +# Skill trust + +## Purpose + +Manage registry records through the AgentGuard MCP tools. +Remember that the registry is shared state at `~/.agentguard/registry.json`. +Treat changes as immediately relevant to this plugin's Skill trust-gate hook and the upstream AgentGuard CLI. + +## Routing + +Parse the first argument as `lookup`, `attest`, `revoke`, or `list`. +When no operation is supplied, ask which operation to perform. +Use the remaining arguments only to resolve the skill identity, filters, trust level, and capability proposal. + +## Tool contract + +Use only these exact input schemas: + +- `registry_lookup` — required ["skill"]; props: skill (object with required string fields id, source, version_ref, artifact_hash). +- `registry_attest` — required ["skill","trust_level","capabilities"]; skill (same four-field shape); trust_level enum untrusted|restricted|trusted; capabilities object whose OWN required list is ["network_allowlist","filesystem_allowlist","exec","secrets_allowlist"] with network_allowlist string[], filesystem_allowlist string[], exec enum allow|deny, secrets_allowlist string[]; optional expires_at (string), reviewed_by (string), notes (string), force (boolean). +- `registry_revoke` — no required; props record_key, source, version_ref, reason (all strings). +- `registry_list` — no required; props trust_level (enum), status (enum active|revoked), source_pattern (string), include_expired (boolean). + +For a pre-attestation scan, use this exact schema: + +- `skill_scanner_scan` — required ["skill","path"]; props: skill (same four-field shape), path (string, path to skill dir), deep (boolean). + +Call the tools with their full names from `allowed-tools`. +For every attestation, include all four capability keys: `network_allowlist`, `filesystem_allowlist`, `exec`, and `secrets_allowlist`. +Treat an empty array as a valid deny-all allowlist. +Set `exec` to exactly `allow` or `deny`. + +### Server quirk — required fields (validated against v1.1.28) + +Always send `id`, `source`, `version_ref`, and `artifact_hash` in every `skill` object for lookup, attest, and scan; use `""` when a value is genuinely unknown, and never invent a plausible-looking hash or version. +Always pass `path` when calling `skill_scanner_scan`. +For attest, prefer real `source`, `version_ref`, and `artifact_hash` values: the server builds `record_key` as `@#`, and later revoke-by-source matching depends on them. Empty strings are accepted but produce a weak, near-useless key. + +## Attest workflow + +If this skill has not been scanned in the current session, run `mcp__agentguard__skill_scanner_scan` first and show its result. +Propose least-privilege capabilities, defaulting to empty allowlists and `exec: "deny"`. +Use this complete default capability object: + +```json +{"network_allowlist":[],"filesystem_allowlist":[],"exec":"deny","secrets_allowlist":[]} +``` + +Require the user to name `untrusted`, `restricted`, or `trusted`, or explicitly approve the proposed level. +Never assign `trusted` when the scan reported findings unless the user explicitly overrides after seeing them. +Ask for the reviewer's name or email unless the user already stated it, then set `reviewed_by`. +Put the scan summary in `notes`. +Pass `force: true` only after the tool itself asks for confirmation and the user confirms in chat. + +## Revoke workflow + +Always require a non-empty `reason`. +Prefer `record_key` when it is known; obtain it through lookup or list first. +Before using `source` or `version_ref`, warn that patterns can match multiple records and show what will match. +Do not call revoke until the target and reason are clear. +When reading a record back, check `status` before `trust_level`: revoke sets `status` to `"revoked"` but leaves `trust_level` unchanged, so `status` is authoritative. + +## List workflow + +Map user filters only to `trust_level`, `status`, `source_pattern`, and `include_expired`. +Render results as a table with `Record key`, `Skill id`, `Level`, `Status`, and `Expiry`. +Preserve missing expiry values as empty or `none` rather than inventing dates. + +## Hard rules + +Never invent capability values. +Never attest without a user-named trust level or explicit approval of the proposal. +Report the tool's response verbatim when a conflict occurs. +Never imply that registry operations install, execute, or delete a skill. diff --git a/plugins/agentguard-toolkit/skills/web3-precheck/SKILL.md b/plugins/agentguard-toolkit/skills/web3-precheck/SKILL.md new file mode 100644 index 0000000..c8af948 --- /dev/null +++ b/plugins/agentguard-toolkit/skills/web3-precheck/SKILL.md @@ -0,0 +1,80 @@ +--- +name: web3-precheck +description: Simulates a Web3 transaction through the GoPlus AgentGuard risk API before it is signed or broadcast, reporting scam, phishing, approval, and asset-risk findings. Use when the user says "simulate this transaction", "is this tx safe", "check this contract call", "check this address before I send", "is this token approval dangerous", "analyze this calldata", or pastes a transaction with to/value/data fields. +user-invocable: true +argument-hint: " [value-wei] [calldata] [--from
]" +allowed-tools: mcp__agentguard__action_scanner_simulate_web3, mcp__agentguard__action_scanner_decide +--- + +# Web3 precheck + +## Purpose + +Perform a strictly read-only, pre-signing risk simulation. +Never sign, send, approve, or broadcast anything. +Never ask for private keys or seed phrases. + +## Gather parameters + +Require `chain_id` and map only these common names: + +| Chain name | Chain ID | +|---|---:| +| ethereum or mainnet | 1 | +| bsc | 56 | +| polygon | 137 | +| base | 8453 | +| arbitrum | 42161 | +| optimism | 10 | +| avalanche | 43114 | + +When the chain name is unknown, ask for the numeric ID instead of guessing. +Check that `to`, when supplied, is a `0x` address. +Treat `value` as a wei string. +When the user gives an ETH amount, convert it to wei and show the conversion before calling the tool. +Treat `data` as hex calldata when supplied. +Set `origin` to the dApp URL when mentioned. +Include `from` only when the user supplies it. + +## Tool contract + +Use only these exact input schemas: + +- `action_scanner_simulate_web3` — required ["chain_id"]; props chain_id (number), from (string), to (string), value (string, wei), data (string), origin (string). +- `action_scanner_decide` — required ["actor","action","context"]; actor = { skill: {id,source,version_ref,artifact_hash} } with all four string fields required; action = { type, data } both required, type enum network_request|web_search|exec_command|read_file|write_file|secret_access|web3_tx|web3_sign, data free-form object; context requires env (enum prod|dev|test), session_id (string), and user_present (boolean). + +Call them as `mcp__agentguard__action_scanner_simulate_web3` and `mcp__agentguard__action_scanner_decide`. +For `action_scanner_simulate_web3`, omit optional transaction fields that the user did not provide. + +### Server quirk — required fields (validated against v1.1.28) + +For `action_scanner_decide`, always send `id`, `source`, `version_ref`, and `artifact_hash` in `actor.skill`; use `""` when a value is genuinely unknown, and never invent a plausible-looking hash or version. +Always send all three context fields: `env`, `session_id`, and `user_present`. Use the real session ID when known, otherwise use `"claude-code-session"`; set `user_present` to `true` when the user is in the chat. + +## Run and layer + +Call `mcp__agentguard__action_scanner_simulate_web3` first. +When the user is deciding whether to proceed, also call `mcp__agentguard__action_scanner_decide`. +Do not add the policy layer when the user is merely curious. +Use `action.type: "web3_tx"` for transactions and `action.type: "web3_sign"` for signature requests. +Put the same supplied transaction fields in `action.data`. +Set `actor.skill` to `{"id":"claude-code-session","source":"","version_ref":"","artifact_hash":""}`. +Set `context.env` to `prod` because real chains are production. +Use the real `context.session_id` when known; otherwise set it to `"claude-code-session"`. +Set `context.user_present` to `true`. + +## Report format + +Start with a risk headline. +List every finding with its severity and a plain-terms explanation. +State explicitly when the GoPlus API returned partial data, including when missing `GOPLUS_API_KEY` limits enrichment. +Without `GOPLUS_API_KEY`, report the confirmed fallback as decision `"confirm"`, risk level `"medium"`, risk tags `["SIMULATION_UNAVAILABLE"]`, and explanation `"GoPlus API not configured - cannot simulate transaction"`. +End with exactly one recommendation: `proceed`, `proceed with caution`, or `do not sign`. +State that the final decision remains with the user. + +## Hard rules + +Never construct or modify calldata for the user's signing. +Never handle private keys or seed phrases. +Never claim that simulation signs, sends, approves, or broadcasts. +Never present a clean simulation as a guarantee.