averecion-lite 1.3.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/README.md +161 -0
- package/dashboard/dash.css +1085 -0
- package/dashboard/dash.js +898 -0
- package/dashboard/index.html +312 -0
- package/dashboard/landing.html +360 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +409 -0
- package/dist/hooks.d.ts +25 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +68 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +64 -0
- package/dist/injectionGuard.d.ts +9 -0
- package/dist/injectionGuard.d.ts.map +1 -0
- package/dist/injectionGuard.js +16 -0
- package/dist/log-watcher.d.ts +26 -0
- package/dist/log-watcher.d.ts.map +1 -0
- package/dist/log-watcher.js +397 -0
- package/dist/metrics.d.ts +53 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +58 -0
- package/dist/policy.d.ts +11 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +60 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +226 -0
- package/dist/src/capability-manifest.d.ts +16 -0
- package/dist/src/capability-manifest.d.ts.map +1 -0
- package/dist/src/capability-manifest.js +228 -0
- package/dist/src/http-proxy.d.ts +4 -0
- package/dist/src/http-proxy.d.ts.map +1 -0
- package/dist/src/http-proxy.js +266 -0
- package/dist/src/risk-engine.d.ts +43 -0
- package/dist/src/risk-engine.d.ts.map +1 -0
- package/dist/src/risk-engine.js +258 -0
- package/dist/src/shell-wrapper.d.ts +3 -0
- package/dist/src/shell-wrapper.d.ts.map +1 -0
- package/dist/src/shell-wrapper.js +264 -0
- package/dist/storage.d.ts +28 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +144 -0
- package/examples/INTEGRATION.md +162 -0
- package/examples/claude-desktop-agent.json +32 -0
- package/examples/clawdbot-agent.json +44 -0
- package/examples/custom-agent.json +20 -0
- package/lite-policy.json +5 -0
- package/package.json +56 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const CLAWGUARD_DIR = path.join(os.homedir(), ".clawguard");
|
|
42
|
+
const CONFIG_FILE = path.join(CLAWGUARD_DIR, "config.json");
|
|
43
|
+
const LOG_DIR = path.join(CLAWGUARD_DIR, "logs");
|
|
44
|
+
const PENDING_DIR = path.join(CLAWGUARD_DIR, "pending");
|
|
45
|
+
const POLICY_FILE = path.join(CLAWGUARD_DIR, "policy.json");
|
|
46
|
+
const DEFAULT_POLICY = [
|
|
47
|
+
{ pattern: "rm\\s+(-rf?|--recursive|--force)\\s+[\\/~]", decision: "block", reason: "Recursive delete from root or home", riskScore: 100 },
|
|
48
|
+
{ pattern: "rm\\s+-[rf]{2}\\s+", decision: "block", reason: "Force recursive delete", riskScore: 100 },
|
|
49
|
+
{ pattern: "mkfs\\.", decision: "block", reason: "Filesystem formatting", riskScore: 100 },
|
|
50
|
+
{ pattern: "dd\\s+if=.*of=\\/dev", decision: "block", reason: "Direct disk write", riskScore: 100 },
|
|
51
|
+
{ pattern: ">\\s*\\/dev\\/sd[a-z]", decision: "block", reason: "Direct device write", riskScore: 100 },
|
|
52
|
+
{ pattern: "chmod\\s+777\\s+\\/", decision: "block", reason: "Dangerous permissions on root", riskScore: 90 },
|
|
53
|
+
{ pattern: "curl.*\\|\\s*(ba)?sh", decision: "block", reason: "Pipe to shell execution", riskScore: 95 },
|
|
54
|
+
{ pattern: "wget.*\\|\\s*(ba)?sh", decision: "block", reason: "Pipe to shell execution", riskScore: 95 },
|
|
55
|
+
{ pattern: ":\\(\\)\\{.*\\}:", decision: "block", reason: "Fork bomb pattern", riskScore: 100 },
|
|
56
|
+
{ pattern: "sudo\\s+rm", decision: "review", reason: "Privileged delete operation", riskScore: 80 },
|
|
57
|
+
{ pattern: "sudo\\s+chmod", decision: "review", reason: "Privileged permission change", riskScore: 70 },
|
|
58
|
+
{ pattern: "sudo\\s+chown", decision: "review", reason: "Privileged ownership change", riskScore: 70 },
|
|
59
|
+
{ pattern: "npm\\s+install\\s+-g", decision: "review", reason: "Global package installation", riskScore: 50 },
|
|
60
|
+
{ pattern: "pip\\s+install", decision: "review", reason: "Python package installation", riskScore: 40 },
|
|
61
|
+
{ pattern: "ssh\\s+", decision: "review", reason: "Remote connection", riskScore: 60 },
|
|
62
|
+
{ pattern: "scp\\s+", decision: "review", reason: "Remote file transfer", riskScore: 60 },
|
|
63
|
+
{ pattern: "rsync\\s+", decision: "review", reason: "Remote sync operation", riskScore: 50 },
|
|
64
|
+
{ pattern: "git\\s+push\\s+.*--force", decision: "review", reason: "Force push to remote", riskScore: 70 },
|
|
65
|
+
{ pattern: "docker\\s+run", decision: "review", reason: "Container execution", riskScore: 50 },
|
|
66
|
+
{ pattern: "systemctl\\s+(start|stop|restart|enable|disable)", decision: "review", reason: "Service management", riskScore: 60 },
|
|
67
|
+
];
|
|
68
|
+
function ensureDir(dir) {
|
|
69
|
+
if (!fs.existsSync(dir)) {
|
|
70
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function loadConfig() {
|
|
74
|
+
ensureDir(CLAWGUARD_DIR);
|
|
75
|
+
try {
|
|
76
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
77
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
78
|
+
return {
|
|
79
|
+
enabled: config.enabled ?? true,
|
|
80
|
+
protectionLevel: config.protectionLevel ?? "balanced",
|
|
81
|
+
realShell: config.realShell ?? process.env.CLAWGUARD_REAL_SHELL ?? "/bin/bash"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
return {
|
|
87
|
+
enabled: true,
|
|
88
|
+
protectionLevel: "balanced",
|
|
89
|
+
realShell: process.env.CLAWGUARD_REAL_SHELL ?? "/bin/bash"
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function loadPolicy() {
|
|
93
|
+
try {
|
|
94
|
+
if (fs.existsSync(POLICY_FILE)) {
|
|
95
|
+
return JSON.parse(fs.readFileSync(POLICY_FILE, "utf-8"));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch { }
|
|
99
|
+
return DEFAULT_POLICY;
|
|
100
|
+
}
|
|
101
|
+
function logAction(command, allowed, reason, riskScore) {
|
|
102
|
+
ensureDir(LOG_DIR);
|
|
103
|
+
const logEntry = {
|
|
104
|
+
ts: new Date().toISOString(),
|
|
105
|
+
type: "shell",
|
|
106
|
+
command,
|
|
107
|
+
allowed,
|
|
108
|
+
reason,
|
|
109
|
+
riskScore,
|
|
110
|
+
pid: process.pid,
|
|
111
|
+
ppid: process.ppid
|
|
112
|
+
};
|
|
113
|
+
const logFile = path.join(LOG_DIR, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
114
|
+
fs.appendFileSync(logFile, JSON.stringify(logEntry) + "\n");
|
|
115
|
+
}
|
|
116
|
+
function assessCommand(command) {
|
|
117
|
+
const policy = loadPolicy();
|
|
118
|
+
for (const rule of policy) {
|
|
119
|
+
try {
|
|
120
|
+
const regex = new RegExp(rule.pattern, "i");
|
|
121
|
+
if (regex.test(command)) {
|
|
122
|
+
return {
|
|
123
|
+
decision: rule.decision,
|
|
124
|
+
reason: rule.reason,
|
|
125
|
+
riskScore: rule.riskScore
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch { }
|
|
130
|
+
}
|
|
131
|
+
return { decision: "allow", reason: "No policy match", riskScore: 0 };
|
|
132
|
+
}
|
|
133
|
+
function generateApprovalId() {
|
|
134
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
135
|
+
}
|
|
136
|
+
function createPendingApproval(command, reason, riskScore) {
|
|
137
|
+
ensureDir(PENDING_DIR);
|
|
138
|
+
const id = generateApprovalId();
|
|
139
|
+
const approval = {
|
|
140
|
+
id,
|
|
141
|
+
type: "shell",
|
|
142
|
+
command,
|
|
143
|
+
reason,
|
|
144
|
+
riskScore,
|
|
145
|
+
createdAt: new Date().toISOString(),
|
|
146
|
+
status: "pending"
|
|
147
|
+
};
|
|
148
|
+
fs.writeFileSync(path.join(PENDING_DIR, `${id}.json`), JSON.stringify(approval, null, 2));
|
|
149
|
+
return id;
|
|
150
|
+
}
|
|
151
|
+
function checkApprovalStatus(id) {
|
|
152
|
+
const filePath = path.join(PENDING_DIR, `${id}.json`);
|
|
153
|
+
try {
|
|
154
|
+
if (fs.existsSync(filePath)) {
|
|
155
|
+
const approval = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
156
|
+
return approval.status;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch { }
|
|
160
|
+
return "pending";
|
|
161
|
+
}
|
|
162
|
+
async function waitForApproval(id, command, timeoutMs = 30000) {
|
|
163
|
+
console.error(`\x1b[33m🛡️ Clawguard: Command requires approval\x1b[0m`);
|
|
164
|
+
console.error(` Command: ${command.substring(0, 100)}${command.length > 100 ? "..." : ""}`);
|
|
165
|
+
console.error(` Approve at: http://localhost:4321/clawguard`);
|
|
166
|
+
console.error(` Or run: clawguard approve ${id}`);
|
|
167
|
+
const startTime = Date.now();
|
|
168
|
+
const pollInterval = 500;
|
|
169
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
170
|
+
const status = checkApprovalStatus(id);
|
|
171
|
+
if (status === "approved") {
|
|
172
|
+
console.error(`\x1b[32m🛡️ Command approved\x1b[0m`);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
if (status === "denied") {
|
|
176
|
+
console.error(`\x1b[31m🛡️ Command denied\x1b[0m`);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
180
|
+
}
|
|
181
|
+
console.error(`\x1b[31m🛡️ Approval timed out - command blocked\x1b[0m`);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
async function executeCommand(command, config) {
|
|
185
|
+
return new Promise((resolve) => {
|
|
186
|
+
const options = {
|
|
187
|
+
shell: config.realShell,
|
|
188
|
+
stdio: "inherit",
|
|
189
|
+
env: {
|
|
190
|
+
...process.env,
|
|
191
|
+
SHELL: config.realShell
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const child = (0, child_process_1.spawn)(command, [], options);
|
|
195
|
+
child.on("close", (code) => {
|
|
196
|
+
resolve(code ?? 0);
|
|
197
|
+
});
|
|
198
|
+
child.on("error", (err) => {
|
|
199
|
+
console.error(`Error: ${err.message}`);
|
|
200
|
+
resolve(1);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
async function main() {
|
|
205
|
+
const config = loadConfig();
|
|
206
|
+
const args = process.argv.slice(2);
|
|
207
|
+
if (args.length === 0) {
|
|
208
|
+
const child = (0, child_process_1.spawn)(config.realShell, [], {
|
|
209
|
+
stdio: "inherit",
|
|
210
|
+
env: { ...process.env, SHELL: config.realShell }
|
|
211
|
+
});
|
|
212
|
+
child.on("close", (code) => process.exit(code ?? 0));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
let command = "";
|
|
216
|
+
if (args[0] === "-c" && args[1]) {
|
|
217
|
+
command = args[1];
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
command = args.join(" ");
|
|
221
|
+
}
|
|
222
|
+
if (!config.enabled) {
|
|
223
|
+
const exitCode = await executeCommand(command, config);
|
|
224
|
+
process.exit(exitCode);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const assessment = assessCommand(command);
|
|
228
|
+
if (assessment.decision === "allow") {
|
|
229
|
+
logAction(command, true, assessment.reason, assessment.riskScore);
|
|
230
|
+
const exitCode = await executeCommand(command, config);
|
|
231
|
+
process.exit(exitCode);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (assessment.decision === "block") {
|
|
235
|
+
logAction(command, false, assessment.reason, assessment.riskScore);
|
|
236
|
+
console.error(`\x1b[31m🛡️ Clawguard: Command blocked\x1b[0m`);
|
|
237
|
+
console.error(` Reason: ${assessment.reason}`);
|
|
238
|
+
console.error(` Risk Score: ${assessment.riskScore}/100`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (assessment.decision === "review") {
|
|
243
|
+
if (config.protectionLevel === "relaxed") {
|
|
244
|
+
logAction(command, true, "Relaxed mode - auto-approved", assessment.riskScore);
|
|
245
|
+
const exitCode = await executeCommand(command, config);
|
|
246
|
+
process.exit(exitCode);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const approvalId = createPendingApproval(command, assessment.reason, assessment.riskScore);
|
|
250
|
+
const approved = await waitForApproval(approvalId, command);
|
|
251
|
+
logAction(command, approved, approved ? "Manual approval" : "Denied/timeout", assessment.riskScore);
|
|
252
|
+
if (approved) {
|
|
253
|
+
const exitCode = await executeCommand(command, config);
|
|
254
|
+
process.exit(exitCode);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
main().catch((err) => {
|
|
262
|
+
console.error(`Clawguard error: ${err.message}`);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface ActionEvent {
|
|
2
|
+
ts: string;
|
|
3
|
+
tool: string;
|
|
4
|
+
decision: "approved" | "blocked" | "manual";
|
|
5
|
+
reason: string;
|
|
6
|
+
egress: string[];
|
|
7
|
+
inputTokens?: number;
|
|
8
|
+
outputTokens?: number;
|
|
9
|
+
estimatedUSD?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function appendEvent(event: ActionEvent): void;
|
|
12
|
+
export declare function getEvents(hoursBack?: number): ActionEvent[];
|
|
13
|
+
export declare function getLastEvents(count?: number): ActionEvent[];
|
|
14
|
+
export declare function getConfig(): {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
showMode: boolean;
|
|
17
|
+
showModeActionsRemaining: number;
|
|
18
|
+
protectionLevel: string;
|
|
19
|
+
consented: boolean;
|
|
20
|
+
} | null;
|
|
21
|
+
export declare function getPendingApprovals(): {
|
|
22
|
+
id: string;
|
|
23
|
+
tool: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
}[];
|
|
27
|
+
export declare function resolveApproval(id: string, approved: boolean): boolean;
|
|
28
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../storage.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA2CD,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,QAW7C;AAED,wBAAgB,SAAS,CAAC,SAAS,SAAK,GAAG,WAAW,EAAE,CAGvD;AAED,wBAAgB,aAAa,CAAC,KAAK,SAAK,GAAG,WAAW,EAAE,CAEvD;AAED,wBAAgB,SAAS,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,wBAAwB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAOzJ;AAED,wBAAgB,mBAAmB,IAAI;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,CAgBvG;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAYtE"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.appendEvent = appendEvent;
|
|
37
|
+
exports.getEvents = getEvents;
|
|
38
|
+
exports.getLastEvents = getLastEvents;
|
|
39
|
+
exports.getConfig = getConfig;
|
|
40
|
+
exports.getPendingApprovals = getPendingApprovals;
|
|
41
|
+
exports.resolveApproval = resolveApproval;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const CLAWGUARD_DIR = path.join(os.homedir(), ".clawguard");
|
|
46
|
+
const LOGS_DIR = path.join(CLAWGUARD_DIR, "logs");
|
|
47
|
+
const CONFIG_FILE = path.join(CLAWGUARD_DIR, "config.json");
|
|
48
|
+
const PENDING_DIR = path.join(CLAWGUARD_DIR, "pending");
|
|
49
|
+
function ensureDir() {
|
|
50
|
+
if (!fs.existsSync(CLAWGUARD_DIR))
|
|
51
|
+
fs.mkdirSync(CLAWGUARD_DIR, { recursive: true });
|
|
52
|
+
if (!fs.existsSync(LOGS_DIR))
|
|
53
|
+
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
function loadLogsFromJsonl() {
|
|
56
|
+
ensureDir();
|
|
57
|
+
const events = [];
|
|
58
|
+
try {
|
|
59
|
+
const files = fs.readdirSync(LOGS_DIR).filter(f => f.endsWith(".jsonl")).sort();
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
const content = fs.readFileSync(path.join(LOGS_DIR, file), "utf-8");
|
|
62
|
+
for (const line of content.trim().split("\n")) {
|
|
63
|
+
if (!line)
|
|
64
|
+
continue;
|
|
65
|
+
try {
|
|
66
|
+
const entry = JSON.parse(line);
|
|
67
|
+
if (entry.phase === "after")
|
|
68
|
+
continue;
|
|
69
|
+
events.push({
|
|
70
|
+
ts: entry.ts,
|
|
71
|
+
tool: entry.tool,
|
|
72
|
+
decision: entry.allowed === false ? "blocked" : (entry.reason === "manualApproval" ? "manual" : "approved"),
|
|
73
|
+
reason: entry.reason || "",
|
|
74
|
+
egress: [],
|
|
75
|
+
inputTokens: entry.inputTokens,
|
|
76
|
+
outputTokens: entry.outputTokens,
|
|
77
|
+
estimatedUSD: entry.estimatedUSD
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch { }
|
|
85
|
+
return events;
|
|
86
|
+
}
|
|
87
|
+
function appendEvent(event) {
|
|
88
|
+
ensureDir();
|
|
89
|
+
const logFile = path.join(LOGS_DIR, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
90
|
+
const logEntry = {
|
|
91
|
+
ts: event.ts,
|
|
92
|
+
tool: event.tool,
|
|
93
|
+
allowed: event.decision !== "blocked",
|
|
94
|
+
reason: event.reason,
|
|
95
|
+
egress: event.egress
|
|
96
|
+
};
|
|
97
|
+
fs.appendFileSync(logFile, JSON.stringify(logEntry) + "\n");
|
|
98
|
+
}
|
|
99
|
+
function getEvents(hoursBack = 24) {
|
|
100
|
+
const cutoff = new Date(Date.now() - hoursBack * 3600000).toISOString();
|
|
101
|
+
return loadLogsFromJsonl().filter(e => e.ts >= cutoff);
|
|
102
|
+
}
|
|
103
|
+
function getLastEvents(count = 10) {
|
|
104
|
+
return loadLogsFromJsonl().slice(-count);
|
|
105
|
+
}
|
|
106
|
+
function getConfig() {
|
|
107
|
+
try {
|
|
108
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
109
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function getPendingApprovals() {
|
|
116
|
+
if (!fs.existsSync(PENDING_DIR))
|
|
117
|
+
return [];
|
|
118
|
+
const approvals = [];
|
|
119
|
+
const files = fs.readdirSync(PENDING_DIR).filter(f => f.endsWith(".json"));
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
try {
|
|
122
|
+
const data = JSON.parse(fs.readFileSync(path.join(PENDING_DIR, file), "utf-8"));
|
|
123
|
+
if (data.status === "pending") {
|
|
124
|
+
approvals.push({ id: data.id, tool: data.tool, reason: data.reason, createdAt: data.createdAt });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch { }
|
|
128
|
+
}
|
|
129
|
+
return approvals.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
130
|
+
}
|
|
131
|
+
function resolveApproval(id, approved) {
|
|
132
|
+
const filePath = path.join(PENDING_DIR, `${id}.json`);
|
|
133
|
+
try {
|
|
134
|
+
if (fs.existsSync(filePath)) {
|
|
135
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
136
|
+
data.status = approved ? "approved" : "denied";
|
|
137
|
+
data.resolvedAt = new Date().toISOString();
|
|
138
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch { }
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Clawguard Integration Examples
|
|
2
|
+
|
|
3
|
+
Clawguard is agent-agnostic - it works with any AI agent framework by intercepting actions at the execution boundary (shell commands, HTTP requests, file operations) rather than hooking into specific agent internals.
|
|
4
|
+
|
|
5
|
+
## Installation Methods
|
|
6
|
+
|
|
7
|
+
### 1. Shell Wrapper (Recommended)
|
|
8
|
+
|
|
9
|
+
The shell wrapper intercepts all terminal commands before execution:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install the shell wrapper
|
|
13
|
+
clawguard install-shell
|
|
14
|
+
|
|
15
|
+
# For any agent, set the SHELL environment variable:
|
|
16
|
+
SHELL=~/.local/bin/clawguard-sh your-agent-command
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 2. HTTP Proxy
|
|
20
|
+
|
|
21
|
+
Monitor and control outbound API calls:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Start the HTTP proxy (runs on port 4322)
|
|
25
|
+
clawguard start
|
|
26
|
+
|
|
27
|
+
# Configure your agent to use the proxy:
|
|
28
|
+
HTTP_PROXY=http://localhost:4322 your-agent-command
|
|
29
|
+
HTTPS_PROXY=http://localhost:4322 your-agent-command
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 3. Direct API Integration
|
|
33
|
+
|
|
34
|
+
For programmatic integration, use the governance API:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// Check if an action should be allowed
|
|
38
|
+
const response = await fetch('http://localhost:4321/api/assess-action', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'X-Lite-Secret': process.env.CLAWGUARD_SECRET
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
type: 'shell',
|
|
46
|
+
command: 'rm -rf /tmp/cache',
|
|
47
|
+
agentId: 'my-agent'
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const { decision, riskScore, reason } = await response.json();
|
|
52
|
+
// decision: 'allow' | 'block' | 'require_approval'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Agent Manifest Registration
|
|
56
|
+
|
|
57
|
+
Register your agent's capabilities for static risk assessment:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Create a manifest file (see examples/*.json)
|
|
61
|
+
clawguard agent register ./my-agent-manifest.json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Example manifest:
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"id": "my-agent",
|
|
68
|
+
"name": "My Custom Agent",
|
|
69
|
+
"version": "1.0.0",
|
|
70
|
+
"framework": "custom",
|
|
71
|
+
"capabilities": [
|
|
72
|
+
{
|
|
73
|
+
"name": "web_search",
|
|
74
|
+
"type": "http",
|
|
75
|
+
"description": "Search the web",
|
|
76
|
+
"riskWeight": 25
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "file_read",
|
|
80
|
+
"type": "filesystem",
|
|
81
|
+
"description": "Read local files",
|
|
82
|
+
"riskWeight": 20
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Framework-Specific Examples
|
|
89
|
+
|
|
90
|
+
### ClawdBot
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Register ClawdBot with Clawguard
|
|
94
|
+
clawguard agent register ./examples/clawdbot-agent.json
|
|
95
|
+
|
|
96
|
+
# Run ClawdBot through the shell wrapper
|
|
97
|
+
SHELL=~/.local/bin/clawguard-sh clawdbot run
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Claude Desktop (MCP)
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Register Claude Desktop
|
|
104
|
+
clawguard agent register ./examples/claude-desktop-agent.json
|
|
105
|
+
|
|
106
|
+
# Claude Desktop uses MCP tools - intercept at shell level
|
|
107
|
+
SHELL=~/.local/bin/clawguard-sh claude-desktop
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### LangChain
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# For LangChain, wrap tool execution with governance checks
|
|
114
|
+
import requests
|
|
115
|
+
|
|
116
|
+
def governed_tool_call(tool_name, **kwargs):
|
|
117
|
+
# Check with Clawguard first
|
|
118
|
+
response = requests.post('http://localhost:4321/api/assess-action', json={
|
|
119
|
+
'type': 'tool',
|
|
120
|
+
'tool': tool_name,
|
|
121
|
+
'agentId': 'langchain-agent'
|
|
122
|
+
}, headers={'X-Lite-Secret': os.environ['CLAWGUARD_SECRET']})
|
|
123
|
+
|
|
124
|
+
result = response.json()
|
|
125
|
+
if result['decision'] == 'block':
|
|
126
|
+
raise PermissionError(f"Action blocked: {result['reason']}")
|
|
127
|
+
|
|
128
|
+
# Execute the tool
|
|
129
|
+
return original_tool_call(tool_name, **kwargs)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### AutoGPT
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Run AutoGPT with shell interception
|
|
136
|
+
SHELL=~/.local/bin/clawguard-sh \
|
|
137
|
+
HTTP_PROXY=http://localhost:4322 \
|
|
138
|
+
autogpt run
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## CLI Commands
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
clawguard start # Start the dashboard server
|
|
145
|
+
clawguard agents # List registered agents
|
|
146
|
+
clawguard agent register <manifest.json> # Register an agent
|
|
147
|
+
clawguard pending # Show pending approvals
|
|
148
|
+
clawguard approve <id> # Approve a pending action
|
|
149
|
+
clawguard deny <id> # Deny a pending action
|
|
150
|
+
clawguard install-shell # Install the shell wrapper
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Dashboard
|
|
154
|
+
|
|
155
|
+
Access the visual dashboard at `http://localhost:4321/clawguard`
|
|
156
|
+
|
|
157
|
+
Features:
|
|
158
|
+
- View registered agents with risk scores
|
|
159
|
+
- Real-time activity monitoring
|
|
160
|
+
- Pending approval workflow
|
|
161
|
+
- Protection level configuration
|
|
162
|
+
- Capability breakdown for each agent
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "claude-desktop",
|
|
3
|
+
"name": "Claude Desktop",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"framework": "claude-desktop",
|
|
6
|
+
"capabilities": [
|
|
7
|
+
{
|
|
8
|
+
"name": "computer",
|
|
9
|
+
"type": "shell",
|
|
10
|
+
"description": "Computer use - full system access",
|
|
11
|
+
"riskWeight": 95
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "bash",
|
|
15
|
+
"type": "shell",
|
|
16
|
+
"description": "Bash command execution",
|
|
17
|
+
"riskWeight": 80
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "text_editor",
|
|
21
|
+
"type": "filesystem",
|
|
22
|
+
"description": "Text file editing",
|
|
23
|
+
"riskWeight": 50
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "browser",
|
|
27
|
+
"type": "network",
|
|
28
|
+
"description": "Web browsing capability",
|
|
29
|
+
"riskWeight": 40
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "clawdbot-main",
|
|
3
|
+
"name": "ClawdBot Main Agent",
|
|
4
|
+
"version": "2026.1.24",
|
|
5
|
+
"framework": "clawdbot",
|
|
6
|
+
"capabilities": [
|
|
7
|
+
{
|
|
8
|
+
"name": "exec",
|
|
9
|
+
"type": "shell",
|
|
10
|
+
"description": "Execute shell commands",
|
|
11
|
+
"riskWeight": 80
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "read",
|
|
15
|
+
"type": "filesystem",
|
|
16
|
+
"description": "Read files from disk",
|
|
17
|
+
"riskWeight": 20
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "write",
|
|
21
|
+
"type": "filesystem",
|
|
22
|
+
"description": "Write files to disk",
|
|
23
|
+
"riskWeight": 50
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "process",
|
|
27
|
+
"type": "process",
|
|
28
|
+
"description": "Process management (list, kill)",
|
|
29
|
+
"riskWeight": 60
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "http",
|
|
33
|
+
"type": "http",
|
|
34
|
+
"description": "Make HTTP requests",
|
|
35
|
+
"riskWeight": 30
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "memory",
|
|
39
|
+
"type": "custom",
|
|
40
|
+
"description": "Long-term memory storage",
|
|
41
|
+
"riskWeight": 20
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|