openclaw-guardian 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fatcatMaoFei
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # openclaw-guardian
2
+
3
+ > **The missing safety layer for AI agents.**
4
+
5
+ ## Why This Exists
6
+
7
+ OpenClaw is powerful — it gives AI agents direct access to shell commands, file operations, email, browser automation, and more. That power is exactly what makes it useful, but it's also what makes people nervous.
8
+
9
+ The community has been vocal: *"security nightmare"*, *"what if the AI deletes my files?"*, *"I don't trust it with my credentials"*. OpenClaw's existing safety (sandbox + allowlist + manual confirmation) only covers `exec`, and it's all-or-nothing — either you trust the agent completely, or you block everything.
10
+
11
+ **openclaw-guardian** fills that gap. It sits between the AI's decision and the actual execution, using a two-tier blacklist to catch dangerous operations and LLM-based intent verification to confirm the user actually asked for them. Think of it as a security checkpoint that only stops you when you're carrying something dangerous — and even then, it just checks your ID before letting you through.
12
+
13
+ The key insight: **99% of what an AI agent does is harmless** (reading files, fetching URLs, writing notes). Only ~1% is potentially dangerous (deleting files, running destructive commands, accessing secrets). Guardian only intervenes on that 1%, so you get safety without sacrificing speed.
14
+
15
+ ## How It Works
16
+
17
+ ```
18
+ AI Agent wants to run a tool (e.g., exec "rm -rf /tmp/data")
19
+
20
+ ┌───────────────────────┐
21
+ │ Blacklist Matcher │ ← Keyword rules, 0ms, no model call
22
+ │ critical / warning │
23
+ └───────────┬───────────┘
24
+
25
+ ┌───────────────┼───────────────┐
26
+ ↓ ↓ ↓
27
+ No match warning critical
28
+ (just go) (1 LLM vote) (3 LLM votes)
29
+ ↓ ↓ ↓
30
+ Execute 1 vote check 3 parallel votes
31
+ 0ms ~1-2s ~2-4s
32
+ ↓ ↓
33
+ confirmed? → ALL 3 confirmed?
34
+ yes: execute yes: execute
35
+ no: block no: block
36
+ ```
37
+
38
+ ### Two-Tier Blacklist
39
+
40
+ | Tier | LLM Votes | Threshold | Latency | When |
41
+ |------|-----------|-----------|---------|------|
42
+ | No match | 0 | — | 0ms | Reading files, fetching URLs, normal operations |
43
+ | Warning | 1 | 1/1 | ~1-2s | `sudo`, `rm -r`, `chmod 777`, writing to `.env` |
44
+ | Critical | 3 | 3/3 | ~2-4s | `rm -rf /`, `mkfs`, `dd of=/dev/`, writing to `/etc/passwd` |
45
+
46
+ ### What Gets Flagged
47
+
48
+ **Critical** (irreversible destruction or system compromise — needs 3/3 unanimous LLM confirmation):
49
+
50
+ | Pattern | Why |
51
+ |---------|-----|
52
+ | `rm -rf` on system paths | Filesystem destruction |
53
+ | `mkfs`, `dd of=/dev/` | Disk-level destruction |
54
+ | Write to `/etc/passwd`, `/etc/shadow`, `/etc/sudoers` | System auth compromise |
55
+ | `shutdown`, `reboot` | System availability |
56
+ | `curl \| bash`, `base64 -d \| sh` | Remote code execution |
57
+ | `xargs rm`, `find -delete` | Indirect bulk deletion |
58
+
59
+ **Warning** (risky but possibly intentional — needs 1/1 LLM confirmation):
60
+
61
+ | Pattern | Why |
62
+ |---------|-----|
63
+ | `sudo` | Privilege escalation |
64
+ | `rm -r` (non-system paths) | Recursive deletion |
65
+ | `chmod 777`, `chmod -R`, `chown -R` | Dangerous permissions |
66
+ | `kill -9`, `killall`, `pkill` | Force kill processes |
67
+ | `systemctl stop/disable` | Service disruption |
68
+ | `eval` | Arbitrary code execution (review before running) |
69
+ | Write to `.env`, `.ssh/`, `openclaw.json` | Sensitive file modification |
70
+
71
+ ### LLM Intent Verification
72
+
73
+ When a blacklist rule matches, Guardian doesn't just block — it reads the recent conversation context and asks a lightweight LLM: **"Did the user explicitly request this operation?"**
74
+
75
+ - Uses the cheapest/fastest model available from your existing OpenClaw config (prefers Haiku, GPT-4o-mini, Gemini Flash)
76
+ - No separate API key needed — piggybacks on whatever you already have configured
77
+ - If LLM is unavailable: critical → block (fail-safe), warning → ask user
78
+
79
+ ### Whitelist (Always Allowed)
80
+
81
+ These commands are considered safe and will never be flagged, even if they appear inside a broader command:
82
+
83
+ | Pattern | Why |
84
+ |---------|-----|
85
+ | `mkdir` | Creating directories is non-destructive |
86
+ | `touch` | Creating empty files is non-destructive |
87
+ | `tar`, `unzip`, `gzip`, `gunzip`, `bzip2`, `xz` | Archive extraction/compression — normal dev workflow |
88
+ | `openclaw` (CLI) | OpenClaw's own CLI commands (e.g., `openclaw gateway status`) |
89
+
90
+ Whitelist rules are checked **before** the blacklist. If a command matches a whitelist pattern, it passes through immediately with zero overhead.
91
+
92
+ ### Tool-Level Blacklist
93
+
94
+ Guardian doesn't just inspect `exec`, `write`, and `edit` — it also scans tool calls for **any** tool (e.g., `message`, `browser`, database clients, email plugins). To avoid false positives on normal payloads, it only checks action-oriented fields: `action`, `method`, `command`, and `operation` — not the entire parameter object.
95
+
96
+ **Critical** (3/3 unanimous LLM confirmation):
97
+
98
+ | Pattern | Why |
99
+ |---------|-----|
100
+ | `batchDelete`, `expunge`, `emptyTrash`, `purge` | Bulk email deletion — irreversible mailbox destruction |
101
+ | `DROP DATABASE`, `DROP TABLE`, `TRUNCATE`, `DELETE FROM` | Database destruction — irreversible data loss |
102
+
103
+ **Warning** (1/1 LLM confirmation):
104
+
105
+ | Pattern | Why |
106
+ |---------|-----|
107
+ | `delete`, `trash` | Single-item deletion — usually intentional but worth confirming |
108
+
109
+ Everyday operations like `send`, `get`, `web_fetch`, `cron`, `snapshot`, etc. are completely unaffected — they never match any blacklist pattern.
110
+
111
+ ### Dual Protection Protocol (双重防护)
112
+
113
+ Guardian provides two layers of protection that work together:
114
+
115
+ **Layer 1 — Guardian Plugin (automatic):** Regex pattern matching + LLM intent verification. When a dangerous operation is detected, Guardian blocks the tool call and returns a rejection message to the agent.
116
+
117
+ **Layer 2 — Agent Self-Discipline (behavioral):** When an agent receives a Guardian block notification, it **must immediately stop**, report the blocked command and reason to the human user, and **wait for explicit confirmation** before proceeding. The agent must not attempt to bypass, retry, or find alternative ways to execute the blocked operation.
118
+
119
+ **The protection chain:**
120
+
121
+ ```
122
+ Tool call → Regex match → Guardian blocks → Agent stops → Reports to human → Human decides → Continue or abort
123
+ ```
124
+
125
+ This dual approach ensures that even if an agent is determined to perform a dangerous action, it cannot silently retry or work around the block. The human always stays in the loop for any operation Guardian considers risky.
126
+
127
+ #### Recommended AGENTS.md Rule
128
+
129
+ To activate Layer 2, add this rule to your `AGENTS.md` (or equivalent agent instructions file):
130
+
131
+ ```markdown
132
+ ### Guardian 双重防护协议(硬规则)
133
+ 1. **第一层(Guardian 插件)**:regex 初筛 + LLM 意图确认,自动拦截危险操作
134
+ 2. **第二层(Agent 自觉)**:当 Guardian 拦截命令时,agent 收到拦截通知后**必须立刻停下**,向用户报告被拦截的命令和原因,等待确认后才能继续。禁止自行绕过、重试或换方式执行被拦截的操作。
135
+ 3. **防护链**:regex 初筛 → Guardian 拦截 → agent 停下 → 人类确认 → 继续/放弃
136
+ ```
137
+
138
+ This ensures the agent treats Guardian blocks as hard stops rather than soft suggestions.
139
+
140
+ ### Why Not Just Use LLMs for Everything?
141
+
142
+ Guardian's blacklist uses **zero-cost keyword rules** — no model calls for pattern matching. Regex like `rm -rf /` → critical, `sudo` → warning is instant and deterministic. LLM verification is only triggered for the ~1% of operations that actually hit the blacklist, and its only job is confirming user intent — not scoring risk.
143
+
144
+ ## Quick Start (One Command)
145
+
146
+ ### 1. Clone into your OpenClaw workspace
147
+
148
+ ```bash
149
+ cd ~/.openclaw/workspace
150
+ git clone https://github.com/fatcatMaoFei/openclaw-guardian.git
151
+ ```
152
+
153
+ ### 2. Register the plugin
154
+
155
+ Add to your `openclaw.json`:
156
+
157
+ ```json
158
+ {
159
+ "plugins": {
160
+ "load": {
161
+ "paths": ["./openclaw-guardian"]
162
+ },
163
+ "entries": {
164
+ "openclaw-guardian": {
165
+ "enabled": true
166
+ }
167
+ }
168
+ }
169
+ }
170
+ ```
171
+
172
+ ### 3. Restart
173
+
174
+ ```bash
175
+ openclaw gateway restart
176
+ ```
177
+
178
+ That's it. Guardian is now active. Every tool call goes through blacklist checking automatically.
179
+
180
+ ## Customization
181
+
182
+ ### Enable / Disable
183
+
184
+ Edit `default-policies.json`:
185
+
186
+ ```json
187
+ {
188
+ "enabled": true
189
+ }
190
+ ```
191
+
192
+ Set to `false` to disable Guardian entirely without uninstalling.
193
+
194
+ ### Blacklist Rules
195
+
196
+ The blacklist is defined in `src/blacklist.ts` with two levels of rules:
197
+
198
+ - **`CRITICAL_EXEC` / `CRITICAL_PATH`** — patterns that trigger 3-vote unanimous LLM verification
199
+ - **`WARNING_EXEC` / `WARNING_PATH`** — patterns that trigger 1-vote LLM verification
200
+ - **`SAFE_EXEC`** — whitelisted commands that skip blacklist entirely (e.g., `ls`, `cat`, `git status`)
201
+
202
+ To add your own rules, add a regex + reason to the appropriate array. For example:
203
+
204
+ ```typescript
205
+ // Add to WARNING_EXEC to flag any docker commands
206
+ { pattern: /\bdocker\s+(?:rm|rmi|system\s+prune)\b/, reason: "docker resource removal" },
207
+ ```
208
+
209
+ ### LLM Model Selection
210
+
211
+ Guardian automatically picks the cheapest available model from your OpenClaw config. Preference order:
212
+
213
+ 1. `claude-haiku-4-5` (Anthropic)
214
+ 2. `gpt-4o-mini` (OpenAI)
215
+ 3. `gemini-2.0-flash` (Google)
216
+ 4. First available model (fallback)
217
+
218
+ No extra configuration needed.
219
+
220
+ ## Audit Trail
221
+
222
+ Every blacklist-matched operation is logged to `~/.openclaw/guardian-audit.jsonl` with SHA-256 hash chaining:
223
+
224
+ ```json
225
+ {
226
+ "timestamp": "2026-02-24T09:30:00.000Z",
227
+ "toolName": "exec",
228
+ "blacklistLevel": "critical",
229
+ "blacklistReason": "rm -rf on root-level system path",
230
+ "pattern": "rm\\s+(-[a-zA-Z]*r[a-zA-Z]*\\s+|--recursive\\s+)\\/",
231
+ "userConfirmed": false,
232
+ "finalReason": "Only 1/3 confirmed (need 3)",
233
+ "hash": "a1b2c3...",
234
+ "prevHash": "d4e5f6..."
235
+ }
236
+ ```
237
+
238
+ Tamper-evident: each entry's hash includes the previous entry's hash. Break one link and the whole chain fails verification.
239
+
240
+ ## Architecture
241
+
242
+ ```
243
+ openclaw-guardian/
244
+ ├── openclaw.plugin.json # Plugin manifest (v2.0.0)
245
+ ├── index.ts # Entry — registers before_tool_call hook, routes blacklist hits to LLM
246
+ ├── src/
247
+ │ ├── blacklist.ts # Two-tier keyword rules (critical/warning), 0ms, no model calls
248
+ │ ├── llm-voter.ts # LLM intent verification (single vote or 3-vote unanimous)
249
+ │ └── audit-log.ts # SHA-256 hash-chain audit logger
250
+ ├── default-policies.json # Enable/disable toggle
251
+ ├── package.json
252
+ └── tsconfig.json
253
+ ```
254
+
255
+ ### How It Hooks Into OpenClaw
256
+
257
+ OpenClaw's agent loop: `Model → tool_call → Tool Executor → result → Model`
258
+
259
+ Guardian registers a `before_tool_call` plugin hook. This hook fires **after** the model decides to call a tool but **before** the tool actually executes. If Guardian returns `{ block: true }`, the tool is stopped and the model receives a rejection message instead.
260
+
261
+ This is the same hook interface OpenClaw uses internally for loop detection — battle-tested, async-safe, and zero modifications to core code.
262
+
263
+ ## Token Cost
264
+
265
+ | Tier | % of Operations | Extra Cost |
266
+ |------|----------------|------------|
267
+ | No match (pass) | ~99% | 0 (no model call) |
268
+ | Warning (1 vote) | ~0.5-1% | ~500 tokens per review |
269
+ | Critical (3 votes) | <0.5% | ~1500 tokens per review |
270
+
271
+ **Average overhead: near zero.** The vast majority of operations never hit the blacklist. When they do, Guardian uses the cheapest model available.
272
+
273
+ ## Status
274
+
275
+ 🚧 Under active development — contributions welcome.
276
+
277
+ ## License
278
+
279
+ MIT
@@ -0,0 +1,3 @@
1
+ {
2
+ "enabled": true
3
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OpenClaw Guardian v2 — Blacklist + LLM Intent Verification
3
+ *
4
+ * Flow:
5
+ * tool call → only check exec/write/edit
6
+ * → blacklist match? no → pass (99%)
7
+ * → yes, critical → 3 LLM votes (all must confirm user intent)
8
+ * → yes, warning → 1 LLM vote (confirm user intent)
9
+ * → LLM down → critical: block, warning: ask user
10
+ */
11
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
12
+ export default function setup(api: OpenClawPluginApi): void;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AA0B7D,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI,CAsE1D"}
package/dist/index.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * OpenClaw Guardian v2 — Blacklist + LLM Intent Verification
3
+ *
4
+ * Flow:
5
+ * tool call → only check exec/write/edit
6
+ * → blacklist match? no → pass (99%)
7
+ * → yes, critical → 3 LLM votes (all must confirm user intent)
8
+ * → yes, warning → 1 LLM vote (confirm user intent)
9
+ * → LLM down → critical: block, warning: ask user
10
+ */
11
+ import { readFileSync } from "node:fs";
12
+ import { join, dirname, resolve, normalize } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ function canonicalizePath(raw) {
15
+ if (!raw)
16
+ return raw;
17
+ // Expand ~ to home dir
18
+ if (raw.startsWith("~/"))
19
+ raw = raw.replace("~", process.env.HOME ?? "/root");
20
+ // Resolve to absolute + normalize (removes ../ etc)
21
+ return normalize(resolve(raw));
22
+ }
23
+ import { checkExecBlacklist, checkPathBlacklist, checkToolBlacklist } from "./src/blacklist.js";
24
+ import { initLlm, singleVote, multiVote } from "./src/llm-voter.js";
25
+ import { initAuditLog, writeAuditEntry } from "./src/audit-log.js";
26
+ function loadEnabled() {
27
+ try {
28
+ const dir = dirname(fileURLToPath(import.meta.url));
29
+ const raw = readFileSync(join(dir, "default-policies.json"), "utf-8");
30
+ return JSON.parse(raw).enabled !== false;
31
+ }
32
+ catch {
33
+ return true; // default on if config missing
34
+ }
35
+ }
36
+ export default function setup(api) {
37
+ if (!loadEnabled()) {
38
+ api.logger.info("[guardian] Disabled by policy");
39
+ return;
40
+ }
41
+ initAuditLog();
42
+ initLlm(api.config);
43
+ const log = api.logger;
44
+ log.info("[guardian] v2 active — blacklist + LLM intent verification");
45
+ api.on("before_tool_call", async (event, ctx) => {
46
+ const { toolName, params } = event;
47
+ // Only check exec, write, edit — everything else passes instantly
48
+ let match = null;
49
+ if (toolName === "exec") {
50
+ match = checkExecBlacklist((params?.command ?? ""));
51
+ }
52
+ else if (toolName === "write" || toolName === "edit") {
53
+ const rawPath = (params?.file_path ?? params?.path ?? "");
54
+ const safePath = canonicalizePath(rawPath);
55
+ match = checkPathBlacklist(safePath);
56
+ }
57
+ if (!match) {
58
+ // Check tool-level blacklist (covers all other tools like email, message, etc.)
59
+ match = checkToolBlacklist(toolName, (params ?? {}));
60
+ }
61
+ if (!match)
62
+ return; // 99% of calls end here
63
+ const detail = toolName === "exec"
64
+ ? (params?.command ?? "").toString().slice(0, 120)
65
+ : (params?.file_path ?? params?.path ?? "").toString().slice(0, 120);
66
+ log.warn(`[guardian] ⚠️ Blacklist hit: ${match.level.toUpperCase()} | tool=${toolName} | ${detail} | rule=${match.reason}`);
67
+ // Blacklist hit — verify user intent via LLM
68
+ const sessionKey = ctx?.sessionKey;
69
+ if (match.level === "critical") {
70
+ const result = await multiVote(toolName, params ?? {}, sessionKey, 3, 3);
71
+ writeAuditEntry(toolName, params ?? {}, match, result.confirmed, result.reason);
72
+ if (!result.confirmed) {
73
+ log.error(`[guardian] 🛑 BLOCKED CRITICAL | tool=${toolName} | ${detail} | votes=${result.reason}`);
74
+ return {
75
+ block: true,
76
+ blockReason: `🛡️ Guardian: 危险操作被拦截 — ${match.reason}。${result.reason}`,
77
+ };
78
+ }
79
+ log.info(`[guardian] ✅ CRITICAL passed (3/3 confirmed) | tool=${toolName} | ${detail}`);
80
+ return;
81
+ }
82
+ // Warning level: 1 vote
83
+ const result = await singleVote(toolName, params ?? {}, sessionKey);
84
+ writeAuditEntry(toolName, params ?? {}, match, result.confirmed, result.reason);
85
+ if (!result.confirmed) {
86
+ log.warn(`[guardian] 🚫 BLOCKED WARNING | tool=${toolName} | ${detail} | reason=${result.reason}`);
87
+ return {
88
+ block: true,
89
+ blockReason: `🛡️ Guardian: 此操作需要用户确认 — ${match.reason}。请先询问用户是否要执行此操作。`,
90
+ };
91
+ }
92
+ log.info(`[guardian] ✅ WARNING passed (user confirmed) | tool=${toolName} | ${detail}`);
93
+ return;
94
+ });
95
+ }
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Audit Log — SHA-256 hash-chain append-only trail.
3
+ * Only logs blacklist-matched operations (not every tool call).
4
+ */
5
+ import type { BlacklistMatch } from "./blacklist.js";
6
+ export type AuditEntry = {
7
+ timestamp: string;
8
+ toolName: string;
9
+ blacklistLevel: string;
10
+ blacklistReason: string;
11
+ pattern: string;
12
+ userConfirmed: boolean;
13
+ finalReason: string;
14
+ hash: string;
15
+ prevHash: string;
16
+ };
17
+ export declare function initAuditLog(): void;
18
+ export declare function writeAuditEntry(toolName: string, params: Record<string, unknown>, match: BlacklistMatch, userConfirmed: boolean, reason: string): void;
19
+ //# sourceMappingURL=audit-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.d.ts","sourceRoot":"","sources":["../../src/audit-log.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AA6BF,wBAAgB,YAAY,IAAI,IAAI,CAKnC;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,EAAE,cAAc,EACrB,aAAa,EAAE,OAAO,EACtB,MAAM,EAAE,MAAM,GACb,IAAI,CAqBN"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Audit Log — SHA-256 hash-chain append-only trail.
3
+ * Only logs blacklist-matched operations (not every tool call).
4
+ */
5
+ import { createHash } from "node:crypto";
6
+ import { appendFileSync, readFileSync, mkdirSync, existsSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { homedir } from "node:os";
9
+ let lastHash = "";
10
+ let logPath = "";
11
+ function getLogPath() {
12
+ if (logPath)
13
+ return logPath;
14
+ logPath = join(homedir(), ".openclaw", "guardian-audit.jsonl");
15
+ return logPath;
16
+ }
17
+ function computeHash(data) {
18
+ return createHash("sha256").update(data).digest("hex");
19
+ }
20
+ function recoverLastHash(path) {
21
+ try {
22
+ if (!existsSync(path))
23
+ return "";
24
+ const content = readFileSync(path, "utf-8").trim();
25
+ if (!content)
26
+ return "";
27
+ const lines = content.split("\n");
28
+ const lastLine = lines[lines.length - 1];
29
+ const entry = JSON.parse(lastLine);
30
+ return entry.hash ?? "";
31
+ }
32
+ catch {
33
+ return "";
34
+ }
35
+ }
36
+ export function initAuditLog() {
37
+ const path = getLogPath();
38
+ const dir = dirname(path);
39
+ if (!existsSync(dir))
40
+ mkdirSync(dir, { recursive: true });
41
+ lastHash = recoverLastHash(path);
42
+ }
43
+ export function writeAuditEntry(toolName, params, match, userConfirmed, reason) {
44
+ const entry = {
45
+ timestamp: new Date().toISOString(),
46
+ toolName,
47
+ blacklistLevel: match.level,
48
+ blacklistReason: match.reason,
49
+ pattern: match.pattern,
50
+ userConfirmed,
51
+ finalReason: reason,
52
+ prevHash: lastHash,
53
+ };
54
+ const hashInput = JSON.stringify(entry);
55
+ entry.hash = computeHash(hashInput);
56
+ lastHash = entry.hash;
57
+ try {
58
+ appendFileSync(getLogPath(), JSON.stringify(entry) + "\n", "utf-8");
59
+ }
60
+ catch (err) {
61
+ console.error(`[guardian] audit write failed: ${err}`);
62
+ }
63
+ }
64
+ //# sourceMappingURL=audit-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.js","sourceRoot":"","sources":["../../src/audit-log.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAelC,IAAI,QAAQ,GAAG,EAAE,CAAC;AAClB,IAAI,OAAO,GAAG,EAAE,CAAC;AAEjB,SAAS,UAAU;IACjB,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,sBAAsB,CAAC,CAAC;IAC/D,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACnC,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACnD,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAe,CAAC;QACjD,OAAO,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,MAA+B,EAC/B,KAAqB,EACrB,aAAsB,EACtB,MAAc;IAEd,MAAM,KAAK,GAAiD;QAC1D,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,QAAQ;QACR,cAAc,EAAE,KAAK,CAAC,KAAK;QAC3B,eAAe,EAAE,KAAK,CAAC,MAAM;QAC7B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,aAAa;QACb,WAAW,EAAE,MAAM;QACnB,QAAQ,EAAE,QAAQ;KACnB,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACxC,KAAK,CAAC,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IACpC,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC;IAEtB,IAAI,CAAC;QACH,cAAc,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,kCAAkC,GAAG,EAAE,CAAC,CAAC;IACzD,CAAC;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Blacklist rules — pure pattern matching, no LLM involved.
3
+ * Two levels: "critical" (needs 3/3 LLM votes) and "warning" (needs 1/1).
4
+ *
5
+ * IMPORTANT: These patterns are checked against:
6
+ * - exec: the command string
7
+ * - write/edit: the file path
8
+ * The caller (index.ts) decides what text to pass in.
9
+ */
10
+ export type BlacklistMatch = {
11
+ level: "critical" | "warning";
12
+ pattern: string;
13
+ reason: string;
14
+ };
15
+ /**
16
+ * Check a command (exec) against blacklist.
17
+ * Splits on shell operators and checks each segment.
18
+ * Returns null if no match (99% of calls).
19
+ */
20
+ export declare function checkExecBlacklist(command: string): BlacklistMatch | null;
21
+ /**
22
+ * Check a file path (write/edit) against blacklist.
23
+ * Returns null if no match.
24
+ */
25
+ export declare function checkPathBlacklist(filePath: string): BlacklistMatch | null;
26
+ /**
27
+ * Check any tool call's params against tool-level blacklist.
28
+ * Only checks specific param fields (not full serialization) to avoid false positives.
29
+ * Skips exec/write/edit (already handled by dedicated checkers).
30
+ * Returns null if no match.
31
+ */
32
+ export declare function checkToolBlacklist(toolName: string, params: Record<string, unknown>): BlacklistMatch | null;
33
+ //# sourceMappingURL=blacklist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blacklist.d.ts","sourceRoot":"","sources":["../../src/blacklist.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAiLF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CA4BzE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAI1E;AAqBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc,GAAG,IAAI,CAmB3G"}
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Blacklist rules — pure pattern matching, no LLM involved.
3
+ * Two levels: "critical" (needs 3/3 LLM votes) and "warning" (needs 1/1).
4
+ *
5
+ * IMPORTANT: These patterns are checked against:
6
+ * - exec: the command string
7
+ * - write/edit: the file path
8
+ * The caller (index.ts) decides what text to pass in.
9
+ */
10
+ // ── CRITICAL: irreversible destruction or system compromise ────────
11
+ // Needs 3/3 LLM votes confirming user intent to pass
12
+ const CRITICAL_EXEC = [
13
+ // Filesystem destruction — only recursive rm on system paths
14
+ { pattern: /rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+|--recursive\s+)\/(?!tmp\/|home\/clawdbot\/)/, reason: "rm -rf on root-level system path" },
15
+ { pattern: /rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+|--recursive\s+)~\//, reason: "rm -rf on home directory" },
16
+ { pattern: /mkfs\b/, reason: "filesystem format (mkfs)" },
17
+ { pattern: /dd\s+if=.*of=\/dev\//, reason: "raw disk write (dd)" },
18
+ { pattern: />\s*\/dev\/sd/, reason: "redirect to block device" },
19
+ // System auth files (write/modify, not read)
20
+ { pattern: /(?:tee|>>?)\s*\/etc\/(?:passwd|shadow|sudoers)/, reason: "write to system auth file" },
21
+ { pattern: /sed\s+-i.*\/etc\/(?:passwd|shadow|sudoers)/, reason: "in-place edit of system auth file" },
22
+ // System shutdown
23
+ { pattern: /\b(?:shutdown|reboot)\b/, reason: "system shutdown/reboot" },
24
+ { pattern: /\binit\s+[06]\b/, reason: "system halt/reboot (init)" },
25
+ // Kill SSH (locks out remote access)
26
+ { pattern: /systemctl\s+(?:stop|disable)\s+sshd/, reason: "disable SSH (remote lockout)" },
27
+ // === BYPASS PREVENTION ===
28
+ // Absolute path to rm
29
+ { pattern: /\/bin\/rm\s+(-[a-zA-Z]*r[a-zA-Z]*)\s+/, reason: "rm via absolute path" },
30
+ { pattern: /\/usr\/bin\/rm\s+(-[a-zA-Z]*r[a-zA-Z]*)\s+/, reason: "rm via absolute path" },
31
+ // (eval, perl -e, ruby -e moved to WARNING)
32
+ // xargs with dangerous commands
33
+ { pattern: /xargs\s+.*\brm\b/, reason: "xargs rm (indirect deletion)" },
34
+ { pattern: /xargs\s+.*\bchmod\b/, reason: "xargs chmod (indirect permission change)" },
35
+ // find -exec with dangerous commands
36
+ { pattern: /find\s+.*-exec\s+.*\brm\b/, reason: "find -exec rm (indirect deletion)" },
37
+ { pattern: /find\s+.*-delete\b/, reason: "find -delete (bulk deletion)" },
38
+ ];
39
+ const CRITICAL_PATH = [
40
+ { pattern: /^\/etc\/(?:passwd|shadow|sudoers)$/, reason: "write to system auth file" },
41
+ { pattern: /^\/boot\//, reason: "write to boot partition" },
42
+ ];
43
+ // ── WARNING: risky but possibly intentional ────────────────────────
44
+ // Needs 1/1 LLM vote confirming user intent to pass
45
+ const WARNING_EXEC = [
46
+ // Recursive delete (non-system paths — CRITICAL already catches system paths)
47
+ { pattern: /rm\s+(-[a-zA-Z]*r[a-zA-Z]*)\s+/, reason: "recursive file deletion" },
48
+ // Privilege escalation
49
+ { pattern: /\bsudo\s+/, reason: "privilege escalation (sudo)" },
50
+ // Dangerous permissions
51
+ { pattern: /chmod\s+[47]77\b/, reason: "world-writable permission (chmod 777)" },
52
+ { pattern: /chmod\s+-R\s+/, reason: "recursive permission change" },
53
+ { pattern: /chown\s+-R\s+/, reason: "recursive ownership change" },
54
+ // Force kill
55
+ { pattern: /kill\s+-9\s+/, reason: "force kill process (SIGKILL)" },
56
+ { pattern: /\bkillall\s+/, reason: "killall processes" },
57
+ { pattern: /\bpkill\s+/, reason: "pkill processes" },
58
+ // Service management (only stop/disable — restart is less dangerous)
59
+ { pattern: /systemctl\s+(?:stop|disable)\s+/, reason: "systemctl stop/disable service" },
60
+ // Database destruction
61
+ { pattern: /DROP\s+(?:DATABASE|TABLE)\b/i, reason: "DROP DATABASE/TABLE" },
62
+ { pattern: /TRUNCATE\s+/i, reason: "TRUNCATE table" },
63
+ // Interpreter inline execution (only check eval — python/node/perl/ruby are normal dev tools)
64
+ { pattern: /\beval\s+/, reason: "eval execution (arbitrary code)" },
65
+ // Network/firewall changes
66
+ { pattern: /\biptables\s+/, reason: "firewall rule change (iptables)" },
67
+ { pattern: /\bufw\s+(?:allow|deny|delete|disable)\b/, reason: "firewall rule change (ufw)" },
68
+ // Crontab modification
69
+ { pattern: /\bcrontab\s+(-r|-e)\b/, reason: "crontab modification" },
70
+ // Disk operations
71
+ { pattern: /\bfdisk\s+/, reason: "disk partition operation" },
72
+ { pattern: /\bparted\s+/, reason: "disk partition operation" },
73
+ { pattern: /\bmount\s+/, reason: "filesystem mount operation" },
74
+ { pattern: /\bumount\s+/, reason: "filesystem unmount operation" },
75
+ // SSH key operations
76
+ { pattern: /ssh-keygen\s+/, reason: "SSH key generation/modification" },
77
+ // Environment variable manipulation that could affect security
78
+ { pattern: /export\s+(?:PATH|LD_PRELOAD|LD_LIBRARY_PATH)=/, reason: "security-sensitive environment variable change" },
79
+ ];
80
+ const WARNING_PATH = [
81
+ { pattern: /^\/etc\//, reason: "write to /etc/ system config" },
82
+ { pattern: /^\/root\//, reason: "write to /root/ directory" },
83
+ ];
84
+ // ── Safe Command Patterns (whitelist, checked before blacklist) ─────
85
+ const SAFE_EXEC = [
86
+ // git rm --cached only removes from index, not filesystem
87
+ /^git\s+rm\s+.*--cached/,
88
+ // git operations are generally safe
89
+ /^git\s+(?:add|commit|push|pull|fetch|log|status|diff|branch|checkout|merge|rebase|stash|tag|remote|clone)\b/,
90
+ // echo/printf — ONLY safe if not piped to shell (pipe splits into separate segments)
91
+ /^(?:echo|printf)\s+/,
92
+ // read-only commands — exclude find (can be used with -exec/-delete)
93
+ /^(?:cat|head|tail|less|more|grep|ls|stat|file|wc|du|df|which|whereis|type|id|whoami|hostname|uname|date|uptime)\s*/,
94
+ // package info (not install)
95
+ /^(?:apt|dpkg|pip|npm)\s+(?:list|show|info|search)\b/,
96
+ // safe file operations (create only, no overwrite risk)
97
+ /^(?:mkdir|touch)\s+/,
98
+ // archive/compression (read-heavy, low risk)
99
+ /^(?:tar|unzip|gzip|gunzip|bzip2|xz|7z)\s+/,
100
+ // openclaw CLI
101
+ /^openclaw\s+/,
102
+ ];
103
+ // ── Quote/Comment Detection ────────────────────────────────────────
104
+ function isQuotedOrCommented(text, matchIndex) {
105
+ const before = text.slice(0, matchIndex);
106
+ // Inside double quotes?
107
+ const doubleQuotes = (before.match(/"/g) || []).length;
108
+ if (doubleQuotes % 2 === 1)
109
+ return true;
110
+ // Inside single quotes?
111
+ const singleQuotes = (before.match(/'/g) || []).length;
112
+ if (singleQuotes % 2 === 1)
113
+ return true;
114
+ // After a comment character on the same line?
115
+ const lastNewline = before.lastIndexOf("\n");
116
+ const currentLine = before.slice(lastNewline + 1);
117
+ if (currentLine.includes("#"))
118
+ return true;
119
+ return false;
120
+ }
121
+ // ── Matching ───────────────────────────────────────────────────────
122
+ function matchRules(text, rules, level) {
123
+ for (const rule of rules) {
124
+ const m = rule.pattern.exec(text);
125
+ if (m && !isQuotedOrCommented(text, m.index)) {
126
+ return { level, pattern: rule.pattern.source, reason: rule.reason };
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+ // ── Command Segmentation ───────────────────────────────────────────
132
+ function splitCommand(cmd) {
133
+ // Split on shell operators, but not inside quotes
134
+ const segments = [];
135
+ let current = "";
136
+ let inSingle = false;
137
+ let inDouble = false;
138
+ for (let i = 0; i < cmd.length; i++) {
139
+ const ch = cmd[i];
140
+ if (ch === "'" && !inDouble) {
141
+ inSingle = !inSingle;
142
+ current += ch;
143
+ continue;
144
+ }
145
+ if (ch === '"' && !inSingle) {
146
+ inDouble = !inDouble;
147
+ current += ch;
148
+ continue;
149
+ }
150
+ if (!inSingle && !inDouble) {
151
+ // Check double-char operators first: && and ||
152
+ if ((ch === '&' && cmd[i + 1] === '&') || (ch === '|' && cmd[i + 1] === '|')) {
153
+ segments.push(current.trim());
154
+ current = "";
155
+ i++; // skip second char
156
+ continue;
157
+ }
158
+ // Single-char separators: ; | \n
159
+ if (ch === ';' || ch === '|' || ch === '\n') {
160
+ segments.push(current.trim());
161
+ current = "";
162
+ continue;
163
+ }
164
+ }
165
+ current += ch;
166
+ }
167
+ if (current.trim())
168
+ segments.push(current.trim());
169
+ return segments.filter(Boolean);
170
+ }
171
+ // ── Public API ─────────────────────────────────────────────────────
172
+ /**
173
+ * Check a command (exec) against blacklist.
174
+ * Splits on shell operators and checks each segment.
175
+ * Returns null if no match (99% of calls).
176
+ */
177
+ export function checkExecBlacklist(command) {
178
+ if (!command)
179
+ return null;
180
+ // Phase 1: Check the FULL command string for pipe-based attacks
181
+ // These patterns span across pipe boundaries and must be checked before splitting
182
+ const PIPE_ATTACKS = [
183
+ { pattern: /base64\s+(-d|--decode).*\|\s*(?:bash|sh|zsh|dash)/, reason: "base64 decoded pipe to shell" },
184
+ { pattern: /\bcurl\b.*\|\s*(?:bash|sh|zsh|dash|python|perl|ruby)/, reason: "curl pipe to shell (remote code execution)" },
185
+ { pattern: /\bwget\b.*\|\s*(?:bash|sh|zsh|dash|python|perl|ruby)/, reason: "wget pipe to shell (remote code execution)" },
186
+ { pattern: /\becho\b.*\|\s*(?:bash|sh|zsh|dash)\b/, reason: "echo pipe to shell" },
187
+ { pattern: /\bprintf\b.*\|\s*(?:bash|sh|zsh|dash)\b/, reason: "printf pipe to shell" },
188
+ { pattern: /\|\s*(?:bash|sh|zsh|dash)\s*$/, reason: "pipe to shell interpreter" },
189
+ { pattern: /\|\s*(?:bash|sh|zsh|dash)\s*[;&|]/, reason: "pipe to shell interpreter" },
190
+ ];
191
+ const fullMatch = matchRules(command, PIPE_ATTACKS, "critical");
192
+ if (fullMatch)
193
+ return fullMatch;
194
+ // Phase 2: Split on shell operators and check each segment
195
+ const segments = splitCommand(command);
196
+ for (const seg of segments) {
197
+ // Whitelist check: safe commands skip blacklist entirely
198
+ if (SAFE_EXEC.some(re => re.test(seg)))
199
+ continue;
200
+ const m = matchRules(seg, CRITICAL_EXEC, "critical")
201
+ ?? matchRules(seg, WARNING_EXEC, "warning");
202
+ if (m)
203
+ return m;
204
+ }
205
+ return null;
206
+ }
207
+ /**
208
+ * Check a file path (write/edit) against blacklist.
209
+ * Returns null if no match.
210
+ */
211
+ export function checkPathBlacklist(filePath) {
212
+ if (!filePath)
213
+ return null;
214
+ return matchRules(filePath, CRITICAL_PATH, "critical")
215
+ ?? matchRules(filePath, WARNING_PATH, "warning");
216
+ }
217
+ const TOOL_RULES = [
218
+ // Email: bulk delete / trash / expunge (irreversible)
219
+ { tool: /.*/, param: "*", pattern: /\b(?:batchDelete|expunge|emptyTrash|purge)\b/i, level: "critical", reason: "bulk email deletion (irreversible)" },
220
+ // Email: single delete/trash (only matches action field value)
221
+ { tool: /.*/, param: "*", pattern: /\b(?:delete|trash)\b/i, level: "warning", reason: "email/message deletion" },
222
+ // Destructive database queries embedded in tool params
223
+ { tool: /.*/, param: "*", pattern: /\b(?:DROP\s+(?:DATABASE|TABLE)|TRUNCATE\s+|DELETE\s+FROM)\b/i, level: "critical", reason: "destructive database query in tool params" },
224
+ ];
225
+ /**
226
+ * Check any tool call's params against tool-level blacklist.
227
+ * Only checks specific param fields (not full serialization) to avoid false positives.
228
+ * Skips exec/write/edit (already handled by dedicated checkers).
229
+ * Returns null if no match.
230
+ */
231
+ export function checkToolBlacklist(toolName, params) {
232
+ // exec/write/edit already have dedicated checkers
233
+ if (toolName === "exec" || toolName === "write" || toolName === "edit")
234
+ return null;
235
+ if (!params || Object.keys(params).length === 0)
236
+ return null;
237
+ // Only check action-like fields for all rules
238
+ const actionFields = ["action", "method", "command", "operation"];
239
+ const actionValue = actionFields
240
+ .map(f => typeof params[f] === "string" ? params[f] : "")
241
+ .filter(Boolean)
242
+ .join(" ")
243
+ .toLowerCase();
244
+ for (const rule of TOOL_RULES) {
245
+ if (!rule.tool.test(toolName))
246
+ continue;
247
+ const m = rule.pattern.exec(actionValue);
248
+ if (m)
249
+ return { level: rule.level, pattern: rule.pattern.source, reason: rule.reason };
250
+ }
251
+ return null;
252
+ }
253
+ //# sourceMappingURL=blacklist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blacklist.js","sourceRoot":"","sources":["../../src/blacklist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * LLM voter — calls a lightweight model to check if user explicitly requested/confirmed
3
+ * the flagged operation. Single job: "Did the user ask for this?"
4
+ *
5
+ * LLM config is read from OpenClaw's own model provider config (api.config.models.providers).
6
+ * No separate API key needed — uses whatever the user already configured.
7
+ */
8
+ export type Vote = {
9
+ voter: number;
10
+ confirmed: boolean;
11
+ reason: string;
12
+ };
13
+ export type VoteResult = {
14
+ confirmed: boolean;
15
+ reason: string;
16
+ votes?: Vote[];
17
+ };
18
+ /**
19
+ * Initialize LLM config from OpenClaw's provider config.
20
+ * Called once at plugin setup.
21
+ */
22
+ export declare function initLlm(config: Record<string, unknown>): void;
23
+ export declare function readRecentContext(_sessionKey?: string): string;
24
+ export declare function singleVote(toolName: string, params: Record<string, any>, sessionKey?: string): Promise<VoteResult>;
25
+ export declare function multiVote(toolName: string, params: Record<string, any>, sessionKey?: string, count?: number, threshold?: number): Promise<VoteResult & {
26
+ votes: Vote[];
27
+ }>;
28
+ //# sourceMappingURL=llm-voter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-voter.d.ts","sourceRoot":"","sources":["../../src/llm-voter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,MAAM,MAAM,IAAI,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzE,MAAM,MAAM,UAAU,GAAG;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;CAAE,CAAC;AAuBhF;;;GAGG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA6C7D;AA0ED,wBAAgB,iBAAiB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAmC9D;AAqFD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GACjE,OAAO,CAAC,UAAU,CAAC,CAQrB;AAED,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,EAClE,KAAK,SAAI,EAAE,SAAS,SAAI,GACvB,OAAO,CAAC,UAAU,GAAG;IAAE,KAAK,EAAE,IAAI,EAAE,CAAA;CAAE,CAAC,CAkBzC"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * LLM voter — calls a lightweight model to check if user explicitly requested/confirmed
3
+ * the flagged operation. Single job: "Did the user ask for this?"
4
+ *
5
+ * LLM config is read from OpenClaw's own model provider config (api.config.models.providers).
6
+ * No separate API key needed — uses whatever the user already configured.
7
+ */
8
+ import { readFileSync, readdirSync, statSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ // ── LLM Config (resolved at init from OpenClaw config) ─────────────
11
+ let llmUrl = "";
12
+ let llmApiKey = "";
13
+ let llmModel = "";
14
+ let llmApi = "anthropic-messages";
15
+ let llmHeaders = {};
16
+ let llmReady = false;
17
+ const LLM_TIMEOUT_MS = 5000;
18
+ const LLM_MAX_TOKENS = 200;
19
+ // Preferred models for guardian voting (cheap + fast)
20
+ const PREFERRED_MODELS = [
21
+ "claude-haiku-4-5-20251001",
22
+ "claude-3-5-haiku",
23
+ "claude-3-haiku",
24
+ "gpt-4o-mini",
25
+ "gemini-2.0-flash",
26
+ ];
27
+ /**
28
+ * Initialize LLM config from OpenClaw's provider config.
29
+ * Called once at plugin setup.
30
+ */
31
+ export function initLlm(config) {
32
+ const providers = config?.models?.providers;
33
+ if (!providers || typeof providers !== "object") {
34
+ console.error("[guardian] No model providers found in config");
35
+ return;
36
+ }
37
+ // Strategy: find a provider with a cheap/fast model
38
+ for (const preferred of PREFERRED_MODELS) {
39
+ for (const [, provider] of Object.entries(providers)) {
40
+ if (!provider.baseUrl || !provider.apiKey)
41
+ continue;
42
+ const models = provider.models ?? [];
43
+ const found = models.find((m) => m.id === preferred || m.id?.includes(preferred) || m.name?.includes(preferred));
44
+ if (found) {
45
+ llmUrl = provider.baseUrl.replace(/\/$/, "");
46
+ llmApiKey = provider.apiKey;
47
+ llmModel = found.id;
48
+ llmApi = found.api ?? provider.api ?? "anthropic-messages";
49
+ llmHeaders = { ...provider.headers, ...found.headers };
50
+ llmReady = true;
51
+ console.log(`[guardian] LLM ready: ${llmModel} via ${llmUrl}`);
52
+ return;
53
+ }
54
+ }
55
+ }
56
+ // Fallback: use the first provider with any model
57
+ for (const [, provider] of Object.entries(providers)) {
58
+ if (!provider.baseUrl || !provider.apiKey)
59
+ continue;
60
+ const models = provider.models ?? [];
61
+ if (models.length > 0) {
62
+ llmUrl = provider.baseUrl.replace(/\/$/, "");
63
+ llmApiKey = provider.apiKey;
64
+ llmModel = models[0].id;
65
+ llmApi = models[0].api ?? provider.api ?? "anthropic-messages";
66
+ llmHeaders = { ...provider.headers, ...models[0].headers };
67
+ llmReady = true;
68
+ console.log(`[guardian] LLM fallback: ${llmModel} via ${llmUrl}`);
69
+ return;
70
+ }
71
+ }
72
+ console.error("[guardian] No usable LLM provider found");
73
+ }
74
+ // ── System Prompt ──────────────────────────────────────────────────
75
+ const SYSTEM_PROMPT = `You are a security confirmation checker for an AI agent.
76
+
77
+ Your ONLY job is to determine: Did the user explicitly request or confirm this operation?
78
+
79
+ You will receive:
80
+ 1. A tool call that was flagged as potentially dangerous
81
+ 2. Recent conversation messages for context
82
+
83
+ Rules:
84
+ - If the user clearly asked for this operation (e.g., "delete that folder", "remove the old files", "restart the service"), answer YES.
85
+ - If the user confirmed after being asked (e.g., "yes", "do it", "confirmed", "go ahead"), answer YES.
86
+ - If there is no clear user intent or confirmation for this specific operation, answer NO.
87
+ - When in doubt, answer NO.
88
+ - Do NOT evaluate whether the operation is dangerous — that's already been determined. You are ONLY checking user intent.
89
+
90
+ Examples:
91
+
92
+ User said: "帮我删掉 /data/old-backup 这个文件夹"
93
+ Tool: exec, Command: rm -rf /data/old-backup
94
+ → {"confirmed": true, "reason": "User explicitly asked to delete /data/old-backup"}
95
+
96
+ User said: "看看磁盘空间"
97
+ Tool: exec, Command: rm -rf /var/log/old
98
+ → {"confirmed": false, "reason": "User asked about disk space, not file deletion"}
99
+
100
+ User said: "好的,执行吧"
101
+ Tool: exec, Command: sudo systemctl restart nginx
102
+ → {"confirmed": true, "reason": "User said go ahead, confirming the operation"}
103
+
104
+ User said: "我要重启服务器"
105
+ Tool: exec, Command: reboot
106
+ → {"confirmed": true, "reason": "User explicitly requested server reboot"}
107
+
108
+ User said: "检查一下 nginx 状态"
109
+ Tool: exec, Command: systemctl stop nginx
110
+ → {"confirmed": false, "reason": "User asked to check status, not stop the service"}
111
+
112
+ User said: "yes"
113
+ Tool: exec, Command: rm -rf /home/user/project
114
+ → {"confirmed": true, "reason": "User confirmed with yes"}
115
+
116
+ User said: (no recent messages)
117
+ Tool: exec, Command: rm -rf /tmp/cache
118
+ → {"confirmed": false, "reason": "No user messages found to confirm this operation"}
119
+
120
+ User said: "echo 'rm -rf /' 这个命令很危险"
121
+ Tool: exec, Command: rm -rf /
122
+ → {"confirmed": false, "reason": "User was discussing the command as dangerous, not requesting execution"}
123
+
124
+ Respond with EXACTLY one JSON object:
125
+ {"confirmed": true/false, "reason": "brief explanation"}`;
126
+ // ── Context Reader ─────────────────────────────────────────────────
127
+ function resolveSessionsDir() {
128
+ // Try common paths
129
+ const candidates = [
130
+ join(process.env.HOME ?? "/root", ".openclaw/agents/main/sessions"),
131
+ "/root/.openclaw/agents/main/sessions",
132
+ "/home/clawdbot/.openclaw/agents/main/sessions",
133
+ ];
134
+ for (const dir of candidates) {
135
+ try {
136
+ readdirSync(dir);
137
+ return dir;
138
+ }
139
+ catch { /* try next */ }
140
+ }
141
+ return candidates[0]; // fallback
142
+ }
143
+ export function readRecentContext(_sessionKey) {
144
+ try {
145
+ const sessDir = resolveSessionsDir();
146
+ const files = readdirSync(sessDir)
147
+ .filter((f) => f.endsWith(".jsonl"))
148
+ .map((f) => ({ name: f, mtime: statSync(join(sessDir, f)).mtimeMs }))
149
+ .sort((a, b) => b.mtime - a.mtime);
150
+ if (files.length === 0)
151
+ return "(no session context available)";
152
+ const latest = join(sessDir, files[0].name);
153
+ const raw = readFileSync(latest, "utf-8");
154
+ const lines = raw.split("\n").slice(-50).join("\n");
155
+ const userMessages = [];
156
+ for (const line of lines.split("\n")) {
157
+ if (!line.trim())
158
+ continue;
159
+ try {
160
+ const entry = JSON.parse(line);
161
+ const msg = entry.message ?? entry;
162
+ if (msg.role === "user") {
163
+ const text = typeof msg.content === "string"
164
+ ? msg.content
165
+ : Array.isArray(msg.content)
166
+ ? msg.content.filter((b) => b.type === "text").map((b) => b.text).join(" ")
167
+ : "";
168
+ if (text.trim())
169
+ userMessages.push(text.trim().slice(0, 500));
170
+ }
171
+ }
172
+ catch { /* skip malformed lines */ }
173
+ }
174
+ return userMessages.slice(-3).join("\n---\n") || "(no user messages found)";
175
+ }
176
+ catch {
177
+ return "(failed to read session context)";
178
+ }
179
+ }
180
+ // ── LLM Call (supports anthropic-messages and openai-completions) ──
181
+ async function callLLM(userPrompt) {
182
+ if (!llmReady)
183
+ throw new Error("LLM not initialized");
184
+ const controller = new AbortController();
185
+ const timer = setTimeout(() => controller.abort(), LLM_TIMEOUT_MS);
186
+ try {
187
+ let resp;
188
+ if (llmApi === "anthropic-messages") {
189
+ const endpoint = llmUrl.endsWith("/messages") ? llmUrl : `${llmUrl}/v1/messages`;
190
+ resp = await fetch(endpoint, {
191
+ method: "POST",
192
+ headers: {
193
+ "Content-Type": "application/json",
194
+ "x-api-key": llmApiKey,
195
+ "anthropic-version": "2023-06-01",
196
+ ...llmHeaders,
197
+ },
198
+ body: JSON.stringify({
199
+ model: llmModel,
200
+ max_tokens: LLM_MAX_TOKENS,
201
+ temperature: 0,
202
+ system: SYSTEM_PROMPT,
203
+ messages: [{ role: "user", content: userPrompt }],
204
+ }),
205
+ signal: controller.signal,
206
+ });
207
+ }
208
+ else {
209
+ // OpenAI-compatible (openai-completions, ollama, etc.)
210
+ const endpoint = llmUrl.endsWith("/chat/completions")
211
+ ? llmUrl
212
+ : `${llmUrl}/v1/chat/completions`;
213
+ resp = await fetch(endpoint, {
214
+ method: "POST",
215
+ headers: {
216
+ "Content-Type": "application/json",
217
+ "Authorization": `Bearer ${llmApiKey}`,
218
+ ...llmHeaders,
219
+ },
220
+ body: JSON.stringify({
221
+ model: llmModel,
222
+ max_tokens: LLM_MAX_TOKENS,
223
+ temperature: 0,
224
+ messages: [
225
+ { role: "system", content: SYSTEM_PROMPT },
226
+ { role: "user", content: userPrompt },
227
+ ],
228
+ }),
229
+ signal: controller.signal,
230
+ });
231
+ }
232
+ if (!resp.ok)
233
+ throw new Error(`LLM HTTP ${resp.status}`);
234
+ const data = (await resp.json());
235
+ // Extract text from either Anthropic or OpenAI response format
236
+ const text = data.content?.[0]?.text
237
+ ?? data.choices?.[0]?.message?.content
238
+ ?? "";
239
+ const jsonMatch = text.match(/\{[\s\S]*?\}/);
240
+ if (!jsonMatch)
241
+ throw new Error("No JSON in LLM response");
242
+ const parsed = JSON.parse(jsonMatch[0]);
243
+ return { confirmed: !!parsed.confirmed, reason: parsed.reason ?? "" };
244
+ }
245
+ finally {
246
+ clearTimeout(timer);
247
+ }
248
+ }
249
+ // ── Prompt Builder ─────────────────────────────────────────────────
250
+ function buildPrompt(toolName, params, context) {
251
+ const detail = toolName === "exec"
252
+ ? `Command: ${params.command ?? "(empty)"}`
253
+ : `File path: ${params.file_path ?? params.path ?? "(empty)"}`;
254
+ return `Flagged tool call:\n- Tool: ${toolName}\n- ${detail}\n\nRecent user messages:\n${context}`;
255
+ }
256
+ // ── Public API ─────────────────────────────────────────────────────
257
+ export async function singleVote(toolName, params, sessionKey) {
258
+ const context = readRecentContext(sessionKey);
259
+ const prompt = buildPrompt(toolName, params, context);
260
+ try {
261
+ return await callLLM(prompt);
262
+ }
263
+ catch (e) {
264
+ return { confirmed: false, reason: `LLM unavailable: ${e.message}` };
265
+ }
266
+ }
267
+ export async function multiVote(toolName, params, sessionKey, count = 3, threshold = 3) {
268
+ const context = readRecentContext(sessionKey);
269
+ const prompt = buildPrompt(toolName, params, context);
270
+ const promises = Array.from({ length: count }, (_, i) => callLLM(prompt)
271
+ .then((r) => ({ voter: i + 1, confirmed: r.confirmed, reason: r.reason }))
272
+ .catch((e) => ({ voter: i + 1, confirmed: false, reason: `LLM error: ${e.message}` })));
273
+ const votes = await Promise.all(promises);
274
+ const yesCount = votes.filter((v) => v.confirmed).length;
275
+ const confirmed = yesCount >= threshold;
276
+ const reason = confirmed
277
+ ? `${yesCount}/${count} voters confirmed user intent`
278
+ : `Only ${yesCount}/${count} confirmed (need ${threshold})`;
279
+ return { confirmed, reason, votes };
280
+ }
281
+ //# sourceMappingURL=llm-voter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-voter.js","sourceRoot":"","sources":["../../src/llm-voter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,sEAAsE;AAEtE,IAAI,MAAM,GAAG,EAAE,CAAC;AAChB,IAAI,SAAS,GAAG,EAAE,CAAC;AACnB,IAAI,QAAQ,GAAG,EAAE,CAAC;AAClB,IAAI,MAAM,GAAW,oBAAoB,CAAC;AAC1C,IAAI,UAAU,GAA2B,EAAE,CAAC;AAC5C,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB,MAAM,cAAc,GAAG,IAAI,CAAC;AAC5B,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,sDAAsD;AACtD,MAAM,gBAAgB,GAAG;IACvB,2BAA2B;IAC3B,kBAAkB;IAClB,gBAAgB;IAChB,aAAa;IACb,kBAAkB;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,MAA+B;IACrD,MAAM,SAAS,GAAI,MAA8B,EAAE,MAAM,EAAE,SAAS,CAAC;IACrE,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;QAC/D,OAAO;IACT,CAAC;IAED,oDAAoD;IACpD,KAAK,MAAM,SAAS,IAAI,gBAAgB,EAAE,CAAC;QACzC,KAAK,MAAM,CAAC,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAoB,EAAE,CAAC;YACxE,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM;gBAAE,SAAS;YACpD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CACnC,CAAC,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,CAC/E,CAAC;YACF,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAC7C,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC;gBAC5B,QAAQ,GAAG,KAAK,CAAC,EAAE,CAAC;gBACpB,MAAM,GAAG,KAAK,CAAC,GAAG,IAAI,QAAQ,CAAC,GAAG,IAAI,oBAAoB,CAAC;gBAC3D,UAAU,GAAG,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;gBACvD,QAAQ,GAAG,IAAI,CAAC;gBAChB,OAAO,CAAC,GAAG,CAAC,yBAAyB,QAAQ,QAAQ,MAAM,EAAE,CAAC,CAAC;gBAC/D,OAAO;YACT,CAAC;QACH,CAAC;IACH,CAAC;IAED,kDAAkD;IAClD,KAAK,MAAM,CAAC,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAoB,EAAE,CAAC;QACxE,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM;YAAE,SAAS;QACpD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;QACrC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC7C,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC;YAC5B,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,QAAQ,CAAC,GAAG,IAAI,oBAAoB,CAAC;YAC/D,UAAU,GAAG,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YAC3D,QAAQ,GAAG,IAAI,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,4BAA4B,QAAQ,QAAQ,MAAM,EAAE,CAAC,CAAC;YAClE,OAAO;QACT,CAAC;IACH,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;AAC3D,CAAC;AAED,sEAAsE;AAEtE,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yDAkDmC,CAAC;AAE1D,sEAAsE;AAEtE,SAAS,kBAAkB;IACzB,mBAAmB;IACnB,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,gCAAgC,CAAC;QACnE,sCAAsC;QACtC,+CAA+C;KAChD,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,WAAW,CAAC,GAAG,CAAC,CAAC;YACjB,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW;AACnC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,WAAoB;IACpD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC;aAC/B,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;aAC3C,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;aAC5E,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAErC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,gCAAgC,CAAC;QAEhE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEpD,MAAM,YAAY,GAAa,EAAE,CAAC;QAClC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,SAAS;YAC3B,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC;gBACnC,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBACxB,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;wBAC1C,CAAC,CAAC,GAAG,CAAC,OAAO;wBACb,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;4BAC1B,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;4BACrF,CAAC,CAAC,EAAE,CAAC;oBACT,IAAI,IAAI,CAAC,IAAI,EAAE;wBAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;gBAChE,CAAC;YACH,CAAC;YAAC,MAAM,CAAC,CAAC,0BAA0B,CAAC,CAAC;QACxC,CAAC;QAED,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,0BAA0B,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,kCAAkC,CAAC;IAC5C,CAAC;AACH,CAAC;AAED,sEAAsE;AAEtE,KAAK,UAAU,OAAO,CAAC,UAAkB;IACvC,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAEtD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,cAAc,CAAC,CAAC;IAEnE,IAAI,CAAC;QACH,IAAI,IAAc,CAAC;QAEnB,IAAI,MAAM,KAAK,oBAAoB,EAAE,CAAC;YACpC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,cAAc,CAAC;YACjF,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;gBAC3B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,WAAW,EAAE,SAAS;oBACtB,mBAAmB,EAAE,YAAY;oBACjC,GAAG,UAAU;iBACd;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,KAAK,EAAE,QAAQ;oBACf,UAAU,EAAE,cAAc;oBAC1B,WAAW,EAAE,CAAC;oBACd,MAAM,EAAE,aAAa;oBACrB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;iBAClD,CAAC;gBACF,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,uDAAuD;YACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC;gBACnD,CAAC,CAAC,MAAM;gBACR,CAAC,CAAC,GAAG,MAAM,sBAAsB,CAAC;YACpC,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;gBAC3B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,eAAe,EAAE,UAAU,SAAS,EAAE;oBACtC,GAAG,UAAU;iBACd;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,KAAK,EAAE,QAAQ;oBACf,UAAU,EAAE,cAAc;oBAC1B,WAAW,EAAE,CAAC;oBACd,QAAQ,EAAE;wBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE;wBAC1C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE;qBACtC;iBACF,CAAC;gBACF,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAQ,CAAC;QAExC,+DAA+D;QAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI;eAC/B,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO;eACnC,EAAE,CAAC;QAER,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAC7C,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;IACxE,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,sEAAsE;AAEtE,SAAS,WAAW,CAAC,QAAgB,EAAE,MAA2B,EAAE,OAAe;IACjF,MAAM,MAAM,GAAG,QAAQ,KAAK,MAAM;QAChC,CAAC,CAAC,YAAY,MAAM,CAAC,OAAO,IAAI,SAAS,EAAE;QAC3C,CAAC,CAAC,cAAc,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,IAAI,SAAS,EAAE,CAAC;IACjE,OAAO,+BAA+B,QAAQ,OAAO,MAAM,8BAA8B,OAAO,EAAE,CAAC;AACrG,CAAC;AAED,sEAAsE;AAEtE,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAgB,EAAE,MAA2B,EAAE,UAAmB;IAElE,MAAM,OAAO,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;IACvE,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAgB,EAAE,MAA2B,EAAE,UAAmB,EAClE,KAAK,GAAG,CAAC,EAAE,SAAS,GAAG,CAAC;IAExB,MAAM,OAAO,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAEtD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACtD,OAAO,CAAC,MAAM,CAAC;SACZ,IAAI,CAAC,CAAC,CAAC,EAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;SAC/E,KAAK,CAAC,CAAC,CAAM,EAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CACpG,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;IACzD,MAAM,SAAS,GAAG,QAAQ,IAAI,SAAS,CAAC;IACxC,MAAM,MAAM,GAAG,SAAS;QACtB,CAAC,CAAC,GAAG,QAAQ,IAAI,KAAK,+BAA+B;QACrD,CAAC,CAAC,QAAQ,QAAQ,IAAI,KAAK,oBAAoB,SAAS,GAAG,CAAC;IAE9D,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "openclaw-guardian",
3
+ "name": "OpenClaw Guardian",
4
+ "description": "Security gate plugin — whitelist + LLM judge + rule fallback. 99% of operations pass via whitelist with 0ms latency.",
5
+ "version": "2.0.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "enabled": { "type": "boolean", "default": true }
11
+ }
12
+ }
13
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "openclaw-guardian",
3
+ "version": "0.1.0",
4
+ "description": "Security gate plugin for OpenClaw — two-tier blacklist (regex + LLM intent verification) prevents dangerous tool executions",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/",
16
+ "openclaw.plugin.json",
17
+ "default-policies.json",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "openclaw",
27
+ "openclaw-plugin",
28
+ "security",
29
+ "safety",
30
+ "guardian",
31
+ "blacklist",
32
+ "llm",
33
+ "tool-safety"
34
+ ],
35
+ "author": "fatcatMaoFei",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/fatcatMaoFei/openclaw-guardian.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/fatcatMaoFei/openclaw-guardian/issues"
43
+ },
44
+ "homepage": "https://github.com/fatcatMaoFei/openclaw-guardian#readme",
45
+ "peerDependencies": {
46
+ "openclaw": ">=2026.1.26"
47
+ },
48
+ "devDependencies": {
49
+ "typescript": "^5.9.0"
50
+ }
51
+ }