shroud-privacy 2.2.16 → 2.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -80,6 +80,15 @@ Shroud does not guarantee compliance — regex-based detection has limitations (
80
80
 
81
81
  > **Requires OpenClaw 2026.3.22 or later.**
82
82
 
83
+ ### OpenClaw support policy
84
+
85
+ - **Formal minimum supported version:** `2026.3.24` (from `openclaw.plugin.json` `minOpenClawVersion`).
86
+ - **Release validation matrix (this release):**
87
+ - **Baseline:** `2026.3.28` (includes WhatsApp E2E path)
88
+ - **Latest-at-release:** `2026.4.5` (Slack E2E path)
89
+ - **Latest caveat:** on OpenClaw builds where WhatsApp provisioning via `channels add` is unsupported, latest-focused compat runs skip WhatsApp E2E and validate Slack E2E.
90
+ - **Source of truth for current matrix:** `docs/ci-current-state.md` and `CHANGELOG.md`.
91
+
83
92
  ---
84
93
 
85
94
  ## Install
@@ -9,6 +9,7 @@
9
9
  * 10. Confidence decay by frequency (common words lose confidence)
10
10
  */
11
11
  import { Category } from "../types.js";
12
+ import { isDocExample } from "./regex.js";
12
13
  /**
13
14
  * Single-pass multi-string scanner using a combined regex.
14
15
  * Replaces per-string indexOf loops with one regex alternation pass — O(M)
@@ -28,6 +29,10 @@ function scanMultiplePatterns(text, values, covered, category, confidence, detec
28
29
  const val = m[0];
29
30
  const key = `${pos}:${pos + val.length}`;
30
31
  if (!covered.has(key)) {
32
+ if (isDocExample(val, category)) {
33
+ covered.add(key);
34
+ continue;
35
+ }
31
36
  covered.add(key);
32
37
  results.push({
33
38
  value: val,
@@ -330,6 +335,9 @@ export class ContextDetector {
330
335
  e.category === Category.HOSTNAME)) {
331
336
  // Only learn values that look like identifiers (not too short, not common words)
332
337
  if (e.value.length >= 3 && !COMMON_WORDS.has(e.value.toLowerCase())) {
338
+ if (isDocExample(e.value, e.category)) {
339
+ continue;
340
+ }
333
341
  this._learnedEntities.set(e.value, e.category);
334
342
  }
335
343
  }
@@ -172,6 +172,17 @@ export function isDocExample(value, category) {
172
172
  case Category.HOSTNAME:
173
173
  if (DOC_HOSTNAMES.has(value) || DOC_HOSTNAMES.has(value.toUpperCase()))
174
174
  return true;
175
+ {
176
+ const lower = value.toLowerCase();
177
+ for (const d of DOC_DOMAINS) {
178
+ if (lower === d || lower.endsWith(`.${d}`))
179
+ return true;
180
+ }
181
+ for (const d of PUBLIC_DOMAINS) {
182
+ if (lower === d || lower.endsWith(`.${d}`))
183
+ return true;
184
+ }
185
+ }
175
186
  for (const pfx of DOC_HOSTNAME_PREFIXES) {
176
187
  if (value.toUpperCase().startsWith(pfx))
177
188
  return true;
package/dist/hooks.js CHANGED
@@ -179,7 +179,7 @@ export function registerHooks(api, obfuscator) {
179
179
  // cannot deobfuscate CGNAT surrogates in outbound channel messages.
180
180
  function stripSlackLinksForHook(text) {
181
181
  text = text.replace(/<mailto:[^|>]+\|([^>]*)>/g, "$1");
182
- text = text.replace(/<https?:\/\/[^|>]+\|([^>]*)>/g, "$1");
182
+ text = text.replace(/<(https?:\/\/[^|>]+)\|[^>]*>/g, "$1");
183
183
  text = text.replace(/<(https?:\/\/[^>]+)>/g, "$1");
184
184
  return text;
185
185
  }
@@ -225,12 +225,23 @@ export function registerHooks(api, obfuscator) {
225
225
  // All hook closures must use the shared obfuscator, not the local parameter.
226
226
  // OpenClaw loads the plugin multiple times; only one instance has the mappings.
227
227
  const ob = () => getSharedObfuscator(obfuscator);
228
+ const sessionScope = globalThis;
228
229
  const config = ob().config;
229
230
  const auditActive = config.auditEnabled || config.verboseLogging;
230
231
  // -----------------------------------------------------------------------
231
232
  // 1. before_prompt_build (async): obfuscate user prompt
232
233
  // -----------------------------------------------------------------------
233
- api.on("before_prompt_build", async (event) => {
234
+ api.on("before_prompt_build", async (event, ctx) => {
235
+ // Scope mapping and learned-entity state to the OpenClaw session key.
236
+ // The obfuscator instance is shared globally across plugin instances, so
237
+ // without this reset independent sessions can contaminate each other.
238
+ if (typeof ctx?.sessionKey === "string" && ctx.sessionKey) {
239
+ const prevSessionKey = sessionScope.__shroudLastSessionKey;
240
+ if (typeof prevSessionKey === "string" && prevSessionKey !== ctx.sessionKey) {
241
+ ob().reset();
242
+ }
243
+ sessionScope.__shroudLastSessionKey = ctx.sessionKey;
244
+ }
234
245
  // Reset tool depth at the start of each turn — tool calls from the
235
246
  // previous turn are complete, so the counter should not carry over.
236
247
  if (ob().toolDepth > 0) {
@@ -880,7 +891,7 @@ export function registerHooks(api, obfuscator) {
880
891
  // leaking real PII to the LLM.
881
892
  function stripSlackLinks(text) {
882
893
  text = text.replace(/<mailto:[^|>]+\|([^>]*)>/g, "$1");
883
- text = text.replace(/<https?:\/\/[^|>]+\|([^>]*)>/g, "$1");
894
+ text = text.replace(/<(https?:\/\/[^|>]+)\|[^>]*>/g, "$1");
884
895
  text = text.replace(/<(https?:\/\/[^>]+)>/g, "$1");
885
896
  return text;
886
897
  }
@@ -101,8 +101,8 @@ function compressIPv6(addr) {
101
101
  function stripSlackLinks(text) {
102
102
  // <mailto:X|DISPLAY> → DISPLAY (email links)
103
103
  text = text.replace(/<mailto:[^|>]+\|([^>]*)>/g, "$1");
104
- // <URL|DISPLAY> → DISPLAY (URL links with display text)
105
- text = text.replace(/<https?:\/\/[^|>]+\|([^>]*)>/g, "$1");
104
+ // <URL|DISPLAY> → URL (preserve real URL for passthrough checks)
105
+ text = text.replace(/<(https?:\/\/[^|>]+)\|[^>]*>/g, "$1");
106
106
  // <URL> → URL (bare URL links, no display text)
107
107
  text = text.replace(/<(https?:\/\/[^>]+)>/g, "$1");
108
108
  return text;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "shroud-privacy",
3
3
  "name": "Shroud",
4
- "version": "2.2.12",
4
+ "version": "2.2.17",
5
5
  "description": "Privacy obfuscation with deterministic fake values and deobfuscation — PII never reaches the LLM, tool calls still work",
6
6
  "configSchema": {
7
7
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shroud-privacy",
3
- "version": "2.2.16",
3
+ "version": "2.2.18",
4
4
  "description": "Privacy and infrastructure protection for AI agents — detects sensitive data (PII, network topology, credentials, OT/SCADA) and replaces with deterministic fakes before anything reaches the LLM.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,7 +23,6 @@
23
23
  "test:unit": "vitest run",
24
24
  "test:integration": "node tests/harness/run.mjs",
25
25
  "test:docker": "bash compat/run-e2e.sh latest",
26
- "test:docker:legacy": "bash compat/run-compat.sh latest",
27
26
  "test:all": "npm run test:unit && npm run test:integration && npm run test:docker",
28
27
  "test:watch": "vitest",
29
28
  "lint": "tsc --noEmit",