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 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.destroy();
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.destroy();
136
+ conn.end();
137
137
  return;
138
138
  }
139
- if (msg.type !== "request" || msg.token !== configToken) {
140
- conn.destroy();
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.write(JSON.stringify({ type: "decision", decision }) + "\n");
161
- conn.destroy();
165
+ conn.end(JSON.stringify({ type: "decision", decision }) + "\n");
162
166
  }).catch(() => {
163
- conn.write(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
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: "deny",
325
+ security: "ask",
305
326
  ask: "always",
306
- askFallback: "deny",
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
- writeConfig({ ...readConfig(), policyId: arg });
349
- process.stdout.write(`\u2713 FastGRC policy ID saved to ${CONFIG_PATH}
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
- process.stdout.write(' Run "fastgrc-hook get-policy" to verify.\n');
352
- process.exit(0);
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
- writeConfig({ ...readConfig(), apiKey: apiKeyArg });
400
- process.stdout.write("\u2713 API key saved to ~/.fastgrc.json\n");
401
- if (policyIdArg) {
402
- writeConfig({ ...readConfig(), policyId: policyIdArg });
403
- process.stdout.write("\u2713 Policy ID saved to ~/.fastgrc.json\n");
404
- } else {
405
- process.stdout.write(" (no policy ID \u2014 org-wide default will be used)\n");
406
- }
407
- doInstallHook(process.cwd());
408
- doConfigureExecApprovals();
409
- process.stdout.write("\n\u2713 Config, HOOK.md, and exec-approvals done.\n");
410
- process.stdout.write('Restart OpenClaw, then run "fastgrc-hook test" to verify.\n');
411
- process.exit(0);
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.destroy();
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.destroy();
113
+ conn.end();
114
114
  return;
115
115
  }
116
- if (msg.type !== "request" || msg.token !== configToken) {
117
- conn.destroy();
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.write(JSON.stringify({ type: "decision", decision }) + "\n");
138
- conn.destroy();
142
+ conn.end(JSON.stringify({ type: "decision", decision }) + "\n");
139
143
  }).catch(() => {
140
- conn.write(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
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: "deny",
302
+ security: "ask",
282
303
  ask: "always",
283
- askFallback: "deny",
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
- writeConfig({ ...readConfig(), policyId: arg });
326
- process.stdout.write(`\u2713 FastGRC policy ID saved to ${CONFIG_PATH}
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
- process.stdout.write(' Run "fastgrc-hook get-policy" to verify.\n');
329
- process.exit(0);
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
- writeConfig({ ...readConfig(), apiKey: apiKeyArg });
377
- process.stdout.write("\u2713 API key saved to ~/.fastgrc.json\n");
378
- if (policyIdArg) {
379
- writeConfig({ ...readConfig(), policyId: policyIdArg });
380
- process.stdout.write("\u2713 Policy ID saved to ~/.fastgrc.json\n");
381
- } else {
382
- process.stdout.write(" (no policy ID \u2014 org-wide default will be used)\n");
383
- }
384
- doInstallHook(process.cwd());
385
- doConfigureExecApprovals();
386
- process.stdout.write("\n\u2713 Config, HOOK.md, and exec-approvals done.\n");
387
- process.stdout.write('Restart OpenClaw, then run "fastgrc-hook test" to verify.\n');
388
- process.exit(0);
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.destroy();
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.destroy();
158
+ conn.end();
159
159
  return;
160
160
  }
161
- if (msg.type !== "request" || msg.token !== configToken) {
162
- conn.destroy();
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.write(JSON.stringify({ type: "decision", decision }) + "\n");
183
- conn.destroy();
187
+ conn.end(JSON.stringify({ type: "decision", decision }) + "\n");
184
188
  }).catch(() => {
185
- conn.write(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
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.destroy();
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.destroy();
122
+ conn.end();
123
123
  return;
124
124
  }
125
- if (msg.type !== "request" || msg.token !== configToken) {
126
- conn.destroy();
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.write(JSON.stringify({ type: "decision", decision }) + "\n");
147
- conn.destroy();
151
+ conn.end(JSON.stringify({ type: "decision", decision }) + "\n");
148
152
  }).catch(() => {
149
- conn.write(JSON.stringify({ type: "decision", decision: "allow-once" }) + "\n");
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.30",
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",