shroud-privacy 2.2.11 → 2.2.13

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.
Files changed (40) hide show
  1. package/README.md +19 -10
  2. package/dist/hooks.js +246 -14
  3. package/openclaw.plugin.json +1 -1
  4. package/package.json +3 -2
  5. package/dist/agent-session.d.ts +0 -259
  6. package/dist/agent-session.js +0 -693
  7. package/dist/compliance.d.ts +0 -44
  8. package/dist/compliance.js +0 -76
  9. package/dist/dashboard.d.ts +0 -42
  10. package/dist/dashboard.js +0 -1558
  11. package/dist/detectors/injection-multilingual.d.ts +0 -27
  12. package/dist/detectors/injection-multilingual.js +0 -399
  13. package/dist/detectors/injection-signatures.d.ts +0 -26
  14. package/dist/detectors/injection-signatures.js +0 -508
  15. package/dist/detectors/injection.d.ts +0 -56
  16. package/dist/detectors/injection.js +0 -269
  17. package/dist/detectors/tool-guard.d.ts +0 -27
  18. package/dist/detectors/tool-guard.js +0 -418
  19. package/dist/event-grader.d.ts +0 -97
  20. package/dist/event-grader.js +0 -214
  21. package/dist/exposure.d.ts +0 -29
  22. package/dist/exposure.js +0 -72
  23. package/dist/policy.d.ts +0 -99
  24. package/dist/policy.js +0 -212
  25. package/dist/profiler-analysis.d.ts +0 -35
  26. package/dist/profiler-analysis.js +0 -230
  27. package/dist/profiler-store.d.ts +0 -33
  28. package/dist/profiler-store.js +0 -118
  29. package/dist/profiler-types.d.ts +0 -128
  30. package/dist/profiler-types.js +0 -16
  31. package/dist/profiler.d.ts +0 -81
  32. package/dist/profiler.js +0 -392
  33. package/dist/security-event.d.ts +0 -70
  34. package/dist/security-event.js +0 -80
  35. package/dist/siem.d.ts +0 -49
  36. package/dist/siem.js +0 -113
  37. package/dist/signature-loader.d.ts +0 -113
  38. package/dist/signature-loader.js +0 -255
  39. package/dist/store-file.d.ts +0 -26
  40. package/dist/store-file.js +0 -79
