@solongate/proxy 0.81.86 → 0.81.87

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.
@@ -6536,7 +6536,7 @@ import { resolve, join, dirname, isAbsolute } from "node:path";
6536
6536
  import { homedir } from "node:os";
6537
6537
  import { gunzipSync } from "node:zlib";
6538
6538
  import { createHash } from "node:crypto";
6539
- var HOOK_VERSION = 42;
6539
+ var HOOK_VERSION = 43;
6540
6540
  function localLogsOnly(security) {
6541
6541
  if (security && typeof security === "object") {
6542
6542
  const l = security.localLogs;
@@ -7180,6 +7180,29 @@ function dlpGlobToRe(glob) {
7180
7180
  }
7181
7181
  return new RegExp(re, "i");
7182
7182
  }
7183
+ function dlpViews(text) {
7184
+ const views = [text];
7185
+ try {
7186
+ const dequoted = text.replace(/[`'"\\]/g, "");
7187
+ if (dequoted !== text)
7188
+ views.push(dequoted);
7189
+ const src = dequoted !== text ? text + "\n" + dequoted : text;
7190
+ const toks = src.match(/[A-Za-z0-9+/]{16,}={0,2}/g) || [];
7191
+ let decoded = "";
7192
+ for (const t of toks.slice(0, 60)) {
7193
+ try {
7194
+ const d = Buffer.from(t, "base64").toString("latin1");
7195
+ if (/[ -~]{8,}/.test(d))
7196
+ decoded += d + "\n";
7197
+ } catch {
7198
+ }
7199
+ }
7200
+ if (decoded)
7201
+ views.push(decoded);
7202
+ } catch {
7203
+ }
7204
+ return views;
7205
+ }
7183
7206
  function dlpScan(args, cfg) {
7184
7207
  if (!cfg)
7185
7208
  return null;
@@ -7189,14 +7212,16 @@ function dlpScan(args, cfg) {
7189
7212
  } catch {
7190
7213
  return null;
7191
7214
  }
7215
+ const views = dlpViews(text);
7192
7216
  const allow = new Set(Array.isArray(cfg.patterns) ? cfg.patterns : []);
7193
7217
  for (const p of DLP_PATTERNS) {
7194
- if (allow.has(p.name) && p.re.test(text))
7218
+ if (allow.has(p.name) && views.some((v) => p.re.test(v)))
7195
7219
  return p.name;
7196
7220
  }
7197
7221
  for (const c of Array.isArray(cfg.custom) ? cfg.custom : []) {
7198
7222
  try {
7199
- if (dlpGlobToRe(c.re).test(text))
7223
+ const re = dlpGlobToRe(c.re);
7224
+ if (views.some((v) => re.test(v)))
7200
7225
  return c.name || "custom pattern";
7201
7226
  } catch {
7202
7227
  }
package/hooks/guard.mjs CHANGED
@@ -32,7 +32,7 @@ import { createHash } from 'node:crypto';
32
32
  // the installed hook self-updates when the cloud version is higher (see
33
33
  // maybeSelfUpdate). This is what makes guard fixes propagate without a manual
34
34
  // reinstall — the same trust model as the OPA WASM this hook already runs.
35
- const HOOK_VERSION = 42;
35
+ const HOOK_VERSION = 43;
36
36
 
37
37
  // True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
38
38
  // nothing is sent to the cloud audit log.
@@ -900,18 +900,45 @@ function dlpGlobToRe(glob) {
900
900
  }
901
901
  return new RegExp(re, 'i');
902
902
  }
903
+ // De-obfuscated VIEWS of the scanned text, so an agent can't smuggle a secret
904
+ // past the literal patterns by SPLITTING it (printf "AKIA""3XZ9…") or ENCODING it
905
+ // (echo <base64> | base64 -d). We scan every view. Not exhaustive — pattern DLP
906
+ // can never be — but it closes the two obvious bypasses.
907
+ function dlpViews(text) {
908
+ const views = [text];
909
+ try {
910
+ // 1) Drop shell quotes + backslashes so a split secret collapses back to a
911
+ // contiguous run: "AKIA""3XZ9…" / 'AKIA'\''…' / AKIA\3XZ9 → AKIA3XZ9…
912
+ const dequoted = text.replace(/[`'"\\]/g, '');
913
+ if (dequoted !== text) views.push(dequoted);
914
+ // 2) Decode base64-looking tokens (from both the raw and the dequoted view —
915
+ // the payload itself may be split too) and scan the decoded bytes.
916
+ const src = dequoted !== text ? text + '\n' + dequoted : text;
917
+ const toks = src.match(/[A-Za-z0-9+/]{16,}={0,2}/g) || [];
918
+ let decoded = '';
919
+ for (const t of toks.slice(0, 60)) {
920
+ try {
921
+ const d = Buffer.from(t, 'base64').toString('latin1');
922
+ if (/[ -~]{8,}/.test(d)) decoded += d + '\n'; // keep printable decodes only
923
+ } catch { /* not base64 */ }
924
+ }
925
+ if (decoded) views.push(decoded);
926
+ } catch { /* fall back to the raw view */ }
927
+ return views;
928
+ }
903
929
  // Scan args against the enabled built-in patterns + any user custom patterns.
904
930
  // `cfg` = { patterns: string[], custom: {name,re}[] }.
905
931
  function dlpScan(args, cfg) {
906
932
  if (!cfg) return null;
907
933
  let text = '';
908
934
  try { text = JSON.stringify(args || {}); } catch { return null; }
935
+ const views = dlpViews(text);
909
936
  const allow = new Set(Array.isArray(cfg.patterns) ? cfg.patterns : []);
910
937
  for (const p of DLP_PATTERNS) {
911
- if (allow.has(p.name) && p.re.test(text)) return p.name;
938
+ if (allow.has(p.name) && views.some((v) => p.re.test(v))) return p.name;
912
939
  }
913
940
  for (const c of Array.isArray(cfg.custom) ? cfg.custom : []) {
914
- try { if (dlpGlobToRe(c.re).test(text)) return c.name || 'custom pattern'; } catch { /* skip invalid */ }
941
+ try { const re = dlpGlobToRe(c.re); if (views.some((v) => re.test(v))) return c.name || 'custom pattern'; } catch { /* skip invalid */ }
915
942
  }
916
943
  return null;
917
944
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.86",
3
+ "version": "0.81.87",
4
4
  "description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
5
5
  "type": "module",
6
6
  "bin": {