@goplus/agentguard 1.1.9 → 1.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +25 -8
  2. package/dist/adapters/common.d.ts.map +1 -1
  3. package/dist/adapters/common.js +3 -1
  4. package/dist/adapters/common.js.map +1 -1
  5. package/dist/adapters/openclaw-plugin.d.ts.map +1 -1
  6. package/dist/adapters/openclaw-plugin.js +17 -3
  7. package/dist/adapters/openclaw-plugin.js.map +1 -1
  8. package/dist/cli.js +170 -11
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +3 -0
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +18 -0
  13. package/dist/config.js.map +1 -1
  14. package/dist/feed/cron.d.ts +27 -0
  15. package/dist/feed/cron.d.ts.map +1 -1
  16. package/dist/feed/cron.js +721 -54
  17. package/dist/feed/cron.js.map +1 -1
  18. package/dist/installers.d.ts +1 -1
  19. package/dist/installers.d.ts.map +1 -1
  20. package/dist/installers.js +164 -13
  21. package/dist/installers.js.map +1 -1
  22. package/dist/postinstall.js +29 -0
  23. package/dist/postinstall.js.map +1 -1
  24. package/dist/registry/storage.d.ts.map +1 -1
  25. package/dist/registry/storage.js +5 -1
  26. package/dist/registry/storage.js.map +1 -1
  27. package/dist/runtime/types.d.ts +1 -1
  28. package/dist/runtime/types.d.ts.map +1 -1
  29. package/dist/tests/cli-init.test.d.ts +2 -0
  30. package/dist/tests/cli-init.test.d.ts.map +1 -0
  31. package/dist/tests/cli-init.test.js +130 -0
  32. package/dist/tests/cli-init.test.js.map +1 -0
  33. package/dist/tests/cli-policy.test.js +47 -0
  34. package/dist/tests/cli-policy.test.js.map +1 -1
  35. package/dist/tests/cli-subscribe.test.js +33 -0
  36. package/dist/tests/cli-subscribe.test.js.map +1 -1
  37. package/dist/tests/feed-cron.test.js +441 -13
  38. package/dist/tests/feed-cron.test.js.map +1 -1
  39. package/dist/tests/installer.test.js +37 -2
  40. package/dist/tests/installer.test.js.map +1 -1
  41. package/dist/tests/integration.test.js +9 -5
  42. package/dist/tests/integration.test.js.map +1 -1
  43. package/dist/tests/postinstall.test.d.ts +2 -0
  44. package/dist/tests/postinstall.test.d.ts.map +1 -0
  45. package/dist/tests/postinstall.test.js +31 -0
  46. package/dist/tests/postinstall.test.js.map +1 -0
  47. package/dist/tests/setup-script.test.d.ts +2 -0
  48. package/dist/tests/setup-script.test.d.ts.map +1 -0
  49. package/dist/tests/setup-script.test.js +63 -0
  50. package/dist/tests/setup-script.test.js.map +1 -0
  51. package/dist/tests/smoke.test.js +88 -1
  52. package/dist/tests/smoke.test.js.map +1 -1
  53. package/docs/codex.md +1 -1
  54. package/docs/hermes.md +3 -3
  55. package/package.json +1 -1
  56. package/skills/agentguard/SKILL.md +424 -194
  57. package/skills/agentguard/hermes-hooks.yaml +2 -2
  58. package/skills/agentguard/scan-rules.md +13 -2
  59. package/skills/agentguard/scripts/{action-cli.ts → action-cli.js} +13 -18
  60. package/skills/agentguard/scripts/auto-scan.js +3 -1
  61. package/skills/agentguard/scripts/checkup-score.js +369 -0
  62. package/skills/agentguard/scripts/hermes-hook.js +103 -16
  63. package/skills/agentguard/scripts/scan-to-sarif.js +195 -0
  64. package/skills/agentguard/scripts/{trust-cli.ts → trust-cli.js} +12 -16
  65. package/skills/agentguard/suppress.example.yaml +67 -0
