@goplus/agentguard 1.1.3 → 1.1.5
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 +28 -2
- package/dist/action/index.d.ts.map +1 -1
- package/dist/action/index.js +3 -1
- package/dist/action/index.js.map +1 -1
- package/dist/adapters/hermes.d.ts +25 -0
- package/dist/adapters/hermes.d.ts.map +1 -0
- package/dist/adapters/hermes.js +131 -0
- package/dist/adapters/hermes.js.map +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +3 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/openclaw-plugin.d.ts +12 -1
- package/dist/adapters/openclaw-plugin.d.ts.map +1 -1
- package/dist/adapters/openclaw-plugin.js +165 -9
- package/dist/adapters/openclaw-plugin.js.map +1 -1
- package/dist/cli.js +236 -0
- package/dist/cli.js.map +1 -1
- package/dist/cloud/client.d.ts +22 -0
- package/dist/cloud/client.d.ts.map +1 -1
- package/dist/cloud/client.js +61 -2
- package/dist/cloud/client.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -2
- package/dist/config.js.map +1 -1
- package/dist/feed/cron.d.ts +25 -0
- package/dist/feed/cron.d.ts.map +1 -0
- package/dist/feed/cron.js +173 -0
- package/dist/feed/cron.js.map +1 -0
- package/dist/feed/selfcheck.d.ts +36 -0
- package/dist/feed/selfcheck.d.ts.map +1 -0
- package/dist/feed/selfcheck.js +198 -0
- package/dist/feed/selfcheck.js.map +1 -0
- package/dist/feed/state.d.ts +14 -0
- package/dist/feed/state.d.ts.map +1 -0
- package/dist/feed/state.js +57 -0
- package/dist/feed/state.js.map +1 -0
- package/dist/feed/types.d.ts +102 -0
- package/dist/feed/types.d.ts.map +1 -0
- package/dist/feed/types.js +15 -0
- package/dist/feed/types.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/installers.js +0 -1
- package/dist/installers.js.map +1 -1
- package/dist/runtime/protect.d.ts.map +1 -1
- package/dist/runtime/protect.js +6 -1
- package/dist/runtime/protect.js.map +1 -1
- package/dist/tests/adapter.test.js +146 -0
- package/dist/tests/adapter.test.js.map +1 -1
- package/dist/tests/feed-cloud.test.d.ts +2 -0
- package/dist/tests/feed-cloud.test.d.ts.map +1 -0
- package/dist/tests/feed-cloud.test.js +93 -0
- package/dist/tests/feed-cloud.test.js.map +1 -0
- package/dist/tests/feed-cron.test.d.ts +2 -0
- package/dist/tests/feed-cron.test.d.ts.map +1 -0
- package/dist/tests/feed-cron.test.js +78 -0
- package/dist/tests/feed-cron.test.js.map +1 -0
- package/dist/tests/feed-selfcheck.test.d.ts +2 -0
- package/dist/tests/feed-selfcheck.test.d.ts.map +1 -0
- package/dist/tests/feed-selfcheck.test.js +118 -0
- package/dist/tests/feed-selfcheck.test.js.map +1 -0
- package/dist/tests/installer.test.js +3 -1
- package/dist/tests/installer.test.js.map +1 -1
- package/dist/tests/integration.test.js +211 -1
- package/dist/tests/integration.test.js.map +1 -1
- package/dist/tests/runtime-cloud.test.js +30 -2
- package/dist/tests/runtime-cloud.test.js.map +1 -1
- package/dist/tests/smoke.test.js +141 -7
- package/dist/tests/smoke.test.js.map +1 -1
- package/docs/hermes.md +70 -0
- package/package.json +1 -1
- package/skills/agentguard/README.md +12 -0
- package/skills/agentguard/SKILL.md +104 -3
- package/skills/agentguard/hermes-hooks.yaml +31 -0
- package/skills/agentguard/package.json +1 -1
- package/skills/agentguard/scripts/auto-scan.js +3 -2
- package/skills/agentguard/scripts/hermes-hook.js +201 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# GoPlus AgentGuard hook template for Hermes Agent.
|
|
2
|
+
#
|
|
3
|
+
# Copy this block into ~/.hermes/config.yaml and replace AGENTGUARD_SKILL_DIR
|
|
4
|
+
# with the absolute path to the installed AgentGuard skill directory, e.g.
|
|
5
|
+
# ~/.hermes/skills/agentguard or ~/.openclaw/skills/agentguard.
|
|
6
|
+
|
|
7
|
+
hooks:
|
|
8
|
+
on_session_start:
|
|
9
|
+
- command: "env AGENTGUARD_AUTO_SCAN=1 node \"AGENTGUARD_SKILL_DIR/scripts/auto-scan.js\""
|
|
10
|
+
timeout: 30
|
|
11
|
+
|
|
12
|
+
pre_tool_call:
|
|
13
|
+
- matcher: "terminal|execute_code"
|
|
14
|
+
command: "node \"AGENTGUARD_SKILL_DIR/scripts/hermes-hook.js\""
|
|
15
|
+
timeout: 10
|
|
16
|
+
- matcher: "write_file|patch|skill_manage"
|
|
17
|
+
command: "node \"AGENTGUARD_SKILL_DIR/scripts/hermes-hook.js\""
|
|
18
|
+
timeout: 10
|
|
19
|
+
- matcher: "read_file"
|
|
20
|
+
command: "node \"AGENTGUARD_SKILL_DIR/scripts/hermes-hook.js\""
|
|
21
|
+
timeout: 10
|
|
22
|
+
- matcher: "web_search|web_extract|browser_navigate"
|
|
23
|
+
command: "node \"AGENTGUARD_SKILL_DIR/scripts/hermes-hook.js\""
|
|
24
|
+
timeout: 10
|
|
25
|
+
|
|
26
|
+
post_tool_call:
|
|
27
|
+
- matcher: "terminal|execute_code|write_file|patch|skill_manage|read_file|web_search|web_extract|browser_navigate"
|
|
28
|
+
command: "node \"AGENTGUARD_SKILL_DIR/scripts/hermes-hook.js\""
|
|
29
|
+
timeout: 5
|
|
30
|
+
|
|
31
|
+
hooks_auto_accept: false
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* GoPlus AgentGuard — SessionStart Auto-Scan Hook
|
|
5
5
|
*
|
|
6
6
|
* Runs on session startup to discover and scan newly installed skills.
|
|
7
|
-
* For each skill in
|
|
7
|
+
* For each skill in supported agent skill directories:
|
|
8
8
|
* 1. Calculate artifact hash
|
|
9
9
|
* 2. Check trust registry — skip if already registered with same hash
|
|
10
10
|
* 3. Run quickScan for new/updated skills
|
|
@@ -59,6 +59,7 @@ try {
|
|
|
59
59
|
|
|
60
60
|
const SKILLS_DIRS = [
|
|
61
61
|
join(homedir(), '.claude', 'skills'),
|
|
62
|
+
join(homedir(), '.hermes', 'skills'),
|
|
62
63
|
join(homedir(), '.openclaw', 'skills'),
|
|
63
64
|
];
|
|
64
65
|
const AGENTGUARD_DIR = join(homedir(), '.agentguard');
|
|
@@ -84,7 +85,7 @@ function writeAuditLog(entry) {
|
|
|
84
85
|
// ---------------------------------------------------------------------------
|
|
85
86
|
|
|
86
87
|
/**
|
|
87
|
-
* Find all skill directories under
|
|
88
|
+
* Find all skill directories under supported agent skill roots.
|
|
88
89
|
* A skill directory is one that contains a SKILL.md file.
|
|
89
90
|
*/
|
|
90
91
|
function discoverSkills() {
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GoPlus AgentGuard Hermes shell hook.
|
|
5
|
+
*
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
|
|
15
|
+
function isPostHook(input) {
|
|
16
|
+
const event = typeof input?.hook_event_name === 'string' ? input.hook_event_name : '';
|
|
17
|
+
return event.startsWith('post');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isPreHook(input) {
|
|
21
|
+
return !isPostHook(input);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function toolNameFrom(input) {
|
|
25
|
+
return typeof input?.tool_name === 'string' ? input.tool_name : '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toolInputFrom(input) {
|
|
29
|
+
const toolInput = input?.tool_input ?? input?.args;
|
|
30
|
+
return toolInput && typeof toolInput === 'object' && !Array.isArray(toolInput)
|
|
31
|
+
? toolInput
|
|
32
|
+
: {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function firstString(...values) {
|
|
36
|
+
for (const value of values) {
|
|
37
|
+
if (typeof value === 'string' && value.length > 0) return value;
|
|
38
|
+
}
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function validatePreToolPayload(input) {
|
|
43
|
+
const toolName = toolNameFrom(input);
|
|
44
|
+
const toolInput = toolInputFrom(input);
|
|
45
|
+
|
|
46
|
+
switch (toolName) {
|
|
47
|
+
case 'terminal':
|
|
48
|
+
if (!firstString(toolInput.command)) return 'Hermes terminal hook payload is missing command';
|
|
49
|
+
return null;
|
|
50
|
+
case 'execute_code':
|
|
51
|
+
if (!firstString(toolInput.code, toolInput.command)) return 'Hermes execute_code hook payload is missing code';
|
|
52
|
+
return null;
|
|
53
|
+
case 'write_file':
|
|
54
|
+
case 'patch':
|
|
55
|
+
case 'read_file':
|
|
56
|
+
if (!firstString(toolInput.path, toolInput.file_path)) return `Hermes ${toolName} hook payload is missing path`;
|
|
57
|
+
return null;
|
|
58
|
+
case 'skill_manage':
|
|
59
|
+
if (!firstString(toolInput.path, toolInput.file_path, toolInput.target, toolInput.skill_path)) {
|
|
60
|
+
return 'Hermes skill_manage hook payload is missing target path';
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
case 'web_extract':
|
|
64
|
+
case 'browser_navigate':
|
|
65
|
+
if (!firstString(toolInput.url, toolInput.href, toolInput.target)) return `Hermes ${toolName} hook payload is missing URL`;
|
|
66
|
+
return null;
|
|
67
|
+
case 'web_search':
|
|
68
|
+
if (!firstString(toolInput.query, toolInput.url)) return 'Hermes web_search hook payload is missing query';
|
|
69
|
+
return null;
|
|
70
|
+
default:
|
|
71
|
+
return `Hermes tool "${toolName || '(missing)'}" is not recognized by AgentGuard`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function shouldFailClosed(input) {
|
|
76
|
+
return !input || isPreHook(input);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Load AgentGuard engine + Hermes adapter
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
const agentguardPath = join(import.meta.url.replace('file://', ''), '..', '..', '..', '..', 'dist', 'index.js');
|
|
84
|
+
|
|
85
|
+
let createAgentGuard, HermesAdapter, evaluateHook, loadConfig;
|
|
86
|
+
|
|
87
|
+
async function loadEngine() {
|
|
88
|
+
if (process.env.AGENTGUARD_TEST_FORCE_ENGINE_LOAD_FAILURE === '1') {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const gs = await import(agentguardPath);
|
|
94
|
+
return {
|
|
95
|
+
createAgentGuard: gs.createAgentGuard || gs.default,
|
|
96
|
+
HermesAdapter: gs.HermesAdapter,
|
|
97
|
+
evaluateHook: gs.evaluateHook,
|
|
98
|
+
loadConfig: gs.loadConfig,
|
|
99
|
+
};
|
|
100
|
+
} catch {
|
|
101
|
+
try {
|
|
102
|
+
const gs = await import('@goplus/agentguard');
|
|
103
|
+
return {
|
|
104
|
+
createAgentGuard: gs.createAgentGuard || gs.default,
|
|
105
|
+
HermesAdapter: gs.HermesAdapter,
|
|
106
|
+
evaluateHook: gs.evaluateHook,
|
|
107
|
+
loadConfig: gs.loadConfig,
|
|
108
|
+
};
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Read stdin
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
function readStdin() {
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
let data = '';
|
|
122
|
+
let settled = false;
|
|
123
|
+
const finish = (value) => {
|
|
124
|
+
if (settled) return;
|
|
125
|
+
settled = true;
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
resolve(value);
|
|
128
|
+
};
|
|
129
|
+
const timer = setTimeout(() => finish(null), 5000);
|
|
130
|
+
|
|
131
|
+
process.stdin.setEncoding('utf-8');
|
|
132
|
+
process.stdin.on('data', (chunk) => (data += chunk));
|
|
133
|
+
process.stdin.on('end', () => {
|
|
134
|
+
try {
|
|
135
|
+
finish(JSON.parse(data));
|
|
136
|
+
} catch {
|
|
137
|
+
finish(null);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
process.stdin.on('error', () => finish(null));
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Hermes output helpers
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
function outputBlock(reason) {
|
|
149
|
+
console.log(JSON.stringify({
|
|
150
|
+
action: 'block',
|
|
151
|
+
message: reason || 'GoPlus AgentGuard blocked this action',
|
|
152
|
+
}));
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function outputAllow() {
|
|
157
|
+
console.log('{}');
|
|
158
|
+
process.exit(0);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Main
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
async function main() {
|
|
166
|
+
const input = await readStdin();
|
|
167
|
+
if (!input) {
|
|
168
|
+
outputBlock('GoPlus AgentGuard: invalid or missing Hermes hook payload');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const validationError = isPreHook(input) ? validatePreToolPayload(input) : null;
|
|
172
|
+
if (validationError) {
|
|
173
|
+
outputBlock(`GoPlus AgentGuard: ${validationError}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const engine = await loadEngine();
|
|
177
|
+
if (!engine) {
|
|
178
|
+
if (shouldFailClosed(input)) {
|
|
179
|
+
outputBlock('GoPlus AgentGuard: unable to load Hermes hook engine; blocking fail-closed');
|
|
180
|
+
}
|
|
181
|
+
outputAllow();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
({ createAgentGuard, HermesAdapter, evaluateHook, loadConfig } = engine);
|
|
185
|
+
|
|
186
|
+
const adapter = new HermesAdapter();
|
|
187
|
+
const config = loadConfig();
|
|
188
|
+
const agentguard = createAgentGuard();
|
|
189
|
+
|
|
190
|
+
const result = await evaluateHook(adapter, input, { config, agentguard });
|
|
191
|
+
|
|
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');
|
|
196
|
+
} else {
|
|
197
|
+
outputAllow();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
main();
|