palaryn 0.5.11 → 0.6.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/dist/src/billing/plan-enforcer.d.ts.map +1 -1
- package/dist/src/billing/plan-enforcer.js +0 -2
- package/dist/src/billing/plan-enforcer.js.map +1 -1
- package/dist/src/dlp/circuit-breaker.d.ts +44 -0
- package/dist/src/dlp/circuit-breaker.d.ts.map +1 -0
- package/dist/src/dlp/circuit-breaker.js +69 -0
- package/dist/src/dlp/circuit-breaker.js.map +1 -0
- package/dist/src/dlp/deberta-backend.d.ts +2 -0
- package/dist/src/dlp/deberta-backend.d.ts.map +1 -1
- package/dist/src/dlp/deberta-backend.js +21 -3
- package/dist/src/dlp/deberta-backend.js.map +1 -1
- package/dist/src/dlp/index.d.ts +2 -0
- package/dist/src/dlp/index.d.ts.map +1 -1
- package/dist/src/dlp/index.js +5 -1
- package/dist/src/dlp/index.js.map +1 -1
- package/dist/src/dlp/llm-classifier.d.ts +2 -0
- package/dist/src/dlp/llm-classifier.d.ts.map +1 -1
- package/dist/src/dlp/llm-classifier.js +8 -0
- package/dist/src/dlp/llm-classifier.js.map +1 -1
- package/dist/src/dlp/multipart-extractor.d.ts +20 -0
- package/dist/src/dlp/multipart-extractor.d.ts.map +1 -0
- package/dist/src/dlp/multipart-extractor.js +60 -0
- package/dist/src/dlp/multipart-extractor.js.map +1 -0
- package/dist/src/dlp/navigation-instruction-backend.d.ts +6 -0
- package/dist/src/dlp/navigation-instruction-backend.d.ts.map +1 -0
- package/dist/src/dlp/navigation-instruction-backend.js +286 -0
- package/dist/src/dlp/navigation-instruction-backend.js.map +1 -0
- package/dist/src/dlp/nemo-backend.d.ts +2 -0
- package/dist/src/dlp/nemo-backend.d.ts.map +1 -1
- package/dist/src/dlp/nemo-backend.js +8 -0
- package/dist/src/dlp/nemo-backend.js.map +1 -1
- package/dist/src/dlp/prompt-injection-patterns.d.ts.map +1 -1
- package/dist/src/dlp/prompt-injection-patterns.js +36 -0
- package/dist/src/dlp/prompt-injection-patterns.js.map +1 -1
- package/dist/src/dlp/text-normalizer.d.ts +2 -15
- package/dist/src/dlp/text-normalizer.d.ts.map +1 -1
- package/dist/src/dlp/text-normalizer.js +34 -7
- package/dist/src/dlp/text-normalizer.js.map +1 -1
- package/dist/src/dlp/tool-patterns.d.ts +10 -0
- package/dist/src/dlp/tool-patterns.d.ts.map +1 -1
- package/dist/src/dlp/tool-patterns.js +40 -1
- package/dist/src/dlp/tool-patterns.js.map +1 -1
- package/dist/src/executor/filesystem-executor.d.ts +5 -5
- package/dist/src/executor/filesystem-executor.d.ts.map +1 -1
- package/dist/src/executor/filesystem-executor.js +43 -0
- package/dist/src/executor/filesystem-executor.js.map +1 -1
- package/dist/src/metrics/collector.d.ts +5 -0
- package/dist/src/metrics/collector.d.ts.map +1 -1
- package/dist/src/metrics/collector.js +14 -0
- package/dist/src/metrics/collector.js.map +1 -1
- package/dist/src/policy/engine.d.ts.map +1 -1
- package/dist/src/policy/engine.js +39 -3
- package/dist/src/policy/engine.js.map +1 -1
- package/dist/src/policy/opa-engine.d.ts.map +1 -1
- package/dist/src/policy/opa-engine.js +2 -1
- package/dist/src/policy/opa-engine.js.map +1 -1
- package/dist/src/server/app.d.ts.map +1 -1
- package/dist/src/server/app.js +17 -9
- package/dist/src/server/app.js.map +1 -1
- package/dist/src/server/gateway.d.ts +4 -0
- package/dist/src/server/gateway.d.ts.map +1 -1
- package/dist/src/server/gateway.js +146 -4
- package/dist/src/server/gateway.js.map +1 -1
- package/dist/src/types/config.d.ts +9 -0
- package/dist/src/types/config.d.ts.map +1 -1
- package/dist/src/types/policy.d.ts +4 -0
- package/dist/src/types/policy.d.ts.map +1 -1
- package/dist/src/types/tool-call.d.ts +4 -0
- package/dist/src/types/tool-call.d.ts.map +1 -1
- package/dist/tests/integration/navigation-chain.test.d.ts +9 -0
- package/dist/tests/integration/navigation-chain.test.d.ts.map +1 -0
- package/dist/tests/integration/navigation-chain.test.js +474 -0
- package/dist/tests/integration/navigation-chain.test.js.map +1 -0
- package/dist/tests/unit/adversarial-pipeline.test.js +173 -15
- package/dist/tests/unit/adversarial-pipeline.test.js.map +1 -1
- package/dist/tests/unit/cli.test.js +3 -7
- package/dist/tests/unit/cli.test.js.map +1 -1
- package/dist/tests/unit/filesystem-executor.test.js +88 -0
- package/dist/tests/unit/filesystem-executor.test.js.map +1 -1
- package/dist/tests/unit/multipart-extractor.test.d.ts +2 -0
- package/dist/tests/unit/multipart-extractor.test.d.ts.map +1 -0
- package/dist/tests/unit/multipart-extractor.test.js +118 -0
- package/dist/tests/unit/multipart-extractor.test.js.map +1 -0
- package/dist/tests/unit/navigation-instruction-backend.test.d.ts +8 -0
- package/dist/tests/unit/navigation-instruction-backend.test.d.ts.map +1 -0
- package/dist/tests/unit/navigation-instruction-backend.test.js +561 -0
- package/dist/tests/unit/navigation-instruction-backend.test.js.map +1 -0
- package/dist/tests/unit/policy-engine.test.js +314 -1
- package/dist/tests/unit/policy-engine.test.js.map +1 -1
- package/dist/tests/unit/prompt-injection-backend.test.js +1 -1
- package/dist/tests/unit/prompt-injection-backend.test.js.map +1 -1
- package/package.json +1 -1
- package/policy-packs/default.yaml +76 -0
- package/src/billing/plan-enforcer.ts +0 -2
- package/src/dlp/circuit-breaker.ts +83 -0
- package/src/dlp/deberta-backend.ts +21 -3
- package/src/dlp/index.ts +2 -0
- package/src/dlp/llm-classifier.ts +10 -0
- package/src/dlp/multipart-extractor.ts +66 -0
- package/src/dlp/navigation-instruction-backend.ts +309 -0
- package/src/dlp/nemo-backend.ts +10 -0
- package/src/dlp/prompt-injection-patterns.ts +37 -0
- package/src/dlp/text-normalizer.ts +36 -7
- package/src/dlp/tool-patterns.ts +40 -0
- package/src/executor/filesystem-executor.ts +51 -0
- package/src/metrics/collector.ts +17 -0
- package/src/policy/engine.ts +39 -3
- package/src/policy/opa-engine.ts +2 -1
- package/src/server/app.ts +19 -10
- package/src/server/gateway.ts +155 -4
- package/src/types/config.ts +9 -0
- package/src/types/policy.ts +5 -0
- package/src/types/tool-call.ts +4 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { DLPBackend, DLPDetection } from './interfaces';
|
|
2
|
+
import { DLPPattern } from './patterns';
|
|
3
|
+
import { DLPSeverity } from '../types/tool-result';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DLP backend that detects navigation instructions embedded in response content.
|
|
7
|
+
*
|
|
8
|
+
* Addresses the "nested page" attack vector: an agent opens allowed page ABC,
|
|
9
|
+
* which contains instructions (HTML redirects, JS navigation, explicit text
|
|
10
|
+
* commands) to open page XYZ. Even though XYZ may pass policy independently,
|
|
11
|
+
* the agent was *tricked* into requesting it by embedded content.
|
|
12
|
+
*
|
|
13
|
+
* Detection categories (all patterns use /gi flags):
|
|
14
|
+
*
|
|
15
|
+
* 1. HTML meta-refresh redirects (high)
|
|
16
|
+
* 2. JavaScript navigation assignments (high)
|
|
17
|
+
* 3. JavaScript navigation function calls (high)
|
|
18
|
+
* 4. HTML embedding tags (iframe, frame, object, embed) (medium)
|
|
19
|
+
* 5. HTML base tag hijacking (high)
|
|
20
|
+
* 6. Auto-submitting forms (high)
|
|
21
|
+
* 7. JS fetch / XHR / sendBeacon (medium)
|
|
22
|
+
* 8. WebSocket / EventSource connections (medium)
|
|
23
|
+
* 9. Service Worker / dynamic import (high)
|
|
24
|
+
* 10. Data URI with HTML content (high)
|
|
25
|
+
* 11. Explicit textual navigation commands (medium)
|
|
26
|
+
*
|
|
27
|
+
* Pattern names are prefixed with `navigation_instruction_` for namespacing.
|
|
28
|
+
*
|
|
29
|
+
* **False-positive mitigation**: Patterns are designed to match *executable*
|
|
30
|
+
* navigation directives, not passive hyperlinks (<a href>), CSS url() for
|
|
31
|
+
* stylesheets, or <link rel="stylesheet">. Documentation containing code
|
|
32
|
+
* examples may trigger medium-severity detections, which can be filtered
|
|
33
|
+
* by the `navigation_instruction_action` config setting.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Category 1: HTML meta-refresh redirects (high)
|
|
38
|
+
// Matches: <meta http-equiv="refresh" content="0;url=https://evil.com">
|
|
39
|
+
// Also handles: content="5; URL=..." with optional spaces and quoting
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
const META_REFRESH: DLPPattern = {
|
|
42
|
+
name: 'navigation_instruction_meta_refresh',
|
|
43
|
+
pattern: /<meta\s[^>]*http-equiv\s*=\s*["']?\s*refresh\s*["']?\s[^>]*content\s*=\s*["']?\s*\d+\s*[;,]\s*url\s*=\s*[^"'\s>]+/gi,
|
|
44
|
+
severity: 'high',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Category 2: JavaScript navigation assignments (high)
|
|
49
|
+
// Matches: window.location = "...", window.location.href = "...",
|
|
50
|
+
// document.location = "...", document.location.href = "...",
|
|
51
|
+
// location.href = "...", self.location = "..."
|
|
52
|
+
// Captures both assignment (=) and property set patterns.
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
const JS_LOCATION_ASSIGN: DLPPattern = {
|
|
55
|
+
name: 'navigation_instruction_js_redirect',
|
|
56
|
+
pattern: /(?:window|document|self|top|parent)?\s*\.?\s*location\s*(?:\.(?:href|replace|assign))?\s*=\s*["'`][^"'`]{1,2000}["'`]/gi,
|
|
57
|
+
severity: 'high',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Category 3: JavaScript navigation function calls (high)
|
|
62
|
+
// Matches: window.location.replace("..."), window.location.assign("..."),
|
|
63
|
+
// window.open("..."), location.replace("...")
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
const JS_LOCATION_FUNC: DLPPattern = {
|
|
66
|
+
name: 'navigation_instruction_js_navigate_call',
|
|
67
|
+
pattern: /(?:window|document|self|top|parent)?\s*\.?\s*(?:location\s*\.\s*(?:replace|assign)|open)\s*\(\s*["'`][^"'`]{1,2000}["'`]/gi,
|
|
68
|
+
severity: 'high',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Category 4: HTML embedding tags (medium)
|
|
73
|
+
// Matches: <iframe src="...">, <frame src="...">, <object data="...">,
|
|
74
|
+
// <embed src="...">
|
|
75
|
+
// These load external content automatically and can be used to redirect
|
|
76
|
+
// or exfiltrate data. Severity is medium because iframes are common in
|
|
77
|
+
// legitimate HTML responses.
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
const HTML_IFRAME: DLPPattern = {
|
|
80
|
+
name: 'navigation_instruction_html_embed',
|
|
81
|
+
pattern: /<(?:iframe|frame)\s[^>]*src\s*=\s*["']?\s*https?:\/\/[^"'\s>]+/gi,
|
|
82
|
+
severity: 'medium',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const HTML_OBJECT_EMBED: DLPPattern = {
|
|
86
|
+
name: 'navigation_instruction_html_object',
|
|
87
|
+
pattern: /<(?:object\s[^>]*data|embed\s[^>]*src)\s*=\s*["']?\s*https?:\/\/[^"'\s>]+/gi,
|
|
88
|
+
severity: 'medium',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Category 5: HTML base tag hijacking (high)
|
|
93
|
+
// Matches: <base href="https://attacker.com">
|
|
94
|
+
// Silently rewrites all relative URLs in the page to point to attacker domain.
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
const HTML_BASE_HIJACK: DLPPattern = {
|
|
97
|
+
name: 'navigation_instruction_base_hijack',
|
|
98
|
+
pattern: /<base\s[^>]*href\s*=\s*["']?\s*https?:\/\/[^"'\s>]+/gi,
|
|
99
|
+
severity: 'high',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Category 6: Auto-submitting forms (high)
|
|
104
|
+
// Matches: <form ... with a nearby .submit() call — common CSRF/redirect pattern.
|
|
105
|
+
// Two-part detection: form with action + submit() in close proximity.
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
const AUTO_SUBMIT_FORM: DLPPattern = {
|
|
108
|
+
name: 'navigation_instruction_auto_form_submit',
|
|
109
|
+
pattern: /<form\s[^>]*action\s*=\s*["']?https?:\/\/[^"'\s>]+[^]*?\.submit\s*\(\s*\)/gi,
|
|
110
|
+
severity: 'high',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Category 7: JS fetch / XHR / sendBeacon (medium)
|
|
115
|
+
// Matches: fetch("https://..."), new XMLHttpRequest() ... .open("GET","https://..."),
|
|
116
|
+
// navigator.sendBeacon("https://...")
|
|
117
|
+
// Medium severity: these are very common in legitimate web pages.
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
const JS_FETCH: DLPPattern = {
|
|
120
|
+
name: 'navigation_instruction_js_fetch',
|
|
121
|
+
pattern: /(?:fetch|navigator\s*\.\s*sendBeacon)\s*\(\s*["'`]https?:\/\/[^"'`]{1,2000}["'`]/gi,
|
|
122
|
+
severity: 'medium',
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Category 8: WebSocket / EventSource connections (medium)
|
|
127
|
+
// Matches: new WebSocket("wss://..."), new EventSource("https://...")
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
const JS_WEBSOCKET: DLPPattern = {
|
|
130
|
+
name: 'navigation_instruction_websocket',
|
|
131
|
+
pattern: /new\s+(?:WebSocket|EventSource)\s*\(\s*["'`](?:wss?|https?):\/\/[^"'`]{1,2000}["'`]/gi,
|
|
132
|
+
severity: 'medium',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Category 9: Service Worker / dynamic import (high)
|
|
137
|
+
// Matches: navigator.serviceWorker.register("..."), import("https://...")
|
|
138
|
+
// High severity: service workers can intercept all subsequent requests.
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
const JS_SERVICE_WORKER: DLPPattern = {
|
|
141
|
+
name: 'navigation_instruction_service_worker',
|
|
142
|
+
pattern: /navigator\s*\.\s*serviceWorker\s*\.\s*register\s*\(\s*["'`][^"'`]{1,2000}["'`]/gi,
|
|
143
|
+
severity: 'high',
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const JS_DYNAMIC_IMPORT: DLPPattern = {
|
|
147
|
+
name: 'navigation_instruction_dynamic_import',
|
|
148
|
+
pattern: /import\s*\(\s*["'`]https?:\/\/[^"'`]{1,2000}["'`]\s*\)/gi,
|
|
149
|
+
severity: 'high',
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Category 10: Data URI with active content (high)
|
|
154
|
+
// Matches: data:text/html, data:image/svg+xml, data:application/javascript,
|
|
155
|
+
// data:text/javascript, data:text/xml, data:application/xml
|
|
156
|
+
// All can contain executable content (scripts, redirects, event handlers).
|
|
157
|
+
// Note: The text-normalizer already decodes base64 data URIs, but this
|
|
158
|
+
// pattern catches them at the structural level before normalization.
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
const DATA_URI_ACTIVE: DLPPattern = {
|
|
161
|
+
name: 'navigation_instruction_data_uri',
|
|
162
|
+
pattern: /data:(?:text\/html|image\/svg\+xml|application\/javascript|text\/javascript|text\/xml|application\/xml)[^"'\s)>]{0,2000}/gi,
|
|
163
|
+
severity: 'high',
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Backward-compatible alias for tests referencing the old name
|
|
167
|
+
const DATA_URI_HTML = DATA_URI_ACTIVE;
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Category 11: Explicit textual navigation commands (medium)
|
|
171
|
+
// Matches: "visit https://...", "navigate to https://...", "open https://...",
|
|
172
|
+
// "go to https://...", "browse to https://...", "fetch https://..."
|
|
173
|
+
// Medium severity: may appear in legitimate documentation or instructions.
|
|
174
|
+
// Only matches when followed by an actual URL to reduce false positives.
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
const EXPLICIT_NAVIGATE_TEXT: DLPPattern = {
|
|
177
|
+
name: 'navigation_instruction_explicit_text',
|
|
178
|
+
pattern: /(?:visit|navigate\s+to|open|go\s+to|browse\s+to|fetch|request|call|access|load)\s+(?:the\s+(?:url|page|link|endpoint|site)\s+)?(?:at\s+)?["']?https?:\/\/[^\s"'<>]{4,500}/gi,
|
|
179
|
+
severity: 'medium',
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Category 12: SVG with embedded script (high)
|
|
184
|
+
// SVG files are XML and can contain <script> tags with full JS execution.
|
|
185
|
+
// This is one of the most dangerous image-based attack vectors because
|
|
186
|
+
// SVGs are often treated as "images" but carry executable code.
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
const SVG_SCRIPT: DLPPattern = {
|
|
189
|
+
name: 'navigation_instruction_svg_script',
|
|
190
|
+
pattern: /<svg\b[^>]*>[^]*?<script\b[^>]*>[^]*?<\/script>/gi,
|
|
191
|
+
severity: 'high',
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Category 13: SVG event handlers (high)
|
|
196
|
+
// SVG elements support JavaScript event handlers like onload, onerror.
|
|
197
|
+
// <svg onload="malicious()"> executes immediately when the SVG loads.
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
const SVG_EVENT_HANDLER: DLPPattern = {
|
|
200
|
+
name: 'navigation_instruction_svg_event_handler',
|
|
201
|
+
pattern: /<svg\b[^>]*\son(?:load|error|click|mouseover|focus|blur)\s*=\s*["'][^"']*["']/gi,
|
|
202
|
+
severity: 'high',
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// Category 14: SVG foreignObject (high)
|
|
207
|
+
// <foreignObject> embeds arbitrary HTML/XHTML inside SVG, including forms,
|
|
208
|
+
// scripts, iframes — essentially a full HTML injection point within an "image".
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
const SVG_FOREIGN_OBJECT: DLPPattern = {
|
|
211
|
+
name: 'navigation_instruction_svg_foreign_object',
|
|
212
|
+
pattern: /<foreignObject\b[^>]*>[^]*?<\/foreignObject>/gi,
|
|
213
|
+
severity: 'high',
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Category 15: HTML event handlers in any tag (high)
|
|
218
|
+
// Catches event handlers (onload, onerror, etc.) on any HTML element,
|
|
219
|
+
// not just SVG. These auto-execute JavaScript without user interaction.
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
const HTML_EVENT_HANDLER: DLPPattern = {
|
|
222
|
+
name: 'navigation_instruction_html_event_handler',
|
|
223
|
+
pattern: /<[a-z][a-z0-9]*\s[^>]*?on(?:load|error|click|mouseover|focus|blur|mouseenter|submit|change|input)\s*=\s*["'][^"']*["']/gi,
|
|
224
|
+
severity: 'high',
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// All patterns collected for iteration
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
const NAVIGATION_PATTERNS: DLPPattern[] = [
|
|
231
|
+
META_REFRESH,
|
|
232
|
+
JS_LOCATION_ASSIGN,
|
|
233
|
+
JS_LOCATION_FUNC,
|
|
234
|
+
HTML_IFRAME,
|
|
235
|
+
HTML_OBJECT_EMBED,
|
|
236
|
+
HTML_BASE_HIJACK,
|
|
237
|
+
AUTO_SUBMIT_FORM,
|
|
238
|
+
JS_FETCH,
|
|
239
|
+
JS_WEBSOCKET,
|
|
240
|
+
JS_SERVICE_WORKER,
|
|
241
|
+
JS_DYNAMIC_IMPORT,
|
|
242
|
+
DATA_URI_ACTIVE,
|
|
243
|
+
SVG_SCRIPT,
|
|
244
|
+
SVG_EVENT_HANDLER,
|
|
245
|
+
SVG_FOREIGN_OBJECT,
|
|
246
|
+
HTML_EVENT_HANDLER,
|
|
247
|
+
EXPLICIT_NAVIGATE_TEXT,
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Extract target URLs from a navigation instruction match.
|
|
252
|
+
* Returns the first URL found in the matched string, or null.
|
|
253
|
+
*/
|
|
254
|
+
function extractTargetUrl(match: string): string | null {
|
|
255
|
+
// Try to extract URL from common patterns
|
|
256
|
+
const urlPatterns = [
|
|
257
|
+
/url\s*=\s*["']?([^"'\s;>]+)/i, // meta-refresh url=...
|
|
258
|
+
/=\s*["'`](https?:\/\/[^"'`]+)["'`]/i, // assignment = "https://..."
|
|
259
|
+
/\(\s*["'`](https?:\/\/[^"'`]+)["'`]/i, // function call("https://...")
|
|
260
|
+
/(?:src|data|href|action)\s*=\s*["']?(https?:\/\/[^"'\s>]+)/i, // HTML attributes
|
|
261
|
+
/(https?:\/\/[^\s"'<>]+)/i, // bare URL fallback
|
|
262
|
+
/((?:wss?):\/\/[^\s"'<>]+)/i, // WebSocket URL
|
|
263
|
+
/(data:text\/html[^\s"'<>)]*)/i, // data URI
|
|
264
|
+
];
|
|
265
|
+
|
|
266
|
+
for (const urlPat of urlPatterns) {
|
|
267
|
+
const m = urlPat.exec(match);
|
|
268
|
+
if (m && m[1]) {
|
|
269
|
+
return m[1];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export class NavigationInstructionBackend implements DLPBackend {
|
|
276
|
+
readonly name = 'navigation_instruction';
|
|
277
|
+
|
|
278
|
+
scanString(value: string): DLPDetection[] {
|
|
279
|
+
const detections: DLPDetection[] = [];
|
|
280
|
+
|
|
281
|
+
// Skip very short strings — no meaningful navigation instructions possible
|
|
282
|
+
if (value.length < 15) return detections;
|
|
283
|
+
|
|
284
|
+
for (const pat of NAVIGATION_PATTERNS) {
|
|
285
|
+
pat.pattern.lastIndex = 0;
|
|
286
|
+
let m: RegExpExecArray | null;
|
|
287
|
+
while ((m = pat.pattern.exec(value)) !== null) {
|
|
288
|
+
const targetUrl = extractTargetUrl(m[0]);
|
|
289
|
+
detections.push({
|
|
290
|
+
pattern_name: pat.name,
|
|
291
|
+
severity: pat.severity as DLPSeverity,
|
|
292
|
+
match: m[0].slice(0, 500), // Truncate long matches for logging
|
|
293
|
+
start: m.index,
|
|
294
|
+
end: m.index + m[0].length,
|
|
295
|
+
...(targetUrl ? { target_url: targetUrl } : {}),
|
|
296
|
+
} as DLPDetection & { target_url?: string });
|
|
297
|
+
|
|
298
|
+
// Guard against zero-length matches causing infinite loops
|
|
299
|
+
if (m[0].length === 0) {
|
|
300
|
+
pat.pattern.lastIndex++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Reset lastIndex for stateful /g regex reuse
|
|
304
|
+
pat.pattern.lastIndex = 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return detections;
|
|
308
|
+
}
|
|
309
|
+
}
|
package/src/dlp/nemo-backend.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from 'child_process';
|
|
2
2
|
import { DLPBackend, DLPDetection } from './interfaces';
|
|
3
3
|
import { DLPSeverity } from '../types/tool-result';
|
|
4
|
+
import { CircuitBreaker } from './circuit-breaker';
|
|
4
5
|
|
|
5
6
|
export interface NemoGuardrailsConfig {
|
|
6
7
|
/** NeMo Guardrails API URL (e.g. 'http://nemo:8000'). */
|
|
@@ -25,15 +26,22 @@ export class NemoGuardrailsBackend implements DLPBackend {
|
|
|
25
26
|
|
|
26
27
|
private readonly apiUrl: string;
|
|
27
28
|
private readonly timeoutMs: number;
|
|
29
|
+
readonly circuitBreaker: CircuitBreaker;
|
|
28
30
|
|
|
29
31
|
constructor(config: NemoGuardrailsConfig) {
|
|
30
32
|
this.apiUrl = config.api_url.replace(/\/+$/, '');
|
|
31
33
|
this.timeoutMs = config.timeout_ms ?? 5000;
|
|
34
|
+
this.circuitBreaker = new CircuitBreaker({ name: 'nemo', failureThreshold: 5, resetTimeoutMs: 30_000 });
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
scanString(value: string): DLPDetection[] {
|
|
35
38
|
if (!value || value.length < 5) return [];
|
|
36
39
|
|
|
40
|
+
if (!this.circuitBreaker.allowRequest()) {
|
|
41
|
+
console.warn(`[NemoGuardrailsBackend] circuit OPEN — skipping external call`);
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
try {
|
|
38
46
|
const payload = JSON.stringify({
|
|
39
47
|
messages: [{ role: 'user', content: value }],
|
|
@@ -53,8 +61,10 @@ export class NemoGuardrailsBackend implements DLPBackend {
|
|
|
53
61
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
54
62
|
});
|
|
55
63
|
|
|
64
|
+
this.circuitBreaker.recordSuccess();
|
|
56
65
|
return this.parseResponse(stdout, value);
|
|
57
66
|
} catch (err: unknown) {
|
|
67
|
+
this.circuitBreaker.recordFailure();
|
|
58
68
|
const message = err instanceof Error ? err.message : String(err);
|
|
59
69
|
console.warn(`[NemoGuardrailsBackend] scan failed: ${message}`);
|
|
60
70
|
return [];
|
|
@@ -401,6 +401,43 @@ export const PROMPT_INJECTION_PATTERNS: DLPPattern[] = [
|
|
|
401
401
|
pattern: /(?:you\s+have\s+)?no\s+(?:rules|limits|limitations|boundaries|restrictions|ethical\s+guidelines|safety\s+(?:measures|protocols|guidelines))/gi,
|
|
402
402
|
severity: 'medium',
|
|
403
403
|
},
|
|
404
|
+
|
|
405
|
+
// -----------------------------------------------------------------------
|
|
406
|
+
// Category 18: Policy/config self-modification (high)
|
|
407
|
+
// Detects instructions to modify the agent's own policy, config, or
|
|
408
|
+
// governance files — the most dangerous form of prompt injection as it
|
|
409
|
+
// can disable all security controls.
|
|
410
|
+
// -----------------------------------------------------------------------
|
|
411
|
+
{
|
|
412
|
+
name: 'prompt_injection_modify_policy',
|
|
413
|
+
pattern: /(?:modify|change|update|edit|overwrite|replace|rewrite|alter)\s+(?:the\s+|your\s+)?(?:policy|policies|policy[\s_-]*(?:pack|file|yaml|config|rules?))/gi,
|
|
414
|
+
severity: 'high',
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: 'prompt_injection_write_policy_file',
|
|
418
|
+
pattern: /(?:write|save|create|output|put|dump)\s+(?:this\s+|the\s+following\s+)?(?:to|into|in)\s+(?:the\s+)?(?:policy|config|yaml|yml|configuration)\s*(?:file|pack)?/gi,
|
|
419
|
+
severity: 'high',
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: 'prompt_injection_disable_security',
|
|
423
|
+
pattern: /(?:disable|turn\s+off|deactivate|remove|bypass|skip)\s+(?:all\s+)?(?:the\s+)?(?:security|DLP|firewall|policy|protection|enforcement|validation|rate[\s_-]*limit|budget[\s_-]*check|approval)/gi,
|
|
424
|
+
severity: 'high',
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: 'prompt_injection_allow_all_policy',
|
|
428
|
+
pattern: /(?:set|change|make|switch)\s+(?:the\s+)?(?:default\s+)?(?:policy|effect|rule)\s+(?:to\s+)?(?:allow[\s_-]*all|permissive|allow\s+everything)/gi,
|
|
429
|
+
severity: 'high',
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: 'prompt_injection_policy_pack_path',
|
|
433
|
+
pattern: /policy[\s_-]*packs?\/[^\s"']+\.ya?ml/gi,
|
|
434
|
+
severity: 'high',
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: 'prompt_injection_remove_rules',
|
|
438
|
+
pattern: /(?:remove|delete|clear|empty|wipe)\s+(?:all\s+)?(?:the\s+)?(?:policy\s+)?(?:rules?|restrictions?|blocklist|denylist|deny\s+rules?)/gi,
|
|
439
|
+
severity: 'high',
|
|
440
|
+
},
|
|
404
441
|
];
|
|
405
442
|
|
|
406
443
|
// ---------------------------------------------------------------------------
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* into canonical ASCII text before pattern matching.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
10
|
+
const punycode = require('punycode/');
|
|
11
|
+
|
|
9
12
|
// ---------------------------------------------------------------------------
|
|
10
13
|
// Zero-width character stripping
|
|
11
14
|
// ---------------------------------------------------------------------------
|
|
@@ -13,9 +16,10 @@
|
|
|
13
16
|
/** Regex matching zero-width and invisible Unicode characters.
|
|
14
17
|
* Comprehensive list covering: zero-width spaces/joiners, soft hyphen, BOM,
|
|
15
18
|
* directional marks/isolates, word joiners, invisible operators,
|
|
16
|
-
* combining grapheme joiner, Arabic letter mark,
|
|
19
|
+
* combining grapheme joiner, Arabic letter mark, deprecated formatting chars,
|
|
20
|
+
* and Unicode Tags block (U+E0001-U+E007F, deprecated language tags).
|
|
17
21
|
*/
|
|
18
|
-
export const ZERO_WIDTH_REGEX = /[\u200B\u200C\u200D\u00AD\uFEFF\u200E\u200F\u034F\u061C\u180E\u2060\u2061\u2062\u2063\u2064\u2066\u2067\u2068\u2069\u206A\u206B\u206C\u206D\u206E\u206F]/
|
|
22
|
+
export const ZERO_WIDTH_REGEX = /[\u200B\u200C\u200D\u00AD\uFEFF\u200E\u200F\u034F\u061C\u180E\u2060\u2061\u2062\u2063\u2064\u2066\u2067\u2068\u2069\u206A\u206B\u206C\u206D\u206E\u206F\u{E0001}-\u{E007F}]/gu;
|
|
19
23
|
|
|
20
24
|
// ---------------------------------------------------------------------------
|
|
21
25
|
// Homoglyph map (visually similar characters -> ASCII equivalents)
|
|
@@ -263,23 +267,39 @@ function stripEmojisBetweenLetters(input: string): string {
|
|
|
263
267
|
* Normalize text for bypass-resistant pattern matching.
|
|
264
268
|
*
|
|
265
269
|
* Applies transformations in order:
|
|
266
|
-
* 1. Strip zero-width / invisible Unicode characters
|
|
270
|
+
* 1. Strip zero-width / invisible Unicode characters (incl. Unicode Tags block)
|
|
267
271
|
* 2. Unicode NFKC normalization (collapses fullwidth, ligatures, etc.)
|
|
268
272
|
* 3. Decode HTML entities (named + numeric)
|
|
269
|
-
* 4. Decode URL percent-encoding
|
|
273
|
+
* 4. Decode URL percent-encoding (multi-pass, up to 3 iterations)
|
|
274
|
+
* 4.5. Decode Punycode domains (xn-- labels -> Unicode -> homoglyph collapse)
|
|
270
275
|
* 5. Collapse homoglyphs (Cyrillic/Greek lookalikes -> ASCII)
|
|
271
276
|
* 6. Collapse repeated whitespace to single space
|
|
272
277
|
*
|
|
273
278
|
* @param input - The raw text to normalize.
|
|
274
279
|
* @returns The normalized text suitable for pattern matching.
|
|
275
280
|
*/
|
|
281
|
+
/**
|
|
282
|
+
* Decode Punycode (xn--) domain labels to Unicode.
|
|
283
|
+
* Enables homoglyph normalization to collapse Punycode-encoded lookalike domains.
|
|
284
|
+
*/
|
|
285
|
+
function decodePunycodeDomains(input: string): string {
|
|
286
|
+
// Match xn-- labels in domain-like contexts (dot-separated labels)
|
|
287
|
+
return input.replace(/\bxn--[a-z0-9-]+(?:\.[a-z0-9-]+)*/gi, (domain) => {
|
|
288
|
+
try {
|
|
289
|
+
return punycode.toUnicode(domain);
|
|
290
|
+
} catch {
|
|
291
|
+
return domain; // Leave as-is if decode fails
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
276
296
|
export function normalizeText(input: string): string {
|
|
277
297
|
// Early exit for very short strings
|
|
278
298
|
if (input.length === 0) return input;
|
|
279
299
|
|
|
280
300
|
let text = input;
|
|
281
301
|
|
|
282
|
-
// 1. Strip zero-width characters
|
|
302
|
+
// 1. Strip zero-width characters (including Unicode Tags block U+E0001-E007F)
|
|
283
303
|
text = text.replace(ZERO_WIDTH_REGEX, '');
|
|
284
304
|
|
|
285
305
|
// 2. NFKC normalization (fullwidth -> ASCII, ligatures -> components, etc.)
|
|
@@ -296,8 +316,17 @@ export function normalizeText(input: string): string {
|
|
|
296
316
|
// 3. Decode HTML entities
|
|
297
317
|
text = decodeHTMLEntities(text);
|
|
298
318
|
|
|
299
|
-
// 4. Decode URL percent-encoding
|
|
300
|
-
|
|
319
|
+
// 4. Decode URL percent-encoding (multi-pass for layered encoding like %2569 -> %69 -> i)
|
|
320
|
+
let prevText: string;
|
|
321
|
+
let passes = 0;
|
|
322
|
+
do {
|
|
323
|
+
prevText = text;
|
|
324
|
+
text = decodeURLEncoding(text);
|
|
325
|
+
passes++;
|
|
326
|
+
} while (text !== prevText && passes < 3);
|
|
327
|
+
|
|
328
|
+
// 4.5 Decode Punycode domains (xn-- labels -> Unicode for homoglyph matching)
|
|
329
|
+
text = decodePunycodeDomains(text);
|
|
301
330
|
|
|
302
331
|
// 5. Collapse homoglyphs
|
|
303
332
|
text = text.replace(homoglyphRegex, (ch) => HOMOGLYPH_MAP[ch] || ch);
|
package/src/dlp/tool-patterns.ts
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
import { DLPPattern } from './patterns';
|
|
2
|
+
import { DLPBackend, DLPDetection } from './interfaces';
|
|
3
|
+
import { DLPSeverity } from '../types/tool-result';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DLP backend that scans for sensitive file paths in tool arguments.
|
|
7
|
+
* Detects references to policy packs, .env files, and governance config
|
|
8
|
+
* that could indicate a policy self-modification attack.
|
|
9
|
+
*/
|
|
10
|
+
export class SensitiveFileBackend implements DLPBackend {
|
|
11
|
+
readonly name = 'sensitive_file';
|
|
12
|
+
|
|
13
|
+
scanString(value: string): DLPDetection[] {
|
|
14
|
+
const detections: DLPDetection[] = [];
|
|
15
|
+
if (value.length < 4) return detections;
|
|
16
|
+
|
|
17
|
+
for (const pat of SENSITIVE_FILE_PATTERNS) {
|
|
18
|
+
pat.pattern.lastIndex = 0;
|
|
19
|
+
let m: RegExpExecArray | null;
|
|
20
|
+
while ((m = pat.pattern.exec(value)) !== null) {
|
|
21
|
+
detections.push({
|
|
22
|
+
pattern_name: pat.name,
|
|
23
|
+
severity: pat.severity as DLPSeverity,
|
|
24
|
+
match: m[0],
|
|
25
|
+
start: m.index,
|
|
26
|
+
end: m.index + m[0].length,
|
|
27
|
+
});
|
|
28
|
+
if (m[0].length === 0) {
|
|
29
|
+
pat.pattern.lastIndex++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
pat.pattern.lastIndex = 0;
|
|
33
|
+
}
|
|
34
|
+
return detections;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
2
37
|
|
|
3
38
|
// Shell injection patterns
|
|
4
39
|
export const SHELL_INJECTION_PATTERNS: DLPPattern[] = [
|
|
@@ -32,6 +67,11 @@ export const SENSITIVE_FILE_PATTERNS: DLPPattern[] = [
|
|
|
32
67
|
{ name: 'sensitive_file_gitconfig', pattern: /\.gitconfig/gi, severity: 'low' },
|
|
33
68
|
{ name: 'sensitive_file_shadow', pattern: /\/etc\/shadow/gi, severity: 'high' },
|
|
34
69
|
{ name: 'sensitive_file_shell_rc', pattern: /\.(?:bashrc|zshrc|profile|bash_history|zsh_history)/gi, severity: 'medium' },
|
|
70
|
+
// Palaryn governance / policy files — modification disables security controls
|
|
71
|
+
{ name: 'sensitive_file_policy_pack', pattern: /policy[\s_-]*packs?\/[^\s"']*\.ya?ml/gi, severity: 'high' },
|
|
72
|
+
{ name: 'sensitive_file_policy_yaml', pattern: /(?:^|[\\/])(?:policy|policies|rules)\.ya?ml/gi, severity: 'high' },
|
|
73
|
+
{ name: 'sensitive_file_env', pattern: /(?:^|[\\/\s"'])\.env(?:\.\w+)?(?=$|[\s"'\\/])/gi, severity: 'high' },
|
|
74
|
+
{ name: 'sensitive_file_palaryn_config', pattern: /palaryn[\s_-]*(?:config|settings)\.(?:ya?ml|json|toml)/gi, severity: 'high' },
|
|
35
75
|
];
|
|
36
76
|
|
|
37
77
|
// SQL injection patterns
|
|
@@ -10,6 +10,24 @@ import { FilesystemExecutorConfig } from '../types/config';
|
|
|
10
10
|
* Handles tool calls with tool name `file.*` (e.g., file.read, file.write).
|
|
11
11
|
* All paths are resolved relative to and contained within base_dir.
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* Hardcoded protected path patterns that CANNOT be written to or deleted.
|
|
15
|
+
* This is a defense-in-depth measure against policy self-modification attacks
|
|
16
|
+
* where a prompt injection instructs the agent to overwrite governance files.
|
|
17
|
+
* These paths are blocked regardless of config, allowlist, or policy settings.
|
|
18
|
+
*/
|
|
19
|
+
const PROTECTED_PATH_PATTERNS = [
|
|
20
|
+
/policy[\s_-]*packs?\//i,
|
|
21
|
+
/\.env(?:\.\w+)?$/i,
|
|
22
|
+
/palaryn[\s_-]*config\./i,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const PROTECTED_FILENAMES = [
|
|
26
|
+
'policy.yaml', 'policy.yml', 'policies.yaml', 'policies.yml',
|
|
27
|
+
'rules.yaml', 'rules.yml',
|
|
28
|
+
'.env', '.env.local', '.env.production', '.env.development',
|
|
29
|
+
];
|
|
30
|
+
|
|
13
31
|
export class FilesystemExecutor implements ToolExecutor {
|
|
14
32
|
private config: FilesystemExecutorConfig;
|
|
15
33
|
private resolvedBaseDir: string;
|
|
@@ -19,6 +37,29 @@ export class FilesystemExecutor implements ToolExecutor {
|
|
|
19
37
|
this.resolvedBaseDir = path.resolve(config.base_dir);
|
|
20
38
|
}
|
|
21
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Check if a file path targets a protected governance/config file.
|
|
42
|
+
* This is a hardcoded safeguard that cannot be disabled by configuration.
|
|
43
|
+
*/
|
|
44
|
+
private isProtectedPath(filePath: string): boolean {
|
|
45
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
46
|
+
const basename = path.basename(normalized).toLowerCase();
|
|
47
|
+
|
|
48
|
+
// Check against protected filenames
|
|
49
|
+
if (PROTECTED_FILENAMES.includes(basename)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check against protected path patterns
|
|
54
|
+
for (const pattern of PROTECTED_PATH_PATTERNS) {
|
|
55
|
+
if (pattern.test(normalized)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
22
63
|
async execute(toolCall: ToolCall): Promise<ToolOutput> {
|
|
23
64
|
const action = this.resolveAction(toolCall);
|
|
24
65
|
|
|
@@ -102,6 +143,11 @@ export class FilesystemExecutor implements ToolExecutor {
|
|
|
102
143
|
if (!filePath || typeof filePath !== 'string') {
|
|
103
144
|
throw new Error('Missing or invalid "path" argument for file.write');
|
|
104
145
|
}
|
|
146
|
+
|
|
147
|
+
// Hardcoded protection: block writes to governance/policy/config files
|
|
148
|
+
if (this.isProtectedPath(filePath)) {
|
|
149
|
+
throw new Error(`Write denied: "${filePath}" is a protected governance/config file and cannot be modified by agent tool calls`);
|
|
150
|
+
}
|
|
105
151
|
if (content === undefined || content === null) {
|
|
106
152
|
throw new Error('Missing "content" argument for file.write');
|
|
107
153
|
}
|
|
@@ -138,6 +184,11 @@ export class FilesystemExecutor implements ToolExecutor {
|
|
|
138
184
|
throw new Error('Missing or invalid "path" argument for file.delete');
|
|
139
185
|
}
|
|
140
186
|
|
|
187
|
+
// Hardcoded protection: block deleting governance/policy/config files
|
|
188
|
+
if (this.isProtectedPath(filePath)) {
|
|
189
|
+
throw new Error(`Delete denied: "${filePath}" is a protected governance/config file and cannot be deleted by agent tool calls`);
|
|
190
|
+
}
|
|
191
|
+
|
|
141
192
|
const resolved = this.resolveSafePath(filePath);
|
|
142
193
|
|
|
143
194
|
const stat = await fs.stat(resolved);
|
package/src/metrics/collector.ts
CHANGED
|
@@ -31,6 +31,7 @@ export class GatewayMetrics {
|
|
|
31
31
|
// Histograms
|
|
32
32
|
private requestDuration: promClient.Histogram;
|
|
33
33
|
private costPerRequest: promClient.Histogram;
|
|
34
|
+
private policyEvaluationSeconds: promClient.Histogram;
|
|
34
35
|
|
|
35
36
|
// Gauge
|
|
36
37
|
private activeApprovals: promClient.Gauge;
|
|
@@ -69,6 +70,15 @@ export class GatewayMetrics {
|
|
|
69
70
|
registers: [this.registry],
|
|
70
71
|
});
|
|
71
72
|
|
|
73
|
+
// palaryn_policy_evaluation_seconds
|
|
74
|
+
this.policyEvaluationSeconds = new promClient.Histogram({
|
|
75
|
+
name: 'palaryn_policy_evaluation_seconds',
|
|
76
|
+
help: 'Time spent evaluating policy rules',
|
|
77
|
+
labelNames: ['engine'],
|
|
78
|
+
buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0],
|
|
79
|
+
registers: [this.registry],
|
|
80
|
+
});
|
|
81
|
+
|
|
72
82
|
// palaryn_dlp_detections_total
|
|
73
83
|
this.dlpDetectionsTotal = new promClient.Counter({
|
|
74
84
|
name: 'palaryn_dlp_detections_total',
|
|
@@ -220,6 +230,13 @@ export class GatewayMetrics {
|
|
|
220
230
|
this.policyDecisionsTotal.inc({ decision, rule_id: ruleId });
|
|
221
231
|
}
|
|
222
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Record policy evaluation duration.
|
|
235
|
+
*/
|
|
236
|
+
recordPolicyEvaluation(engine: string, durationSeconds: number): void {
|
|
237
|
+
this.policyEvaluationSeconds.observe({ engine }, durationSeconds);
|
|
238
|
+
}
|
|
239
|
+
|
|
223
240
|
/**
|
|
224
241
|
* Record a DLP detection event.
|
|
225
242
|
*/
|