guard-scanner 1.1.0 â 1.1.1
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 +7 -4
- package/SKILL.md +15 -8
- package/hooks/guard-scanner/handler.ts +98 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
<h1 align="center">đĄī¸ guard-scanner</h1>
|
|
3
3
|
<p align="center">
|
|
4
4
|
<strong>Static security scanner for AI agent skills</strong><br>
|
|
5
|
-
Detect prompt injection, credential theft, exfiltration, identity hijacking, and 17 more threat categories
|
|
5
|
+
Detect prompt injection, credential theft, exfiltration, identity hijacking, and 17 more threat categories.<br>
|
|
6
|
+
<sub>Runtime Guard hook included â pending <a href="https://github.com/openclaw/openclaw/issues/18677">OpenClaw hook API adoption</a></sub>
|
|
6
7
|
</p>
|
|
7
8
|
<p align="center">
|
|
8
9
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
|
|
@@ -90,6 +91,8 @@ openclaw skill install guard-scanner
|
|
|
90
91
|
guard-scanner ~/.openclaw/workspace/skills/ --self-exclude --verbose
|
|
91
92
|
```
|
|
92
93
|
|
|
94
|
+
> **â ī¸ Runtime Guard (handler.ts)** â The real-time `before_tool_call` hook requires OpenClaw's Hook API ([Issue #18677](https://github.com/openclaw/openclaw/issues/18677)). The hook is registered and runs on `agent:before_tool_call` events, but OpenClaw's `InternalHookEvent` does not yet expose a cancel/veto mechanism â so **detections are warned but not blocked**. The static scanner (`npx guard-scanner`) works fully and independently.
|
|
95
|
+
|
|
93
96
|
---
|
|
94
97
|
|
|
95
98
|
## Threat Categories
|
|
@@ -128,7 +131,7 @@ guard-scanner covers **20 threat categories** derived from three taxonomies:
|
|
|
128
131
|
### Terminal (Default)
|
|
129
132
|
|
|
130
133
|
```
|
|
131
|
-
đĄī¸ guard-scanner v1.
|
|
134
|
+
đĄī¸ guard-scanner v1.1.0
|
|
132
135
|
ââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
133
136
|
đ Scanning: ./skills/
|
|
134
137
|
đĻ Skills found: 22
|
|
@@ -391,7 +394,7 @@ guard-scanner/
|
|
|
391
394
|
â âââ cli.js # CLI entry point and argument parser
|
|
392
395
|
âââ hooks/
|
|
393
396
|
â âââ guard-scanner/
|
|
394
|
-
â âââ handler.ts # Runtime Guard â before_tool_call hook
|
|
397
|
+
â âââ handler.ts # Runtime Guard â before_tool_call hook (experimental, pending OpenClaw API)
|
|
395
398
|
âââ test/
|
|
396
399
|
â âââ scanner.test.js # 55 tests across 13 sections
|
|
397
400
|
â âââ fixtures/ # Malicious, clean, complex, config-changer samples
|
|
@@ -605,7 +608,7 @@ guard-scanner catches threats **before** installation. But what happens **after*
|
|
|
605
608
|
| | guard-scanner (OSS) | GuavaSuite (Private) |
|
|
606
609
|
|---|---|---|
|
|
607
610
|
| Static scan | â
20 categories | â
20 categories |
|
|
608
|
-
| Runtime blocking |
|
|
611
|
+
| Runtime blocking | â ī¸ Warn only (cancel API pending) | â
Real-time `before_tool_call` guard |
|
|
609
612
|
| SOUL.md integrity | Pattern detection only | â
SHA-256 hash watchdog |
|
|
610
613
|
| On-chain verification | â | â
SoulChain (Polygon) |
|
|
611
614
|
| Identity recovery | â | â
Automatic rollback |
|
package/SKILL.md
CHANGED
|
@@ -29,7 +29,7 @@ metadata:
|
|
|
29
29
|
# guard-scanner đĄī¸
|
|
30
30
|
|
|
31
31
|
Static + runtime security scanner for AI agent skills.
|
|
32
|
-
**
|
|
32
|
+
**186+ threat patterns** across **20 categories** â zero dependencies.
|
|
33
33
|
|
|
34
34
|
## When To Use This Skill
|
|
35
35
|
|
|
@@ -54,7 +54,9 @@ Scan a specific skill:
|
|
|
54
54
|
node skills/guard-scanner/src/cli.js /path/to/new-skill/ --strict --verbose
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
### 2. Runtime Guard (Real-time Protection)
|
|
57
|
+
### 2. Runtime Guard (Real-time Protection) â â ī¸ Experimental
|
|
58
|
+
|
|
59
|
+
> **Note:** Requires the OpenClaw Hook API ([Issue #18677](https://github.com/openclaw/openclaw/issues/18677)), which has not been officially adopted yet. The handler is included for early testing and will be updated once the API is finalized.
|
|
58
60
|
|
|
59
61
|
Install the hook to block dangerous tool calls before execution:
|
|
60
62
|
|
|
@@ -83,11 +85,13 @@ openclaw hooks enable guard-scanner
|
|
|
83
85
|
|
|
84
86
|
Set in `openclaw.json` â `hooks.internal.entries.guard-scanner.mode`:
|
|
85
87
|
|
|
86
|
-
| Mode | Behavior |
|
|
87
|
-
|
|
88
|
-
| `monitor` | Log all, never block |
|
|
89
|
-
| `enforce` (default) | Block CRITICAL threats |
|
|
90
|
-
| `strict` | Block HIGH + CRITICAL |
|
|
88
|
+
| Mode | Intended Behavior | Current Status |
|
|
89
|
+
|------|-------------------|----------------|
|
|
90
|
+
| `monitor` | Log all, never block | â
Fully working |
|
|
91
|
+
| `enforce` (default) | Block CRITICAL threats | â ī¸ Warn only (cancel API pending) |
|
|
92
|
+
| `strict` | Block HIGH + CRITICAL | â ī¸ Warn only (cancel API pending) |
|
|
93
|
+
|
|
94
|
+
> **Note:** OpenClaw's `InternalHookEvent` does not yet expose a `cancel`/`veto` mechanism. All detections are currently logged and warned via `event.messages`, but tool execution cannot be blocked. Blocking will be enabled when the cancel API is added.
|
|
91
95
|
|
|
92
96
|
## Threat Categories
|
|
93
97
|
|
|
@@ -110,6 +114,9 @@ Set in `openclaw.json` â `hooks.internal.entries.guard-scanner.mode`:
|
|
|
110
114
|
| 15 | CVE Patterns | Known agent vulnerabilities |
|
|
111
115
|
| 16 | MCP Security | Tool/schema poisoning, SSRF |
|
|
112
116
|
| 17 | Identity Hijacking | SOUL.md/IDENTITY.md tampering |
|
|
117
|
+
| 18 | Sandbox Validation | Dangerous binaries, broad file scope, sensitive env |
|
|
118
|
+
| 19 | Code Complexity | Excessive file length, deep nesting, eval density |
|
|
119
|
+
| 20 | Config Impact | openclaw.json writes, exec approval bypass |
|
|
113
120
|
|
|
114
121
|
## External Endpoints
|
|
115
122
|
|
|
@@ -140,7 +147,7 @@ an AI agent's SOUL.md personality file, and no existing tool could detect it.
|
|
|
140
147
|
|
|
141
148
|
- **Open source**: Full source code available at https://github.com/koatora20/guard-scanner
|
|
142
149
|
- **Zero dependencies**: Nothing to audit, no transitive risks
|
|
143
|
-
- **Test suite**:
|
|
150
|
+
- **Test suite**: 55 tests across 13 sections, 100% pass rate
|
|
144
151
|
- **Taxonomy**: Based on Snyk ToxicSkills (Feb 2026), OWASP MCP Top 10, and original research
|
|
145
152
|
- **Complementary to VirusTotal**: Detects prompt injection and LLM-specific attacks
|
|
146
153
|
that VirusTotal's signature-based scanning cannot catch
|
|
@@ -1,26 +1,69 @@
|
|
|
1
|
-
import type { HookHandler } from "../../src/hooks/hooks.js";
|
|
2
|
-
import { appendFileSync, mkdirSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { homedir } from "os";
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
|
-
* guard-scanner Runtime Guard â
|
|
8
|
-
*
|
|
9
|
-
* Intercepts tool
|
|
10
|
-
* threat intelligence patterns. Zero dependencies.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
2
|
+
* guard-scanner Runtime Guard â Hook Handler
|
|
3
|
+
*
|
|
4
|
+
* Intercepts agent tool calls and checks arguments against
|
|
5
|
+
* runtime threat intelligence patterns. Zero dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Registered for event: agent:before_tool_call
|
|
8
|
+
*
|
|
9
|
+
* Current limitation:
|
|
10
|
+
* The OpenClaw InternalHookEvent interface does not yet expose a
|
|
11
|
+
* `cancel` / `veto` mechanism. This handler can WARN via
|
|
12
|
+
* event.messages but cannot block tool execution.
|
|
13
|
+
* When a cancel API is introduced, this handler will be updated
|
|
14
|
+
* to actually block CRITICAL/HIGH threats.
|
|
15
|
+
*
|
|
16
|
+
* Modes (for future blocking behaviour):
|
|
17
|
+
* monitor â log only (current effective behaviour for all modes)
|
|
18
|
+
* enforce â will block CRITICAL when cancel API is available
|
|
19
|
+
* strict â will block HIGH+CRITICAL when cancel API is available
|
|
20
|
+
*
|
|
17
21
|
* @author Guava đ & Dee
|
|
18
|
-
* @version 1.
|
|
22
|
+
* @version 1.1.0
|
|
19
23
|
* @license MIT
|
|
20
24
|
*/
|
|
21
25
|
|
|
26
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
27
|
+
import { join } from "path";
|
|
28
|
+
import { homedir } from "os";
|
|
29
|
+
|
|
30
|
+
// ââ OpenClaw Hook Types (from openclaw/src/hooks/internal-hooks.ts) ââ
|
|
31
|
+
// Inline types to avoid broken relative-path imports.
|
|
32
|
+
// These match the official InternalHookEvent / InternalHookHandler
|
|
33
|
+
// from OpenClaw v2026.2.15.
|
|
34
|
+
|
|
35
|
+
type InternalHookEventType = "command" | "session" | "agent" | "gateway";
|
|
36
|
+
|
|
37
|
+
interface InternalHookEvent {
|
|
38
|
+
/** The type of event */
|
|
39
|
+
type: InternalHookEventType;
|
|
40
|
+
/** The specific action within the type (e.g., "before_tool_call") */
|
|
41
|
+
action: string;
|
|
42
|
+
/** The session key this event relates to */
|
|
43
|
+
sessionKey: string;
|
|
44
|
+
/** Additional context specific to the event */
|
|
45
|
+
context: Record<string, unknown>;
|
|
46
|
+
/** Timestamp when the event occurred */
|
|
47
|
+
timestamp: Date;
|
|
48
|
+
/** Messages to send back to the user (hooks can push to this array) */
|
|
49
|
+
messages: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type InternalHookHandler = (event: InternalHookEvent) => Promise<void> | void;
|
|
53
|
+
|
|
54
|
+
// Re-export as the public types for compatibility
|
|
55
|
+
type HookHandler = InternalHookHandler;
|
|
56
|
+
type HookEvent = InternalHookEvent;
|
|
57
|
+
|
|
22
58
|
// ââ Runtime threat patterns (12 checks) ââ
|
|
23
|
-
|
|
59
|
+
interface RuntimeCheck {
|
|
60
|
+
id: string;
|
|
61
|
+
severity: "CRITICAL" | "HIGH" | "MEDIUM";
|
|
62
|
+
desc: string;
|
|
63
|
+
test: (s: string) => boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const RUNTIME_CHECKS: RuntimeCheck[] = [
|
|
24
67
|
{
|
|
25
68
|
id: 'RT_REVSHELL', severity: 'CRITICAL', desc: 'Reverse shell attempt',
|
|
26
69
|
test: (s: string) => /\/dev\/tcp\/|nc\s+-e|ncat\s+-e|bash\s+-i\s+>&|socat\s+TCP/i.test(s)
|
|
@@ -78,11 +121,11 @@ const RUNTIME_CHECKS = [
|
|
|
78
121
|
const AUDIT_DIR = join(homedir(), ".openclaw", "guard-scanner");
|
|
79
122
|
const AUDIT_FILE = join(AUDIT_DIR, "audit.jsonl");
|
|
80
123
|
|
|
81
|
-
function ensureAuditDir() {
|
|
124
|
+
function ensureAuditDir(): void {
|
|
82
125
|
try { mkdirSync(AUDIT_DIR, { recursive: true }); } catch { }
|
|
83
126
|
}
|
|
84
127
|
|
|
85
|
-
function logAudit(entry: Record<string, unknown>) {
|
|
128
|
+
function logAudit(entry: Record<string, unknown>): void {
|
|
86
129
|
ensureAuditDir();
|
|
87
130
|
const line = JSON.stringify({ ...entry, ts: new Date().toISOString() }) + '\n';
|
|
88
131
|
try { appendFileSync(AUDIT_FILE, line); } catch { }
|
|
@@ -90,16 +133,21 @@ function logAudit(entry: Record<string, unknown>) {
|
|
|
90
133
|
|
|
91
134
|
// ââ Main Handler ââ
|
|
92
135
|
const handler: HookHandler = async (event) => {
|
|
93
|
-
// Only handle before_tool_call
|
|
136
|
+
// Only handle agent:before_tool_call events
|
|
94
137
|
if (event.type !== "agent" || event.action !== "before_tool_call") return;
|
|
95
138
|
|
|
96
|
-
const { toolName, toolArgs } =
|
|
139
|
+
const { toolName, toolArgs } = event.context as {
|
|
140
|
+
toolName?: string;
|
|
141
|
+
toolArgs?: Record<string, unknown>;
|
|
142
|
+
};
|
|
97
143
|
if (!toolName || !toolArgs) return;
|
|
98
144
|
|
|
99
|
-
// Get mode from config
|
|
100
|
-
const
|
|
145
|
+
// Get mode from context config (if available)
|
|
146
|
+
const cfg = event.context.cfg as Record<string, unknown> | undefined;
|
|
147
|
+
const hookEntries = (cfg as any)?.hooks?.internal?.entries?.['guard-scanner'] as Record<string, unknown> | undefined;
|
|
148
|
+
const mode = (hookEntries?.mode as string) || 'enforce';
|
|
101
149
|
|
|
102
|
-
// Only check
|
|
150
|
+
// Only check tools that can cause damage
|
|
103
151
|
const dangerousTools = new Set(['exec', 'write', 'edit', 'browser', 'web_fetch', 'message']);
|
|
104
152
|
if (!dangerousTools.has(toolName)) return;
|
|
105
153
|
|
|
@@ -113,35 +161,39 @@ const handler: HookHandler = async (event) => {
|
|
|
113
161
|
severity: check.severity,
|
|
114
162
|
desc: check.desc,
|
|
115
163
|
mode,
|
|
116
|
-
action: '
|
|
117
|
-
session:
|
|
164
|
+
action: 'warned' as string,
|
|
165
|
+
session: event.sessionKey,
|
|
118
166
|
};
|
|
119
167
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
|
|
168
|
+
// NOTE: OpenClaw InternalHookEvent does not currently support
|
|
169
|
+
// a cancel/veto mechanism. When it does, uncomment the blocking
|
|
170
|
+
// logic below. For now, all detections are warnings only.
|
|
171
|
+
//
|
|
172
|
+
// if (mode === 'strict' && (check.severity === 'CRITICAL' || check.severity === 'HIGH')) {
|
|
173
|
+
// entry.action = 'blocked';
|
|
174
|
+
// logAudit(entry);
|
|
175
|
+
// event.messages.push(`đĄī¸ guard-scanner BLOCKED: ${check.desc} [${check.id}]`);
|
|
176
|
+
// event.cancel = true; // Not yet in the public API
|
|
177
|
+
// return;
|
|
178
|
+
// }
|
|
179
|
+
//
|
|
180
|
+
// if (mode === 'enforce' && check.severity === 'CRITICAL') {
|
|
181
|
+
// entry.action = 'blocked';
|
|
182
|
+
// logAudit(entry);
|
|
183
|
+
// event.messages.push(`đĄī¸ guard-scanner BLOCKED: ${check.desc} [${check.id}]`);
|
|
184
|
+
// event.cancel = true; // Not yet in the public API
|
|
185
|
+
// return;
|
|
186
|
+
// }
|
|
187
|
+
|
|
188
|
+
// Current behaviour: warn and log for all modes
|
|
140
189
|
logAudit(entry);
|
|
141
190
|
|
|
142
191
|
if (check.severity === 'CRITICAL') {
|
|
143
192
|
event.messages.push(`đĄī¸ guard-scanner WARNING: ${check.desc} [${check.id}]`);
|
|
144
193
|
console.warn(`[guard-scanner] â ī¸ WARNING: ${check.desc} [${check.id}]`);
|
|
194
|
+
} else if (check.severity === 'HIGH') {
|
|
195
|
+
event.messages.push(`đĄī¸ guard-scanner NOTICE: ${check.desc} [${check.id}]`);
|
|
196
|
+
console.warn(`[guard-scanner] âšī¸ NOTICE: ${check.desc} [${check.id}]`);
|
|
145
197
|
}
|
|
146
198
|
}
|
|
147
199
|
}
|
package/package.json
CHANGED