@@ -1,269 +0,0 @@
1
- /**
2
- * Prompt injection signature detector.
3
- *
4
- * Scans text for known injection patterns on the request side
5
- * (direct/indirect injection) and response side (exfiltration confirmation).
6
- *
7
- * This is a standalone scanner — it does NOT integrate into the obfuscation
8
- * detector chain. The obfuscation pipeline remains untouched.
9
- */
10
- import { ThreatClass, } from "../security-event.js";
11
- import { REQUEST_SIGNATURES, RESPONSE_SIGNATURES, } from "./injection-signatures.js";
12
- import { MULTILINGUAL_REQUEST_SIGNATURES, } from "./injection-multilingual.js";
13
- /** Injection keywords checked inside decoded Base64 payloads. */
14
- const BASE64_INJECTION_KEYWORDS = [
15
- "ignore",
16
- "instructions",
17
- "system prompt",
18
- "override",
19
- "jailbreak",
20
- "disregard",
21
- "forget",
22
- "bypass",
23
- "unrestricted",
24
- "developer mode",
25
- "you are now",
26
- "act as",
27
- ];
28
- const SEVERITY_RANK = {
29
- low: 0,
30
- medium: 1,
31
- high: 2,
32
- };
33
- /**
34
- * Check if a text span is inside quotation marks, backticks, or code blocks.
35
- * Used to reduce severity of injection patterns that are being discussed/quoted
36
- * rather than used as actual attacks.
37
- */
38
- function isInsideQuotes(text, matchStart, matchEnd) {
39
- // Look backwards from matchStart for an unmatched opening quote
40
- const before = text.slice(Math.max(0, matchStart - 200), matchStart);
41
- const after = text.slice(matchEnd, Math.min(text.length, matchEnd + 200));
42
- // Check for surrounding double quotes
43
- if (before.includes('"') && after.includes('"')) {
44
- const lastQuoteBefore = before.lastIndexOf('"');
45
- const quotesBefore = before.slice(lastQuoteBefore).split('"').length - 1;
46
- if (quotesBefore % 2 === 1)
47
- return true; // odd number = inside quotes
48
- }
49
- // Check for surrounding single quotes
50
- if (before.includes("'") && after.includes("'")) {
51
- const lastQuoteBefore = before.lastIndexOf("'");
52
- const quotesBefore = before.slice(lastQuoteBefore).split("'").length - 1;
53
- if (quotesBefore % 2 === 1)
54
- return true;
55
- }
56
- // Check for backticks (inline code)
57
- if (before.includes("`") && after.includes("`"))
58
- return true;
59
- // Check for parenthetical context: (DAN), (XSS), etc.
60
- // The match might include the closing paren, so check if before ends with (
61
- // or the matched text itself starts right after a (
62
- if (before.endsWith("("))
63
- return true;
64
- if (before.trimEnd().endsWith("("))
65
- return true;
66
- // Check for 'like "X"' or 'such as "X"' or 'phrases like "X"' patterns
67
- // These indicate the text is being discussed, not executed
68
- const discussionPatterns = /(?:like|such\s+as|example|e\.g\.|called|known\s+as|termed|phrase|pattern|classified|documented|described|first\s+appeared)/i;
69
- if (discussionPatterns.test(before.slice(-100)))
70
- return true;
71
- return false;
72
- }
73
- /**
74
- * Strip token smuggling characters — invisible Unicode chars that attackers
75
- * insert between tokens to break regex matching.
76
- *
77
- * Strips: zero-width space (U+200B), zero-width non-joiner (U+200C),
78
- * zero-width joiner (U+200D), byte order mark (U+FEFF), word joiner (U+2060),
79
- * soft hyphen (U+00AD), Mongolian vowel separator (U+180E),
80
- * and all variation selectors (U+FE00-FE0F).
81
- */
82
- function stripTokenSmuggling(text) {
83
- return text.replace(/[\u200B\u200C\u200D\uFEFF\u2060\u00AD\u180E\uFE00-\uFE0F]/g, "");
84
- }
85
- /**
86
- * Standalone injection scanner. Not a BaseDetector — runs parallel to
87
- * the obfuscation pipeline, never touches entity replacement.
88
- */
89
- export class InjectionDetector {
90
- _config;
91
- _requestSigs;
92
- _responseSigs;
93
- _externalRequestSigs = [];
94
- _externalResponseSigs = [];
95
- constructor(config) {
96
- this._config = config;
97
- const minRank = SEVERITY_RANK[config.minSeverity];
98
- // Pre-filter signatures by severity and disabled list
99
- // Include multilingual patterns alongside English ones
100
- this._requestSigs = [...REQUEST_SIGNATURES, ...MULTILINGUAL_REQUEST_SIGNATURES].filter((s) => !config.disabledSignatures.has(s.id) &&
101
- SEVERITY_RANK[s.severity] >= minRank);
102
- this._responseSigs = RESPONSE_SIGNATURES.filter((s) => !config.disabledSignatures.has(s.id) &&
103
- SEVERITY_RANK[s.severity] >= minRank);
104
- }
105
- /** Hot-load external signatures. Atomic swap — takes effect on next scan. */
106
- loadExternalSignatures(sigs) {
107
- const minRank = SEVERITY_RANK[this._config.minSeverity];
108
- const asSigDef = sigs.map(s => ({
109
- id: s.id,
110
- threatClass: s.threatClass,
111
- pattern: s.pattern,
112
- severity: s.severity,
113
- description: s.description,
114
- direction: s.direction,
115
- })).filter(s => !this._config.disabledSignatures.has(s.id) &&
116
- SEVERITY_RANK[s.severity] >= minRank);
117
- this._externalRequestSigs = asSigDef.filter(s => s.direction === "request" || s.direction === "both");
118
- this._externalResponseSigs = asSigDef.filter(s => s.direction === "response" || s.direction === "both");
119
- }
120
- /** Get count of loaded external signatures. */
121
- getExternalSignatureCount() {
122
- return this._externalRequestSigs.length + this._externalResponseSigs.length;
123
- }
124
- /** Scan request/outbound text for injection patterns. */
125
- scanRequest(text) {
126
- if (this._config.action === "off")
127
- return [];
128
- // Token smuggling defence: strip invisible characters then re-scan.
129
- // Attackers insert zero-width spaces, soft hyphens, word joiners etc.
130
- // between tokens to break regex matching: "ig​nore pre​vious in​structions"
131
- const cleaned = stripTokenSmuggling(text);
132
- const smuggled = cleaned !== text;
133
- const allRequestSigs = this._externalRequestSigs.length > 0
134
- ? [...this._requestSigs, ...this._externalRequestSigs]
135
- : this._requestSigs;
136
- const events = this._scanPatterns(text, allRequestSigs, "request");
137
- // If smuggling chars were present, also scan the cleaned version
138
- // to catch patterns that were broken by invisible chars
139
- if (smuggled) {
140
- const cleanedEvents = this._scanPatterns(cleaned, this._requestSigs, "request");
141
- // Add cleaned-text detections that weren't found in original
142
- const existingIds = new Set(events.map(e => e.signatureId));
143
- for (const evt of cleanedEvents) {
144
- if (!existingIds.has(evt.signatureId)) {
145
- evt.description = `[token-smuggling stripped] ${evt.description}`;
146
- events.push(evt);
147
- }
148
- }
149
- // Also flag the smuggling itself
150
- const action = this._config.action === "block" ? "blocked" : "flagged";
151
- events.push({
152
- timestamp: Date.now(),
153
- eventType: "injection_detected",
154
- direction: "request",
155
- threatClass: ThreatClass.ENCODING_BYPASS,
156
- signatureId: "eb_token_smuggling",
157
- severity: "medium",
158
- matchedText: `[${text.length - cleaned.length} invisible chars stripped]`,
159
- matchStart: 0,
160
- matchEnd: text.length,
161
- textLength: text.length,
162
- action,
163
- description: `Token smuggling: ${text.length - cleaned.length} invisible characters removed, revealing injection patterns`,
164
- });
165
- }
166
- // Base64 decode-and-rescan
167
- events.push(...this._scanEncodedPayloads(text, "request"));
168
- return events;
169
- }
170
- /** Scan response/inbound text for exfiltration patterns. */
171
- scanResponse(text) {
172
- if (this._config.action === "off" || !this._config.scanResponses)
173
- return [];
174
- const allResponseSigs = this._externalResponseSigs.length > 0
175
- ? [...this._responseSigs, ...this._externalResponseSigs]
176
- : this._responseSigs;
177
- return this._scanPatterns(text, allResponseSigs, "response");
178
- }
179
- _scanPatterns(text, signatures, direction) {
180
- const events = [];
181
- const action = this._config.action === "block" ? "blocked" : "flagged";
182
- for (const sig of signatures) {
183
- // Reset lastIndex for global regexes
184
- sig.pattern.lastIndex = 0;
185
- let match;
186
- while ((match = sig.pattern.exec(text)) !== null) {
187
- // Skip OpenClaw system context: "System: [2026-03-30 10:11:29 GMT+2]"
188
- // This is structural metadata, not a conversation mockup injection.
189
- if (sig.id === "cm_role_markers") {
190
- const after = text.slice(match.index + match[0].length - 1, match.index + match[0].length + 30);
191
- if (/^\[\d{4}-\d{2}-\d{2}/.test(after))
192
- continue;
193
- }
194
- // Context-aware severity reduction: if the match is inside
195
- // quotation marks or backticks, it's likely being discussed/quoted
196
- // rather than used as an attack. Reduce severity to "low".
197
- let severity = sig.severity;
198
- if (isInsideQuotes(text, match.index, match.index + match[0].length)) {
199
- severity = "low";
200
- }
201
- events.push({
202
- timestamp: Date.now(),
203
- eventType: "injection_detected",
204
- direction,
205
- threatClass: sig.threatClass,
206
- signatureId: sig.id,
207
- severity,
208
- matchedText: match[0].slice(0, 200),
209
- matchStart: match.index,
210
- matchEnd: match.index + match[0].length,
211
- textLength: text.length,
212
- action,
213
- description: severity !== sig.severity
214
- ? `[quoted context] ${sig.description}`
215
- : sig.description,
216
- });
217
- // For non-global patterns, break after first match
218
- if (!sig.pattern.global)
219
- break;
220
- }
221
- }
222
- return events;
223
- }
224
- /**
225
- * Detect Base64-encoded injection payloads.
226
- *
227
- * Finds Base64-looking blocks (40+ chars), decodes them (sync via Buffer),
228
- * and checks if the decoded text contains injection keywords.
229
- */
230
- _scanEncodedPayloads(text, direction) {
231
- const events = [];
232
- const action = this._config.action === "block" ? "blocked" : "flagged";
233
- const b64Re = /[A-Za-z0-9+/]{40,}={0,2}/g;
234
- let match;
235
- while ((match = b64Re.exec(text)) !== null) {
236
- try {
237
- const decoded = Buffer.from(match[0], "base64").toString("utf-8");
238
- // Check if decoded text is mostly printable ASCII (heuristic for valid text)
239
- const printableRatio = decoded.replace(/[^\x20-\x7E]/g, "").length / decoded.length;
240
- if (printableRatio < 0.7)
241
- continue;
242
- const lower = decoded.toLowerCase();
243
- for (const keyword of BASE64_INJECTION_KEYWORDS) {
244
- if (lower.includes(keyword)) {
245
- events.push({
246
- timestamp: Date.now(),
247
- eventType: "injection_detected",
248
- direction,
249
- threatClass: ThreatClass.ENCODING_BYPASS,
250
- signatureId: "eb_base64_injection",
251
- severity: "high",
252
- matchedText: `[base64→"${decoded.slice(0, 100)}"]`,
253
- matchStart: match.index,
254
- matchEnd: match.index + match[0].length,
255
- textLength: text.length,
256
- action,
257
- description: `Encoding bypass: Base64-encoded text contains injection keyword "${keyword}"`,
258
- });
259
- break; // One event per base64 block
260
- }
261
- }
262
- }
263
- catch {
264
- // Invalid base64 — skip
265
- }
266
- }
267
- return events;
268
- }
269
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Tool call guard — detects dangerous tool invocations.
3
- *
4
- * Scans tool names and parameters for destructive, exfiltration,
5
- * or privilege escalation patterns. Runs in the before_tool_call hook
6
- * where it can BLOCK the call before execution.
7
- *
8
- * This is the "exec shutdown" detector — catches:
9
- * - Destructive commands (rm -rf, drop table, shutdown, format, kill -9)
10
- * - Exfiltration (curl/wget to external, scp, netcat listeners)
11
- * - Credential access (cat /etc/shadow, reading .ssh keys)
12
- * - Reverse shells (bash -i >& /dev/tcp, nc -e, python -c import socket)
13
- * - Privilege escalation (sudo, su, chmod 777, chown root)
14
- * - Crypto mining (xmrig, minerd, coin patterns)
15
- */
16
- import { SecurityEvent } from "../security-event.js";
17
- /**
18
- * Scan a tool call for dangerous patterns.
19
- *
20
- * @param toolName - The tool being called (e.g. "exec", "write", "code_execution")
21
- * @param params - The tool parameters (will be serialized to JSON for scanning)
22
- * @returns Array of security events. Check `.block` on the pattern for block recommendation.
23
- */
24
- export declare function scanToolCall(toolName: string, params: unknown): {
25
- events: SecurityEvent[];
26
- shouldBlock: boolean;
27
- };