@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.
- package/README.md +25 -8
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +3 -1
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/openclaw-plugin.d.ts.map +1 -1
- package/dist/adapters/openclaw-plugin.js +17 -3
- package/dist/adapters/openclaw-plugin.js.map +1 -1
- package/dist/cli.js +170 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -1
- package/dist/feed/cron.d.ts +27 -0
- package/dist/feed/cron.d.ts.map +1 -1
- package/dist/feed/cron.js +721 -54
- package/dist/feed/cron.js.map +1 -1
- package/dist/installers.d.ts +1 -1
- package/dist/installers.d.ts.map +1 -1
- package/dist/installers.js +164 -13
- package/dist/installers.js.map +1 -1
- package/dist/postinstall.js +29 -0
- package/dist/postinstall.js.map +1 -1
- package/dist/registry/storage.d.ts.map +1 -1
- package/dist/registry/storage.js +5 -1
- package/dist/registry/storage.js.map +1 -1
- package/dist/runtime/types.d.ts +1 -1
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/tests/cli-init.test.d.ts +2 -0
- package/dist/tests/cli-init.test.d.ts.map +1 -0
- package/dist/tests/cli-init.test.js +130 -0
- package/dist/tests/cli-init.test.js.map +1 -0
- package/dist/tests/cli-policy.test.js +47 -0
- package/dist/tests/cli-policy.test.js.map +1 -1
- package/dist/tests/cli-subscribe.test.js +33 -0
- package/dist/tests/cli-subscribe.test.js.map +1 -1
- package/dist/tests/feed-cron.test.js +441 -13
- package/dist/tests/feed-cron.test.js.map +1 -1
- package/dist/tests/installer.test.js +37 -2
- package/dist/tests/installer.test.js.map +1 -1
- package/dist/tests/integration.test.js +9 -5
- package/dist/tests/integration.test.js.map +1 -1
- package/dist/tests/postinstall.test.d.ts +2 -0
- package/dist/tests/postinstall.test.d.ts.map +1 -0
- package/dist/tests/postinstall.test.js +31 -0
- package/dist/tests/postinstall.test.js.map +1 -0
- package/dist/tests/setup-script.test.d.ts +2 -0
- package/dist/tests/setup-script.test.d.ts.map +1 -0
- package/dist/tests/setup-script.test.js +63 -0
- package/dist/tests/setup-script.test.js.map +1 -0
- package/dist/tests/smoke.test.js +88 -1
- package/dist/tests/smoke.test.js.map +1 -1
- package/docs/codex.md +1 -1
- package/docs/hermes.md +3 -3
- package/package.json +1 -1
- package/skills/agentguard/SKILL.md +424 -194
- package/skills/agentguard/hermes-hooks.yaml +2 -2
- package/skills/agentguard/scan-rules.md +13 -2
- package/skills/agentguard/scripts/{action-cli.ts → action-cli.js} +13 -18
- package/skills/agentguard/scripts/auto-scan.js +3 -1
- package/skills/agentguard/scripts/checkup-score.js +369 -0
- package/skills/agentguard/scripts/hermes-hook.js +103 -16
- package/skills/agentguard/scripts/scan-to-sarif.js +195 -0
- package/skills/agentguard/scripts/{trust-cli.ts → trust-cli.js} +12 -16
- 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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
257
|
+
if (!result) {
|
|
258
|
+
debugLog('allow: no runtime action was built');
|
|
259
|
+
outputAllow();
|
|
260
|
+
}
|
|
189
261
|
|
|
190
|
-
|
|
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 === '
|
|
193
|
-
outputBlock(result
|
|
194
|
-
} else if (result.decision === '
|
|
195
|
-
outputBlock(result
|
|
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.
|
|
8
|
-
* node trust-cli.
|
|
9
|
-
* node trust-cli.
|
|
10
|
-
* node trust-cli.
|
|
11
|
-
* node trust-cli.
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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.
|
|
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"
|