@@ -4,10 +4,10 @@
4
4
  * GoPlus AgentGuard Hermes shell hook.
5
5
  *
6
6
  * Hermes shell hooks read JSON from stdin and use stdout JSON to influence
7
- * behavior. For pre_tool_call, returning { action: "block", message: "..." }
8
- * vetoes tool execution. There is no native "ask" decision in Hermes'
9
- * pre_tool_call contract, so AgentGuard's ask decision is represented as a
10
- * block with a confirmation-oriented message.
7
+ * behavior. For pre_tool_call, returning a block decision vetoes tool
8
+ * execution. There is no native "ask" decision in Hermes' pre_tool_call
9
+ * contract, so AgentGuard's confirmation decision is represented as a block
10
+ * with a confirmation-oriented message.
11
11
  */
12
12
 
13
13
  import { join } from 'node:path';
@@ -62,6 +62,11 @@ function validatePreToolPayload(input) {
62
62
  return null;
63
63
  case 'web_extract':
64
64
  case 'browser_navigate':
65
+ case 'browser_open':
66
+ case 'web_open':
67
+ case 'open_url':
68
+ case 'visit_url':
69
+ case 'open':
65
70
  if (!firstString(toolInput.url, toolInput.href, toolInput.target)) return `Hermes ${toolName} hook payload is missing URL`;
66
71
  return null;
67
72
  case 'web_search':
@@ -82,7 +87,7 @@ function shouldFailClosed(input) {
82
87
 
83
88
  const agentguardPath = join(import.meta.url.replace('file://', ''), '..', '..', '..', '..', 'dist', 'index.js');
84
89
 
85
- let createAgentGuard, HermesAdapter, evaluateHook, loadConfig;
90
+ let loadRuntimeConfig, loadHookConfig, protectAction, createAgentGuard, HermesAdapter, evaluateHook;
86
91
 
87
92
  async function loadEngine() {
88
93
  if (process.env.AGENTGUARD_TEST_FORCE_ENGINE_LOAD_FAILURE === '1') {
@@ -92,19 +97,23 @@ async function loadEngine() {
92
97
  try {
93
98
  const gs = await import(agentguardPath);
94
99
  return {
100
+ loadRuntimeConfig: gs.loadAgentGuardConfig || gs.ensureConfig,
101
+ loadHookConfig: gs.loadConfig,
102
+ protectAction: gs.protectAction,
95
103
  createAgentGuard: gs.createAgentGuard || gs.default,
96
104
  HermesAdapter: gs.HermesAdapter,
97
105
  evaluateHook: gs.evaluateHook,
98
- loadConfig: gs.loadConfig,
99
106
  };
100
107
  } catch {
101
108
  try {
102
109
  const gs = await import('@goplus/agentguard');
103
110
  return {
111
+ loadRuntimeConfig: gs.loadAgentGuardConfig || gs.ensureConfig,
112
+ loadHookConfig: gs.loadConfig,
113
+ protectAction: gs.protectAction,
104
114
  createAgentGuard: gs.createAgentGuard || gs.default,
105
115
  HermesAdapter: gs.HermesAdapter,
106
116
  evaluateHook: gs.evaluateHook,
107
- loadConfig: gs.loadConfig,
108
117
  };
109
118
  } catch {
110
119
  return null;
@@ -148,7 +157,10 @@ function readStdin() {
148
157
  function outputBlock(reason) {
149
158
  console.log(JSON.stringify({
150
159
  action: 'block',
160
+ decision: 'block',
161
+ block: true,
151
162
  message: reason || 'GoPlus AgentGuard blocked this action',
163
+ reason: reason || 'GoPlus AgentGuard blocked this action',
152
164
  }));
153
165
  process.exit(0);
154
166
  }
@@ -158,6 +170,41 @@ function outputAllow() {
158
170
  process.exit(0);
159
171
  }
160
172
 
173
+ function runtimeActionTypeFrom(toolName) {
174
+ switch (toolName) {
175
+ case 'terminal':
176
+ case 'execute_code':
177
+ return 'shell';
178
+ case 'write_file':
179
+ case 'patch':
180
+ case 'skill_manage':
181
+ return 'file_write';
182
+ case 'read_file':
183
+ return 'file_read';
184
+ case 'web_search':
185
+ case 'web_extract':
186
+ case 'browser_navigate':
187
+ case 'browser_open':
188
+ case 'web_open':
189
+ case 'open_url':
190
+ case 'visit_url':
191
+ case 'open':
192
+ return 'network';
193
+ default:
194
+ return 'other';
195
+ }
196
+ }
197
+
198
+ function runtimeToolNameFrom(toolName) {
199
+ return toolName || 'HermesTool';
200
+ }
201
+
202
+ function debugLog(message, details) {
203
+ if (process.env.AGENTGUARD_HERMES_DEBUG !== '1') return;
204
+ const suffix = details === undefined ? '' : ` ${JSON.stringify(details)}`;
205
+ console.error(`[AgentGuard Hermes] ${message}${suffix}`);
206
+ }
207
+
161
208
  // ---------------------------------------------------------------------------
162
209
  // Main
163
210
  // ---------------------------------------------------------------------------
@@ -181,21 +228,61 @@ async function main() {
181
228
  outputAllow();
182
229
  }
183
230
 
184
- ({ createAgentGuard, HermesAdapter, evaluateHook, loadConfig } = engine);
231
+ ({ loadRuntimeConfig, loadHookConfig, protectAction, createAgentGuard, HermesAdapter, evaluateHook } = engine);
232
+
233
+ if (isPostHook(input)) {
234
+ try {
235
+ if (createAgentGuard && HermesAdapter && evaluateHook) {
236
+ const adapter = new HermesAdapter();
237
+ const config = loadHookConfig ? loadHookConfig() : { level: loadRuntimeConfig().level };
238
+ const agentguard = createAgentGuard();
239
+ await evaluateHook(adapter, input, { config, agentguard });
240
+ }
241
+ } catch {
242
+ // Post hooks are audit-only; never affect Hermes execution.
243
+ }
244
+ outputAllow();
245
+ }
246
+
247
+ const config = loadRuntimeConfig();
248
+ const result = await protectAction({
249
+ config,
250
+ rawInput: input,
251
+ agentHost: 'hermes',
252
+ actionType: runtimeActionTypeFrom(toolNameFrom(input)),
253
+ toolName: runtimeToolNameFrom(toolNameFrom(input)),
254
+ sessionId: typeof input.session_id === 'string' ? input.session_id : undefined,
255
+ });
185
256
 
186
- const adapter = new HermesAdapter();
187
- const config = loadConfig();
188
- const agentguard = createAgentGuard();
257
+ if (!result) {
258
+ debugLog('allow: no runtime action was built');
259
+ outputAllow();
260
+ }
189
261
 
190
- const result = await evaluateHook(adapter, input, { config, agentguard });
262
+ debugLog('decision', {
263
+ decision: result.decision.decision,
264
+ riskLevel: result.decision.riskLevel,
265
+ riskScore: result.decision.riskScore,
266
+ policySource: result.policySource,
267
+ });
191
268
 
192
- if (result.decision === 'deny') {
193
- outputBlock(result.reason || 'GoPlus AgentGuard blocked this Hermes tool call');
194
- } else if (result.decision === 'ask') {
195
- outputBlock(result.reason || 'GoPlus AgentGuard requires confirmation for this Hermes tool call');
269
+ if (result.decision.decision === 'block') {
270
+ outputBlock(formatDecisionReason(result, 'blocked this Hermes tool call'));
271
+ } else if (result.decision.decision === 'require_approval') {
272
+ outputBlock(formatDecisionReason(result, 'requires confirmation for this Hermes tool call'));
196
273
  } else {
197
274
  outputAllow();
198
275
  }
199
276
  }
200
277
 
278
+ function formatDecisionReason(result, fallback) {
279
+ const titles = result.decision.reasons
280
+ .map((item) => item.title)
281
+ .filter(Boolean)
282
+ .slice(0, 3)
283
+ .join(', ');
284
+ const suffix = titles ? ` Reasons: ${titles}.` : '';
285
+ return `GoPlus AgentGuard ${fallback} (action: ${result.decision.actionId}, risk: ${result.decision.riskScore}/100, level: ${result.decision.riskLevel}).${suffix}`;
286
+ }
287
+
201
288
  main();
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scan-to-sarif.js — convert AgentGuard scan findings to SARIF 2.1.0
4
+ *
5
+ * Usage:
6
+ * node scripts/scan-to-sarif.js --file <findings.json>
7
+ * cat findings.json | node scripts/scan-to-sarif.js
8
+ *
9
+ * Input schema (findings.json):
10
+ * {
11
+ * "target": "<scanned path>",
12
+ * "scanned_at": "<ISO 8601>",
13
+ * "files_scanned": <number>,
14
+ * "risk_level": "CRITICAL|HIGH|MEDIUM|LOW",
15
+ * "findings": [
16
+ * {
17
+ * "rule_id": "SHELL_EXEC",
18
+ * "severity": "CRITICAL|HIGH|MEDIUM|LOW",
19
+ * "file": "relative/path/to/file.js",
20
+ * "line": 42,
21
+ * "evidence": "matched content snippet"
22
+ * }
23
+ * ]
24
+ * }
25
+ *
26
+ * Output: SARIF 2.1.0 JSON to stdout
27
+ */
28
+
29
+ import { readFileSync } from 'node:fs';
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Rule definitions (all 24 AgentGuard rules)
33
+ // ---------------------------------------------------------------------------
34
+
35
+ const RULES = [
36
+ { id: 'SHELL_EXEC', name: 'Shell Command Execution', severity: 'HIGH', description: 'Detects capabilities to execute shell commands (child_process, subprocess, os.system).' },
37
+ { id: 'AUTO_UPDATE', name: 'Auto-Update / Download-Execute', severity: 'CRITICAL', description: 'Detects scheduled self-update or download-and-execute patterns (curl|bash, wget|sh).' },
38
+ { id: 'REMOTE_LOADER', name: 'Remote Code Loader', severity: 'CRITICAL', description: 'Detects dynamic code loading from remote sources (dynamic import, eval(fetch(...))).' },
39
+ { id: 'READ_ENV_SECRETS', name: 'Environment Secret Access', severity: 'MEDIUM', description: 'Detects access to environment variables that may contain secrets (process.env, os.environ).' },
40
+ { id: 'READ_SSH_KEYS', name: 'SSH Key Access', severity: 'CRITICAL', description: 'Detects references to SSH key files (~/.ssh/id_rsa, authorized_keys, etc.).' },
41
+ { id: 'READ_KEYCHAIN', name: 'System Keychain Access', severity: 'CRITICAL', description: 'Detects access to system keychains, browser credential stores, or Windows Credential Manager.' },
42
+ { id: 'PRIVATE_KEY_PATTERN', name: 'Hardcoded Private Key', severity: 'CRITICAL', description: 'Detects hardcoded Ethereum private keys or PEM private key headers in source code.' },
43
+ { id: 'MNEMONIC_PATTERN', name: 'Hardcoded Mnemonic Phrase', severity: 'CRITICAL', description: 'Detects hardcoded BIP-39 mnemonic seed phrases in source code.' },
44
+ { id: 'WALLET_DRAINING', name: 'Wallet Draining Pattern', severity: 'CRITICAL', description: 'Detects approve+transferFrom or permit patterns that can drain wallets.' },
45
+ { id: 'UNLIMITED_APPROVAL', name: 'Unlimited Token Approval', severity: 'HIGH', description: 'Detects ERC-20 approve(MaxUint256) or setApprovalForAll(true) patterns.' },
46
+ { id: 'DANGEROUS_SELFDESTRUCT', name: 'Dangerous selfdestruct', severity: 'HIGH', description: 'Detects selfdestruct() or suicide() calls in Solidity contracts.' },
47
+ { id: 'HIDDEN_TRANSFER', name: 'Hidden Transfer', severity: 'MEDIUM', description: 'Detects ETH transfers hidden inside non-transfer functions.' },
48
+ { id: 'PROXY_UPGRADE', name: 'Proxy Upgrade Pattern', severity: 'MEDIUM', description: 'Detects upgradeable proxy patterns that may allow unauthorized logic replacement.' },
49
+ { id: 'FLASH_LOAN_RISK', name: 'Flash Loan Risk', severity: 'MEDIUM', description: 'Detects flash loan usage that may enable price manipulation or reentrancy attacks.' },
50
+ { id: 'REENTRANCY_PATTERN', name: 'Reentrancy Vulnerability', severity: 'HIGH', description: 'Detects external calls followed by state changes, violating Checks-Effects-Interactions.' },
51
+ { id: 'SIGNATURE_REPLAY', name: 'Signature Replay Risk', severity: 'HIGH', description: 'Detects ecrecover usage without nonce protection, enabling signature replay attacks.' },
52
+ { id: 'OBFUSCATION', name: 'Code Obfuscation', severity: 'HIGH', description: 'Detects obfuscated code patterns (eval, hex escape sequences, packed JS, etc.).' },
53
+ { id: 'PROMPT_INJECTION', name: 'Prompt Injection Attempt', severity: 'CRITICAL', description: 'Detects instructions that attempt to override AI agent behavior or bypass safety controls.' },
54
+ { id: 'NET_EXFIL_UNRESTRICTED', name: 'Unrestricted Network Exfiltration', severity: 'HIGH', description: 'Detects unrestricted POST requests or file uploads that may exfiltrate data.' },
55
+ { id: 'WEBHOOK_EXFIL', name: 'Webhook Exfiltration', severity: 'CRITICAL', description: 'Detects hardcoded webhook URLs (Discord, Telegram, Slack, ngrok) used for data exfiltration.' },
56
+ { id: 'TROJAN_DISTRIBUTION', name: 'Trojan Binary Distribution', severity: 'CRITICAL', description: 'Detects trojanized binary download patterns (URL + password + execute instructions).' },
57
+ { id: 'SUSPICIOUS_PASTE_URL', name: 'Suspicious Paste Site URL', severity: 'HIGH', description: 'Detects URLs pointing to paste sites (Pastebin, Hastebin, etc.) used for payload delivery.' },
58
+ { id: 'SUSPICIOUS_IP', name: 'Hardcoded Public IP Address', severity: 'MEDIUM', description: 'Detects hardcoded public IPv4 addresses that may point to attacker-controlled infrastructure.' },
59
+ { id: 'SOCIAL_ENGINEERING', name: 'Social Engineering Pressure', severity: 'HIGH', description: 'Detects manipulative language combined with execution instructions targeting agent users.' },
60
+ ];
61
+
62
+ const RULE_INDEX = new Map(RULES.map(r => [r.id, r]));
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Severity mapping: AgentGuard → SARIF
66
+ // ---------------------------------------------------------------------------
67
+
68
+ function toSarifLevel(severity) {
69
+ switch ((severity || '').toUpperCase()) {
70
+ case 'CRITICAL': return 'error';
71
+ case 'HIGH': return 'warning';
72
+ case 'MEDIUM': return 'note';
73
+ default: return 'none';
74
+ }
75
+ }
76
+
77
+ // Map AgentGuard severity to SARIF security-severity score (CVSS-like, 0.0–10.0)
78
+ function toSecuritySeverity(severity) {
79
+ switch ((severity || '').toUpperCase()) {
80
+ case 'CRITICAL': return '9.0';
81
+ case 'HIGH': return '7.0';
82
+ case 'MEDIUM': return '4.0';
83
+ default: return '1.0';
84
+ }
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Input
89
+ // ---------------------------------------------------------------------------
90
+
91
+ function readInput() {
92
+ const fileIdx = process.argv.indexOf('--file');
93
+ if (fileIdx !== -1 && process.argv[fileIdx + 1]) {
94
+ return JSON.parse(readFileSync(process.argv[fileIdx + 1], 'utf-8'));
95
+ }
96
+ return JSON.parse(readFileSync('/dev/stdin', 'utf-8'));
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Build SARIF
101
+ // ---------------------------------------------------------------------------
102
+
103
+ function buildSarif(input) {
104
+ const findings = input.findings || [];
105
+
106
+ // Collect which rules actually fired (for the driver.rules array)
107
+ const firedRuleIds = new Set(findings.map(f => f.rule_id));
108
+
109
+ // Build driver rules — include all fired rules, plus any referenced ones
110
+ const driverRules = RULES
111
+ .filter(r => firedRuleIds.has(r.id))
112
+ .map(r => ({
113
+ id: r.id,
114
+ name: r.name,
115
+ shortDescription: { text: r.name },
116
+ fullDescription: { text: r.description },
117
+ defaultConfiguration: {
118
+ level: toSarifLevel(r.severity),
119
+ },
120
+ properties: {
121
+ tags: ['security'],
122
+ 'security-severity': toSecuritySeverity(r.severity),
123
+ 'problem.severity': r.severity.toLowerCase(),
124
+ },
125
+ helpUri: 'https://github.com/GoPlusSecurity/agentguard',
126
+ }));
127
+
128
+ // Build results
129
+ const results = findings.map(f => {
130
+ const rule = RULE_INDEX.get(f.rule_id) || { severity: f.severity || 'MEDIUM' };
131
+ const level = toSarifLevel(f.severity || rule.severity);
132
+
133
+ const result = {
134
+ ruleId: f.rule_id,
135
+ level,
136
+ message: {
137
+ text: f.evidence
138
+ ? `${f.rule_id}: ${f.evidence}`
139
+ : (rule.description || f.rule_id),
140
+ },
141
+ };
142
+
143
+ if (f.file) {
144
+ result.locations = [{
145
+ physicalLocation: {
146
+ artifactLocation: {
147
+ uri: f.file.replace(/\\/g, '/'),
148
+ uriBaseId: '%SRCROOT%',
149
+ },
150
+ ...(f.line ? { region: { startLine: Number(f.line) } } : {}),
151
+ },
152
+ }];
153
+ }
154
+
155
+ return result;
156
+ });
157
+
158
+ return {
159
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
160
+ version: '2.1.0',
161
+ runs: [{
162
+ tool: {
163
+ driver: {
164
+ name: 'GoPlus AgentGuard',
165
+ version: '1.1',
166
+ informationUri: 'https://github.com/GoPlusSecurity/agentguard',
167
+ rules: driverRules,
168
+ },
169
+ },
170
+ invocations: [{
171
+ executionSuccessful: true,
172
+ commandLine: `agentguard scan ${input.target || ''}`,
173
+ startTimeUtc: input.scanned_at || new Date().toISOString(),
174
+ }],
175
+ artifacts: input.target ? [{
176
+ location: { uri: input.target, uriBaseId: '%SRCROOT%' },
177
+ description: { text: 'Scanned path' },
178
+ }] : [],
179
+ results,
180
+ properties: {
181
+ riskLevel: input.risk_level || 'UNKNOWN',
182
+ filesScanned: input.files_scanned || 0,
183
+ totalFindings: findings.length,
184
+ },
185
+ }],
186
+ };
187
+ }
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // Main
191
+ // ---------------------------------------------------------------------------
192
+
193
+ const input = readInput();
194
+ const sarif = buildSarif(input);
195
+ process.stdout.write(JSON.stringify(sarif, null, 2) + '\n');
@@ -4,11 +4,11 @@
4
4
  * GoPlus AgentGuard Trust CLI — lightweight wrapper for SkillRegistry operations.
5
5
  *
6
6
  * Usage:
7
- * node trust-cli.ts lookup --id <id> --source <source> --version <version> --hash <hash>
8
- * node trust-cli.ts attest --id <id> --source <source> --version <version> --hash <hash> --trust-level <level> [--preset <preset>] [--capabilities <json>] [--reviewed-by <name>] [--notes <text>] [--expires <iso>] [--force]
9
- * node trust-cli.ts revoke [--source <source>] [--key <record_key>] --reason <reason>
10
- * node trust-cli.ts list [--trust-level <level>] [--status <status>] [--source-pattern <pattern>]
11
- * node trust-cli.ts hash --path <dir>
7
+ * node trust-cli.js lookup --id <id> --source <source> --version <version> --hash <hash>
8
+ * node trust-cli.js attest --id <id> --source <source> --version <version> --hash <hash> --trust-level <level> [--preset <preset>] [--capabilities <json>] [--reviewed-by <name>] [--notes <text>] [--expires <iso>] [--force]
9
+ * node trust-cli.js revoke [--source <source>] [--key <record_key>] --reason <reason>
10
+ * node trust-cli.js list [--trust-level <level>] [--status <status>] [--source-pattern <pattern>]
11
+ * node trust-cli.js hash --path <dir>
12
12
  */
13
13
 
14
14
  import { createAgentGuard, CAPABILITY_PRESETS, SkillScanner } from '@goplus/agentguard';
@@ -16,13 +16,13 @@ import { createAgentGuard, CAPABILITY_PRESETS, SkillScanner } from '@goplus/agen
16
16
  const args = process.argv.slice(2);
17
17
  const command = args[0];
18
18
 
19
- function getArg(name: string): string | undefined {
19
+ function getArg(name) {
20
20
  const idx = args.indexOf(`--${name}`);
21
21
  if (idx === -1 || idx + 1 >= args.length) return undefined;
22
22
  return args[idx + 1];
23
23
  }
24
24
 
25
- function hasFlag(name: string): boolean {
25
+ function hasFlag(name) {
26
26
  return args.includes(`--${name}`);
27
27
  }
28
28
 
@@ -50,18 +50,14 @@ async function main() {
50
50
  version_ref: getArg('version') || '',
51
51
  artifact_hash: getArg('hash') || '',
52
52
  };
53
- const trustLevel = (getArg('trust-level') || 'restricted') as
54
- | 'untrusted'
55
- | 'restricted'
56
- | 'trusted';
53
+ const trustLevel = getArg('trust-level') || 'restricted';
57
54
 
58
55
  let capabilities;
59
56
  const preset = getArg('preset');
60
57
  if (preset && preset in CAPABILITY_PRESETS) {
61
- capabilities =
62
- CAPABILITY_PRESETS[preset as keyof typeof CAPABILITY_PRESETS];
58
+ capabilities = CAPABILITY_PRESETS[preset];
63
59
  } else if (getArg('capabilities')) {
64
- capabilities = JSON.parse(getArg('capabilities')!);
60
+ capabilities = JSON.parse(getArg('capabilities'));
65
61
  }
66
62
 
67
63
  const force = hasFlag('force');
@@ -94,7 +90,7 @@ async function main() {
94
90
  }
95
91
 
96
92
  case 'list': {
97
- const filters: Record<string, string> = {};
93
+ const filters = {};
98
94
  const trustLevel = getArg('trust-level');
99
95
  const status = getArg('status');
100
96
  const sourcePattern = getArg('source-pattern');
@@ -121,7 +117,7 @@ async function main() {
121
117
 
122
118
  default:
123
119
  console.error(
124
- 'Usage: trust-cli.ts <lookup|attest|revoke|list|hash> [options]'
120
+ 'Usage: trust-cli.js <lookup|attest|revoke|list|hash> [options]'
125
121
  );
126
122
  console.error('Run with --help for details.');
127
123
  process.exit(1);
@@ -0,0 +1,67 @@
1
+ # .agentguard-suppress.yaml
2
+ #
3
+ # Place this file in the root of your project (or skill directory) to suppress
4
+ # known false-positive findings from /agentguard scan.
5
+ #
6
+ # Rename this file to .agentguard-suppress.yaml to activate it.
7
+ #
8
+ # Fields per entry:
9
+ # rule (required) — AgentGuard rule ID to suppress (e.g. PRIVATE_KEY_PATTERN)
10
+ # paths (optional) — Glob patterns matched against the finding's file path.
11
+ # If omitted, the rule is suppressed across all files.
12
+ # domains (optional) — Substring/wildcard patterns matched against the finding's
13
+ # evidence text. Useful for WEBHOOK_EXFIL to allowlist known hosts.
14
+ # reason (required) — Human-readable explanation; appears in the suppression summary.
15
+ #
16
+ # Matching logic:
17
+ # - A finding is suppressed when its rule_id matches AND
18
+ # (paths match the file path OR domains match the evidence text).
19
+ # - If neither paths nor domains are specified, all findings for that rule are suppressed.
20
+ # - Glob path patterns: * matches within a directory, ** matches across directories.
21
+ # - Domain patterns: * is a wildcard prefix/suffix (e.g. *.internal.example.com).
22
+ #
23
+ # Suppressed findings are excluded from the report but counted in a summary line
24
+ # at the bottom: "N finding(s) suppressed via .agentguard-suppress.yaml"
25
+ #
26
+
27
+ suppress:
28
+ # Example 1: Suppress hardcoded-key findings in test fixture directories.
29
+ # These are dummy keys used only in unit tests and are never deployed.
30
+ - rule: PRIVATE_KEY_PATTERN
31
+ paths:
32
+ - "tests/fixtures/**"
33
+ - "test/data/**"
34
+ - "**/__mocks__/**"
35
+ reason: "Test fixture dummy keys — not real credentials"
36
+
37
+ # Example 2: Suppress mnemonic findings in test directories.
38
+ - rule: MNEMONIC_PATTERN
39
+ paths:
40
+ - "tests/**"
41
+ - "test/**"
42
+ - "spec/**"
43
+ reason: "Test mnemonic phrases — BIP-39 test vectors, not production seeds"
44
+
45
+ # Example 3: Allowlist an internal monitoring webhook domain.
46
+ # The webhook posts to an internal Grafana Oncall instance, not an attacker server.
47
+ - rule: WEBHOOK_EXFIL
48
+ domains:
49
+ - "hooks.monitoring.internal"
50
+ - "*.ops.example.com"
51
+ reason: "Internal monitoring webhook — not an exfiltration endpoint"
52
+
53
+ # Example 4: Suppress shell execution findings in dev-only tooling scripts.
54
+ # These scripts are not bundled in the published package.
55
+ - rule: SHELL_EXEC
56
+ paths:
57
+ - "scripts/dev/**"
58
+ - "tools/**"
59
+ reason: "Development tooling scripts — excluded from published package"
60
+
61
+ # Example 5: Suppress environment variable access in a configuration module
62
+ # that is specifically designed to read and validate env vars.
63
+ - rule: READ_ENV_SECRETS
64
+ paths:
65
+ - "src/config.ts"
66
+ - "src/config/**"
67
+ reason: "Intentional env var access — this module owns config loading"