@intflows/genkit-guard 0.0.9 → 0.0.11

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
@@ -1,7 +1,5 @@
1
1
  # **@intflows/genkit-guard**
2
2
 
3
- #### _This version uses OpenAI/privacy-filter instead of bert-base-NER_
4
-
5
3
  ### **Lightweight Intent, PII, and Safety Guardrails for Genkit**
6
4
 
7
5
  `@intflows/genkit-guard` provides a modular guardrail layer for Genkit flows.
@@ -114,6 +112,29 @@ const response = await ai.generate({
114
112
 
115
113
  ![Image showing PII data masked ](./MaskedPII.png)
116
114
 
115
+ ---
116
+ ## Example
117
+
118
+ An example genkit flow is present in `example` directory.
119
+
120
+ ```bash
121
+ git clone https://github.com/IntFlows/genkit-guard.git
122
+ cd genkit-guard/example
123
+ npm install
124
+ node node_modules/@intflows/genkit-guard/scripts/download-model.js
125
+ npx tsx src/index.ts
126
+ ```
127
+
128
+ Or you can run the flow with genkit dev UI
129
+
130
+ ```bash
131
+ git clone https://github.com/IntFlows/genkit-guard.git
132
+ cd genkit-guard/example
133
+ npm install
134
+ node node_modules/@intflows/genkit-guard/scripts/download-model.js
135
+ genkit start -- npx tsx src/index.ts
136
+ ```
137
+
117
138
  ---
118
139
 
119
140
  ## 🧠 How It Works
@@ -177,6 +198,46 @@ pii: {
177
198
  }
178
199
  ```
179
200
 
201
+ ### **PII Vault Isolation and External Storage**
202
+
203
+ By default, PII is stored in an in-memory vault scoped to a single tokenizer instance. Tokens include a generated vault scope:
204
+
205
+ ```txt
206
+ "Email john.doe@example.com" -> "Email [[EMAIL_<namespace>_0]]"
207
+ ```
208
+
209
+ That generated namespace prevents two concurrent calls from sharing the same visible placeholder names. Vault lookups are isolated by the configured storage scope, so User A and User B can safely produce their own email tokens without cross-resolving each other's PII.
210
+
211
+ For applications that need persistence, distributed workers, audits, or tenant-specific storage, provide a vault storage backend. Redis clients can be passed through the built-in helper:
212
+
213
+ ```ts
214
+ import { createClient } from "redis";
215
+ import { guard, createRedisPiiVaultStorage } from "@intflows/genkit-guard";
216
+
217
+ const redis = createClient({ url: "redis://localhost:6379" });
218
+ await redis.connect();
219
+
220
+ guard({
221
+ pii: {
222
+ reversible: true,
223
+ vault: {
224
+ storage: createRedisPiiVaultStorage(redis, {
225
+ keyPrefix: "my-app:pii",
226
+ ttlSeconds: 3600
227
+ }),
228
+ scopeId: (req, ctx) => ctx?.auth?.sessionId ?? req?.metadata?.requestId
229
+ }
230
+ }
231
+ });
232
+ ```
233
+
234
+ For another backend, use `createPiiVaultStorage({ get, set, entries, getByToken })` with your database, cache, or secret store.
235
+
236
+ Choose a `scopeId` that matches your isolation boundary, such as request ID, session ID, tenant/user ID, or a combination like `tenantId:userId:requestId`. A shared external backend should never ignore `scopeId`, because placeholders are only safe when resolved against the correct vault scope. The placeholder sent to the model uses an opaque generated namespace rather than exposing your `scopeId`.
237
+
238
+ ### Screenshots
239
+ ![Redis Stored PII ](redis-scan.png)
240
+
180
241
  ---
181
242
 
182
243
  ## 🛡️ Why This Library Exists
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- export { guard } from './middleware/middleware.js';
1
+ export { guard, guardAction, guardMiddleware, guardPlugin } from './middleware/middleware.js';
2
+ export type { GuardConfig } from './middleware/middleware.js';
3
+ export { InMemoryPiiVaultStorage, createPiiVaultStorage, createRedisPiiVaultStorage, defaultPiiVaultStorage, } from './pii/storage.js';
4
+ export type { PiiVaultEntry, PiiVaultStorage, PiiVaultStorageAdapter, RedisPiiVaultClient, RedisPiiVaultStorageOptions, } from './pii/storage.js';
2
5
  export * from './core/types.js';
3
6
  /**
4
7
  * Pre-load the model to avoid cold-start delay on first user request.
package/dist/index.js CHANGED
@@ -1,12 +1,31 @@
1
1
  import { ModelSingleton } from './util/singleton.js';
2
2
  // export { intentGuard, piiGuard } from './middleware/middleware.js';
3
- export { guard } from './middleware/middleware.js';
3
+ export { guard, guardAction, guardMiddleware, guardPlugin } from './middleware/middleware.js';
4
+ export { InMemoryPiiVaultStorage, createPiiVaultStorage, createRedisPiiVaultStorage, defaultPiiVaultStorage, } from './pii/storage.js';
4
5
  export * from './core/types.js';
6
+ function logGuardEvent(eventName, body, attributes = {}) {
7
+ console.log(JSON.stringify({
8
+ timestamp: new Date().toISOString(),
9
+ severityText: 'INFO',
10
+ severityNumber: 9,
11
+ body,
12
+ resource: {
13
+ attributes: {
14
+ 'service.name': '@intflows/genkit-guard',
15
+ },
16
+ },
17
+ attributes: {
18
+ 'event.name': eventName,
19
+ 'code.namespace': 'genkit-guard',
20
+ ...attributes,
21
+ },
22
+ }));
23
+ }
5
24
  /**
6
25
  * Pre-load the model to avoid cold-start delay on first user request.
7
26
  */
8
27
  export async function initGuard(config) {
9
- console.log('[Guard] Loading local models...');
28
+ logGuardEvent('guard.models.loading', 'Loading local guard models');
10
29
  const extractorModel = config?.models?.extractor ?? 'Xenova/all-MiniLM-L6-v2';
11
30
  const piiModel = config?.pii?.model;
12
31
  const piiMode = config?.pii?.mode ?? 'ner';
@@ -18,5 +37,7 @@ export async function initGuard(config) {
18
37
  tasks.push(ModelSingleton.getPIIClassifier(piiModel ?? 'openai/privacy-filter'));
19
38
  }
20
39
  await Promise.all(tasks);
21
- console.log('[Guard] Models loaded');
40
+ logGuardEvent('guard.models.loaded', 'Local guard models loaded', {
41
+ piiMode,
42
+ });
22
43
  }
@@ -1,54 +1,35 @@
1
1
  import { z } from 'genkit';
2
- declare const guardConfigSchema: z.ZodObject<{
3
- intent: z.ZodOptional<z.ZodObject<{
4
- mode: z.ZodOptional<z.ZodString>;
5
- allowedIntent: z.ZodOptional<z.ZodString>;
6
- semantic: z.ZodObject<{
7
- threshold: z.ZodOptional<z.ZodNumber>;
8
- intents: z.ZodRecord<z.ZodString, z.ZodString>;
9
- }, "strip", z.ZodTypeAny, {
10
- intents: Record<string, string>;
11
- threshold?: number | undefined;
12
- }, {
2
+ import { type PiiVaultStorage } from '../pii/storage.js';
3
+ export type GuardConfig = {
4
+ intent?: {
5
+ mode?: string;
6
+ allowedIntent?: string;
7
+ semantic?: {
8
+ threshold?: number;
13
9
  intents: Record<string, string>;
14
- threshold?: number | undefined;
15
- }>;
16
- }, "strip", z.ZodTypeAny, {
17
- semantic: {
18
- intents: Record<string, string>;
19
- threshold?: number | undefined;
20
10
  };
21
- mode?: string | undefined;
22
- allowedIntent?: string | undefined;
23
- }, {
24
- semantic: {
25
- intents: Record<string, string>;
26
- threshold?: number | undefined;
11
+ };
12
+ pii?: {
13
+ reversible?: boolean;
14
+ model?: string;
15
+ mode?: 'ner' | 'classifier';
16
+ vault?: {
17
+ storage?: PiiVaultStorage;
18
+ scopeId?: string | ((req: any, ctx: any) => string | undefined);
27
19
  };
28
- mode?: string | undefined;
29
- allowedIntent?: string | undefined;
30
- }>>;
31
- pii: z.ZodOptional<z.ZodObject<{
32
- reversible: z.ZodOptional<z.ZodBoolean>;
33
- model: z.ZodOptional<z.ZodString>;
34
- mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
35
- }, "strip", z.ZodTypeAny, {
36
- mode?: "ner" | "classifier" | undefined;
37
- reversible?: boolean | undefined;
38
- model?: string | undefined;
39
- }, {
40
- mode?: "ner" | "classifier" | undefined;
41
- reversible?: boolean | undefined;
42
- model?: string | undefined;
43
- }>>;
44
- models: z.ZodOptional<z.ZodObject<{
45
- extractor: z.ZodOptional<z.ZodString>;
46
- }, "strip", z.ZodTypeAny, {
47
- extractor?: string | undefined;
48
- }, {
49
- extractor?: string | undefined;
50
- }>>;
51
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
20
+ };
21
+ logging?: {
22
+ enabled?: boolean;
23
+ level?: LogSeverity;
24
+ serviceName?: string;
25
+ };
26
+ models?: {
27
+ extractor?: string;
28
+ };
29
+ [key: string]: any;
30
+ };
31
+ type LogSeverity = 'debug' | 'info' | 'warn' | 'error';
32
+ export declare const guardMiddleware: import("genkit").GenerateMiddleware<z.ZodObject<{
52
33
  intent: z.ZodOptional<z.ZodObject<{
53
34
  mode: z.ZodOptional<z.ZodString>;
54
35
  allowedIntent: z.ZodOptional<z.ZodString>;
@@ -81,113 +62,45 @@ declare const guardConfigSchema: z.ZodObject<{
81
62
  reversible: z.ZodOptional<z.ZodBoolean>;
82
63
  model: z.ZodOptional<z.ZodString>;
83
64
  mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
84
- }, "strip", z.ZodTypeAny, {
85
- mode?: "ner" | "classifier" | undefined;
86
- reversible?: boolean | undefined;
87
- model?: string | undefined;
88
- }, {
89
- mode?: "ner" | "classifier" | undefined;
90
- reversible?: boolean | undefined;
91
- model?: string | undefined;
92
- }>>;
93
- models: z.ZodOptional<z.ZodObject<{
94
- extractor: z.ZodOptional<z.ZodString>;
95
- }, "strip", z.ZodTypeAny, {
96
- extractor?: string | undefined;
97
- }, {
98
- extractor?: string | undefined;
99
- }>>;
100
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
101
- intent: z.ZodOptional<z.ZodObject<{
102
- mode: z.ZodOptional<z.ZodString>;
103
- allowedIntent: z.ZodOptional<z.ZodString>;
104
- semantic: z.ZodObject<{
105
- threshold: z.ZodOptional<z.ZodNumber>;
106
- intents: z.ZodRecord<z.ZodString, z.ZodString>;
65
+ vault: z.ZodOptional<z.ZodObject<{
66
+ storage: z.ZodOptional<z.ZodAny>;
67
+ scopeId: z.ZodOptional<z.ZodAny>;
107
68
  }, "strip", z.ZodTypeAny, {
108
- intents: Record<string, string>;
109
- threshold?: number | undefined;
69
+ storage?: any;
70
+ scopeId?: any;
110
71
  }, {
111
- intents: Record<string, string>;
112
- threshold?: number | undefined;
113
- }>;
114
- }, "strip", z.ZodTypeAny, {
115
- semantic: {
116
- intents: Record<string, string>;
117
- threshold?: number | undefined;
118
- };
119
- mode?: string | undefined;
120
- allowedIntent?: string | undefined;
121
- }, {
122
- semantic: {
123
- intents: Record<string, string>;
124
- threshold?: number | undefined;
125
- };
126
- mode?: string | undefined;
127
- allowedIntent?: string | undefined;
128
- }>>;
129
- pii: z.ZodOptional<z.ZodObject<{
130
- reversible: z.ZodOptional<z.ZodBoolean>;
131
- model: z.ZodOptional<z.ZodString>;
132
- mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
72
+ storage?: any;
73
+ scopeId?: any;
74
+ }>>;
133
75
  }, "strip", z.ZodTypeAny, {
134
76
  mode?: "ner" | "classifier" | undefined;
135
77
  reversible?: boolean | undefined;
136
78
  model?: string | undefined;
79
+ vault?: {
80
+ storage?: any;
81
+ scopeId?: any;
82
+ } | undefined;
137
83
  }, {
138
84
  mode?: "ner" | "classifier" | undefined;
139
85
  reversible?: boolean | undefined;
140
86
  model?: string | undefined;
87
+ vault?: {
88
+ storage?: any;
89
+ scopeId?: any;
90
+ } | undefined;
141
91
  }>>;
142
- models: z.ZodOptional<z.ZodObject<{
143
- extractor: z.ZodOptional<z.ZodString>;
92
+ logging: z.ZodOptional<z.ZodObject<{
93
+ enabled: z.ZodOptional<z.ZodBoolean>;
94
+ level: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>;
95
+ serviceName: z.ZodOptional<z.ZodString>;
144
96
  }, "strip", z.ZodTypeAny, {
145
- extractor?: string | undefined;
97
+ enabled?: boolean | undefined;
98
+ level?: "debug" | "info" | "warn" | "error" | undefined;
99
+ serviceName?: string | undefined;
146
100
  }, {
147
- extractor?: string | undefined;
148
- }>>;
149
- }, z.ZodTypeAny, "passthrough">>;
150
- export declare const guardMiddleware: import("genkit").GenerateMiddleware<z.ZodObject<{
151
- intent: z.ZodOptional<z.ZodObject<{
152
- mode: z.ZodOptional<z.ZodString>;
153
- allowedIntent: z.ZodOptional<z.ZodString>;
154
- semantic: z.ZodObject<{
155
- threshold: z.ZodOptional<z.ZodNumber>;
156
- intents: z.ZodRecord<z.ZodString, z.ZodString>;
157
- }, "strip", z.ZodTypeAny, {
158
- intents: Record<string, string>;
159
- threshold?: number | undefined;
160
- }, {
161
- intents: Record<string, string>;
162
- threshold?: number | undefined;
163
- }>;
164
- }, "strip", z.ZodTypeAny, {
165
- semantic: {
166
- intents: Record<string, string>;
167
- threshold?: number | undefined;
168
- };
169
- mode?: string | undefined;
170
- allowedIntent?: string | undefined;
171
- }, {
172
- semantic: {
173
- intents: Record<string, string>;
174
- threshold?: number | undefined;
175
- };
176
- mode?: string | undefined;
177
- allowedIntent?: string | undefined;
178
- }>>;
179
- pii: z.ZodOptional<z.ZodObject<{
180
- reversible: z.ZodOptional<z.ZodBoolean>;
181
- model: z.ZodOptional<z.ZodString>;
182
- mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
183
- }, "strip", z.ZodTypeAny, {
184
- mode?: "ner" | "classifier" | undefined;
185
- reversible?: boolean | undefined;
186
- model?: string | undefined;
187
- }, {
188
- mode?: "ner" | "classifier" | undefined;
189
- reversible?: boolean | undefined;
190
- model?: string | undefined;
101
+ enabled?: boolean | undefined;
102
+ level?: "debug" | "info" | "warn" | "error" | undefined;
103
+ serviceName?: string | undefined;
191
104
  }>>;
192
105
  models: z.ZodOptional<z.ZodObject<{
193
106
  extractor: z.ZodOptional<z.ZodString>;
@@ -229,14 +142,45 @@ export declare const guardMiddleware: import("genkit").GenerateMiddleware<z.ZodO
229
142
  reversible: z.ZodOptional<z.ZodBoolean>;
230
143
  model: z.ZodOptional<z.ZodString>;
231
144
  mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
145
+ vault: z.ZodOptional<z.ZodObject<{
146
+ storage: z.ZodOptional<z.ZodAny>;
147
+ scopeId: z.ZodOptional<z.ZodAny>;
148
+ }, "strip", z.ZodTypeAny, {
149
+ storage?: any;
150
+ scopeId?: any;
151
+ }, {
152
+ storage?: any;
153
+ scopeId?: any;
154
+ }>>;
232
155
  }, "strip", z.ZodTypeAny, {
233
156
  mode?: "ner" | "classifier" | undefined;
234
157
  reversible?: boolean | undefined;
235
158
  model?: string | undefined;
159
+ vault?: {
160
+ storage?: any;
161
+ scopeId?: any;
162
+ } | undefined;
236
163
  }, {
237
164
  mode?: "ner" | "classifier" | undefined;
238
165
  reversible?: boolean | undefined;
239
166
  model?: string | undefined;
167
+ vault?: {
168
+ storage?: any;
169
+ scopeId?: any;
170
+ } | undefined;
171
+ }>>;
172
+ logging: z.ZodOptional<z.ZodObject<{
173
+ enabled: z.ZodOptional<z.ZodBoolean>;
174
+ level: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>;
175
+ serviceName: z.ZodOptional<z.ZodString>;
176
+ }, "strip", z.ZodTypeAny, {
177
+ enabled?: boolean | undefined;
178
+ level?: "debug" | "info" | "warn" | "error" | undefined;
179
+ serviceName?: string | undefined;
180
+ }, {
181
+ enabled?: boolean | undefined;
182
+ level?: "debug" | "info" | "warn" | "error" | undefined;
183
+ serviceName?: string | undefined;
240
184
  }>>;
241
185
  models: z.ZodOptional<z.ZodObject<{
242
186
  extractor: z.ZodOptional<z.ZodString>;
@@ -278,14 +222,45 @@ export declare const guardMiddleware: import("genkit").GenerateMiddleware<z.ZodO
278
222
  reversible: z.ZodOptional<z.ZodBoolean>;
279
223
  model: z.ZodOptional<z.ZodString>;
280
224
  mode: z.ZodOptional<z.ZodEnum<["ner", "classifier"]>>;
225
+ vault: z.ZodOptional<z.ZodObject<{
226
+ storage: z.ZodOptional<z.ZodAny>;
227
+ scopeId: z.ZodOptional<z.ZodAny>;
228
+ }, "strip", z.ZodTypeAny, {
229
+ storage?: any;
230
+ scopeId?: any;
231
+ }, {
232
+ storage?: any;
233
+ scopeId?: any;
234
+ }>>;
281
235
  }, "strip", z.ZodTypeAny, {
282
236
  mode?: "ner" | "classifier" | undefined;
283
237
  reversible?: boolean | undefined;
284
238
  model?: string | undefined;
239
+ vault?: {
240
+ storage?: any;
241
+ scopeId?: any;
242
+ } | undefined;
285
243
  }, {
286
244
  mode?: "ner" | "classifier" | undefined;
287
245
  reversible?: boolean | undefined;
288
246
  model?: string | undefined;
247
+ vault?: {
248
+ storage?: any;
249
+ scopeId?: any;
250
+ } | undefined;
251
+ }>>;
252
+ logging: z.ZodOptional<z.ZodObject<{
253
+ enabled: z.ZodOptional<z.ZodBoolean>;
254
+ level: z.ZodOptional<z.ZodEnum<["debug", "info", "warn", "error"]>>;
255
+ serviceName: z.ZodOptional<z.ZodString>;
256
+ }, "strip", z.ZodTypeAny, {
257
+ enabled?: boolean | undefined;
258
+ level?: "debug" | "info" | "warn" | "error" | undefined;
259
+ serviceName?: string | undefined;
260
+ }, {
261
+ enabled?: boolean | undefined;
262
+ level?: "debug" | "info" | "warn" | "error" | undefined;
263
+ serviceName?: string | undefined;
289
264
  }>>;
290
265
  models: z.ZodOptional<z.ZodObject<{
291
266
  extractor: z.ZodOptional<z.ZodString>;
@@ -296,5 +271,6 @@ export declare const guardMiddleware: import("genkit").GenerateMiddleware<z.ZodO
296
271
  }>>;
297
272
  }, z.ZodTypeAny, "passthrough">>, void>;
298
273
  export declare const guardPlugin: (pluginOptions: void) => import("@genkit-ai/ai").GenkitPluginV2;
299
- export declare function guard(config?: z.infer<typeof guardConfigSchema>): (req: any, ctxOrNext: any, maybeNext?: any) => Promise<any>;
274
+ export declare function guard(config?: GuardConfig): (req: any, ctxOrNext: any, maybeNext?: any) => Promise<any>;
275
+ export declare const guardAction: typeof guard;
300
276
  export {};