@cdot65/prisma-airs 0.3.0-alpha.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -122,8 +122,6 @@ Each security feature supports three modes:
122
122
  - `prisma_airs_scan_response` — replaces outbound scanning
123
123
  - `prisma_airs_check_tool_safety` — replaces tool gating
124
124
 
125
- **Backward compatibility**: Old boolean flags (`audit_enabled`, `context_injection_enabled`, etc.) still work. `true` maps to `deterministic`, `false` maps to `off`. New `*_mode` fields take precedence.
126
-
127
125
  **`fail_closed` constraint**: When `fail_closed=true` (default), all features must be `deterministic` or `off`. Probabilistic mode is rejected because the model might skip scanning.
128
126
 
129
127
  ## Usage
@@ -42,6 +42,6 @@ This hook runs asynchronously on every inbound message. It:
42
42
 
43
43
  Controlled by plugin config:
44
44
 
45
- - `audit_enabled`: Enable/disable audit logging (default: true)
45
+ - `audit_mode`: Scanning mode (default: `deterministic`). Options: `deterministic` / `probabilistic` / `off`
46
46
  - `profile_name`: AIRS profile to use for scanning
47
47
  - `app_name`: Application name for scan metadata
@@ -41,7 +41,6 @@ interface PluginConfig {
41
41
  entries?: {
42
42
  "prisma-airs"?: {
43
43
  config?: {
44
- audit_enabled?: boolean;
45
44
  profile_name?: string;
46
45
  app_name?: string;
47
46
  api_key?: string;
@@ -64,7 +63,7 @@ function getPluginConfig(ctx: HookContext & { cfg?: PluginConfig }): {
64
63
  } {
65
64
  const cfg = ctx.cfg?.plugins?.entries?.["prisma-airs"]?.config;
66
65
  return {
67
- enabled: cfg?.audit_enabled !== false,
66
+ enabled: true,
68
67
  profileName: cfg?.profile_name ?? "default",
69
68
  appName: cfg?.app_name ?? "openclaw",
70
69
  apiKey: cfg?.api_key ?? "",
@@ -37,5 +37,5 @@ The hook provides category-specific instructions to the agent:
37
37
 
38
38
  ## Configuration
39
39
 
40
- - `context_injection_enabled`: Enable/disable (default: true)
40
+ - `context_injection_mode`: Scanning mode (default: `deterministic`). Options: `deterministic` / `probabilistic` / `off`
41
41
  - `fail_closed`: Block on scan failure (default: true)
@@ -47,7 +47,6 @@ interface PluginConfig {
47
47
  entries?: {
48
48
  "prisma-airs"?: {
49
49
  config?: {
50
- context_injection_enabled?: boolean;
51
50
  profile_name?: string;
52
51
  app_name?: string;
53
52
  api_key?: string;
@@ -145,7 +144,7 @@ function getPluginConfig(ctx: HookContext): {
145
144
  } {
146
145
  const cfg = ctx.cfg?.plugins?.entries?.["prisma-airs"]?.config;
147
146
  return {
148
- enabled: cfg?.context_injection_enabled !== false,
147
+ enabled: true,
149
148
  profileName: cfg?.profile_name ?? "default",
150
149
  appName: cfg?.app_name ?? "openclaw",
151
150
  apiKey: cfg?.api_key ?? "",
@@ -28,7 +28,7 @@ Enable/disable via plugin config:
28
28
  plugins:
29
29
  prisma-airs:
30
30
  config:
31
- reminder_enabled: true # default
31
+ reminder_mode: "on" # default ("on" / "off")
32
32
  ```
33
33
 
34
34
  ## Requirements
@@ -60,27 +60,6 @@ describe("prisma-airs-guard hook", () => {
60
60
  expect(files[1].path).toBe("SECURITY.md");
61
61
  });
62
62
 
63
- it("does not inject when reminder_enabled is false", async () => {
64
- const event: TestEvent = {
65
- type: "agent",
66
- action: "bootstrap",
67
- context: {
68
- bootstrapFiles: [],
69
- cfg: {
70
- plugins: {
71
- entries: {
72
- "prisma-airs": { config: { reminder_enabled: false } },
73
- },
74
- },
75
- },
76
- },
77
- };
78
-
79
- await handler(event);
80
-
81
- expect(event.context!.bootstrapFiles).toHaveLength(0);
82
- });
83
-
84
63
  it("ignores non-bootstrap events", async () => {
85
64
  const event: TestEvent = {
86
65
  type: "agent",
@@ -158,7 +137,7 @@ describe("prisma-airs-guard hook", () => {
158
137
  expect(event.context!.bootstrapFiles).toHaveLength(0);
159
138
  });
160
139
 
161
- it("reminder_mode takes precedence over reminder_enabled", async () => {
140
+ it("injects when reminder_mode is on", async () => {
162
141
  const event: TestEvent = {
163
142
  type: "agent",
164
143
  action: "bootstrap",
@@ -168,7 +147,7 @@ describe("prisma-airs-guard hook", () => {
168
147
  plugins: {
169
148
  entries: {
170
149
  "prisma-airs": {
171
- config: { reminder_mode: "on", reminder_enabled: false },
150
+ config: { reminder_mode: "on" },
172
151
  },
173
152
  },
174
153
  },
@@ -161,11 +161,9 @@ const handler: HookHandler = async (event: HookEvent) => {
161
161
  const pluginSettings = prismaConfig?.config as Record<string, unknown> | undefined;
162
162
 
163
163
  // Check if reminder is enabled (default true)
164
- // Support both new reminder_mode and deprecated reminder_enabled
165
164
  const reminderMode = pluginSettings?.reminder_mode as string | undefined;
166
- const reminderEnabled = pluginSettings?.reminder_enabled as boolean | undefined;
167
165
 
168
- if (reminderMode === "off" || (reminderMode === undefined && reminderEnabled === false)) {
166
+ if (reminderMode === "off") {
169
167
  return;
170
168
  }
171
169
 
@@ -38,6 +38,6 @@ Masked patterns include:
38
38
 
39
39
  ## Configuration
40
40
 
41
- - `outbound_scanning_enabled`: Enable/disable (default: true)
41
+ - `outbound_mode`: Scanning mode (default: `deterministic`). Options: `deterministic` / `probabilistic` / `off`
42
42
  - `fail_closed`: Block on scan failure (default: true)
43
43
  - `dlp_mask_only`: Mask DLP instead of blocking (default: true)
@@ -61,7 +61,6 @@ describe("prisma-airs-outbound handler", () => {
61
61
  entries: {
62
62
  "prisma-airs": {
63
63
  config: {
64
- outbound_scanning_enabled: true,
65
64
  profile_name: "default",
66
65
  app_name: "test-app",
67
66
  api_key: "test-api-key",
@@ -299,29 +298,6 @@ describe("prisma-airs-outbound handler", () => {
299
298
  });
300
299
  });
301
300
 
302
- describe("disabled scanning", () => {
303
- it("should skip scanning when disabled", async () => {
304
- const ctxDisabled = {
305
- ...baseCtx,
306
- cfg: {
307
- plugins: {
308
- entries: {
309
- "prisma-airs": {
310
- config: {
311
- outbound_scanning_enabled: false,
312
- },
313
- },
314
- },
315
- },
316
- },
317
- };
318
-
319
- const result = await handler(baseEvent, ctxDisabled);
320
- expect(result).toBeUndefined();
321
- expect(mockScan).not.toHaveBeenCalled();
322
- });
323
- });
324
-
325
301
  describe("empty content", () => {
326
302
  it("should skip empty content", async () => {
327
303
  const emptyEvent = { ...baseEvent, content: "" };
@@ -40,7 +40,6 @@ interface PluginConfig {
40
40
  entries?: {
41
41
  "prisma-airs"?: {
42
42
  config?: {
43
- outbound_scanning_enabled?: boolean;
44
43
  profile_name?: string;
45
44
  app_name?: string;
46
45
  api_key?: string;
@@ -129,7 +128,7 @@ function getPluginConfig(ctx: HookContext): {
129
128
  } {
130
129
  const cfg = ctx.cfg?.plugins?.entries?.["prisma-airs"]?.config;
131
130
  return {
132
- enabled: cfg?.outbound_scanning_enabled !== false,
131
+ enabled: true,
133
132
  profileName: cfg?.profile_name ?? "default",
134
133
  appName: cfg?.app_name ?? "openclaw",
135
134
  apiKey: cfg?.api_key ?? "",
@@ -36,5 +36,5 @@ These tools are blocked on ANY detected threat:
36
36
 
37
37
  ## Configuration
38
38
 
39
- - `tool_gating_enabled`: Enable/disable (default: true)
39
+ - `tool_gating_mode`: Scanning mode (default: `deterministic`). Options: `deterministic` / `probabilistic` / `off`
40
40
  - `high_risk_tools`: List of tools to block on any threat
@@ -30,7 +30,6 @@ interface PluginConfig {
30
30
  entries?: {
31
31
  "prisma-airs"?: {
32
32
  config?: {
33
- tool_gating_enabled?: boolean;
34
33
  high_risk_tools?: string[];
35
34
  api_key?: string;
36
35
  };
@@ -149,7 +148,7 @@ function getPluginConfig(ctx: HookContext): {
149
148
  } {
150
149
  const cfg = ctx.cfg?.plugins?.entries?.["prisma-airs"]?.config;
151
150
  return {
152
- enabled: cfg?.tool_gating_enabled !== false,
151
+ enabled: true,
153
152
  highRiskTools: cfg?.high_risk_tools ?? DEFAULT_HIGH_RISK_TOOLS,
154
153
  };
155
154
  }
package/index.ts CHANGED
@@ -419,7 +419,7 @@ export default function register(api: PluginApi): void {
419
419
  const hasApiKey = isConfigured(cfg.api_key);
420
420
  respond(true, {
421
421
  plugin: "prisma-airs",
422
- version: "0.3.0-alpha.0",
422
+ version: "0.3.0",
423
423
  modes,
424
424
  config: {
425
425
  profile_name: cfg.profile_name ?? "default",
@@ -511,7 +511,7 @@ export default function register(api: PluginApi): void {
511
511
  const hasKey = isConfigured(cfg.api_key);
512
512
  console.log("Prisma AIRS Plugin Status");
513
513
  console.log("-------------------------");
514
- console.log(`Version: 0.3.0-alpha.0`);
514
+ console.log(`Version: 0.3.0`);
515
515
  console.log(`Profile: ${cfg.profile_name ?? "default"}`);
516
516
  console.log(`App Name: ${cfg.app_name ?? "openclaw"}`);
517
517
  console.log(`Modes:`);
@@ -567,7 +567,7 @@ export default function register(api: PluginApi): void {
567
567
  // Export plugin metadata for discovery
568
568
  export const id = "prisma-airs";
569
569
  export const name = "Prisma AIRS Security";
570
- export const version = "0.3.0-alpha.0";
570
+ export const version = "0.3.0";
571
571
 
572
572
  // Re-export scanner types and functions
573
573
  export { scan, isConfigured } from "./src/scanner";
@@ -2,7 +2,7 @@
2
2
  "id": "prisma-airs",
3
3
  "name": "Prisma AIRS Security",
4
4
  "description": "AI Runtime Security - full AIRS detection suite with audit logging, context injection, outbound blocking, and tool gating",
5
- "version": "0.3.0-alpha.0",
5
+ "version": "0.3.0",
6
6
  "entrypoint": "index.ts",
7
7
  "hooks": [
8
8
  "hooks/prisma-airs-guard",
@@ -55,36 +55,6 @@
55
55
  "default": "deterministic",
56
56
  "description": "Tool gating mode: deterministic (hook, always gate), probabilistic (tool, model decides), or off"
57
57
  },
58
- "reminder_enabled": {
59
- "type": "boolean",
60
- "default": true,
61
- "description": "DEPRECATED: Use reminder_mode instead. Inject security scanning reminder on agent bootstrap",
62
- "deprecated": true
63
- },
64
- "audit_enabled": {
65
- "type": "boolean",
66
- "default": true,
67
- "description": "DEPRECATED: Use audit_mode instead. Enable audit logging of all inbound messages",
68
- "deprecated": true
69
- },
70
- "context_injection_enabled": {
71
- "type": "boolean",
72
- "default": true,
73
- "description": "DEPRECATED: Use context_injection_mode instead. Inject security warnings into agent context",
74
- "deprecated": true
75
- },
76
- "outbound_scanning_enabled": {
77
- "type": "boolean",
78
- "default": true,
79
- "description": "DEPRECATED: Use outbound_mode instead. Enable scanning and blocking of outbound responses",
80
- "deprecated": true
81
- },
82
- "tool_gating_enabled": {
83
- "type": "boolean",
84
- "default": true,
85
- "description": "DEPRECATED: Use tool_gating_mode instead. Block dangerous tools when threats are detected",
86
- "deprecated": true
87
- },
88
58
  "fail_closed": {
89
59
  "type": "boolean",
90
60
  "default": true,
@@ -152,26 +122,6 @@
152
122
  "label": "Tool Gating Mode",
153
123
  "description": "deterministic: always gate tool calls via hook; probabilistic: model checks tool safety via tool; off: disabled"
154
124
  },
155
- "reminder_enabled": {
156
- "label": "Enable Bootstrap Reminder (deprecated)",
157
- "description": "DEPRECATED: Use reminder_mode instead"
158
- },
159
- "audit_enabled": {
160
- "label": "Enable Audit Logging (deprecated)",
161
- "description": "DEPRECATED: Use audit_mode instead"
162
- },
163
- "context_injection_enabled": {
164
- "label": "Enable Context Injection (deprecated)",
165
- "description": "DEPRECATED: Use context_injection_mode instead"
166
- },
167
- "outbound_scanning_enabled": {
168
- "label": "Enable Outbound Scanning (deprecated)",
169
- "description": "DEPRECATED: Use outbound_mode instead"
170
- },
171
- "tool_gating_enabled": {
172
- "label": "Enable Tool Gating (deprecated)",
173
- "description": "DEPRECATED: Use tool_gating_mode instead"
174
- },
175
125
  "fail_closed": {
176
126
  "label": "Fail Closed",
177
127
  "description": "Block on scan failure (recommended for security)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdot65/prisma-airs",
3
- "version": "0.3.0-alpha.0",
3
+ "version": "0.3.0",
4
4
  "description": "Prisma AIRS (AI Runtime Security) plugin for OpenClaw - Full security suite with audit logging, context injection, outbound blocking, and tool gating",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -6,59 +6,37 @@ import { describe, it, expect } from "vitest";
6
6
  import { resolveMode, resolveReminderMode, resolveAllModes } from "./config";
7
7
 
8
8
  describe("resolveMode", () => {
9
- it("returns default when both undefined", () => {
10
- expect(resolveMode(undefined, undefined)).toBe("deterministic");
9
+ it("returns default when undefined", () => {
10
+ expect(resolveMode(undefined)).toBe("deterministic");
11
11
  });
12
12
 
13
13
  it("returns custom default", () => {
14
- expect(resolveMode(undefined, undefined, "off")).toBe("off");
15
- });
16
-
17
- it("mode string takes precedence over boolean", () => {
18
- expect(resolveMode("probabilistic", true)).toBe("probabilistic");
19
- expect(resolveMode("off", true)).toBe("off");
20
- expect(resolveMode("deterministic", false)).toBe("deterministic");
21
- });
22
-
23
- it("falls back to boolean when mode undefined", () => {
24
- expect(resolveMode(undefined, true)).toBe("deterministic");
25
- expect(resolveMode(undefined, false)).toBe("off");
26
- });
27
-
28
- it("ignores invalid mode string and falls back to boolean", () => {
29
- expect(resolveMode("invalid", true)).toBe("deterministic");
30
- expect(resolveMode("invalid", false)).toBe("off");
14
+ expect(resolveMode(undefined, "off")).toBe("off");
31
15
  });
32
16
 
33
17
  it("ignores invalid mode string and falls back to default", () => {
34
- expect(resolveMode("invalid", undefined)).toBe("deterministic");
18
+ expect(resolveMode("invalid")).toBe("deterministic");
35
19
  });
36
20
 
37
21
  it("accepts all valid mode values", () => {
38
- expect(resolveMode("deterministic", undefined)).toBe("deterministic");
39
- expect(resolveMode("probabilistic", undefined)).toBe("probabilistic");
40
- expect(resolveMode("off", undefined)).toBe("off");
22
+ expect(resolveMode("deterministic")).toBe("deterministic");
23
+ expect(resolveMode("probabilistic")).toBe("probabilistic");
24
+ expect(resolveMode("off")).toBe("off");
41
25
  });
42
26
  });
43
27
 
44
28
  describe("resolveReminderMode", () => {
45
- it("returns default when both undefined", () => {
46
- expect(resolveReminderMode(undefined, undefined)).toBe("on");
29
+ it("returns default when undefined", () => {
30
+ expect(resolveReminderMode(undefined)).toBe("on");
47
31
  });
48
32
 
49
- it("mode string takes precedence", () => {
50
- expect(resolveReminderMode("off", true)).toBe("off");
51
- expect(resolveReminderMode("on", false)).toBe("on");
52
- });
53
-
54
- it("falls back to boolean", () => {
55
- expect(resolveReminderMode(undefined, true)).toBe("on");
56
- expect(resolveReminderMode(undefined, false)).toBe("off");
33
+ it("resolves valid mode values", () => {
34
+ expect(resolveReminderMode("off")).toBe("off");
35
+ expect(resolveReminderMode("on")).toBe("on");
57
36
  });
58
37
 
59
38
  it("ignores invalid mode string", () => {
60
- expect(resolveReminderMode("invalid", true)).toBe("on");
61
- expect(resolveReminderMode("invalid", undefined)).toBe("on");
39
+ expect(resolveReminderMode("invalid")).toBe("on");
62
40
  });
63
41
  });
64
42
 
@@ -92,33 +70,6 @@ describe("resolveAllModes", () => {
92
70
  });
93
71
  });
94
72
 
95
- it("resolves deprecated booleans", () => {
96
- const modes = resolveAllModes({
97
- reminder_enabled: false,
98
- audit_enabled: false,
99
- context_injection_enabled: true,
100
- outbound_scanning_enabled: false,
101
- tool_gating_enabled: true,
102
- fail_closed: false,
103
- });
104
- expect(modes).toEqual({
105
- reminder: "off",
106
- audit: "off",
107
- context: "deterministic",
108
- outbound: "off",
109
- toolGating: "deterministic",
110
- });
111
- });
112
-
113
- it("new mode takes precedence over deprecated boolean", () => {
114
- const modes = resolveAllModes({
115
- audit_mode: "probabilistic",
116
- audit_enabled: false, // would be "off", but mode overrides
117
- fail_closed: false,
118
- });
119
- expect(modes.audit).toBe("probabilistic");
120
- });
121
-
122
73
  it("throws when fail_closed=true with probabilistic audit", () => {
123
74
  expect(() =>
124
75
  resolveAllModes({
package/src/config.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Configuration mode resolution for Prisma AIRS plugin.
3
3
  *
4
- * Maps new tri-state mode enums + deprecated boolean flags to resolved modes.
4
+ * Maps tri-state mode enums to resolved modes.
5
5
  */
6
6
 
7
7
  export type FeatureMode = "deterministic" | "probabilistic" | "off";
@@ -17,19 +17,11 @@ export interface ResolvedModes {
17
17
 
18
18
  /** Raw plugin config (from openclaw.plugin.json) */
19
19
  export interface RawPluginConfig {
20
- // New mode fields
21
20
  reminder_mode?: string;
22
21
  audit_mode?: string;
23
22
  context_injection_mode?: string;
24
23
  outbound_mode?: string;
25
24
  tool_gating_mode?: string;
26
- // Deprecated boolean fields
27
- reminder_enabled?: boolean;
28
- audit_enabled?: boolean;
29
- context_injection_enabled?: boolean;
30
- outbound_scanning_enabled?: boolean;
31
- tool_gating_enabled?: boolean;
32
- // Other config
33
25
  fail_closed?: boolean;
34
26
  [key: string]: unknown;
35
27
  }
@@ -38,36 +30,26 @@ const VALID_FEATURE_MODES: FeatureMode[] = ["deterministic", "probabilistic", "o
38
30
  const VALID_REMINDER_MODES: ReminderMode[] = ["on", "off"];
39
31
 
40
32
  /**
41
- * Resolve a single feature mode from new mode string + deprecated boolean.
42
- * New mode field takes precedence when both are set.
33
+ * Resolve a single feature mode from mode string.
43
34
  */
44
35
  export function resolveMode(
45
36
  modeValue: string | undefined,
46
- enabledValue: boolean | undefined,
47
37
  defaultMode: FeatureMode = "deterministic"
48
38
  ): FeatureMode {
49
- // New mode field takes precedence
50
39
  if (modeValue !== undefined) {
51
40
  if (VALID_FEATURE_MODES.includes(modeValue as FeatureMode)) {
52
41
  return modeValue as FeatureMode;
53
42
  }
54
- // Invalid value → fall through to boolean/default
55
- }
56
-
57
- // Deprecated boolean fallback
58
- if (enabledValue !== undefined) {
59
- return enabledValue ? "deterministic" : "off";
60
43
  }
61
44
 
62
45
  return defaultMode;
63
46
  }
64
47
 
65
48
  /**
66
- * Resolve reminder mode from new mode string + deprecated boolean.
49
+ * Resolve reminder mode from mode string.
67
50
  */
68
51
  export function resolveReminderMode(
69
52
  modeValue: string | undefined,
70
- enabledValue: boolean | undefined,
71
53
  defaultMode: ReminderMode = "on"
72
54
  ): ReminderMode {
73
55
  if (modeValue !== undefined) {
@@ -76,10 +58,6 @@ export function resolveReminderMode(
76
58
  }
77
59
  }
78
60
 
79
- if (enabledValue !== undefined) {
80
- return enabledValue ? "on" : "off";
81
- }
82
-
83
61
  return defaultMode;
84
62
  }
85
63
 
@@ -89,11 +67,11 @@ export function resolveReminderMode(
89
67
  */
90
68
  export function resolveAllModes(config: RawPluginConfig): ResolvedModes {
91
69
  const modes: ResolvedModes = {
92
- reminder: resolveReminderMode(config.reminder_mode, config.reminder_enabled),
93
- audit: resolveMode(config.audit_mode, config.audit_enabled),
94
- context: resolveMode(config.context_injection_mode, config.context_injection_enabled),
95
- outbound: resolveMode(config.outbound_mode, config.outbound_scanning_enabled),
96
- toolGating: resolveMode(config.tool_gating_mode, config.tool_gating_enabled),
70
+ reminder: resolveReminderMode(config.reminder_mode),
71
+ audit: resolveMode(config.audit_mode),
72
+ context: resolveMode(config.context_injection_mode),
73
+ outbound: resolveMode(config.outbound_mode),
74
+ toolGating: resolveMode(config.tool_gating_mode),
97
75
  };
98
76
 
99
77
  // Validate: fail_closed + probabilistic is not allowed