fastgrc-openclaw 1.0.19 → 1.0.23
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/dist/bin.js +253 -66
- package/dist/bin.mjs +259 -66
- package/dist/plugin.js +1 -1
- package/dist/plugin.mjs +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -38,12 +38,12 @@ async function evaluate(payload) {
|
|
|
38
38
|
agentId,
|
|
39
39
|
agentType,
|
|
40
40
|
agentName,
|
|
41
|
-
apiKey
|
|
42
|
-
policyId
|
|
43
|
-
baseUrl
|
|
41
|
+
apiKey,
|
|
42
|
+
policyId,
|
|
43
|
+
baseUrl = DEFAULT_BASE_URL,
|
|
44
44
|
timeoutMs = DEFAULT_TIMEOUT_MS
|
|
45
45
|
} = payload;
|
|
46
|
-
const evalUrl = `${
|
|
46
|
+
const evalUrl = `${baseUrl.replace(/\/$/, "")}/api/v1/policy-router/evaluate`;
|
|
47
47
|
const content = `tool_name: ${toolName}
|
|
48
48
|
args: ${JSON.stringify(args)}`;
|
|
49
49
|
try {
|
|
@@ -52,7 +52,7 @@ args: ${JSON.stringify(args)}`;
|
|
|
52
52
|
const res = await fetch(evalUrl, {
|
|
53
53
|
method: "POST",
|
|
54
54
|
headers: {
|
|
55
|
-
"Authorization": `Bearer ${
|
|
55
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
56
56
|
"Content-Type": "application/json"
|
|
57
57
|
},
|
|
58
58
|
body: JSON.stringify({
|
|
@@ -62,7 +62,7 @@ args: ${JSON.stringify(args)}`;
|
|
|
62
62
|
agentId,
|
|
63
63
|
agentType,
|
|
64
64
|
agentName,
|
|
65
|
-
...
|
|
65
|
+
...policyId ? { policyId } : {}
|
|
66
66
|
}),
|
|
67
67
|
signal: controller.signal
|
|
68
68
|
});
|
|
@@ -149,12 +149,47 @@ if (cmd === "unset-policy") {
|
|
|
149
149
|
process.stdout.write("FastGRC policy ID removed \u2014 org-wide default will be used.\n");
|
|
150
150
|
process.exit(0);
|
|
151
151
|
}
|
|
152
|
+
function computeHandler() {
|
|
153
|
+
const binPath = process.argv[1];
|
|
154
|
+
const homeDir = os.homedir();
|
|
155
|
+
return { binPath, homeDir, handlerStr: `HOME=${homeDir} node ${binPath}` };
|
|
156
|
+
}
|
|
157
|
+
function printTestSnippet(handlerStr) {
|
|
158
|
+
process.stdout.write(
|
|
159
|
+
`
|
|
160
|
+
Test it directly (bypasses OpenClaw):
|
|
161
|
+
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /production"},"session_id":"t1"}' \\
|
|
162
|
+
| ${handlerStr}
|
|
163
|
+
# exit 2 = blocked, exit 0 = allowed
|
|
164
|
+
`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (cmd === "restart-openclaw") {
|
|
168
|
+
const { spawn, execSync } = require("child_process");
|
|
169
|
+
try {
|
|
170
|
+
execSync("pkill -x openclaw", { stdio: "ignore" });
|
|
171
|
+
process.stdout.write("Stopped existing openclaw process(es).\n");
|
|
172
|
+
} catch {
|
|
173
|
+
process.stdout.write("No openclaw process was running.\n");
|
|
174
|
+
}
|
|
175
|
+
const logPath = process.env.OPENCLAW_LOG ?? "/tmp/openclaw.log";
|
|
176
|
+
const workDir = arg || process.cwd();
|
|
177
|
+
const out = fs.openSync(logPath, "a");
|
|
178
|
+
const child = spawn("openclaw", [], {
|
|
179
|
+
detached: true,
|
|
180
|
+
stdio: ["ignore", out, out],
|
|
181
|
+
cwd: workDir
|
|
182
|
+
});
|
|
183
|
+
child.unref();
|
|
184
|
+
process.stdout.write(`\u2713 OpenClaw started (PID: ${child.pid}, cwd: ${workDir})
|
|
185
|
+
Logs: tail -f ${logPath}
|
|
186
|
+
`);
|
|
187
|
+
process.exit(0);
|
|
188
|
+
}
|
|
152
189
|
if (cmd === "install-hook") {
|
|
153
190
|
const targetDir = arg || process.cwd();
|
|
154
191
|
const hookMdPath = path.join(targetDir, "HOOK.md");
|
|
155
|
-
const
|
|
156
|
-
const homeDir = os.homedir();
|
|
157
|
-
const handlerStr = `HOME=${homeDir} node ${binPath}`;
|
|
192
|
+
const { handlerStr } = computeHandler();
|
|
158
193
|
const HOOK_ENTRY = ` - matcher: PreToolUse
|
|
159
194
|
handler: "${handlerStr}"
|
|
160
195
|
`;
|
|
@@ -166,15 +201,19 @@ if (cmd === "install-hook") {
|
|
|
166
201
|
if (!fs.existsSync(hookMdPath)) {
|
|
167
202
|
fs.writeFileSync(hookMdPath, HOOK_BLOCK, "utf8");
|
|
168
203
|
process.stdout.write(`\u2713 Created ${hookMdPath}
|
|
204
|
+
Handler: ${handlerStr}
|
|
169
205
|
|
|
170
206
|
Restart OpenClaw \u2014 FastGRC will evaluate every tool call.
|
|
171
207
|
`);
|
|
208
|
+
printTestSnippet(handlerStr);
|
|
172
209
|
process.exit(0);
|
|
173
210
|
}
|
|
174
211
|
const existing = fs.readFileSync(hookMdPath, "utf8");
|
|
175
212
|
if (existing.includes(handlerStr)) {
|
|
176
213
|
process.stdout.write(`\u2713 FastGRC hook already up to date in ${hookMdPath}
|
|
214
|
+
Handler: ${handlerStr}
|
|
177
215
|
`);
|
|
216
|
+
printTestSnippet(handlerStr);
|
|
178
217
|
process.exit(0);
|
|
179
218
|
}
|
|
180
219
|
if (existing.includes("fastgrc-hook") || existing.includes("fastgrc-openclaw")) {
|
|
@@ -184,9 +223,11 @@ Restart OpenClaw \u2014 FastGRC will evaluate every tool call.
|
|
|
184
223
|
);
|
|
185
224
|
fs.writeFileSync(hookMdPath, patched, "utf8");
|
|
186
225
|
process.stdout.write(`\u2713 Updated handler in ${hookMdPath} \u2014 now uses absolute path.
|
|
226
|
+
Handler: ${handlerStr}
|
|
187
227
|
|
|
188
228
|
Restart OpenClaw to activate.
|
|
189
229
|
`);
|
|
230
|
+
printTestSnippet(handlerStr);
|
|
190
231
|
process.exit(0);
|
|
191
232
|
}
|
|
192
233
|
const fmEnd = existing.indexOf("\n---", 3);
|
|
@@ -201,82 +242,228 @@ ${HOOK_ENTRY}`;
|
|
|
201
242
|
}
|
|
202
243
|
fs.writeFileSync(hookMdPath, updated, "utf8");
|
|
203
244
|
process.stdout.write(`\u2713 Updated ${hookMdPath} \u2014 FastGRC hook added.
|
|
245
|
+
Handler: ${handlerStr}
|
|
204
246
|
|
|
205
247
|
Restart OpenClaw to activate.
|
|
206
248
|
`);
|
|
249
|
+
printTestSnippet(handlerStr);
|
|
207
250
|
process.exit(0);
|
|
208
251
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
process.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
let agentId;
|
|
224
|
-
if (raw) {
|
|
225
|
-
try {
|
|
226
|
-
const ctx = JSON.parse(raw);
|
|
227
|
-
args = ctx.tool_input ?? ctx.input ?? ctx.args ?? {};
|
|
228
|
-
agentId = ctx.agent_id ?? ctx.agentId ?? ctx.session_id;
|
|
229
|
-
} catch {
|
|
252
|
+
if (cmd === "setup") {
|
|
253
|
+
const { execSync } = require("child_process");
|
|
254
|
+
const rest = process.argv.slice(3);
|
|
255
|
+
let apiKeyArg;
|
|
256
|
+
let policyIdArg;
|
|
257
|
+
for (let i = 0; i < rest.length; i++) {
|
|
258
|
+
if (rest[i] === "--api-key" && rest[i + 1]) {
|
|
259
|
+
apiKeyArg = rest[++i];
|
|
260
|
+
} else if (rest[i] === "--policy-id" && rest[i + 1]) {
|
|
261
|
+
policyIdArg = rest[++i];
|
|
262
|
+
} else if (!apiKeyArg && !rest[i].startsWith("--")) {
|
|
263
|
+
apiKeyArg = rest[i];
|
|
264
|
+
} else if (!policyIdArg && !rest[i].startsWith("--")) {
|
|
265
|
+
policyIdArg = rest[i];
|
|
230
266
|
}
|
|
231
267
|
}
|
|
232
|
-
if (!
|
|
268
|
+
if (!apiKeyArg) {
|
|
269
|
+
process.stderr.write("Usage: fastgrc-hook setup --api-key <key> [--policy-id <id>]\n");
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
const selfBin = process.argv[1];
|
|
273
|
+
process.stdout.write("Step 1/4: Installing OpenClaw plugin as root...\n");
|
|
274
|
+
try {
|
|
275
|
+
execSync("sudo openclaw plugins remove fastgrc-openclaw", { stdio: "ignore" });
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
execSync("sudo openclaw plugins install fastgrc-openclaw", { stdio: "inherit" });
|
|
279
|
+
writeConfig({ ...readConfig(), apiKey: apiKeyArg });
|
|
280
|
+
process.stdout.write("Step 2/4: API key saved to ~/.fastgrc.json\n");
|
|
281
|
+
if (policyIdArg) {
|
|
282
|
+
writeConfig({ ...readConfig(), policyId: policyIdArg });
|
|
283
|
+
process.stdout.write("Step 3/4: Policy ID saved to ~/.fastgrc.json\n");
|
|
284
|
+
} else {
|
|
285
|
+
process.stdout.write("Step 3/4: No policy ID provided \u2014 org-wide default will be used.\n");
|
|
286
|
+
}
|
|
287
|
+
process.stdout.write("Step 4/4: Wiring up HOOK.md and restarting OpenClaw...\n");
|
|
288
|
+
execSync(`node "${selfBin}" install-hook`, { stdio: "inherit" });
|
|
289
|
+
execSync(`node "${selfBin}" restart-openclaw`, { stdio: "inherit" });
|
|
290
|
+
process.stdout.write('\n\u2713 FastGRC setup complete. Run "fastgrc-hook test" to verify.\n');
|
|
291
|
+
process.exit(0);
|
|
292
|
+
}
|
|
293
|
+
if (cmd === "uninstall-hook") {
|
|
294
|
+
const targetDir = arg || process.cwd();
|
|
295
|
+
const hookMdPath = path.join(targetDir, "HOOK.md");
|
|
296
|
+
if (!fs.existsSync(hookMdPath)) {
|
|
297
|
+
process.stdout.write("No HOOK.md found \u2014 nothing to remove.\n");
|
|
233
298
|
process.exit(0);
|
|
234
299
|
}
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
300
|
+
const existing = fs.readFileSync(hookMdPath, "utf8");
|
|
301
|
+
const patched = existing.replace(
|
|
302
|
+
/[ \t]*-[ \t]*matcher:[ \t]*PreToolUse\n[ \t]*handler:[ \t]*"[^"]*(?:fastgrc-hook|fastgrc-openclaw)[^"]*"\n?/,
|
|
303
|
+
""
|
|
304
|
+
);
|
|
305
|
+
if (patched === existing) {
|
|
306
|
+
process.stdout.write("FastGRC hook not found in HOOK.md \u2014 nothing to remove.\n");
|
|
307
|
+
} else {
|
|
308
|
+
fs.writeFileSync(hookMdPath, patched, "utf8");
|
|
309
|
+
process.stdout.write(`\u2713 FastGRC hook removed from ${hookMdPath}
|
|
310
|
+
`);
|
|
242
311
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
312
|
+
process.exit(0);
|
|
313
|
+
}
|
|
314
|
+
if (cmd === "uninstall") {
|
|
315
|
+
const { execSync } = require("child_process");
|
|
316
|
+
const selfBin = process.argv[1];
|
|
317
|
+
const targetDir = arg || process.cwd();
|
|
318
|
+
process.stdout.write("Removing FastGRC from this OpenClaw deployment...\n\n");
|
|
319
|
+
process.stdout.write("1. Removing OpenClaw plugin...\n");
|
|
320
|
+
try {
|
|
321
|
+
execSync("sudo openclaw plugins remove fastgrc-openclaw", { stdio: "inherit" });
|
|
322
|
+
} catch {
|
|
323
|
+
process.stdout.write(" (plugin was not installed \u2014 skipped)\n");
|
|
324
|
+
}
|
|
325
|
+
process.stdout.write("2. Clearing config...\n");
|
|
326
|
+
const cfg = readConfig();
|
|
327
|
+
delete cfg.apiKey;
|
|
328
|
+
delete cfg.policyId;
|
|
329
|
+
writeConfig(cfg);
|
|
330
|
+
process.stdout.write(" API key and policy ID removed from ~/.fastgrc.json\n");
|
|
331
|
+
process.stdout.write("3. Removing hook from HOOK.md...\n");
|
|
332
|
+
execSync(`node "${selfBin}" uninstall-hook "${targetDir}"`, { stdio: "inherit" });
|
|
333
|
+
process.stdout.write("4. Restarting OpenClaw...\n");
|
|
334
|
+
execSync(`node "${selfBin}" restart-openclaw`, { stdio: "inherit" });
|
|
335
|
+
process.stdout.write(
|
|
336
|
+
"\n\u2713 FastGRC removed.\nTo fully remove the CLI: npm uninstall -g fastgrc-openclaw\n"
|
|
337
|
+
);
|
|
338
|
+
process.exit(0);
|
|
339
|
+
}
|
|
340
|
+
if (cmd === "test") {
|
|
341
|
+
const testCommand = arg || "rm -rf /production-db";
|
|
342
|
+
const payload = JSON.stringify({
|
|
343
|
+
tool_name: "Bash",
|
|
344
|
+
tool_input: { command: testCommand },
|
|
345
|
+
session_id: "fastgrc-hook-test"
|
|
346
|
+
});
|
|
347
|
+
const { handlerStr } = computeHandler();
|
|
348
|
+
process.stdout.write(`Testing handler: ${handlerStr}
|
|
349
|
+
`);
|
|
350
|
+
process.stdout.write(`Payload: ${payload}
|
|
351
|
+
|
|
352
|
+
`);
|
|
353
|
+
const apiKey = process.env.FASTGRC_API_KEY ?? readConfig().apiKey;
|
|
354
|
+
if (!apiKey) {
|
|
355
|
+
process.stderr.write("No API key configured. Run: fastgrc-hook set-key fgrc_k1_...\n");
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
const baseUrl = process.env.FASTGRC_BASE_URL ?? "https://app.fastgrc.ai";
|
|
359
|
+
const policyId = process.env.FASTGRC_POLICY_ID ?? readConfig().policyId;
|
|
360
|
+
const ctx = JSON.parse(payload);
|
|
361
|
+
evaluate({
|
|
362
|
+
toolName: ctx.tool_name,
|
|
363
|
+
args: ctx.tool_input,
|
|
364
|
+
agentId: ctx.session_id,
|
|
247
365
|
apiKey,
|
|
248
|
-
// narrowed at module level via early-exit guard above
|
|
249
366
|
policyId,
|
|
250
367
|
baseUrl
|
|
368
|
+
}).then((result) => {
|
|
369
|
+
if (!result) {
|
|
370
|
+
process.stdout.write("Evaluate returned null (fail-open) \u2014 network error or timeout.\n");
|
|
371
|
+
process.exit(0);
|
|
372
|
+
}
|
|
373
|
+
process.stdout.write(`Decision: ${result.decision}
|
|
374
|
+
`);
|
|
375
|
+
if (result.reasoning) process.stdout.write(`Reason: ${result.reasoning}
|
|
376
|
+
`);
|
|
377
|
+
if (result.policyContext?.matchedRule) process.stdout.write(`Matched rule: ${result.policyContext.matchedRule}
|
|
378
|
+
`);
|
|
379
|
+
process.exit(result.decision === "block" || result.decision === "require_approval" ? 2 : 0);
|
|
380
|
+
}).catch((err) => {
|
|
381
|
+
process.stderr.write(`Test failed: ${err}
|
|
382
|
+
`);
|
|
383
|
+
process.exit(1);
|
|
251
384
|
});
|
|
252
|
-
|
|
385
|
+
} else if (cmd === "where" || cmd === "which-hook") {
|
|
386
|
+
const { handlerStr, binPath } = computeHandler();
|
|
387
|
+
process.stdout.write(`Binary: ${binPath}
|
|
388
|
+
`);
|
|
389
|
+
process.stdout.write(`Handler: ${handlerStr}
|
|
390
|
+
`);
|
|
391
|
+
process.exit(0);
|
|
392
|
+
}
|
|
393
|
+
if (cmd === "test" || cmd === "where" || cmd === "which-hook" || cmd === "restart-openclaw" || cmd === "setup" || cmd === "uninstall" || cmd === "uninstall-hook") {
|
|
394
|
+
} else {
|
|
395
|
+
const apiKey = process.env.FASTGRC_API_KEY ?? readConfig().apiKey;
|
|
396
|
+
if (!apiKey) {
|
|
397
|
+
process.stderr.write("[fastgrc-hook] No API key configured \u2014 run: fastgrc-hook set-key fgrc_k1_...\n");
|
|
253
398
|
process.exit(0);
|
|
254
399
|
}
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
process.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
400
|
+
const baseUrl = process.env.FASTGRC_BASE_URL ?? "https://app.fastgrc.ai";
|
|
401
|
+
const policyId = process.env.FASTGRC_POLICY_ID ?? readConfig().policyId;
|
|
402
|
+
async function main() {
|
|
403
|
+
const chunks = [];
|
|
404
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
405
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
406
|
+
const toolName = process.env.HOOK_TOOL_NAME ?? "";
|
|
407
|
+
const toolInputRaw = process.env.HOOK_TOOL_INPUT ?? "{}";
|
|
408
|
+
let args = {};
|
|
409
|
+
let agentId;
|
|
410
|
+
if (raw) {
|
|
411
|
+
try {
|
|
412
|
+
const ctx = JSON.parse(raw);
|
|
413
|
+
args = ctx.tool_input ?? ctx.input ?? ctx.args ?? {};
|
|
414
|
+
agentId = ctx.agent_id ?? ctx.agentId ?? ctx.session_id;
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (!toolName && !raw) {
|
|
419
|
+
process.exit(0);
|
|
420
|
+
}
|
|
421
|
+
const resolvedToolName = toolName || (raw ? JSON.parse(raw)?.tool_name ?? "unknown" : "unknown");
|
|
422
|
+
if (!args || Object.keys(args).length === 0) {
|
|
423
|
+
try {
|
|
424
|
+
args = JSON.parse(toolInputRaw);
|
|
425
|
+
} catch {
|
|
426
|
+
args = {};
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const result = await evaluate({
|
|
430
|
+
toolName: resolvedToolName,
|
|
431
|
+
args,
|
|
432
|
+
agentId,
|
|
433
|
+
apiKey,
|
|
434
|
+
// narrowed at module level via early-exit guard above
|
|
435
|
+
policyId,
|
|
436
|
+
baseUrl
|
|
437
|
+
});
|
|
438
|
+
if (!result) {
|
|
439
|
+
process.exit(0);
|
|
440
|
+
}
|
|
441
|
+
const { decision, reasoning, policyContext, reasonTags } = result;
|
|
442
|
+
const matchedRule = policyContext?.matchedRule;
|
|
443
|
+
if (decision === "block") {
|
|
444
|
+
const msg = matchedRule ? `FastGRC blocked: [${matchedRule}] ${reasoning}` : `FastGRC blocked: ${reasoning}`;
|
|
445
|
+
process.stderr.write(msg + "\n");
|
|
446
|
+
process.exit(2);
|
|
447
|
+
}
|
|
448
|
+
if (decision === "require_approval") {
|
|
449
|
+
process.stderr.write(
|
|
450
|
+
`FastGRC requires approval before this action.
|
|
265
451
|
${reasoning}
|
|
266
452
|
Review at: ${baseUrl}/dashboard/agent-policies
|
|
267
453
|
`
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
454
|
+
);
|
|
455
|
+
process.exit(2);
|
|
456
|
+
}
|
|
457
|
+
if (Array.isArray(reasonTags) && reasonTags.includes("override_block_active")) {
|
|
458
|
+
const msg = matchedRule ? `[FastGRC] Observability mode \u2014 would have blocked: [${matchedRule}] ${reasoning}` : `[FastGRC] Observability mode \u2014 would have blocked: ${reasoning}`;
|
|
459
|
+
process.stderr.write(msg + "\n");
|
|
460
|
+
}
|
|
461
|
+
process.exit(0);
|
|
274
462
|
}
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
main().catch((err) => {
|
|
278
|
-
process.stderr.write(`[fastgrc-hook] Unexpected error: ${err}
|
|
463
|
+
main().catch((err) => {
|
|
464
|
+
process.stderr.write(`[fastgrc-hook] Unexpected error: ${err}
|
|
279
465
|
`);
|
|
280
|
-
|
|
281
|
-
});
|
|
466
|
+
process.exit(0);
|
|
467
|
+
});
|
|
468
|
+
}
|
|
282
469
|
module.exports = module.exports.default ?? module.exports;
|
package/dist/bin.mjs
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/bin.ts
|
|
4
10
|
import * as fs from "fs";
|
|
@@ -15,12 +21,12 @@ async function evaluate(payload) {
|
|
|
15
21
|
agentId,
|
|
16
22
|
agentType,
|
|
17
23
|
agentName,
|
|
18
|
-
apiKey
|
|
19
|
-
policyId
|
|
20
|
-
baseUrl
|
|
24
|
+
apiKey,
|
|
25
|
+
policyId,
|
|
26
|
+
baseUrl = DEFAULT_BASE_URL,
|
|
21
27
|
timeoutMs = DEFAULT_TIMEOUT_MS
|
|
22
28
|
} = payload;
|
|
23
|
-
const evalUrl = `${
|
|
29
|
+
const evalUrl = `${baseUrl.replace(/\/$/, "")}/api/v1/policy-router/evaluate`;
|
|
24
30
|
const content = `tool_name: ${toolName}
|
|
25
31
|
args: ${JSON.stringify(args)}`;
|
|
26
32
|
try {
|
|
@@ -29,7 +35,7 @@ args: ${JSON.stringify(args)}`;
|
|
|
29
35
|
const res = await fetch(evalUrl, {
|
|
30
36
|
method: "POST",
|
|
31
37
|
headers: {
|
|
32
|
-
"Authorization": `Bearer ${
|
|
38
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
33
39
|
"Content-Type": "application/json"
|
|
34
40
|
},
|
|
35
41
|
body: JSON.stringify({
|
|
@@ -39,7 +45,7 @@ args: ${JSON.stringify(args)}`;
|
|
|
39
45
|
agentId,
|
|
40
46
|
agentType,
|
|
41
47
|
agentName,
|
|
42
|
-
...
|
|
48
|
+
...policyId ? { policyId } : {}
|
|
43
49
|
}),
|
|
44
50
|
signal: controller.signal
|
|
45
51
|
});
|
|
@@ -126,12 +132,47 @@ if (cmd === "unset-policy") {
|
|
|
126
132
|
process.stdout.write("FastGRC policy ID removed \u2014 org-wide default will be used.\n");
|
|
127
133
|
process.exit(0);
|
|
128
134
|
}
|
|
135
|
+
function computeHandler() {
|
|
136
|
+
const binPath = process.argv[1];
|
|
137
|
+
const homeDir = os.homedir();
|
|
138
|
+
return { binPath, homeDir, handlerStr: `HOME=${homeDir} node ${binPath}` };
|
|
139
|
+
}
|
|
140
|
+
function printTestSnippet(handlerStr) {
|
|
141
|
+
process.stdout.write(
|
|
142
|
+
`
|
|
143
|
+
Test it directly (bypasses OpenClaw):
|
|
144
|
+
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /production"},"session_id":"t1"}' \\
|
|
145
|
+
| ${handlerStr}
|
|
146
|
+
# exit 2 = blocked, exit 0 = allowed
|
|
147
|
+
`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (cmd === "restart-openclaw") {
|
|
151
|
+
const { spawn, execSync } = __require("child_process");
|
|
152
|
+
try {
|
|
153
|
+
execSync("pkill -x openclaw", { stdio: "ignore" });
|
|
154
|
+
process.stdout.write("Stopped existing openclaw process(es).\n");
|
|
155
|
+
} catch {
|
|
156
|
+
process.stdout.write("No openclaw process was running.\n");
|
|
157
|
+
}
|
|
158
|
+
const logPath = process.env.OPENCLAW_LOG ?? "/tmp/openclaw.log";
|
|
159
|
+
const workDir = arg || process.cwd();
|
|
160
|
+
const out = fs.openSync(logPath, "a");
|
|
161
|
+
const child = spawn("openclaw", [], {
|
|
162
|
+
detached: true,
|
|
163
|
+
stdio: ["ignore", out, out],
|
|
164
|
+
cwd: workDir
|
|
165
|
+
});
|
|
166
|
+
child.unref();
|
|
167
|
+
process.stdout.write(`\u2713 OpenClaw started (PID: ${child.pid}, cwd: ${workDir})
|
|
168
|
+
Logs: tail -f ${logPath}
|
|
169
|
+
`);
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
129
172
|
if (cmd === "install-hook") {
|
|
130
173
|
const targetDir = arg || process.cwd();
|
|
131
174
|
const hookMdPath = path.join(targetDir, "HOOK.md");
|
|
132
|
-
const
|
|
133
|
-
const homeDir = os.homedir();
|
|
134
|
-
const handlerStr = `HOME=${homeDir} node ${binPath}`;
|
|
175
|
+
const { handlerStr } = computeHandler();
|
|
135
176
|
const HOOK_ENTRY = ` - matcher: PreToolUse
|
|
136
177
|
handler: "${handlerStr}"
|
|
137
178
|
`;
|
|
@@ -143,15 +184,19 @@ if (cmd === "install-hook") {
|
|
|
143
184
|
if (!fs.existsSync(hookMdPath)) {
|
|
144
185
|
fs.writeFileSync(hookMdPath, HOOK_BLOCK, "utf8");
|
|
145
186
|
process.stdout.write(`\u2713 Created ${hookMdPath}
|
|
187
|
+
Handler: ${handlerStr}
|
|
146
188
|
|
|
147
189
|
Restart OpenClaw \u2014 FastGRC will evaluate every tool call.
|
|
148
190
|
`);
|
|
191
|
+
printTestSnippet(handlerStr);
|
|
149
192
|
process.exit(0);
|
|
150
193
|
}
|
|
151
194
|
const existing = fs.readFileSync(hookMdPath, "utf8");
|
|
152
195
|
if (existing.includes(handlerStr)) {
|
|
153
196
|
process.stdout.write(`\u2713 FastGRC hook already up to date in ${hookMdPath}
|
|
197
|
+
Handler: ${handlerStr}
|
|
154
198
|
`);
|
|
199
|
+
printTestSnippet(handlerStr);
|
|
155
200
|
process.exit(0);
|
|
156
201
|
}
|
|
157
202
|
if (existing.includes("fastgrc-hook") || existing.includes("fastgrc-openclaw")) {
|
|
@@ -161,9 +206,11 @@ Restart OpenClaw \u2014 FastGRC will evaluate every tool call.
|
|
|
161
206
|
);
|
|
162
207
|
fs.writeFileSync(hookMdPath, patched, "utf8");
|
|
163
208
|
process.stdout.write(`\u2713 Updated handler in ${hookMdPath} \u2014 now uses absolute path.
|
|
209
|
+
Handler: ${handlerStr}
|
|
164
210
|
|
|
165
211
|
Restart OpenClaw to activate.
|
|
166
212
|
`);
|
|
213
|
+
printTestSnippet(handlerStr);
|
|
167
214
|
process.exit(0);
|
|
168
215
|
}
|
|
169
216
|
const fmEnd = existing.indexOf("\n---", 3);
|
|
@@ -178,81 +225,227 @@ ${HOOK_ENTRY}`;
|
|
|
178
225
|
}
|
|
179
226
|
fs.writeFileSync(hookMdPath, updated, "utf8");
|
|
180
227
|
process.stdout.write(`\u2713 Updated ${hookMdPath} \u2014 FastGRC hook added.
|
|
228
|
+
Handler: ${handlerStr}
|
|
181
229
|
|
|
182
230
|
Restart OpenClaw to activate.
|
|
183
231
|
`);
|
|
232
|
+
printTestSnippet(handlerStr);
|
|
184
233
|
process.exit(0);
|
|
185
234
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
process.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
let agentId;
|
|
201
|
-
if (raw) {
|
|
202
|
-
try {
|
|
203
|
-
const ctx = JSON.parse(raw);
|
|
204
|
-
args = ctx.tool_input ?? ctx.input ?? ctx.args ?? {};
|
|
205
|
-
agentId = ctx.agent_id ?? ctx.agentId ?? ctx.session_id;
|
|
206
|
-
} catch {
|
|
235
|
+
if (cmd === "setup") {
|
|
236
|
+
const { execSync } = __require("child_process");
|
|
237
|
+
const rest = process.argv.slice(3);
|
|
238
|
+
let apiKeyArg;
|
|
239
|
+
let policyIdArg;
|
|
240
|
+
for (let i = 0; i < rest.length; i++) {
|
|
241
|
+
if (rest[i] === "--api-key" && rest[i + 1]) {
|
|
242
|
+
apiKeyArg = rest[++i];
|
|
243
|
+
} else if (rest[i] === "--policy-id" && rest[i + 1]) {
|
|
244
|
+
policyIdArg = rest[++i];
|
|
245
|
+
} else if (!apiKeyArg && !rest[i].startsWith("--")) {
|
|
246
|
+
apiKeyArg = rest[i];
|
|
247
|
+
} else if (!policyIdArg && !rest[i].startsWith("--")) {
|
|
248
|
+
policyIdArg = rest[i];
|
|
207
249
|
}
|
|
208
250
|
}
|
|
209
|
-
if (!
|
|
251
|
+
if (!apiKeyArg) {
|
|
252
|
+
process.stderr.write("Usage: fastgrc-hook setup --api-key <key> [--policy-id <id>]\n");
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
const selfBin = process.argv[1];
|
|
256
|
+
process.stdout.write("Step 1/4: Installing OpenClaw plugin as root...\n");
|
|
257
|
+
try {
|
|
258
|
+
execSync("sudo openclaw plugins remove fastgrc-openclaw", { stdio: "ignore" });
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
execSync("sudo openclaw plugins install fastgrc-openclaw", { stdio: "inherit" });
|
|
262
|
+
writeConfig({ ...readConfig(), apiKey: apiKeyArg });
|
|
263
|
+
process.stdout.write("Step 2/4: API key saved to ~/.fastgrc.json\n");
|
|
264
|
+
if (policyIdArg) {
|
|
265
|
+
writeConfig({ ...readConfig(), policyId: policyIdArg });
|
|
266
|
+
process.stdout.write("Step 3/4: Policy ID saved to ~/.fastgrc.json\n");
|
|
267
|
+
} else {
|
|
268
|
+
process.stdout.write("Step 3/4: No policy ID provided \u2014 org-wide default will be used.\n");
|
|
269
|
+
}
|
|
270
|
+
process.stdout.write("Step 4/4: Wiring up HOOK.md and restarting OpenClaw...\n");
|
|
271
|
+
execSync(`node "${selfBin}" install-hook`, { stdio: "inherit" });
|
|
272
|
+
execSync(`node "${selfBin}" restart-openclaw`, { stdio: "inherit" });
|
|
273
|
+
process.stdout.write('\n\u2713 FastGRC setup complete. Run "fastgrc-hook test" to verify.\n');
|
|
274
|
+
process.exit(0);
|
|
275
|
+
}
|
|
276
|
+
if (cmd === "uninstall-hook") {
|
|
277
|
+
const targetDir = arg || process.cwd();
|
|
278
|
+
const hookMdPath = path.join(targetDir, "HOOK.md");
|
|
279
|
+
if (!fs.existsSync(hookMdPath)) {
|
|
280
|
+
process.stdout.write("No HOOK.md found \u2014 nothing to remove.\n");
|
|
210
281
|
process.exit(0);
|
|
211
282
|
}
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
283
|
+
const existing = fs.readFileSync(hookMdPath, "utf8");
|
|
284
|
+
const patched = existing.replace(
|
|
285
|
+
/[ \t]*-[ \t]*matcher:[ \t]*PreToolUse\n[ \t]*handler:[ \t]*"[^"]*(?:fastgrc-hook|fastgrc-openclaw)[^"]*"\n?/,
|
|
286
|
+
""
|
|
287
|
+
);
|
|
288
|
+
if (patched === existing) {
|
|
289
|
+
process.stdout.write("FastGRC hook not found in HOOK.md \u2014 nothing to remove.\n");
|
|
290
|
+
} else {
|
|
291
|
+
fs.writeFileSync(hookMdPath, patched, "utf8");
|
|
292
|
+
process.stdout.write(`\u2713 FastGRC hook removed from ${hookMdPath}
|
|
293
|
+
`);
|
|
219
294
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
295
|
+
process.exit(0);
|
|
296
|
+
}
|
|
297
|
+
if (cmd === "uninstall") {
|
|
298
|
+
const { execSync } = __require("child_process");
|
|
299
|
+
const selfBin = process.argv[1];
|
|
300
|
+
const targetDir = arg || process.cwd();
|
|
301
|
+
process.stdout.write("Removing FastGRC from this OpenClaw deployment...\n\n");
|
|
302
|
+
process.stdout.write("1. Removing OpenClaw plugin...\n");
|
|
303
|
+
try {
|
|
304
|
+
execSync("sudo openclaw plugins remove fastgrc-openclaw", { stdio: "inherit" });
|
|
305
|
+
} catch {
|
|
306
|
+
process.stdout.write(" (plugin was not installed \u2014 skipped)\n");
|
|
307
|
+
}
|
|
308
|
+
process.stdout.write("2. Clearing config...\n");
|
|
309
|
+
const cfg = readConfig();
|
|
310
|
+
delete cfg.apiKey;
|
|
311
|
+
delete cfg.policyId;
|
|
312
|
+
writeConfig(cfg);
|
|
313
|
+
process.stdout.write(" API key and policy ID removed from ~/.fastgrc.json\n");
|
|
314
|
+
process.stdout.write("3. Removing hook from HOOK.md...\n");
|
|
315
|
+
execSync(`node "${selfBin}" uninstall-hook "${targetDir}"`, { stdio: "inherit" });
|
|
316
|
+
process.stdout.write("4. Restarting OpenClaw...\n");
|
|
317
|
+
execSync(`node "${selfBin}" restart-openclaw`, { stdio: "inherit" });
|
|
318
|
+
process.stdout.write(
|
|
319
|
+
"\n\u2713 FastGRC removed.\nTo fully remove the CLI: npm uninstall -g fastgrc-openclaw\n"
|
|
320
|
+
);
|
|
321
|
+
process.exit(0);
|
|
322
|
+
}
|
|
323
|
+
if (cmd === "test") {
|
|
324
|
+
const testCommand = arg || "rm -rf /production-db";
|
|
325
|
+
const payload = JSON.stringify({
|
|
326
|
+
tool_name: "Bash",
|
|
327
|
+
tool_input: { command: testCommand },
|
|
328
|
+
session_id: "fastgrc-hook-test"
|
|
329
|
+
});
|
|
330
|
+
const { handlerStr } = computeHandler();
|
|
331
|
+
process.stdout.write(`Testing handler: ${handlerStr}
|
|
332
|
+
`);
|
|
333
|
+
process.stdout.write(`Payload: ${payload}
|
|
334
|
+
|
|
335
|
+
`);
|
|
336
|
+
const apiKey = process.env.FASTGRC_API_KEY ?? readConfig().apiKey;
|
|
337
|
+
if (!apiKey) {
|
|
338
|
+
process.stderr.write("No API key configured. Run: fastgrc-hook set-key fgrc_k1_...\n");
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
const baseUrl = process.env.FASTGRC_BASE_URL ?? "https://app.fastgrc.ai";
|
|
342
|
+
const policyId = process.env.FASTGRC_POLICY_ID ?? readConfig().policyId;
|
|
343
|
+
const ctx = JSON.parse(payload);
|
|
344
|
+
evaluate({
|
|
345
|
+
toolName: ctx.tool_name,
|
|
346
|
+
args: ctx.tool_input,
|
|
347
|
+
agentId: ctx.session_id,
|
|
224
348
|
apiKey,
|
|
225
|
-
// narrowed at module level via early-exit guard above
|
|
226
349
|
policyId,
|
|
227
350
|
baseUrl
|
|
351
|
+
}).then((result) => {
|
|
352
|
+
if (!result) {
|
|
353
|
+
process.stdout.write("Evaluate returned null (fail-open) \u2014 network error or timeout.\n");
|
|
354
|
+
process.exit(0);
|
|
355
|
+
}
|
|
356
|
+
process.stdout.write(`Decision: ${result.decision}
|
|
357
|
+
`);
|
|
358
|
+
if (result.reasoning) process.stdout.write(`Reason: ${result.reasoning}
|
|
359
|
+
`);
|
|
360
|
+
if (result.policyContext?.matchedRule) process.stdout.write(`Matched rule: ${result.policyContext.matchedRule}
|
|
361
|
+
`);
|
|
362
|
+
process.exit(result.decision === "block" || result.decision === "require_approval" ? 2 : 0);
|
|
363
|
+
}).catch((err) => {
|
|
364
|
+
process.stderr.write(`Test failed: ${err}
|
|
365
|
+
`);
|
|
366
|
+
process.exit(1);
|
|
228
367
|
});
|
|
229
|
-
|
|
368
|
+
} else if (cmd === "where" || cmd === "which-hook") {
|
|
369
|
+
const { handlerStr, binPath } = computeHandler();
|
|
370
|
+
process.stdout.write(`Binary: ${binPath}
|
|
371
|
+
`);
|
|
372
|
+
process.stdout.write(`Handler: ${handlerStr}
|
|
373
|
+
`);
|
|
374
|
+
process.exit(0);
|
|
375
|
+
}
|
|
376
|
+
if (cmd === "test" || cmd === "where" || cmd === "which-hook" || cmd === "restart-openclaw" || cmd === "setup" || cmd === "uninstall" || cmd === "uninstall-hook") {
|
|
377
|
+
} else {
|
|
378
|
+
const apiKey = process.env.FASTGRC_API_KEY ?? readConfig().apiKey;
|
|
379
|
+
if (!apiKey) {
|
|
380
|
+
process.stderr.write("[fastgrc-hook] No API key configured \u2014 run: fastgrc-hook set-key fgrc_k1_...\n");
|
|
230
381
|
process.exit(0);
|
|
231
382
|
}
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
process.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
383
|
+
const baseUrl = process.env.FASTGRC_BASE_URL ?? "https://app.fastgrc.ai";
|
|
384
|
+
const policyId = process.env.FASTGRC_POLICY_ID ?? readConfig().policyId;
|
|
385
|
+
async function main() {
|
|
386
|
+
const chunks = [];
|
|
387
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
388
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
389
|
+
const toolName = process.env.HOOK_TOOL_NAME ?? "";
|
|
390
|
+
const toolInputRaw = process.env.HOOK_TOOL_INPUT ?? "{}";
|
|
391
|
+
let args = {};
|
|
392
|
+
let agentId;
|
|
393
|
+
if (raw) {
|
|
394
|
+
try {
|
|
395
|
+
const ctx = JSON.parse(raw);
|
|
396
|
+
args = ctx.tool_input ?? ctx.input ?? ctx.args ?? {};
|
|
397
|
+
agentId = ctx.agent_id ?? ctx.agentId ?? ctx.session_id;
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (!toolName && !raw) {
|
|
402
|
+
process.exit(0);
|
|
403
|
+
}
|
|
404
|
+
const resolvedToolName = toolName || (raw ? JSON.parse(raw)?.tool_name ?? "unknown" : "unknown");
|
|
405
|
+
if (!args || Object.keys(args).length === 0) {
|
|
406
|
+
try {
|
|
407
|
+
args = JSON.parse(toolInputRaw);
|
|
408
|
+
} catch {
|
|
409
|
+
args = {};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const result = await evaluate({
|
|
413
|
+
toolName: resolvedToolName,
|
|
414
|
+
args,
|
|
415
|
+
agentId,
|
|
416
|
+
apiKey,
|
|
417
|
+
// narrowed at module level via early-exit guard above
|
|
418
|
+
policyId,
|
|
419
|
+
baseUrl
|
|
420
|
+
});
|
|
421
|
+
if (!result) {
|
|
422
|
+
process.exit(0);
|
|
423
|
+
}
|
|
424
|
+
const { decision, reasoning, policyContext, reasonTags } = result;
|
|
425
|
+
const matchedRule = policyContext?.matchedRule;
|
|
426
|
+
if (decision === "block") {
|
|
427
|
+
const msg = matchedRule ? `FastGRC blocked: [${matchedRule}] ${reasoning}` : `FastGRC blocked: ${reasoning}`;
|
|
428
|
+
process.stderr.write(msg + "\n");
|
|
429
|
+
process.exit(2);
|
|
430
|
+
}
|
|
431
|
+
if (decision === "require_approval") {
|
|
432
|
+
process.stderr.write(
|
|
433
|
+
`FastGRC requires approval before this action.
|
|
242
434
|
${reasoning}
|
|
243
435
|
Review at: ${baseUrl}/dashboard/agent-policies
|
|
244
436
|
`
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
437
|
+
);
|
|
438
|
+
process.exit(2);
|
|
439
|
+
}
|
|
440
|
+
if (Array.isArray(reasonTags) && reasonTags.includes("override_block_active")) {
|
|
441
|
+
const msg = matchedRule ? `[FastGRC] Observability mode \u2014 would have blocked: [${matchedRule}] ${reasoning}` : `[FastGRC] Observability mode \u2014 would have blocked: ${reasoning}`;
|
|
442
|
+
process.stderr.write(msg + "\n");
|
|
443
|
+
}
|
|
444
|
+
process.exit(0);
|
|
251
445
|
}
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
main().catch((err) => {
|
|
255
|
-
process.stderr.write(`[fastgrc-hook] Unexpected error: ${err}
|
|
446
|
+
main().catch((err) => {
|
|
447
|
+
process.stderr.write(`[fastgrc-hook] Unexpected error: ${err}
|
|
256
448
|
`);
|
|
257
|
-
|
|
258
|
-
});
|
|
449
|
+
process.exit(0);
|
|
450
|
+
});
|
|
451
|
+
}
|
package/dist/plugin.js
CHANGED
|
@@ -114,7 +114,7 @@ args: ${JSON.stringify(args)}`;
|
|
|
114
114
|
var DEFAULT_BASE_URL2 = "https://app.fastgrc.ai";
|
|
115
115
|
var DEFAULT_TIMEOUT_MS2 = 3e3;
|
|
116
116
|
var pluginEntry = {
|
|
117
|
-
id: "fastgrc
|
|
117
|
+
id: "fastgrc",
|
|
118
118
|
name: "FastGRC Policy Router",
|
|
119
119
|
description: "Evaluate every tool call against your FastGRC compliance policy before it executes. Blocks violations, flags drift, builds an audit trail.",
|
|
120
120
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
package/dist/plugin.mjs
CHANGED
|
@@ -78,7 +78,7 @@ args: ${JSON.stringify(args)}`;
|
|
|
78
78
|
var DEFAULT_BASE_URL2 = "https://app.fastgrc.ai";
|
|
79
79
|
var DEFAULT_TIMEOUT_MS2 = 3e3;
|
|
80
80
|
var pluginEntry = {
|
|
81
|
-
id: "fastgrc
|
|
81
|
+
id: "fastgrc",
|
|
82
82
|
name: "FastGRC Policy Router",
|
|
83
83
|
description: "Evaluate every tool call against your FastGRC compliance policy before it executes. Blocks violations, flags drift, builds an audit trail.",
|
|
84
84
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "fastgrc
|
|
2
|
+
"id": "fastgrc",
|
|
3
3
|
"name": "FastGRC Policy Router",
|
|
4
4
|
"description": "Evaluate every tool call against your FastGRC compliance policy before it executes. Blocks violations, flags drift, builds an audit trail.",
|
|
5
5
|
"extensions": ["./dist/plugin.js"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastgrc-openclaw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"description": "FastGRC agent compliance plugin for OpenClaw — evaluates every tool call against your policy before it executes",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|