@intflows/genkit-guard 0.0.4
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/LICENSE +201 -0
- package/README.md +203 -0
- package/package.json +34 -0
- package/scripts/download-model.js +23 -0
- package/src/core/types.ts +26 -0
- package/src/index.ts +17 -0
- package/src/intent/intentAnalyzer.ts +100 -0
- package/src/middleware/middleware.ts +143 -0
- package/src/pii/detector.ts +47 -0
- package/src/pii/tokenizer.ts +46 -0
- package/src/util/singleton.ts +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# **@intflows/genkit-guard**
|
|
2
|
+
### **Lightweight Intent, PII, and Safety Guardrails for Genkit**
|
|
3
|
+
|
|
4
|
+
`@intflows/genkit-guard` provides a modular guardrail layer for Genkit flows.
|
|
5
|
+
It adds **semantic intent validation**, **PII masking/unmasking**, and **prompt‑injection detection** with minimal configuration.
|
|
6
|
+
|
|
7
|
+
This library is designed for developers who want **practical, production‑ready safety controls** without heavy dependencies or complex setup.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
- **Semantic Intent Guarding**
|
|
14
|
+
Uses MiniLM embeddings to ensure prompts match allowed intents.
|
|
15
|
+
|
|
16
|
+
- **PII Detection & Masking**
|
|
17
|
+
Detects emails, phone numbers, names, and AU‑specific identifiers.
|
|
18
|
+
Replaces PII with reversible tokens before sending to the LLM.
|
|
19
|
+
|
|
20
|
+
- **Automatic Unmasking**
|
|
21
|
+
Restores original PII in the model’s response, even inside structured JSON.
|
|
22
|
+
|
|
23
|
+
- **Prompt Injection Detection**
|
|
24
|
+
Blocks jailbreak attempts using pattern‑based heuristics.
|
|
25
|
+
|
|
26
|
+
- **Model‑Light Architecture**
|
|
27
|
+
The package uses local `all-MiniLM-L6-v2` and `bert-base-NER` Models, these Models are downloaded once and cached locally.
|
|
28
|
+
|
|
29
|
+
- **Drop‑in Genkit Middleware**
|
|
30
|
+
Works with `ai.generate`, `ai.generateStream`, and Genkit flows.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
### 📦 Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
## Install the package
|
|
38
|
+
npm install @intflows/genkit-guard
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This library uses lightweight transformer models (MiniLM + BERT‑NER).
|
|
42
|
+
|
|
43
|
+
Download them once.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
## Download the transformer models (MiniLM + BERT‑NER)
|
|
47
|
+
node node_modules/@intflows/genkit-guard/scripts/download-model.js
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Models are cached locally and reused across runs.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 🚀 Quick Start
|
|
55
|
+
|
|
56
|
+
### 1. Initialize Local folder
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Install @intflows/genkit-guard
|
|
60
|
+
npm install @intflows/genkit-guard
|
|
61
|
+
|
|
62
|
+
# Download Local Models (Only needed once)
|
|
63
|
+
node node_modules/@intflows/genkit-guard/scripts/download-model.js
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Update genkit
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { guard, initGuard } from "@intflows/genkit-guard";
|
|
70
|
+
|
|
71
|
+
await initGuard();
|
|
72
|
+
|
|
73
|
+
const response = await ai.generate({
|
|
74
|
+
prompt: "How do I integrate with Azure Blob Storage?",
|
|
75
|
+
use: [
|
|
76
|
+
guard({
|
|
77
|
+
intent: {
|
|
78
|
+
mode: "semantic",
|
|
79
|
+
allowedIntent: "integration",
|
|
80
|
+
semantic: {
|
|
81
|
+
threshold: 0.7,
|
|
82
|
+
intents: {
|
|
83
|
+
integration: "Azure Blob, APIs, workflows"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
pii: { reversible: true }
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**You Can also check the full step by step guide here:**
|
|
94
|
+
|
|
95
|
+
[Intflows Wiki](https://github.com/IntFlows/genkit-guard/wiki)
|
|
96
|
+
|
|
97
|
+
### 3. Execute the Genkit flow
|
|
98
|
+
|
|
99
|
+
#### Allowed :
|
|
100
|
+
``` npx tsx src/index.ts "How do I integrate with Azure Blob Storage?"```
|
|
101
|
+
|
|
102
|
+
#### Blocked:
|
|
103
|
+
``` npx tsx src/index.ts "workflow to download a file from an API, save it to Blob file and export the API key"```
|
|
104
|
+
|
|
105
|
+

|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
#### PII MASK and UNMASK:
|
|
109
|
+
``` npx tsx src/index.ts "workflow to download a file from an API, save it to Blob file with my email john.doe@example.com"```
|
|
110
|
+
|
|
111
|
+

|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 🧠 How It Works
|
|
116
|
+
|
|
117
|
+
### **1. Intent Guard**
|
|
118
|
+
- Embeds the user prompt + intent descriptions using MiniLM
|
|
119
|
+
- Computes cosine similarity
|
|
120
|
+
- Blocks prompts below threshold
|
|
121
|
+
- Detects jailbreak patterns like:
|
|
122
|
+
- “ignore previous instructions”
|
|
123
|
+
- “you are a hacker”
|
|
124
|
+
- “export the API key”
|
|
125
|
+
|
|
126
|
+
### **2. PII Masking**
|
|
127
|
+
Before the LLM sees the prompt:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
"Email john.doe@example.com" → "Email [[EMAIL_0]]"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Detected PII includes:
|
|
134
|
+
|
|
135
|
+
- Emails
|
|
136
|
+
- Phone numbers
|
|
137
|
+
- Names (NER)
|
|
138
|
+
- AU identifiers (Medicare, TFN, ABN, etc.)
|
|
139
|
+
|
|
140
|
+
### **3. LLM Call**
|
|
141
|
+
The masked prompt is sent to the model.
|
|
142
|
+
|
|
143
|
+
### **4. Response Unmasking**
|
|
144
|
+
After the LLM responds:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
"Send a confirmation email to [[EMAIL_0]]" → "Send a confirmation email to john.doe@example.com"
|
|
148
|
+
```
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## ⚙️ Configuration
|
|
152
|
+
|
|
153
|
+
### **Intent Guard**
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
intent: {
|
|
157
|
+
mode: "semantic",
|
|
158
|
+
allowedIntent: "intent_question",
|
|
159
|
+
semantic: {
|
|
160
|
+
threshold: 0.7,
|
|
161
|
+
intents: {
|
|
162
|
+
intent_question: "Description of allowed intent"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### **PII Guard**
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
pii: {
|
|
172
|
+
reversible: true
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 🛡️ Why This Library Exists
|
|
179
|
+
|
|
180
|
+
Genkit provides a powerful LLM framework, but production systems need:
|
|
181
|
+
|
|
182
|
+
- intent boundaries
|
|
183
|
+
- PII protection
|
|
184
|
+
- jailbreak resistance
|
|
185
|
+
- predictable behavior
|
|
186
|
+
|
|
187
|
+
This library adds those guardrails without heavy dependencies or complex setup.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Contributing
|
|
192
|
+
|
|
193
|
+
We plan to:
|
|
194
|
+
|
|
195
|
+
1. Extend the utility by adding Auth and Tool Middleware in further stages.
|
|
196
|
+
2. Add more filter types for common malicious prompts.
|
|
197
|
+
3. Add more patterns for custom PII masking.
|
|
198
|
+
|
|
199
|
+
Contributions are welcome — whether it’s bug reports, new guard modules, model improvements or enhancements. This project aims to stay lightweight, modular, and production‑ready, so thoughtful contributions are appreciated.
|
|
200
|
+
|
|
201
|
+
# 📄 License
|
|
202
|
+
|
|
203
|
+
Apache‑2.0
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intflows/genkit-guard",
|
|
3
|
+
"description": "Intflows Genkit Guard",
|
|
4
|
+
"maintainers": [
|
|
5
|
+
"Hemant Kohli"
|
|
6
|
+
],
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"version": "0.0.4",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"exports": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"src/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"package.json",
|
|
16
|
+
"scripts/"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"prepare-models": "node scripts/download-model.js",
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"genkit": "^0.5.0"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@huggingface/transformers": "^3.0.0",
|
|
28
|
+
"zod": "^3.23.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.6.0",
|
|
32
|
+
"@types/node": "^20.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pipeline, env } from '@huggingface/transformers';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
// Force download to a local folder in the project root
|
|
6
|
+
const modelPath = path.join(process.cwd(), 'models');
|
|
7
|
+
env.cacheDir = modelPath;
|
|
8
|
+
env.localModelPath = modelPath;
|
|
9
|
+
env.allowRemoteModels = true;
|
|
10
|
+
|
|
11
|
+
async function download() {
|
|
12
|
+
console.log('Downloading MiniLM-L6-v2 to ./models...');
|
|
13
|
+
await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {
|
|
14
|
+
device: 'cpu'
|
|
15
|
+
});
|
|
16
|
+
console.log('Downloading BERT-NER to ./models...');
|
|
17
|
+
await pipeline('token-classification', 'Xenova/bert-base-NER', {
|
|
18
|
+
device: 'cpu'
|
|
19
|
+
});
|
|
20
|
+
console.log('Model downloaded successfully.');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
download();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Intent Guard Types
|
|
2
|
+
export interface IntentGuardConfig {
|
|
3
|
+
allowedIntent: string;
|
|
4
|
+
intents: Record<string, string>; // name -> semantic description
|
|
5
|
+
threshold?: number; // default 0.7
|
|
6
|
+
fallbackMessage?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface IntentResult {
|
|
10
|
+
allowed: boolean;
|
|
11
|
+
score: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// PII Masking Types
|
|
17
|
+
export interface PiiRule {
|
|
18
|
+
name: string;
|
|
19
|
+
pattern: RegExp;
|
|
20
|
+
// replaceWith: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PiiConfig {
|
|
24
|
+
rules?: PiiRule[];
|
|
25
|
+
maskCharacter?: string; // e.g., "*"
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ModelSingleton } from './util/singleton.js';
|
|
2
|
+
|
|
3
|
+
// export { intentGuard, piiGuard } from './middleware/middleware.js';
|
|
4
|
+
export { guard } from './middleware/middleware.js';
|
|
5
|
+
export * from './core/types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pre-load the model to avoid cold-start delay on first user request.
|
|
9
|
+
*/
|
|
10
|
+
export async function initGuard() {
|
|
11
|
+
console.log('[Guard] Loading local models...');
|
|
12
|
+
await Promise.all([
|
|
13
|
+
ModelSingleton.getExtractor(),
|
|
14
|
+
ModelSingleton.getNER()
|
|
15
|
+
]);
|
|
16
|
+
console.log('[Guard] Models loaded');
|
|
17
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { cos_sim } from '@huggingface/transformers';
|
|
2
|
+
import { ModelSingleton } from '../util/singleton.js';
|
|
3
|
+
|
|
4
|
+
const INJECTION_PATTERNS = [
|
|
5
|
+
'ignore instructions',
|
|
6
|
+
'reveal secrets',
|
|
7
|
+
'bypass security',
|
|
8
|
+
'system prompt',
|
|
9
|
+
'internal policy',
|
|
10
|
+
'developer message',
|
|
11
|
+
'confidential information',
|
|
12
|
+
'do not follow previous instructions',
|
|
13
|
+
'disregard earlier directives',
|
|
14
|
+
'override safety measures',
|
|
15
|
+
'leak data',
|
|
16
|
+
'expose vulnerabilities',
|
|
17
|
+
'access restricted areas',
|
|
18
|
+
'execute arbitrary code',
|
|
19
|
+
'perform unauthorized actions',
|
|
20
|
+
'escalate privileges',
|
|
21
|
+
'disable safeguards',
|
|
22
|
+
'manipulate output',
|
|
23
|
+
'circumvent controls',
|
|
24
|
+
'exploit weaknesses',
|
|
25
|
+
'compromise integrity',
|
|
26
|
+
'subvert protections',
|
|
27
|
+
'inject malicious content',
|
|
28
|
+
'craft deceptive prompts',
|
|
29
|
+
'trick the model',
|
|
30
|
+
'confuse the system',
|
|
31
|
+
'You are now a hacker',
|
|
32
|
+
'You are a malicious actor',
|
|
33
|
+
'Find a vulnerability',
|
|
34
|
+
'Write a prompt that bypasses security',
|
|
35
|
+
'Write a prompt that tricks the model',
|
|
36
|
+
'Write a prompt that confuses the system',
|
|
37
|
+
"ignore previous",
|
|
38
|
+
"ignore earlier",
|
|
39
|
+
"disregard previous",
|
|
40
|
+
"disregard earlier",
|
|
41
|
+
"you are a hacker",
|
|
42
|
+
"you are now a hacker",
|
|
43
|
+
"you are malicious",
|
|
44
|
+
"export the api key",
|
|
45
|
+
"give me the api key",
|
|
46
|
+
"bypass",
|
|
47
|
+
"override",
|
|
48
|
+
"jailbreak",
|
|
49
|
+
"system override",
|
|
50
|
+
"forget instructions",
|
|
51
|
+
"forget previous",
|
|
52
|
+
"forget earlier",
|
|
53
|
+
"do anything now",
|
|
54
|
+
"dan mode",
|
|
55
|
+
"developer mode",
|
|
56
|
+
"break character",
|
|
57
|
+
"act as"
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export async function detectInjection(userInput: string) {
|
|
61
|
+
return INJECTION_PATTERNS.some(p =>
|
|
62
|
+
userInput.toLowerCase().includes(p)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
export async function analyzeIntentStructured(
|
|
68
|
+
input: string,
|
|
69
|
+
intents: Record<string, string>,
|
|
70
|
+
threshold: number
|
|
71
|
+
) {
|
|
72
|
+
const extractor = await ModelSingleton.getExtractor();
|
|
73
|
+
|
|
74
|
+
let bestIntent = '';
|
|
75
|
+
let bestScore = 0;
|
|
76
|
+
|
|
77
|
+
for (const [key, desc] of Object.entries(intents)) {
|
|
78
|
+
const output = await extractor(
|
|
79
|
+
[`intent: ${desc}`, `intent: ${input}`],
|
|
80
|
+
{ pooling: 'mean', normalize: true }
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const vectors = output.tolist() as number[][];
|
|
84
|
+
const score = cos_sim(vectors[0], vectors[1]);
|
|
85
|
+
|
|
86
|
+
const finalScore =
|
|
87
|
+
typeof score === 'number' ? score : (score as any).data[0];
|
|
88
|
+
|
|
89
|
+
if (finalScore > bestScore) {
|
|
90
|
+
bestScore = finalScore;
|
|
91
|
+
bestIntent = key;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
intent: bestIntent,
|
|
97
|
+
score: bestScore,
|
|
98
|
+
allowed: bestScore >= threshold
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { analyzeIntentStructured, detectInjection } from '../intent/intentAnalyzer.js';
|
|
2
|
+
import { detectPII } from '../pii/detector.js';
|
|
3
|
+
import { PiiTokenizer } from '../pii/tokenizer.js';
|
|
4
|
+
|
|
5
|
+
export function guard(config: any) {
|
|
6
|
+
return async (req: any, next: any) => {
|
|
7
|
+
const input =
|
|
8
|
+
req.prompt ||
|
|
9
|
+
req.messages?.[req.messages.length - 1]?.content?.[0]?.text ||
|
|
10
|
+
"";
|
|
11
|
+
|
|
12
|
+
// -------------------------
|
|
13
|
+
// 1. INTENT ANALYSIS
|
|
14
|
+
// -------------------------
|
|
15
|
+
const isInjection = await detectInjection(input);
|
|
16
|
+
|
|
17
|
+
if (isInjection) {
|
|
18
|
+
console.warn(`[Intent Guard] Prompt injection pattern detected in input`);
|
|
19
|
+
return block("Prompt injection detected", {
|
|
20
|
+
reason: "pattern_match"
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(`[Intent Guard] Analyzing intent for user's input`);
|
|
25
|
+
|
|
26
|
+
const intentResult = await analyzeIntentStructured(
|
|
27
|
+
input,
|
|
28
|
+
config.intent.semantic.intents,
|
|
29
|
+
config.intent.semantic.threshold
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
console.log(`[Intent Guard] Detected intent: ${intentResult.intent} (score: ${intentResult.score.toFixed(2)})`);
|
|
33
|
+
|
|
34
|
+
if (!intentResult.allowed) {
|
|
35
|
+
console.warn(`[Intent Guard] Intent "${intentResult.intent}" not allowed ${intentResult.allowed}`);
|
|
36
|
+
return block("Intent not allowed", {
|
|
37
|
+
intent: intentResult.intent,
|
|
38
|
+
score: intentResult.score
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// -------------------------
|
|
43
|
+
// 2. PII DETECTION + MASKING
|
|
44
|
+
// -------------------------
|
|
45
|
+
const piiMatches = await detectPII(input);
|
|
46
|
+
console.log(`[PII Guard] Detected PII: ${piiMatches.length} matches found`);
|
|
47
|
+
const tokenizer = new PiiTokenizer(); // <-- SINGLE INSTANCE
|
|
48
|
+
|
|
49
|
+
const piiResult = tokenizer.mask(input, piiMatches);
|
|
50
|
+
|
|
51
|
+
console.log(`[PII Guard] Masked PII: ${piiResult.piiTypes.length} types found`);
|
|
52
|
+
|
|
53
|
+
// Attach tokenizer so response can unmask
|
|
54
|
+
req.metadata = {
|
|
55
|
+
...req.metadata,
|
|
56
|
+
piiTokenizer: tokenizer,
|
|
57
|
+
intent: intentResult.intent,
|
|
58
|
+
score: intentResult.score,
|
|
59
|
+
piiDetected: piiMatches.length > 0,
|
|
60
|
+
piiTypes: piiResult.piiTypes,
|
|
61
|
+
maskedInput: piiResult.maskedText
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Replace input
|
|
65
|
+
req.prompt = piiResult.maskedText;
|
|
66
|
+
req.messages = [
|
|
67
|
+
{
|
|
68
|
+
role: 'user',
|
|
69
|
+
content: [{ text: piiResult.maskedText }],
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
// -------------------------
|
|
74
|
+
// 3. LLM CALL
|
|
75
|
+
// -------------------------
|
|
76
|
+
const res = await next(req);
|
|
77
|
+
|
|
78
|
+
// -------------------------
|
|
79
|
+
// 4. RESPONSE UNMASK
|
|
80
|
+
// -------------------------
|
|
81
|
+
console.log(`[PII Guard] Unmasking response if needed`);
|
|
82
|
+
|
|
83
|
+
if (tokenizer) {
|
|
84
|
+
/**
|
|
85
|
+
* RECURSIVE TRANSFORMER
|
|
86
|
+
* This will find every string in the Genkit response (no matter if it's in
|
|
87
|
+
* candidates, message, custom, or output) and unmask it.
|
|
88
|
+
*/
|
|
89
|
+
const transform = (obj: any): any => {
|
|
90
|
+
// 1. If it's a string, unmask it
|
|
91
|
+
if (typeof obj === 'string') {
|
|
92
|
+
return tokenizer.unmask(obj);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2. If it's an array, transform each element
|
|
96
|
+
if (Array.isArray(obj)) {
|
|
97
|
+
return obj.map(transform);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 3. If it's an object, transform each value
|
|
101
|
+
if (obj !== null && typeof obj === 'object') {
|
|
102
|
+
// Note: We iterate keys and mutate the object directly
|
|
103
|
+
// to ensure Genkit's internal references are updated.
|
|
104
|
+
for (const key of Object.keys(obj)) {
|
|
105
|
+
obj[key] = transform(obj[key]);
|
|
106
|
+
}
|
|
107
|
+
return obj;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 4. Return as-is for numbers/booleans/null
|
|
111
|
+
return obj;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// ----------------------------------------------------------------------
|
|
115
|
+
// 5. Transform the entire response object in-place to unmask all strings
|
|
116
|
+
// ----------------------------------------------------------------------
|
|
117
|
+
transform(res);
|
|
118
|
+
|
|
119
|
+
console.log("[PII Guard] Deep unmasking complete across all candidates and custom fields.");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------
|
|
123
|
+
// 6. Return the modified response with unmasked content
|
|
124
|
+
// ---------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
return res;
|
|
127
|
+
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
// Helper to create a blocked response
|
|
133
|
+
function block(message: string, metadata?: any) {
|
|
134
|
+
return {
|
|
135
|
+
finishReason: 'blocked',
|
|
136
|
+
output: {
|
|
137
|
+
type: "error",
|
|
138
|
+
status: "BLOCKED",
|
|
139
|
+
message
|
|
140
|
+
},
|
|
141
|
+
metadata
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ModelSingleton } from '../util/singleton.js';
|
|
2
|
+
|
|
3
|
+
const REGEX_RULES = [
|
|
4
|
+
// EMAIL (keep your existing one)
|
|
5
|
+
{ type: 'EMAIL', pattern: /\b[\w\.-]+@[\w\.-]+\.\w{2,}\b/gi },
|
|
6
|
+
|
|
7
|
+
// AU MOBILE (04xx xxx xxx or +61 4xx xxx xxx)
|
|
8
|
+
{ type: 'AU_MOBILE', pattern: /\b(?:\+?61|0)4\d{2}[-\s]?\d{3}[-\s]?\d{3}\b/g },
|
|
9
|
+
|
|
10
|
+
// AU LANDLINE (02, 03, 07, 08)
|
|
11
|
+
{ type: 'AU_LANDLINE', pattern: /\b(?:\+?61[-\s]?)?(?:2|3|7|8)\d{1}[-\s]?\d{4}[-\s]?\d{4}\b/g },
|
|
12
|
+
|
|
13
|
+
// MEDICARE NUMBER (10 digits, often grouped 4-5-1)
|
|
14
|
+
{ type: 'MEDICARE', pattern: /\b\d{4}[-\s]?\d{5}[-\s]?\d\b/g },
|
|
15
|
+
|
|
16
|
+
// TFN (9 digits)
|
|
17
|
+
{ type: 'TFN', pattern: /\b\d{3}[-\s]?\d{3}[-\s]?\d{3}\b/g },
|
|
18
|
+
|
|
19
|
+
// ABN (11 digits)
|
|
20
|
+
{ type: 'ABN', pattern: /\b\d{2}[-\s]?\d{3}[-\s]?\d{3}[-\s]?\d{3}\b/g },
|
|
21
|
+
|
|
22
|
+
// CREDIT CARD (keep your existing one if needed)
|
|
23
|
+
{ type: 'CREDIT_CARD', pattern: /\b(?:\d[ -]*?){13,16}\b/g }
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export async function detectPII(text: string) {
|
|
27
|
+
const ner = await ModelSingleton.getNER();
|
|
28
|
+
|
|
29
|
+
const results: { type: string; value: string }[] = [];
|
|
30
|
+
|
|
31
|
+
// ---- NER ----
|
|
32
|
+
const entities = await ner(text);
|
|
33
|
+
|
|
34
|
+
for (const e of entities) {
|
|
35
|
+
if (e.entity.includes('PER')) {
|
|
36
|
+
results.push({ type: 'NAME', value: e.word.replace('##', '') });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---- REGEX ----
|
|
41
|
+
for (const rule of REGEX_RULES) {
|
|
42
|
+
const matches = text.match(rule.pattern) || [];
|
|
43
|
+
matches.forEach(m => results.push({ type: rule.type, value: m }));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type PiiResult = {
|
|
2
|
+
maskedText: string;
|
|
3
|
+
pii: Record<string, string>;
|
|
4
|
+
piiTypes: string[];
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export class PiiTokenizer {
|
|
8
|
+
private vault = new Map<string, string>();
|
|
9
|
+
private counter = 0;
|
|
10
|
+
private piiTypes = new Set<string>();
|
|
11
|
+
|
|
12
|
+
private createToken(type: string) {
|
|
13
|
+
return `[[${type}_${this.counter++}]]`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
mask(text: string, matches: { type: string; value: string }[]): PiiResult {
|
|
17
|
+
let masked = text;
|
|
18
|
+
|
|
19
|
+
for (const match of matches) {
|
|
20
|
+
const token = this.createToken(match.type);
|
|
21
|
+
|
|
22
|
+
this.vault.set(token, match.value);
|
|
23
|
+
this.piiTypes.add(match.type.toLowerCase());
|
|
24
|
+
|
|
25
|
+
masked = masked.split(match.value).join(token);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
maskedText: masked,
|
|
30
|
+
pii: Object.fromEntries(this.vault),
|
|
31
|
+
piiTypes: Array.from(this.piiTypes)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
unmask(text: string): string {
|
|
36
|
+
let result = text;
|
|
37
|
+
this.vault.forEach((value, token) => {
|
|
38
|
+
result = result.split(token).join(value); // Global replacement
|
|
39
|
+
});
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getVault() {
|
|
44
|
+
return Object.fromEntries(this.vault);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { pipeline, env, type FeatureExtractionPipeline } from '@huggingface/transformers';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
env.allowRemoteModels = false;
|
|
9
|
+
env.localModelPath = path.join(__dirname, '../../models');
|
|
10
|
+
|
|
11
|
+
export class ModelSingleton {
|
|
12
|
+
private static extractor: any = null;
|
|
13
|
+
private static classifier: any = null;
|
|
14
|
+
|
|
15
|
+
static init() {
|
|
16
|
+
// Always resolve model path relative to the client app, not the library
|
|
17
|
+
const projectRoot = process.cwd();
|
|
18
|
+
const modelPath = path.join(projectRoot, "models");
|
|
19
|
+
|
|
20
|
+
// Ensure directory exists
|
|
21
|
+
if (!fs.existsSync(modelPath)) {
|
|
22
|
+
fs.mkdirSync(modelPath, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
env.cacheDir = modelPath;
|
|
26
|
+
env.localModelPath = modelPath;
|
|
27
|
+
|
|
28
|
+
// Allow remote download if missing
|
|
29
|
+
env.allowRemoteModels = true;
|
|
30
|
+
|
|
31
|
+
console.log("[Guard] Using model directory:", modelPath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
static async getExtractor() {
|
|
36
|
+
if (!this.extractor) {
|
|
37
|
+
this.init();
|
|
38
|
+
this.extractor = await (pipeline as any)('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
|
|
39
|
+
}
|
|
40
|
+
return this.extractor;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static async getNER() {
|
|
44
|
+
if (!this.classifier) {
|
|
45
|
+
this.init();
|
|
46
|
+
// Token classification model for identifying sensitive entities
|
|
47
|
+
this.classifier = await (pipeline as any)('token-classification', 'Xenova/bert-base-NER');
|
|
48
|
+
}
|
|
49
|
+
return this.classifier;
|
|
50
|
+
}
|
|
51
|
+
}
|