fastgrc-openclaw 1.0.30 → 1.0.32
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 +80 -27
- package/dist/bin.mjs +80 -27
- package/dist/plugin.js +11 -8
- package/dist/plugin.mjs +11 -8
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -126,18 +126,23 @@ function startExecApprovalsServer(apiKey, policyId, baseUrl) {
|
|
|
126
126
|
const line = buffer.slice(0, idx).trim();
|
|
127
127
|
buffer = "";
|
|
128
128
|
if (!line) {
|
|
129
|
-
conn.
|
|
129
|
+
conn.end();
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
132
|
let msg;
|
|
133
133
|
try {
|
|
134
134
|
msg = JSON.parse(line);
|
|
135
135
|
} catch {
|
|
136
|
-
conn.
|
|
136
|
+
conn.end();
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
|
-
if (msg.type !== "request"
|
|
140
|
-
conn.
|
|
139
|
+
if (msg.type !== "request") {
|
|
140
|
+
conn.end();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (msg.token !== configToken) {
|
|
144
|
+
console.warn("[fastgrc] exec-approvals: token mismatch \u2014 failing open. Restart fastgrc-approvals service if this persists.");
|
|
145
|
+
conn.end(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
|
|
141
146
|
return;
|
|
142
147
|
}
|
|
143
148
|
const req = msg.request ?? {};
|
|
@@ -157,11 +162,9 @@ function startExecApprovalsServer(apiKey, policyId, baseUrl) {
|
|
|
157
162
|
const rule = result.policyContext?.matchedRule;
|
|
158
163
|
console.warn(rule ? `[fastgrc] exec blocked: [${rule}] ${result.reasoning}` : `[fastgrc] exec blocked: ${result.reasoning}`);
|
|
159
164
|
}
|
|
160
|
-
conn.
|
|
161
|
-
conn.destroy();
|
|
165
|
+
conn.end(JSON.stringify({ type: "decision", decision }) + "\n");
|
|
162
166
|
}).catch(() => {
|
|
163
|
-
conn.
|
|
164
|
-
conn.destroy();
|
|
167
|
+
conn.end(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
|
|
165
168
|
});
|
|
166
169
|
});
|
|
167
170
|
conn.on("error", () => {
|
|
@@ -286,6 +289,24 @@ function doUninstallHook(targetDir) {
|
|
|
286
289
|
`);
|
|
287
290
|
}
|
|
288
291
|
}
|
|
292
|
+
async function validatePolicy(apiKey, policyId, baseUrl = "https://app.fastgrc.ai") {
|
|
293
|
+
try {
|
|
294
|
+
const controller = new AbortController();
|
|
295
|
+
setTimeout(() => controller.abort(), 5e3);
|
|
296
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/api/v1/policy-router/policies/${encodeURIComponent(policyId)}`, {
|
|
297
|
+
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
298
|
+
signal: controller.signal
|
|
299
|
+
});
|
|
300
|
+
if (res.status === 404) return { valid: false, error: "Policy not found \u2014 check the ID belongs to your account." };
|
|
301
|
+
if (res.status === 401) return { valid: false, error: "Invalid API key." };
|
|
302
|
+
if (!res.ok) return { valid: false, error: `API returned ${res.status}` };
|
|
303
|
+
const policy = await res.json();
|
|
304
|
+
return { valid: true, name: policy.name };
|
|
305
|
+
} catch (err) {
|
|
306
|
+
const reason = err instanceof Error && err.name === "AbortError" ? "timeout" : String(err);
|
|
307
|
+
return { valid: false, error: `Could not reach FastGRC API (${reason}) \u2014 skipping validation.` };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
289
310
|
function doConfigureExecApprovals() {
|
|
290
311
|
const cfgPath = path3.join(os3.homedir(), ".openclaw", "exec-approvals.json");
|
|
291
312
|
if (!fs3.existsSync(cfgPath)) {
|
|
@@ -301,9 +322,9 @@ function doConfigureExecApprovals() {
|
|
|
301
322
|
const updated = {
|
|
302
323
|
...existing,
|
|
303
324
|
defaults: {
|
|
304
|
-
security: "
|
|
325
|
+
security: "ask",
|
|
305
326
|
ask: "always",
|
|
306
|
-
askFallback: "
|
|
327
|
+
askFallback: "allow",
|
|
307
328
|
autoAllowSkills: false
|
|
308
329
|
}
|
|
309
330
|
};
|
|
@@ -345,11 +366,29 @@ if (cmd === "set-policy") {
|
|
|
345
366
|
process.stderr.write("Usage: fastgrc-hook set-policy <policy-id>\n");
|
|
346
367
|
process.exit(1);
|
|
347
368
|
}
|
|
348
|
-
|
|
349
|
-
|
|
369
|
+
(async () => {
|
|
370
|
+
const apiKey = readConfig().apiKey ?? process.env.FASTGRC_API_KEY;
|
|
371
|
+
const baseUrl = process.env.FASTGRC_BASE_URL ?? "https://app.fastgrc.ai";
|
|
372
|
+
if (apiKey) {
|
|
373
|
+
process.stdout.write(`Verifying policy ${arg}\u2026
|
|
350
374
|
`);
|
|
351
|
-
|
|
352
|
-
|
|
375
|
+
const check = await validatePolicy(apiKey, arg, baseUrl);
|
|
376
|
+
if (!check.valid) {
|
|
377
|
+
process.stderr.write(`\u2717 ${check.error}
|
|
378
|
+
`);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
process.stdout.write(`\u2713 Policy verified: ${check.name}
|
|
382
|
+
`);
|
|
383
|
+
} else {
|
|
384
|
+
process.stdout.write("\u26A0 No API key set \u2014 skipping policy verification.\n");
|
|
385
|
+
}
|
|
386
|
+
writeConfig({ ...readConfig(), policyId: arg });
|
|
387
|
+
process.stdout.write(`\u2713 FastGRC policy ID saved to ${CONFIG_PATH}
|
|
388
|
+
`);
|
|
389
|
+
process.stdout.write(' Run "fastgrc-hook get-policy" to verify.\n');
|
|
390
|
+
process.exit(0);
|
|
391
|
+
})();
|
|
353
392
|
}
|
|
354
393
|
if (cmd === "get-policy") {
|
|
355
394
|
const id = readConfig().policyId;
|
|
@@ -396,19 +435,33 @@ if (cmd === "setup") {
|
|
|
396
435
|
process.stderr.write("Usage: fastgrc-hook setup --api-key <key> [--policy-id <id>]\n");
|
|
397
436
|
process.exit(1);
|
|
398
437
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
438
|
+
(async () => {
|
|
439
|
+
const baseUrl = process.env.FASTGRC_BASE_URL ?? "https://app.fastgrc.ai";
|
|
440
|
+
writeConfig({ ...readConfig(), apiKey: apiKeyArg });
|
|
441
|
+
process.stdout.write("\u2713 API key saved to ~/.fastgrc.json\n");
|
|
442
|
+
if (policyIdArg) {
|
|
443
|
+
process.stdout.write(`Verifying policy ${policyIdArg}\u2026
|
|
444
|
+
`);
|
|
445
|
+
const check = await validatePolicy(apiKeyArg, policyIdArg, baseUrl);
|
|
446
|
+
if (!check.valid) {
|
|
447
|
+
process.stderr.write(`\u2717 Policy not found: ${check.error}
|
|
448
|
+
`);
|
|
449
|
+
process.stderr.write(" Pass a valid policy ID from your dashboard, or omit --policy-id to use the org default.\n");
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
process.stdout.write(`\u2713 Policy verified: ${check.name}
|
|
453
|
+
`);
|
|
454
|
+
writeConfig({ ...readConfig(), policyId: policyIdArg });
|
|
455
|
+
process.stdout.write("\u2713 Policy ID saved to ~/.fastgrc.json\n");
|
|
456
|
+
} else {
|
|
457
|
+
process.stdout.write(" (no policy ID \u2014 org-wide default will be used)\n");
|
|
458
|
+
}
|
|
459
|
+
doInstallHook(process.cwd());
|
|
460
|
+
doConfigureExecApprovals();
|
|
461
|
+
process.stdout.write("\n\u2713 Config, HOOK.md, and exec-approvals done.\n");
|
|
462
|
+
process.stdout.write('Restart OpenClaw, then run "fastgrc-hook test" to verify.\n');
|
|
463
|
+
process.exit(0);
|
|
464
|
+
})();
|
|
412
465
|
}
|
|
413
466
|
if (cmd === "serve-approvals") {
|
|
414
467
|
const apiKey = resolveApiKey();
|
package/dist/bin.mjs
CHANGED
|
@@ -103,18 +103,23 @@ function startExecApprovalsServer(apiKey, policyId, baseUrl) {
|
|
|
103
103
|
const line = buffer.slice(0, idx).trim();
|
|
104
104
|
buffer = "";
|
|
105
105
|
if (!line) {
|
|
106
|
-
conn.
|
|
106
|
+
conn.end();
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
let msg;
|
|
110
110
|
try {
|
|
111
111
|
msg = JSON.parse(line);
|
|
112
112
|
} catch {
|
|
113
|
-
conn.
|
|
113
|
+
conn.end();
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
|
-
if (msg.type !== "request"
|
|
117
|
-
conn.
|
|
116
|
+
if (msg.type !== "request") {
|
|
117
|
+
conn.end();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (msg.token !== configToken) {
|
|
121
|
+
console.warn("[fastgrc] exec-approvals: token mismatch \u2014 failing open. Restart fastgrc-approvals service if this persists.");
|
|
122
|
+
conn.end(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
|
|
118
123
|
return;
|
|
119
124
|
}
|
|
120
125
|
const req = msg.request ?? {};
|
|
@@ -134,11 +139,9 @@ function startExecApprovalsServer(apiKey, policyId, baseUrl) {
|
|
|
134
139
|
const rule = result.policyContext?.matchedRule;
|
|
135
140
|
console.warn(rule ? `[fastgrc] exec blocked: [${rule}] ${result.reasoning}` : `[fastgrc] exec blocked: ${result.reasoning}`);
|
|
136
141
|
}
|
|
137
|
-
conn.
|
|
138
|
-
conn.destroy();
|
|
142
|
+
conn.end(JSON.stringify({ type: "decision", decision }) + "\n");
|
|
139
143
|
}).catch(() => {
|
|
140
|
-
conn.
|
|
141
|
-
conn.destroy();
|
|
144
|
+
conn.end(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
|
|
142
145
|
});
|
|
143
146
|
});
|
|
144
147
|
conn.on("error", () => {
|
|
@@ -263,6 +266,24 @@ function doUninstallHook(targetDir) {
|
|
|
263
266
|
`);
|
|
264
267
|
}
|
|
265
268
|
}
|
|
269
|
+
async function validatePolicy(apiKey, policyId, baseUrl = "https://app.fastgrc.ai") {
|
|
270
|
+
try {
|
|
271
|
+
const controller = new AbortController();
|
|
272
|
+
setTimeout(() => controller.abort(), 5e3);
|
|
273
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/api/v1/policy-router/policies/${encodeURIComponent(policyId)}`, {
|
|
274
|
+
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
275
|
+
signal: controller.signal
|
|
276
|
+
});
|
|
277
|
+
if (res.status === 404) return { valid: false, error: "Policy not found \u2014 check the ID belongs to your account." };
|
|
278
|
+
if (res.status === 401) return { valid: false, error: "Invalid API key." };
|
|
279
|
+
if (!res.ok) return { valid: false, error: `API returned ${res.status}` };
|
|
280
|
+
const policy = await res.json();
|
|
281
|
+
return { valid: true, name: policy.name };
|
|
282
|
+
} catch (err) {
|
|
283
|
+
const reason = err instanceof Error && err.name === "AbortError" ? "timeout" : String(err);
|
|
284
|
+
return { valid: false, error: `Could not reach FastGRC API (${reason}) \u2014 skipping validation.` };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
266
287
|
function doConfigureExecApprovals() {
|
|
267
288
|
const cfgPath = path3.join(os3.homedir(), ".openclaw", "exec-approvals.json");
|
|
268
289
|
if (!fs3.existsSync(cfgPath)) {
|
|
@@ -278,9 +299,9 @@ function doConfigureExecApprovals() {
|
|
|
278
299
|
const updated = {
|
|
279
300
|
...existing,
|
|
280
301
|
defaults: {
|
|
281
|
-
security: "
|
|
302
|
+
security: "ask",
|
|
282
303
|
ask: "always",
|
|
283
|
-
askFallback: "
|
|
304
|
+
askFallback: "allow",
|
|
284
305
|
autoAllowSkills: false
|
|
285
306
|
}
|
|
286
307
|
};
|
|
@@ -322,11 +343,29 @@ if (cmd === "set-policy") {
|
|
|
322
343
|
process.stderr.write("Usage: fastgrc-hook set-policy <policy-id>\n");
|
|
323
344
|
process.exit(1);
|
|
324
345
|
}
|
|
325
|
-
|
|
326
|
-
|
|
346
|
+
(async () => {
|
|
347
|
+
const apiKey = readConfig().apiKey ?? process.env.FASTGRC_API_KEY;
|
|
348
|
+
const baseUrl = process.env.FASTGRC_BASE_URL ?? "https://app.fastgrc.ai";
|
|
349
|
+
if (apiKey) {
|
|
350
|
+
process.stdout.write(`Verifying policy ${arg}\u2026
|
|
327
351
|
`);
|
|
328
|
-
|
|
329
|
-
|
|
352
|
+
const check = await validatePolicy(apiKey, arg, baseUrl);
|
|
353
|
+
if (!check.valid) {
|
|
354
|
+
process.stderr.write(`\u2717 ${check.error}
|
|
355
|
+
`);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
process.stdout.write(`\u2713 Policy verified: ${check.name}
|
|
359
|
+
`);
|
|
360
|
+
} else {
|
|
361
|
+
process.stdout.write("\u26A0 No API key set \u2014 skipping policy verification.\n");
|
|
362
|
+
}
|
|
363
|
+
writeConfig({ ...readConfig(), policyId: arg });
|
|
364
|
+
process.stdout.write(`\u2713 FastGRC policy ID saved to ${CONFIG_PATH}
|
|
365
|
+
`);
|
|
366
|
+
process.stdout.write(' Run "fastgrc-hook get-policy" to verify.\n');
|
|
367
|
+
process.exit(0);
|
|
368
|
+
})();
|
|
330
369
|
}
|
|
331
370
|
if (cmd === "get-policy") {
|
|
332
371
|
const id = readConfig().policyId;
|
|
@@ -373,19 +412,33 @@ if (cmd === "setup") {
|
|
|
373
412
|
process.stderr.write("Usage: fastgrc-hook setup --api-key <key> [--policy-id <id>]\n");
|
|
374
413
|
process.exit(1);
|
|
375
414
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
415
|
+
(async () => {
|
|
416
|
+
const baseUrl = process.env.FASTGRC_BASE_URL ?? "https://app.fastgrc.ai";
|
|
417
|
+
writeConfig({ ...readConfig(), apiKey: apiKeyArg });
|
|
418
|
+
process.stdout.write("\u2713 API key saved to ~/.fastgrc.json\n");
|
|
419
|
+
if (policyIdArg) {
|
|
420
|
+
process.stdout.write(`Verifying policy ${policyIdArg}\u2026
|
|
421
|
+
`);
|
|
422
|
+
const check = await validatePolicy(apiKeyArg, policyIdArg, baseUrl);
|
|
423
|
+
if (!check.valid) {
|
|
424
|
+
process.stderr.write(`\u2717 Policy not found: ${check.error}
|
|
425
|
+
`);
|
|
426
|
+
process.stderr.write(" Pass a valid policy ID from your dashboard, or omit --policy-id to use the org default.\n");
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
process.stdout.write(`\u2713 Policy verified: ${check.name}
|
|
430
|
+
`);
|
|
431
|
+
writeConfig({ ...readConfig(), policyId: policyIdArg });
|
|
432
|
+
process.stdout.write("\u2713 Policy ID saved to ~/.fastgrc.json\n");
|
|
433
|
+
} else {
|
|
434
|
+
process.stdout.write(" (no policy ID \u2014 org-wide default will be used)\n");
|
|
435
|
+
}
|
|
436
|
+
doInstallHook(process.cwd());
|
|
437
|
+
doConfigureExecApprovals();
|
|
438
|
+
process.stdout.write("\n\u2713 Config, HOOK.md, and exec-approvals done.\n");
|
|
439
|
+
process.stdout.write('Restart OpenClaw, then run "fastgrc-hook test" to verify.\n');
|
|
440
|
+
process.exit(0);
|
|
441
|
+
})();
|
|
389
442
|
}
|
|
390
443
|
if (cmd === "serve-approvals") {
|
|
391
444
|
const apiKey = resolveApiKey();
|
package/dist/plugin.js
CHANGED
|
@@ -148,18 +148,23 @@ function startExecApprovalsServer(apiKey, policyId, baseUrl) {
|
|
|
148
148
|
const line = buffer.slice(0, idx).trim();
|
|
149
149
|
buffer = "";
|
|
150
150
|
if (!line) {
|
|
151
|
-
conn.
|
|
151
|
+
conn.end();
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
let msg;
|
|
155
155
|
try {
|
|
156
156
|
msg = JSON.parse(line);
|
|
157
157
|
} catch {
|
|
158
|
-
conn.
|
|
158
|
+
conn.end();
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
|
-
if (msg.type !== "request"
|
|
162
|
-
conn.
|
|
161
|
+
if (msg.type !== "request") {
|
|
162
|
+
conn.end();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (msg.token !== configToken) {
|
|
166
|
+
console.warn("[fastgrc] exec-approvals: token mismatch \u2014 failing open. Restart fastgrc-approvals service if this persists.");
|
|
167
|
+
conn.end(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
|
|
163
168
|
return;
|
|
164
169
|
}
|
|
165
170
|
const req = msg.request ?? {};
|
|
@@ -179,11 +184,9 @@ function startExecApprovalsServer(apiKey, policyId, baseUrl) {
|
|
|
179
184
|
const rule = result.policyContext?.matchedRule;
|
|
180
185
|
console.warn(rule ? `[fastgrc] exec blocked: [${rule}] ${result.reasoning}` : `[fastgrc] exec blocked: ${result.reasoning}`);
|
|
181
186
|
}
|
|
182
|
-
conn.
|
|
183
|
-
conn.destroy();
|
|
187
|
+
conn.end(JSON.stringify({ type: "decision", decision }) + "\n");
|
|
184
188
|
}).catch(() => {
|
|
185
|
-
conn.
|
|
186
|
-
conn.destroy();
|
|
189
|
+
conn.end(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
|
|
187
190
|
});
|
|
188
191
|
});
|
|
189
192
|
conn.on("error", () => {
|
package/dist/plugin.mjs
CHANGED
|
@@ -112,18 +112,23 @@ function startExecApprovalsServer(apiKey, policyId, baseUrl) {
|
|
|
112
112
|
const line = buffer.slice(0, idx).trim();
|
|
113
113
|
buffer = "";
|
|
114
114
|
if (!line) {
|
|
115
|
-
conn.
|
|
115
|
+
conn.end();
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
let msg;
|
|
119
119
|
try {
|
|
120
120
|
msg = JSON.parse(line);
|
|
121
121
|
} catch {
|
|
122
|
-
conn.
|
|
122
|
+
conn.end();
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
|
-
if (msg.type !== "request"
|
|
126
|
-
conn.
|
|
125
|
+
if (msg.type !== "request") {
|
|
126
|
+
conn.end();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (msg.token !== configToken) {
|
|
130
|
+
console.warn("[fastgrc] exec-approvals: token mismatch \u2014 failing open. Restart fastgrc-approvals service if this persists.");
|
|
131
|
+
conn.end(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
|
|
127
132
|
return;
|
|
128
133
|
}
|
|
129
134
|
const req = msg.request ?? {};
|
|
@@ -143,11 +148,9 @@ function startExecApprovalsServer(apiKey, policyId, baseUrl) {
|
|
|
143
148
|
const rule = result.policyContext?.matchedRule;
|
|
144
149
|
console.warn(rule ? `[fastgrc] exec blocked: [${rule}] ${result.reasoning}` : `[fastgrc] exec blocked: ${result.reasoning}`);
|
|
145
150
|
}
|
|
146
|
-
conn.
|
|
147
|
-
conn.destroy();
|
|
151
|
+
conn.end(JSON.stringify({ type: "decision", decision }) + "\n");
|
|
148
152
|
}).catch(() => {
|
|
149
|
-
conn.
|
|
150
|
-
conn.destroy();
|
|
153
|
+
conn.end(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
|
|
151
154
|
});
|
|
152
155
|
});
|
|
153
156
|
conn.on("error", () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastgrc-openclaw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
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",
|