@intflows/genkit-guard 0.0.12 → 0.0.14
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 +174 -186
- package/dist/core/decision.d.ts +33 -0
- package/dist/core/decision.js +9 -0
- package/dist/guard.config.d.ts +10 -0
- package/dist/guard.config.js +12 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +8 -10
- package/dist/intent/intentAnalyzer.d.ts +1 -1
- package/dist/intent/intentAnalyzer.js +2 -2
- package/dist/middleware/middleware.d.ts +87 -7
- package/dist/middleware/middleware.js +93 -4
- package/dist/pii/detector.d.ts +16 -4
- package/dist/pii/detector.js +72 -3
- package/dist/util/singleton.d.ts +2 -1
- package/dist/util/singleton.js +8 -5
- package/package.json +9 -4
- package/scripts/publish-wiki.js +59 -0
- package/scripts/test-config.js +45 -0
- package/scripts/test-privacy-filter.js +116 -0
- package/scripts/test-publish-wiki.js +30 -0
- package/scripts/test-release1.js +83 -0
- package/scripts/test-types.ts +51 -21
package/README.md
CHANGED
|
@@ -1,276 +1,264 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Genkit Guard
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Keep Genkit agents on topic, mask personal data before model calls, and control which tools can run.
|
|
4
4
|
|
|
5
|
-
`@intflows/genkit-guard`
|
|
6
|
-
It adds **semantic intent validation**, **PII masking/unmasking**, and **prompt‑injection detection** with minimal configuration.
|
|
5
|
+
`@intflows/genkit-guard` adds prompt-injection checks, semantic intent scoring, reversible PII masking, and tool policies to your Genkit application. Guard models run locally through Transformers.js; your generative model can use the provider you choose.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
[Try the Hugging Face Space](https://huggingface.co/spaces/intflows/genkit-guard) · [Documentation](https://github.com/IntFlows/genkit-guard/wiki) · [Examples](./example/src)
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
## Why use it?
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
Agents can access customer data and trigger real workflows. Genkit Guard gives you controls at the model and tool boundaries:
|
|
13
12
|
|
|
14
|
-
- **
|
|
15
|
-
|
|
13
|
+
- **Check requests:** block known injection patterns and reject prompts below your intent similarity threshold.
|
|
14
|
+
- **Reduce PII exposure:** mask detected personal data before model calls and restore tokens in responses and tool inputs.
|
|
15
|
+
- **Control tools:** allow, block, redact detected PII, or require application approval before execution.
|
|
16
|
+
- **Audit decisions:** emit versioned decision events without raw prompts, arguments, or PII values.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
Detects emails, phone numbers, names, and AU‑specific identifiers.
|
|
19
|
-
Replaces PII with reversible tokens before sending to the LLM.
|
|
18
|
+
Detection is based on patterns and model predictions. It can miss attacks or PII and can reject valid requests; evaluate it on your application's inputs and keep authorization in your application.
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
Restores original PII in the model’s response, even inside structured JSON.
|
|
20
|
+
## Quick start
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
Blocks jailbreak attempts using pattern‑based heuristics.
|
|
26
|
-
|
|
27
|
-
- **Model‑Light Architecture**
|
|
28
|
-
The package uses local `all-MiniLM-L6-v2` and `openai/privacy-filter` Models, these Models are downloaded once and cached locally.
|
|
29
|
-
|
|
30
|
-
- **Drop‑in Genkit Middleware**
|
|
31
|
-
Works with `ai.generate`, `ai.generateStream`, and Genkit flows.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
### 📦 Installation
|
|
22
|
+
Install the package in your Genkit application:
|
|
36
23
|
|
|
37
24
|
```bash
|
|
38
|
-
## Install the package
|
|
39
25
|
npm install @intflows/genkit-guard
|
|
40
26
|
```
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Download them once.
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
## Download the transformer models (MiniLM + OpenAI/privacy-filter)
|
|
48
|
-
node node_modules/@intflows/genkit-guard/scripts/download-model.js
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Models are cached locally and reused across runs.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## 🚀 Quick Start
|
|
56
|
-
|
|
57
|
-
### 1. Initialize Local folder
|
|
28
|
+
The examples below target **v0.0.14**. The package declares Genkit **1.39.0** as its peer dependency.
|
|
58
29
|
|
|
59
|
-
|
|
60
|
-
# Install @intflows/genkit-guard
|
|
61
|
-
npm install @intflows/genkit-guard
|
|
30
|
+
Create `src/guard.config.ts`:
|
|
62
31
|
|
|
63
|
-
|
|
64
|
-
|
|
32
|
+
```ts
|
|
33
|
+
import { defineGuardConfig } from "@intflows/genkit-guard";
|
|
34
|
+
|
|
35
|
+
export default defineGuardConfig({
|
|
36
|
+
models: { extractor: "Xenova/all-MiniLM-L6-v2" },
|
|
37
|
+
intent: {
|
|
38
|
+
semantic: {
|
|
39
|
+
threshold: 0.7,
|
|
40
|
+
intents: {
|
|
41
|
+
support: "Technical support for Azure Blob Storage, APIs and integrations"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
pii: {
|
|
46
|
+
mode: "classifier",
|
|
47
|
+
model: "openai/privacy-filter"
|
|
48
|
+
},
|
|
49
|
+
tools: {
|
|
50
|
+
defaultAction: "block",
|
|
51
|
+
rules: { searchDocs: "allow" }
|
|
52
|
+
}
|
|
53
|
+
});
|
|
65
54
|
```
|
|
66
|
-
_ This downloads the models to ./models folder, the total size is ~1.5 GB ( 1GB for Openai/privacy-filter + .5 GB for MiniLM-L6-v2)
|
|
67
55
|
|
|
68
|
-
|
|
56
|
+
Use the same configuration at startup and in your model call:
|
|
69
57
|
|
|
70
58
|
```ts
|
|
71
59
|
import { guard, initGuard } from "@intflows/genkit-guard";
|
|
60
|
+
import guardConfig from "./guard.config.js";
|
|
72
61
|
|
|
73
|
-
|
|
62
|
+
// ai is your configured Genkit instance.
|
|
63
|
+
await initGuard(guardConfig);
|
|
74
64
|
|
|
75
65
|
const response = await ai.generate({
|
|
76
66
|
prompt: "How do I integrate with Azure Blob Storage?",
|
|
77
|
-
use: [
|
|
78
|
-
guard({
|
|
79
|
-
intent: {
|
|
80
|
-
mode: "semantic",
|
|
81
|
-
allowedIntent: "integration",
|
|
82
|
-
semantic: {
|
|
83
|
-
threshold: 0.7,
|
|
84
|
-
intents: {
|
|
85
|
-
integration: "Azure Blob, APIs, workflows"
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
pii: { reversible: true }
|
|
90
|
-
})
|
|
91
|
-
]
|
|
67
|
+
use: [guard(guardConfig)]
|
|
92
68
|
});
|
|
69
|
+
|
|
70
|
+
if (response.finishReason === "blocked") {
|
|
71
|
+
console.log("Request blocked by guard policy");
|
|
72
|
+
} else {
|
|
73
|
+
console.log(response.text);
|
|
74
|
+
}
|
|
93
75
|
```
|
|
94
76
|
|
|
95
|
-
|
|
77
|
+
The tool rules apply to tools you separately register and supply to Genkit; they do not create tools. The example permits `searchDocs` and blocks other tool names. Tune intent descriptions and thresholds with representative requests.
|
|
96
78
|
|
|
97
|
-
|
|
79
|
+
`initGuard()` preloads the selected models and can download missing files. Models are cached under `./models` relative to your application's working directory. Download size and memory use depend on the models selected. Configuration is explicitly imported; there is no automatic file discovery.
|
|
98
80
|
|
|
99
|
-
|
|
81
|
+
For a new application, follow the [full setup guide](https://github.com/IntFlows/genkit-guard/wiki/6.-Full-Setup-Guide).
|
|
100
82
|
|
|
101
|
-
|
|
102
|
-
``` npx tsx src/index.ts "How do I integrate with Azure Blob Storage?"```
|
|
83
|
+
## What happens to a request?
|
|
103
84
|
|
|
104
|
-
|
|
105
|
-
|
|
85
|
+
```text
|
|
86
|
+
User request
|
|
87
|
+
-> Injection-pattern check
|
|
88
|
+
-> Semantic intent check
|
|
89
|
+
-> PII masking
|
|
90
|
+
-> Generative model
|
|
91
|
+
-> Response token restoration
|
|
92
|
+
-> Tool policy check before any requested tool executes
|
|
93
|
+
```
|
|
106
94
|
|
|
107
|
-
|
|
95
|
+
A matching injection phrase such as `ignore previous instructions` blocks the model call. Intent checks compare the prompt with every description in `intent.semantic.intents`; only include categories you want to allow.
|
|
108
96
|
|
|
97
|
+
PII masking replaces detected values with namespaced tokens:
|
|
109
98
|
|
|
110
|
-
|
|
111
|
-
|
|
99
|
+
```text
|
|
100
|
+
Input: Email alice@example.com
|
|
101
|
+
Model receives: Email [[EMAIL_<namespace>_0]]
|
|
102
|
+
Restored: Email alice@example.com
|
|
103
|
+
```
|
|
112
104
|
|
|
113
|
-
|
|
105
|
+
Restoration also works inside structured responses. Tool policy checks determine whether a requested tool may receive restored values or redacted arguments.
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
## Example
|
|
107
|
+
## Tool controls
|
|
117
108
|
|
|
118
|
-
|
|
109
|
+
Add exact tool names to the shared configuration:
|
|
119
110
|
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
111
|
+
```ts
|
|
112
|
+
tools: {
|
|
113
|
+
defaultAction: "block",
|
|
114
|
+
rules: {
|
|
115
|
+
searchDocs: "allow",
|
|
116
|
+
summarizeTicket: "redact",
|
|
117
|
+
deleteTicket: "approval-required"
|
|
118
|
+
},
|
|
119
|
+
approve: async ({ toolName, input, context }) => {
|
|
120
|
+
// Connect a trusted approval service for this exact call and user.
|
|
121
|
+
// This example denies every approval request.
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
126
125
|
```
|
|
127
126
|
|
|
128
|
-
|
|
127
|
+
| Policy | Behavior |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `allow` | Restore tokens, scan input, then execute |
|
|
130
|
+
| `block` | Stop before execution |
|
|
131
|
+
| `redact` | Replace detected PII in nested string arguments with `[REDACTED]`, then execute |
|
|
132
|
+
| `approval-required` | Execute only when the application's callback returns literal `true` |
|
|
129
133
|
|
|
130
|
-
|
|
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
|
-
```
|
|
134
|
+
Blocked, pending, or denied calls throw `GuardToolError`. Approval UI and durable approval storage belong to the application. Redaction may invalidate a tool's input schema, such as an email field; choose a policy appropriate to the tool.
|
|
137
135
|
|
|
138
|
-
|
|
136
|
+
Without tool policies, the default remains allow. With `tools` configured, `guard()` returns a native Genkit middleware reference. Existing configurations without `tools` retain the legacy callable form. Use `guardMiddleware(config)` for native tool hooks without explicit policies.
|
|
139
137
|
|
|
140
|
-
|
|
138
|
+
See [tool controls and logging](https://github.com/IntFlows/genkit-guard/wiki/9.-Tool-Controls-and-Logging) for the complete behavior.
|
|
141
139
|
|
|
142
|
-
|
|
143
|
-
- Embeds the user prompt + intent descriptions using MiniLM
|
|
144
|
-
- Computes cosine similarity
|
|
145
|
-
- Blocks prompts below threshold
|
|
146
|
-
- Detects jailbreak patterns like:
|
|
147
|
-
- “ignore previous instructions”
|
|
148
|
-
- “you are a hacker”
|
|
149
|
-
- “export the API key”
|
|
140
|
+
## Models and PII storage
|
|
150
141
|
|
|
151
|
-
|
|
152
|
-
|
|
142
|
+
| Setting | Default |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| Intent model | `Xenova/all-MiniLM-L6-v2` |
|
|
145
|
+
| PII mode | `ner` |
|
|
146
|
+
| NER model | `Xenova/bert-base-NER` |
|
|
147
|
+
| Classifier model | `openai/privacy-filter` when classifier mode is selected |
|
|
148
|
+
| PII vault | In-memory storage |
|
|
153
149
|
|
|
154
|
-
|
|
155
|
-
"Email john.doe@example.com" → "Email [[EMAIL_0]]"
|
|
156
|
-
```
|
|
150
|
+
The quick start explicitly selects classifier mode. Regex detection also runs for email, Australian phone and identifier patterns, and credit-card-like numbers.
|
|
157
151
|
|
|
158
|
-
|
|
152
|
+
Use `models.extractor` and `pii.model` to select compatible models, and `pii.labelMappings` to map fine-tuned labels to masking types. Classifier mode currently loads `q4` weights. Redis and custom vault adapters support external storage.
|
|
159
153
|
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
- AU identifiers (Medicare, TFN, ABN, etc.)
|
|
163
|
-
- PII detected by local Model (OpenAI/privacy-filter)
|
|
154
|
+
- [Shared model configuration](https://github.com/IntFlows/genkit-guard/wiki/8.-Shared-Model-Configuration)
|
|
155
|
+
- [PII labels, masking and vaults](https://github.com/IntFlows/genkit-guard/wiki/5.-PII-Guard)
|
|
164
156
|
|
|
165
|
-
|
|
166
|
-
The masked prompt is sent to the model.
|
|
157
|
+
## PII Vault Isolation and External Storage
|
|
167
158
|
|
|
168
|
-
|
|
169
|
-
After the LLM responds:
|
|
159
|
+
Masked values are kept in a vault so they can be restored later. By default, the middleware uses process-local in-memory storage with generated vault scopes. Each tokenizer also generates an opaque namespace for its placeholders:
|
|
170
160
|
|
|
161
|
+
```text
|
|
162
|
+
alice@example.com -> [[EMAIL_<namespace>_0]]
|
|
171
163
|
```
|
|
172
|
-
"Send a confirmation email to [[EMAIL_0]]" → "Send a confirmation email to john.doe@example.com"
|
|
173
|
-
```
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## ⚙️ Configuration
|
|
177
164
|
|
|
178
|
-
|
|
165
|
+
Namespaces prevent concurrent calls from creating identical placeholder names. Use `pii.vault.scopeId` to group vault entries by request, session, or another application scope. The scope ID is not exposed in the placeholder.
|
|
179
166
|
|
|
180
|
-
|
|
181
|
-
intent: {
|
|
182
|
-
mode: "semantic",
|
|
183
|
-
allowedIntent: "intent_question",
|
|
184
|
-
semantic: {
|
|
185
|
-
threshold: 0.7,
|
|
186
|
-
intents: {
|
|
187
|
-
intent_question: "Description of allowed intent"
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
```
|
|
167
|
+
### Redis storage
|
|
192
168
|
|
|
193
|
-
|
|
169
|
+
Use Redis when vault entries need to survive application restarts or be available to multiple workers. Install the client separately:
|
|
194
170
|
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
reversible: true
|
|
198
|
-
}
|
|
199
|
-
```
|
|
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]]"
|
|
171
|
+
```bash
|
|
172
|
+
npm install redis
|
|
207
173
|
```
|
|
208
174
|
|
|
209
|
-
|
|
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
|
-
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:
|
|
175
|
+
Extend your shared configuration during application startup:
|
|
213
176
|
|
|
214
177
|
```ts
|
|
215
178
|
import { createClient } from "redis";
|
|
216
|
-
import { guard, createRedisPiiVaultStorage } from "@intflows/genkit-guard";
|
|
179
|
+
import { guard, initGuard, createRedisPiiVaultStorage } from "@intflows/genkit-guard";
|
|
180
|
+
import guardConfig from "./guard.config.js";
|
|
217
181
|
|
|
218
|
-
const redis = createClient({ url: "redis://localhost:6379" });
|
|
182
|
+
const redis = createClient({ url: process.env.REDIS_URL ?? "redis://localhost:6379" });
|
|
183
|
+
redis.on("error", () => console.error("PII vault Redis connection error"));
|
|
219
184
|
await redis.connect();
|
|
220
185
|
|
|
221
|
-
|
|
186
|
+
const config = {
|
|
187
|
+
...guardConfig,
|
|
222
188
|
pii: {
|
|
223
|
-
|
|
189
|
+
...guardConfig.pii,
|
|
224
190
|
vault: {
|
|
225
191
|
storage: createRedisPiiVaultStorage(redis, {
|
|
226
192
|
keyPrefix: "my-app:pii",
|
|
227
193
|
ttlSeconds: 3600,
|
|
228
|
-
fallbackToMemory:
|
|
194
|
+
fallbackToMemory: false
|
|
229
195
|
}),
|
|
230
|
-
|
|
196
|
+
// Populate this from trusted application context.
|
|
197
|
+
// If absent, the middleware generates a new scope.
|
|
198
|
+
scopeId: (_req: unknown, ctx: any) => ctx?.context?.piiScopeId
|
|
231
199
|
}
|
|
232
200
|
}
|
|
233
|
-
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
await initGuard(config);
|
|
204
|
+
// Use guard(config) in your ai.generate({ use: [...] }) calls.
|
|
205
|
+
const middleware = guard(config);
|
|
234
206
|
```
|
|
235
207
|
|
|
236
|
-
`ttlSeconds`
|
|
237
|
-
`fallbackToMemory` is enabled, successful writes are also mirrored in process memory and Redis
|
|
238
|
-
operation failures fall back to that mirror. The fallback is disabled by default, is local to one
|
|
239
|
-
process, and is not a replacement for Redis persistence or multi-worker availability. Its in-memory
|
|
240
|
-
entries observe the same TTL. Redis errors continue to propagate when fallback is disabled.
|
|
208
|
+
`ttlSeconds` expires the scoped vault and shared token-index keys; writes refresh their expiry, so it is not a per-entry retention deadline. Without a TTL, Redis entries remain until removed externally. The default in-memory vault has no automatic expiry.
|
|
241
209
|
|
|
242
|
-
|
|
210
|
+
`fallbackToMemory: true` enables a process-local mirror with the same TTL behavior when Redis operations fail. It is disabled by default, so Redis errors propagate. The mirror is not shared across workers, does not survive restarts, and is not automatically replayed into Redis after recovery.
|
|
243
211
|
|
|
244
|
-
|
|
212
|
+
### Custom storage and isolation boundaries
|
|
245
213
|
|
|
246
|
-
|
|
247
|
-

|
|
214
|
+
Use `createPiiVaultStorage({ get, set, entries, getByToken })` to connect another database or store. `getByToken` is optional and enables recovery of opaque tokens across model/tool turns.
|
|
248
215
|
|
|
249
|
-
|
|
216
|
+
Scoped reads and writes use `scopeId`, but cross-turn recovery can look up tokens across scopes within the same backend. Scope IDs and opaque namespaces are not tenant authorization controls. For separate tenants, use appropriately isolated storage adapters or Redis prefixes, including separate token indexes, and enforce access in your application. Vault entries contain original PII values; the adapter does not encrypt those values itself.
|
|
250
217
|
|
|
251
|
-
|
|
218
|
+
See the [PII vault documentation](https://github.com/IntFlows/genkit-guard/wiki/5.-PII-Guard) and [Redis integration example](./example/src/index.ts).
|
|
252
219
|
|
|
253
|
-
|
|
220
|
+
## Decision logging
|
|
254
221
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
-
|
|
222
|
+
Console logging emits structured JSON. Add an audit callback to your configuration to receive `GuardDecision` events:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
policyVersion: "support-v1",
|
|
226
|
+
logging: {
|
|
227
|
+
enabled: true,
|
|
228
|
+
level: "info",
|
|
229
|
+
onDecision: async decision => {
|
|
230
|
+
// Forward the content-free event to your audit sink.
|
|
231
|
+
console.log(JSON.stringify(decision));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
259
235
|
|
|
260
|
-
|
|
236
|
+
Events include schema version, decision ID, timestamp, guard, policy version, action, reason code, latency, and intent similarity where applicable. The callback runs independently of console settings, is awaited, and stops execution if it fails. It does not provide persistent storage by itself.
|
|
261
237
|
|
|
262
|
-
|
|
238
|
+
## Try it and explore the examples
|
|
263
239
|
|
|
264
|
-
|
|
240
|
+
[Open the Hugging Face Space](https://huggingface.co/spaces/intflows/genkit-guard) to explore the demo. Use synthetic inputs when trying a hosted demo.
|
|
265
241
|
|
|
266
|
-
|
|
242
|
+
- [Integration flow](./example/src/index.ts): Azure Blob workflow with Redis-backed PII storage.
|
|
243
|
+
- [Tool controls](./example/src/tool-controls.ts): optional standalone demo of redaction and approval-required policies. This file is not required to use the package.
|
|
267
244
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
245
|
+
The tool-controls demo uses the local build. Run `npm install` and `npm run build` in the repository root, then `npm install` in `example`. Set `GEMINI_API_KEY` and `GEMINI_MODEL` for your provider account, and run:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# From example/
|
|
249
|
+
npx tsx src/tool-controls.ts
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Roadmap
|
|
253
|
+
|
|
254
|
+
- **v0.0.14:** shared model configuration, custom PII labels, tool policies and versioned decision events.
|
|
255
|
+
- **v0.1.0 planned:** persistent decision logging and explicit fallback-model behavior.
|
|
256
|
+
- **Later:** SQLite vault, memory compaction integration, compatibility hardening and stable v1.
|
|
257
|
+
|
|
258
|
+
## Contributing
|
|
271
259
|
|
|
272
|
-
|
|
260
|
+
Issues, pull requests, model evaluations and security reviews are welcome. Run `npm test` for the deterministic suite; real Redis integration is tested separately with `npm run test:redis`.
|
|
273
261
|
|
|
274
|
-
|
|
262
|
+
## License
|
|
275
263
|
|
|
276
|
-
Apache
|
|
264
|
+
[Apache-2.0](./LICENSE)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type GuardAction = 'allow' | 'block' | 'redact' | 'approval-required';
|
|
2
|
+
export type GuardReasonCode = 'INJECTION_PATTERN' | 'INJECTION_CLEAR' | 'INTENT_ALLOWED' | 'INTENT_REJECTED' | 'PII_DETECTED' | 'PII_CLEAR' | 'TOOL_ALLOWED' | 'TOOL_BLOCKED' | 'TOOL_REDACTED' | 'TOOL_APPROVAL_REQUIRED' | 'TOOL_APPROVED' | 'TOOL_APPROVAL_DENIED' | 'TOOL_POLICY_ERROR';
|
|
3
|
+
/** Content-free audit contract. Version independently of the npm package. */
|
|
4
|
+
export interface GuardDecision {
|
|
5
|
+
schemaVersion: '1';
|
|
6
|
+
decisionId: string;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
guard: 'injection' | 'intent' | 'pii' | 'tool';
|
|
9
|
+
policyVersion: string;
|
|
10
|
+
action: GuardAction;
|
|
11
|
+
reasonCode: GuardReasonCode;
|
|
12
|
+
latencyMs: number;
|
|
13
|
+
confidence?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ToolPolicyContext {
|
|
16
|
+
toolName: string;
|
|
17
|
+
/** A copy of the restored arguments. Never included in GuardDecision events. */
|
|
18
|
+
input: unknown;
|
|
19
|
+
/** Trusted application context supplied by Genkit, not model arguments. */
|
|
20
|
+
context: unknown;
|
|
21
|
+
}
|
|
22
|
+
export interface ToolGuardConfig {
|
|
23
|
+
/** Default allow preserves existing behavior. Use block for an allowlist. */
|
|
24
|
+
defaultAction?: GuardAction;
|
|
25
|
+
rules?: Record<string, GuardAction>;
|
|
26
|
+
/** Called only for approval-required rules; only literal true authorizes execution. */
|
|
27
|
+
approve?: (call: ToolPolicyContext) => boolean | Promise<boolean>;
|
|
28
|
+
}
|
|
29
|
+
/** Thrown before tool execution for blocked, pending, denied or failed policies. */
|
|
30
|
+
export declare class GuardToolError extends Error {
|
|
31
|
+
readonly decision: GuardDecision;
|
|
32
|
+
constructor(decision: GuardDecision);
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Thrown before tool execution for blocked, pending, denied or failed policies. */
|
|
2
|
+
export class GuardToolError extends Error {
|
|
3
|
+
decision;
|
|
4
|
+
constructor(decision) {
|
|
5
|
+
super(`Tool execution stopped: ${decision.reasonCode}`);
|
|
6
|
+
this.decision = decision;
|
|
7
|
+
this.name = 'GuardToolError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GuardConfig } from './middleware/middleware.js';
|
|
2
|
+
/** Model labels (case insensitive, optional BIOES prefix) to masking token types. */
|
|
3
|
+
export type PiiLabelMappings = Record<string, string | null>;
|
|
4
|
+
/** Define an application-owned guard.config.ts and pass it to both public APIs. */
|
|
5
|
+
export declare function defineGuardConfig(config: GuardConfig): GuardConfig;
|
|
6
|
+
export declare function resolveGuardModels(config?: GuardConfig): {
|
|
7
|
+
extractor: string;
|
|
8
|
+
mode: "ner" | "classifier";
|
|
9
|
+
pii: string;
|
|
10
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Define an application-owned guard.config.ts and pass it to both public APIs. */
|
|
2
|
+
export function defineGuardConfig(config) {
|
|
3
|
+
return config;
|
|
4
|
+
}
|
|
5
|
+
export function resolveGuardModels(config) {
|
|
6
|
+
const mode = config?.pii?.mode ?? 'ner';
|
|
7
|
+
return {
|
|
8
|
+
extractor: config?.models?.extractor ?? 'Xenova/all-MiniLM-L6-v2',
|
|
9
|
+
mode,
|
|
10
|
+
pii: config?.pii?.model ?? (mode === 'ner' ? 'Xenova/bert-base-NER' : 'openai/privacy-filter'),
|
|
11
|
+
};
|
|
12
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export * from './core/decision.js';
|
|
2
|
+
import type { GuardConfig } from './middleware/middleware.js';
|
|
3
|
+
export { defineGuardConfig } from './guard.config.js';
|
|
4
|
+
export type { PiiLabelMappings } from './guard.config.js';
|
|
1
5
|
export { guard, guardAction, guardMiddleware, guardPlugin } from './middleware/middleware.js';
|
|
2
6
|
export type { GuardConfig } from './middleware/middleware.js';
|
|
3
7
|
export { InMemoryPiiVaultStorage, createPiiVaultStorage, createRedisPiiVaultStorage, defaultPiiVaultStorage, } from './pii/storage.js';
|
|
@@ -6,4 +10,4 @@ export * from './core/types.js';
|
|
|
6
10
|
/**
|
|
7
11
|
* Pre-load the model to avoid cold-start delay on first user request.
|
|
8
12
|
*/
|
|
9
|
-
export declare function initGuard(config?:
|
|
13
|
+
export declare function initGuard(config?: GuardConfig): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export * from './core/decision.js';
|
|
2
|
+
import { resolveGuardModels } from './guard.config.js';
|
|
3
|
+
export { defineGuardConfig } from './guard.config.js';
|
|
1
4
|
import { ModelSingleton } from './util/singleton.js';
|
|
2
5
|
// export { intentGuard, piiGuard } from './middleware/middleware.js';
|
|
3
6
|
export { guard, guardAction, guardMiddleware, guardPlugin } from './middleware/middleware.js';
|
|
@@ -26,16 +29,11 @@ function logGuardEvent(eventName, body, attributes = {}) {
|
|
|
26
29
|
*/
|
|
27
30
|
export async function initGuard(config) {
|
|
28
31
|
logGuardEvent('guard.models.loading', 'Loading local guard models');
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
tasks.push(ModelSingleton.getNER(piiModel ?? 'Xenova/bert-base-NER'));
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
tasks.push(ModelSingleton.getPIIClassifier(piiModel ?? 'openai/privacy-filter'));
|
|
38
|
-
}
|
|
32
|
+
const { extractor, pii, mode: piiMode } = resolveGuardModels(config);
|
|
33
|
+
const tasks = [ModelSingleton.getExtractor(extractor)];
|
|
34
|
+
tasks.push(piiMode === 'ner'
|
|
35
|
+
? ModelSingleton.getNER(pii)
|
|
36
|
+
: ModelSingleton.getPIIClassifier(pii));
|
|
39
37
|
await Promise.all(tasks);
|
|
40
38
|
logGuardEvent('guard.models.loaded', 'Local guard models loaded', {
|
|
41
39
|
piiMode,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare function detectInjection(userInput: string): Promise<boolean>;
|
|
2
|
-
export declare function analyzeIntentStructured(input: string, intents: Record<string, string>, threshold: number): Promise<{
|
|
2
|
+
export declare function analyzeIntentStructured(input: string, intents: Record<string, string>, threshold: number, model?: string): Promise<{
|
|
3
3
|
intent: string;
|
|
4
4
|
score: number;
|
|
5
5
|
allowed: boolean;
|
|
@@ -58,8 +58,8 @@ const INJECTION_PATTERNS = [
|
|
|
58
58
|
export async function detectInjection(userInput) {
|
|
59
59
|
return INJECTION_PATTERNS.some(p => userInput.toLowerCase().includes(p));
|
|
60
60
|
}
|
|
61
|
-
export async function analyzeIntentStructured(input, intents, threshold) {
|
|
62
|
-
const extractor = await ModelSingleton.getExtractor();
|
|
61
|
+
export async function analyzeIntentStructured(input, intents, threshold, model) {
|
|
62
|
+
const extractor = await ModelSingleton.getExtractor(model);
|
|
63
63
|
let bestIntent = '';
|
|
64
64
|
let bestScore = 0;
|
|
65
65
|
for (const [key, desc] of Object.entries(intents)) {
|