fastgrc-openclaw 1.0.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 ADDED
@@ -0,0 +1,83 @@
1
+ # fastgrc-openclaw
2
+
3
+ FastGRC compliance plugin for [OpenClaw](https://openclaw.ai). Evaluates every agent tool call against your policy before it executes — blocking, flagging, or logging violations in real time.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install fastgrc-openclaw
9
+ ```
10
+
11
+ ## Setup (2 lines)
12
+
13
+ ```typescript
14
+ // openclaw.config.ts
15
+ import { FastGRCPlugin } from 'fastgrc-openclaw';
16
+
17
+ export default {
18
+ plugins: [
19
+ FastGRCPlugin({
20
+ apiKey: process.env.FASTGRC_API_KEY, // fgrc_k1_...
21
+ }),
22
+ ],
23
+ };
24
+ ```
25
+
26
+ Set your environment variable:
27
+
28
+ ```bash
29
+ FASTGRC_API_KEY=fgrc_k1_your_key_here
30
+ ```
31
+
32
+ Get your API key at [fastgrc.ai/connect?source=openclaw](https://fastgrc.ai/connect?source=openclaw) — free, no credit card.
33
+
34
+ ## Options
35
+
36
+ ```typescript
37
+ FastGRCPlugin({
38
+ apiKey: string; // Required. Your FastGRC API key.
39
+ policyId?: string; // Optional. Target a specific policy. Omit for org-wide default.
40
+ onBlock?: 'throw' // (default) Throw FastGRCBlockedError — OpenClaw surfaces it as an agent error
41
+ | 'warn' // console.warn and allow through
42
+ | 'silent'; // Allow through silently
43
+ timeoutMs?: number; // Max ms to wait for FastGRC API. Default: 3000. Fail-open on timeout.
44
+ baseUrl?: string; // Override FastGRC base URL. Default: https://app.fastgrc.ai
45
+ })
46
+ ```
47
+
48
+ ## How it works
49
+
50
+ The plugin registers a `before_tool_call` hook. For every tool invocation:
51
+
52
+ 1. Calls `POST /api/v1/policy-router/evaluate` with the tool name and arguments
53
+ 2. On `decision: block` → throws `FastGRCBlockedError` (OpenClaw surfaces this as an agent error with explanation)
54
+ 3. On `decision: require_approval` → throws `FastGRCApprovalRequiredError` with a link to your dashboard
55
+ 4. On `decision: allow | uncertain` → passes through silently
56
+ 5. On timeout or network error → **fail-open** (allows through, logs a warning) — FastGRC never breaks your agent due to infra issues
57
+
58
+ ## Error types
59
+
60
+ ```typescript
61
+ import { FastGRCBlockedError, FastGRCApprovalRequiredError } from 'fastgrc-openclaw';
62
+
63
+ // Catch in your agent error handler:
64
+ if (err instanceof FastGRCBlockedError) {
65
+ console.log(err.matchedRule); // Which policy rule triggered
66
+ console.log(err.reasoning); // Human-readable explanation
67
+ console.log(err.policyId); // Policy that made the decision
68
+ }
69
+
70
+ if (err instanceof FastGRCApprovalRequiredError) {
71
+ console.log(err.dashboardUrl); // Link to approve in FastGRC dashboard
72
+ }
73
+ ```
74
+
75
+ ## Policy modes
76
+
77
+ Policies start in **Observability Mode** — violations are logged but never blocked. Switch to enforcement from your [FastGRC dashboard](https://app.fastgrc.ai/dashboard/agent-policies) when ready.
78
+
79
+ ## Links
80
+
81
+ - [FastGRC docs](https://docs.fastgrc.ai/integrations/openclaw)
82
+ - [Get your API key](https://fastgrc.ai/connect?source=openclaw)
83
+ - [Dashboard](https://app.fastgrc.ai)
package/dist/bin.d.mts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/bin.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/bin.js ADDED
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/index.ts
5
+ var DEFAULT_BASE_URL = "https://app.fastgrc.ai";
6
+ var DEFAULT_TIMEOUT_MS = 3e3;
7
+ async function evaluate(payload) {
8
+ const {
9
+ toolName,
10
+ args,
11
+ agentId,
12
+ agentType,
13
+ agentName,
14
+ apiKey: apiKey2,
15
+ policyId: policyId2,
16
+ baseUrl: baseUrl2 = DEFAULT_BASE_URL,
17
+ timeoutMs = DEFAULT_TIMEOUT_MS
18
+ } = payload;
19
+ const evalUrl = `${baseUrl2.replace(/\/$/, "")}/api/v1/policy-router/evaluate`;
20
+ const content = `tool_name: ${toolName}
21
+ args: ${JSON.stringify(args)}`;
22
+ try {
23
+ const controller = new AbortController();
24
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
25
+ const res = await fetch(evalUrl, {
26
+ method: "POST",
27
+ headers: {
28
+ "Authorization": `Bearer ${apiKey2}`,
29
+ "Content-Type": "application/json"
30
+ },
31
+ body: JSON.stringify({
32
+ subjectContent: content,
33
+ subjectType: "tool_argument",
34
+ direction: "ingress",
35
+ agentId,
36
+ agentType,
37
+ agentName,
38
+ ...policyId2 ? { policyId: policyId2 } : {}
39
+ }),
40
+ signal: controller.signal
41
+ });
42
+ clearTimeout(timer);
43
+ if (!res.ok) {
44
+ console.warn(`[fastgrc] Evaluate returned ${res.status} \u2014 failing open`);
45
+ return null;
46
+ }
47
+ return await res.json();
48
+ } catch (err) {
49
+ const reason = err instanceof Error && err.name === "AbortError" ? "timeout" : String(err);
50
+ console.warn(`[fastgrc] Evaluate failed (${reason}) \u2014 failing open`);
51
+ return null;
52
+ }
53
+ }
54
+
55
+ // src/bin.ts
56
+ var apiKey = process.env.FASTGRC_API_KEY;
57
+ if (!apiKey) {
58
+ process.stderr.write("[fastgrc-hook] FASTGRC_API_KEY is not set \u2014 allowing tool call\n");
59
+ process.exit(0);
60
+ }
61
+ var baseUrl = process.env.FASTGRC_BASE_URL ?? "https://app.fastgrc.ai";
62
+ var policyId = process.env.FASTGRC_POLICY_ID;
63
+ async function main() {
64
+ const chunks = [];
65
+ for await (const chunk of process.stdin) chunks.push(chunk);
66
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
67
+ const toolName = process.env.HOOK_TOOL_NAME ?? "";
68
+ const toolInputRaw = process.env.HOOK_TOOL_INPUT ?? "{}";
69
+ let args = {};
70
+ let agentId;
71
+ if (raw) {
72
+ try {
73
+ const ctx = JSON.parse(raw);
74
+ args = ctx.tool_input ?? ctx.input ?? ctx.args ?? {};
75
+ agentId = ctx.agent_id ?? ctx.agentId ?? ctx.session_id;
76
+ } catch {
77
+ }
78
+ }
79
+ if (!toolName && !raw) {
80
+ process.exit(0);
81
+ }
82
+ const resolvedToolName = toolName || (raw ? JSON.parse(raw)?.tool_name ?? "unknown" : "unknown");
83
+ if (!args || Object.keys(args).length === 0) {
84
+ try {
85
+ args = JSON.parse(toolInputRaw);
86
+ } catch {
87
+ args = {};
88
+ }
89
+ }
90
+ const result = await evaluate({
91
+ toolName: resolvedToolName,
92
+ args,
93
+ agentId,
94
+ apiKey,
95
+ // narrowed at module level via early-exit guard above
96
+ policyId,
97
+ baseUrl
98
+ });
99
+ if (!result) {
100
+ process.exit(0);
101
+ }
102
+ const { decision, reasoning, policyContext } = result;
103
+ const matchedRule = policyContext?.matchedRule;
104
+ if (decision === "block") {
105
+ const msg = matchedRule ? `FastGRC blocked: [${matchedRule}] ${reasoning}` : `FastGRC blocked: ${reasoning}`;
106
+ process.stderr.write(msg + "\n");
107
+ process.exit(2);
108
+ }
109
+ if (decision === "require_approval") {
110
+ process.stderr.write(
111
+ `FastGRC requires approval before this action.
112
+ ${reasoning}
113
+ Review at: ${baseUrl}/dashboard/agent-policies
114
+ `
115
+ );
116
+ process.exit(2);
117
+ }
118
+ process.exit(0);
119
+ }
120
+ main().catch((err) => {
121
+ process.stderr.write(`[fastgrc-hook] Unexpected error: ${err}
122
+ `);
123
+ process.exit(0);
124
+ });
package/dist/bin.mjs ADDED
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ var DEFAULT_BASE_URL = "https://app.fastgrc.ai";
5
+ var DEFAULT_TIMEOUT_MS = 3e3;
6
+ async function evaluate(payload) {
7
+ const {
8
+ toolName,
9
+ args,
10
+ agentId,
11
+ agentType,
12
+ agentName,
13
+ apiKey: apiKey2,
14
+ policyId: policyId2,
15
+ baseUrl: baseUrl2 = DEFAULT_BASE_URL,
16
+ timeoutMs = DEFAULT_TIMEOUT_MS
17
+ } = payload;
18
+ const evalUrl = `${baseUrl2.replace(/\/$/, "")}/api/v1/policy-router/evaluate`;
19
+ const content = `tool_name: ${toolName}
20
+ args: ${JSON.stringify(args)}`;
21
+ try {
22
+ const controller = new AbortController();
23
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
24
+ const res = await fetch(evalUrl, {
25
+ method: "POST",
26
+ headers: {
27
+ "Authorization": `Bearer ${apiKey2}`,
28
+ "Content-Type": "application/json"
29
+ },
30
+ body: JSON.stringify({
31
+ subjectContent: content,
32
+ subjectType: "tool_argument",
33
+ direction: "ingress",
34
+ agentId,
35
+ agentType,
36
+ agentName,
37
+ ...policyId2 ? { policyId: policyId2 } : {}
38
+ }),
39
+ signal: controller.signal
40
+ });
41
+ clearTimeout(timer);
42
+ if (!res.ok) {
43
+ console.warn(`[fastgrc] Evaluate returned ${res.status} \u2014 failing open`);
44
+ return null;
45
+ }
46
+ return await res.json();
47
+ } catch (err) {
48
+ const reason = err instanceof Error && err.name === "AbortError" ? "timeout" : String(err);
49
+ console.warn(`[fastgrc] Evaluate failed (${reason}) \u2014 failing open`);
50
+ return null;
51
+ }
52
+ }
53
+
54
+ // src/bin.ts
55
+ var apiKey = process.env.FASTGRC_API_KEY;
56
+ if (!apiKey) {
57
+ process.stderr.write("[fastgrc-hook] FASTGRC_API_KEY is not set \u2014 allowing tool call\n");
58
+ process.exit(0);
59
+ }
60
+ var baseUrl = process.env.FASTGRC_BASE_URL ?? "https://app.fastgrc.ai";
61
+ var policyId = process.env.FASTGRC_POLICY_ID;
62
+ async function main() {
63
+ const chunks = [];
64
+ for await (const chunk of process.stdin) chunks.push(chunk);
65
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
66
+ const toolName = process.env.HOOK_TOOL_NAME ?? "";
67
+ const toolInputRaw = process.env.HOOK_TOOL_INPUT ?? "{}";
68
+ let args = {};
69
+ let agentId;
70
+ if (raw) {
71
+ try {
72
+ const ctx = JSON.parse(raw);
73
+ args = ctx.tool_input ?? ctx.input ?? ctx.args ?? {};
74
+ agentId = ctx.agent_id ?? ctx.agentId ?? ctx.session_id;
75
+ } catch {
76
+ }
77
+ }
78
+ if (!toolName && !raw) {
79
+ process.exit(0);
80
+ }
81
+ const resolvedToolName = toolName || (raw ? JSON.parse(raw)?.tool_name ?? "unknown" : "unknown");
82
+ if (!args || Object.keys(args).length === 0) {
83
+ try {
84
+ args = JSON.parse(toolInputRaw);
85
+ } catch {
86
+ args = {};
87
+ }
88
+ }
89
+ const result = await evaluate({
90
+ toolName: resolvedToolName,
91
+ args,
92
+ agentId,
93
+ apiKey,
94
+ // narrowed at module level via early-exit guard above
95
+ policyId,
96
+ baseUrl
97
+ });
98
+ if (!result) {
99
+ process.exit(0);
100
+ }
101
+ const { decision, reasoning, policyContext } = result;
102
+ const matchedRule = policyContext?.matchedRule;
103
+ if (decision === "block") {
104
+ const msg = matchedRule ? `FastGRC blocked: [${matchedRule}] ${reasoning}` : `FastGRC blocked: ${reasoning}`;
105
+ process.stderr.write(msg + "\n");
106
+ process.exit(2);
107
+ }
108
+ if (decision === "require_approval") {
109
+ process.stderr.write(
110
+ `FastGRC requires approval before this action.
111
+ ${reasoning}
112
+ Review at: ${baseUrl}/dashboard/agent-policies
113
+ `
114
+ );
115
+ process.exit(2);
116
+ }
117
+ process.exit(0);
118
+ }
119
+ main().catch((err) => {
120
+ process.stderr.write(`[fastgrc-hook] Unexpected error: ${err}
121
+ `);
122
+ process.exit(0);
123
+ });
@@ -0,0 +1,74 @@
1
+ interface FastGRCPluginOptions {
2
+ /** Your FastGRC API key (fgrc_k1_...) */
3
+ apiKey: string;
4
+ /** Specific policy ID to evaluate against. Omit to use the org-wide default. */
5
+ policyId?: string;
6
+ /**
7
+ * What to do on a block decision when used outside the plugin hook (e.g. manual invocation).
8
+ * The plugin hook always returns { block: true } per OpenClaw's hook protocol.
9
+ * - 'throw' (default) — throw FastGRCBlockedError
10
+ * - 'warn' — console.warn and allow through
11
+ * - 'silent' — allow through silently
12
+ */
13
+ onBlock?: 'throw' | 'warn' | 'silent';
14
+ /** Max ms to wait for the FastGRC API. Fail-open on timeout. Default: 3000 */
15
+ timeoutMs?: number;
16
+ /** Override the FastGRC base URL. Default: https://app.fastgrc.ai */
17
+ baseUrl?: string;
18
+ }
19
+ /** Thrown when FastGRC's policy blocks a tool call (onBlock: 'throw', outside plugin hook) */
20
+ declare class FastGRCBlockedError extends Error {
21
+ readonly matchedRule: string;
22
+ readonly reasoning: string;
23
+ readonly policyId?: string;
24
+ constructor(matchedRule: string, reasoning: string, policyId?: string);
25
+ }
26
+ /** Thrown when FastGRC requires human approval (outside plugin hook) */
27
+ declare class FastGRCApprovalRequiredError extends Error {
28
+ readonly dashboardUrl: string;
29
+ constructor(dashboardUrl: string, reasoning: string);
30
+ }
31
+ type HookDecision = {
32
+ block: true;
33
+ reason?: string;
34
+ } | {
35
+ block: false;
36
+ } | {
37
+ requireApproval: true;
38
+ reason?: string;
39
+ } | undefined;
40
+ interface AgentContext {
41
+ id?: string;
42
+ name?: string;
43
+ type?: string;
44
+ [key: string]: unknown;
45
+ }
46
+ interface OpenClawPlugin {
47
+ name: string;
48
+ hooks: {
49
+ before_tool_call?: (toolName: string, args: unknown, context: AgentContext) => Promise<HookDecision> | HookDecision;
50
+ };
51
+ }
52
+ interface EvaluateResponse {
53
+ decision: 'allow' | 'block' | 'verify' | 'uncertain' | 'require_approval';
54
+ confidence: number;
55
+ reasoning: string;
56
+ policyContext?: {
57
+ policyId?: string;
58
+ matchedRule?: string;
59
+ };
60
+ }
61
+ declare function evaluate(payload: {
62
+ toolName: string;
63
+ args: unknown;
64
+ agentId?: string;
65
+ agentType?: string;
66
+ agentName?: string;
67
+ apiKey: string;
68
+ policyId?: string;
69
+ baseUrl?: string;
70
+ timeoutMs?: number;
71
+ }): Promise<EvaluateResponse | null>;
72
+ declare function FastGRCPlugin(options: FastGRCPluginOptions): OpenClawPlugin;
73
+
74
+ export { FastGRCApprovalRequiredError, FastGRCBlockedError, FastGRCPlugin, type FastGRCPluginOptions, evaluate };
@@ -0,0 +1,74 @@
1
+ interface FastGRCPluginOptions {
2
+ /** Your FastGRC API key (fgrc_k1_...) */
3
+ apiKey: string;
4
+ /** Specific policy ID to evaluate against. Omit to use the org-wide default. */
5
+ policyId?: string;
6
+ /**
7
+ * What to do on a block decision when used outside the plugin hook (e.g. manual invocation).
8
+ * The plugin hook always returns { block: true } per OpenClaw's hook protocol.
9
+ * - 'throw' (default) — throw FastGRCBlockedError
10
+ * - 'warn' — console.warn and allow through
11
+ * - 'silent' — allow through silently
12
+ */
13
+ onBlock?: 'throw' | 'warn' | 'silent';
14
+ /** Max ms to wait for the FastGRC API. Fail-open on timeout. Default: 3000 */
15
+ timeoutMs?: number;
16
+ /** Override the FastGRC base URL. Default: https://app.fastgrc.ai */
17
+ baseUrl?: string;
18
+ }
19
+ /** Thrown when FastGRC's policy blocks a tool call (onBlock: 'throw', outside plugin hook) */
20
+ declare class FastGRCBlockedError extends Error {
21
+ readonly matchedRule: string;
22
+ readonly reasoning: string;
23
+ readonly policyId?: string;
24
+ constructor(matchedRule: string, reasoning: string, policyId?: string);
25
+ }
26
+ /** Thrown when FastGRC requires human approval (outside plugin hook) */
27
+ declare class FastGRCApprovalRequiredError extends Error {
28
+ readonly dashboardUrl: string;
29
+ constructor(dashboardUrl: string, reasoning: string);
30
+ }
31
+ type HookDecision = {
32
+ block: true;
33
+ reason?: string;
34
+ } | {
35
+ block: false;
36
+ } | {
37
+ requireApproval: true;
38
+ reason?: string;
39
+ } | undefined;
40
+ interface AgentContext {
41
+ id?: string;
42
+ name?: string;
43
+ type?: string;
44
+ [key: string]: unknown;
45
+ }
46
+ interface OpenClawPlugin {
47
+ name: string;
48
+ hooks: {
49
+ before_tool_call?: (toolName: string, args: unknown, context: AgentContext) => Promise<HookDecision> | HookDecision;
50
+ };
51
+ }
52
+ interface EvaluateResponse {
53
+ decision: 'allow' | 'block' | 'verify' | 'uncertain' | 'require_approval';
54
+ confidence: number;
55
+ reasoning: string;
56
+ policyContext?: {
57
+ policyId?: string;
58
+ matchedRule?: string;
59
+ };
60
+ }
61
+ declare function evaluate(payload: {
62
+ toolName: string;
63
+ args: unknown;
64
+ agentId?: string;
65
+ agentType?: string;
66
+ agentName?: string;
67
+ apiKey: string;
68
+ policyId?: string;
69
+ baseUrl?: string;
70
+ timeoutMs?: number;
71
+ }): Promise<EvaluateResponse | null>;
72
+ declare function FastGRCPlugin(options: FastGRCPluginOptions): OpenClawPlugin;
73
+
74
+ export { FastGRCApprovalRequiredError, FastGRCBlockedError, FastGRCPlugin, type FastGRCPluginOptions, evaluate };
package/dist/index.js ADDED
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FastGRCApprovalRequiredError: () => FastGRCApprovalRequiredError,
24
+ FastGRCBlockedError: () => FastGRCBlockedError,
25
+ FastGRCPlugin: () => FastGRCPlugin,
26
+ evaluate: () => evaluate
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+ var DEFAULT_BASE_URL = "https://app.fastgrc.ai";
30
+ var DEFAULT_TIMEOUT_MS = 3e3;
31
+ var FastGRCBlockedError = class extends Error {
32
+ constructor(matchedRule, reasoning, policyId) {
33
+ super(
34
+ `FastGRC policy blocked this action.
35
+ Rule: ${matchedRule}
36
+ Reason: ${reasoning}`
37
+ );
38
+ this.name = "FastGRCBlockedError";
39
+ this.matchedRule = matchedRule;
40
+ this.reasoning = reasoning;
41
+ this.policyId = policyId;
42
+ }
43
+ };
44
+ var FastGRCApprovalRequiredError = class extends Error {
45
+ constructor(dashboardUrl, reasoning) {
46
+ super(
47
+ `FastGRC requires human approval before this action.
48
+ Reason: ${reasoning}
49
+ Review at: ${dashboardUrl}`
50
+ );
51
+ this.name = "FastGRCApprovalRequiredError";
52
+ this.dashboardUrl = dashboardUrl;
53
+ }
54
+ };
55
+ async function evaluate(payload) {
56
+ const {
57
+ toolName,
58
+ args,
59
+ agentId,
60
+ agentType,
61
+ agentName,
62
+ apiKey,
63
+ policyId,
64
+ baseUrl = DEFAULT_BASE_URL,
65
+ timeoutMs = DEFAULT_TIMEOUT_MS
66
+ } = payload;
67
+ const evalUrl = `${baseUrl.replace(/\/$/, "")}/api/v1/policy-router/evaluate`;
68
+ const content = `tool_name: ${toolName}
69
+ args: ${JSON.stringify(args)}`;
70
+ try {
71
+ const controller = new AbortController();
72
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
73
+ const res = await fetch(evalUrl, {
74
+ method: "POST",
75
+ headers: {
76
+ "Authorization": `Bearer ${apiKey}`,
77
+ "Content-Type": "application/json"
78
+ },
79
+ body: JSON.stringify({
80
+ subjectContent: content,
81
+ subjectType: "tool_argument",
82
+ direction: "ingress",
83
+ agentId,
84
+ agentType,
85
+ agentName,
86
+ ...policyId ? { policyId } : {}
87
+ }),
88
+ signal: controller.signal
89
+ });
90
+ clearTimeout(timer);
91
+ if (!res.ok) {
92
+ console.warn(`[fastgrc] Evaluate returned ${res.status} \u2014 failing open`);
93
+ return null;
94
+ }
95
+ return await res.json();
96
+ } catch (err) {
97
+ const reason = err instanceof Error && err.name === "AbortError" ? "timeout" : String(err);
98
+ console.warn(`[fastgrc] Evaluate failed (${reason}) \u2014 failing open`);
99
+ return null;
100
+ }
101
+ }
102
+ function FastGRCPlugin(options) {
103
+ const {
104
+ apiKey,
105
+ policyId,
106
+ timeoutMs = DEFAULT_TIMEOUT_MS,
107
+ baseUrl = DEFAULT_BASE_URL
108
+ } = options;
109
+ const dashboardUrl = `${baseUrl.replace(/\/$/, "")}/dashboard/agent-policies`;
110
+ return {
111
+ name: "fastgrc",
112
+ hooks: {
113
+ async before_tool_call(toolName, args, context) {
114
+ const result = await evaluate({
115
+ toolName,
116
+ args,
117
+ agentId: context.id,
118
+ agentType: context.type,
119
+ agentName: context.name,
120
+ apiKey,
121
+ policyId,
122
+ baseUrl,
123
+ timeoutMs
124
+ });
125
+ if (!result) return { block: false };
126
+ const { decision, reasoning, policyContext } = result;
127
+ const matchedRule = policyContext?.matchedRule ?? "policy rule";
128
+ if (decision === "block") {
129
+ return { block: true, reason: `[${matchedRule}] ${reasoning}` };
130
+ }
131
+ if (decision === "require_approval") {
132
+ return {
133
+ requireApproval: true,
134
+ reason: `${reasoning} \u2014 review at ${dashboardUrl}`
135
+ };
136
+ }
137
+ return { block: false };
138
+ }
139
+ }
140
+ };
141
+ }
142
+ // Annotate the CommonJS export names for ESM import in node:
143
+ 0 && (module.exports = {
144
+ FastGRCApprovalRequiredError,
145
+ FastGRCBlockedError,
146
+ FastGRCPlugin,
147
+ evaluate
148
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,120 @@
1
+ // src/index.ts
2
+ var DEFAULT_BASE_URL = "https://app.fastgrc.ai";
3
+ var DEFAULT_TIMEOUT_MS = 3e3;
4
+ var FastGRCBlockedError = class extends Error {
5
+ constructor(matchedRule, reasoning, policyId) {
6
+ super(
7
+ `FastGRC policy blocked this action.
8
+ Rule: ${matchedRule}
9
+ Reason: ${reasoning}`
10
+ );
11
+ this.name = "FastGRCBlockedError";
12
+ this.matchedRule = matchedRule;
13
+ this.reasoning = reasoning;
14
+ this.policyId = policyId;
15
+ }
16
+ };
17
+ var FastGRCApprovalRequiredError = class extends Error {
18
+ constructor(dashboardUrl, reasoning) {
19
+ super(
20
+ `FastGRC requires human approval before this action.
21
+ Reason: ${reasoning}
22
+ Review at: ${dashboardUrl}`
23
+ );
24
+ this.name = "FastGRCApprovalRequiredError";
25
+ this.dashboardUrl = dashboardUrl;
26
+ }
27
+ };
28
+ async function evaluate(payload) {
29
+ const {
30
+ toolName,
31
+ args,
32
+ agentId,
33
+ agentType,
34
+ agentName,
35
+ apiKey,
36
+ policyId,
37
+ baseUrl = DEFAULT_BASE_URL,
38
+ timeoutMs = DEFAULT_TIMEOUT_MS
39
+ } = payload;
40
+ const evalUrl = `${baseUrl.replace(/\/$/, "")}/api/v1/policy-router/evaluate`;
41
+ const content = `tool_name: ${toolName}
42
+ args: ${JSON.stringify(args)}`;
43
+ try {
44
+ const controller = new AbortController();
45
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
46
+ const res = await fetch(evalUrl, {
47
+ method: "POST",
48
+ headers: {
49
+ "Authorization": `Bearer ${apiKey}`,
50
+ "Content-Type": "application/json"
51
+ },
52
+ body: JSON.stringify({
53
+ subjectContent: content,
54
+ subjectType: "tool_argument",
55
+ direction: "ingress",
56
+ agentId,
57
+ agentType,
58
+ agentName,
59
+ ...policyId ? { policyId } : {}
60
+ }),
61
+ signal: controller.signal
62
+ });
63
+ clearTimeout(timer);
64
+ if (!res.ok) {
65
+ console.warn(`[fastgrc] Evaluate returned ${res.status} \u2014 failing open`);
66
+ return null;
67
+ }
68
+ return await res.json();
69
+ } catch (err) {
70
+ const reason = err instanceof Error && err.name === "AbortError" ? "timeout" : String(err);
71
+ console.warn(`[fastgrc] Evaluate failed (${reason}) \u2014 failing open`);
72
+ return null;
73
+ }
74
+ }
75
+ function FastGRCPlugin(options) {
76
+ const {
77
+ apiKey,
78
+ policyId,
79
+ timeoutMs = DEFAULT_TIMEOUT_MS,
80
+ baseUrl = DEFAULT_BASE_URL
81
+ } = options;
82
+ const dashboardUrl = `${baseUrl.replace(/\/$/, "")}/dashboard/agent-policies`;
83
+ return {
84
+ name: "fastgrc",
85
+ hooks: {
86
+ async before_tool_call(toolName, args, context) {
87
+ const result = await evaluate({
88
+ toolName,
89
+ args,
90
+ agentId: context.id,
91
+ agentType: context.type,
92
+ agentName: context.name,
93
+ apiKey,
94
+ policyId,
95
+ baseUrl,
96
+ timeoutMs
97
+ });
98
+ if (!result) return { block: false };
99
+ const { decision, reasoning, policyContext } = result;
100
+ const matchedRule = policyContext?.matchedRule ?? "policy rule";
101
+ if (decision === "block") {
102
+ return { block: true, reason: `[${matchedRule}] ${reasoning}` };
103
+ }
104
+ if (decision === "require_approval") {
105
+ return {
106
+ requireApproval: true,
107
+ reason: `${reasoning} \u2014 review at ${dashboardUrl}`
108
+ };
109
+ }
110
+ return { block: false };
111
+ }
112
+ }
113
+ };
114
+ }
115
+ export {
116
+ FastGRCApprovalRequiredError,
117
+ FastGRCBlockedError,
118
+ FastGRCPlugin,
119
+ evaluate
120
+ };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "fastgrc-openclaw",
3
+ "version": "1.0.0",
4
+ "description": "FastGRC agent compliance plugin for OpenClaw — evaluates every tool call against your policy before it executes",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "bin": {
16
+ "fastgrc-hook": "./dist/bin.js"
17
+ },
18
+ "files": ["dist", "README.md"],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "keywords": ["fastgrc", "openclaw", "ai-agent", "compliance", "policy", "security"],
25
+ "author": "FastGRC <support@fastgrc.ai>",
26
+ "license": "MIT",
27
+ "devDependencies": {
28
+ "tsup": "^8.0.0",
29
+ "typescript": "^5.0.0"
30
+ },
31
+ "engines": { "node": ">=18" }
32
+ }