@solongate/proxy 0.54.0 → 0.56.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/index.js CHANGED
@@ -7017,14 +7017,36 @@ import { createServer, request as httpRequest } from "http";
7017
7017
  import { request as httpsRequest } from "https";
7018
7018
  import { spawn as spawn2 } from "child_process";
7019
7019
  import { URL as URL2 } from "url";
7020
- import { readFileSync as readFileSync6, existsSync as existsSync5 } from "fs";
7020
+ import { readFileSync as readFileSync6, existsSync as existsSync5, readdirSync, statSync } from "fs";
7021
7021
  import { resolve as resolve4 } from "path";
7022
7022
  import { homedir as homedir3 } from "os";
7023
+ function findCacheFile() {
7024
+ const dir = resolve4(homedir3(), ".solongate");
7025
+ const envSel = process.env.SOLONGATE_AGENT_ID;
7026
+ if (envSel) {
7027
+ const f = resolve4(dir, ".policy-cache-" + envSel.replace(/[^a-zA-Z0-9_-]/g, "_") + ".json");
7028
+ if (existsSync5(f)) return f;
7029
+ }
7030
+ let best = null, bestTs = -1;
7031
+ try {
7032
+ for (const name of readdirSync(dir)) {
7033
+ if (name.startsWith(".policy-cache-") && name.endsWith(".json")) {
7034
+ const full = resolve4(dir, name);
7035
+ const ts = statSync(full).mtimeMs;
7036
+ if (ts > bestTs) {
7037
+ bestTs = ts;
7038
+ best = full;
7039
+ }
7040
+ }
7041
+ }
7042
+ } catch {
7043
+ }
7044
+ return best;
7045
+ }
7023
7046
  function loadCfg() {
7024
7047
  try {
7025
- const sel = (process.env.SOLONGATE_AGENT_ID || "default").replace(/[^a-zA-Z0-9_-]/g, "_");
7026
- const f = resolve4(homedir3(), ".solongate", ".policy-cache-" + sel + ".json");
7027
- if (existsSync5(f)) {
7048
+ const f = findCacheFile();
7049
+ if (f && existsSync5(f)) {
7028
7050
  const c3 = JSON.parse(readFileSync6(f, "utf-8"));
7029
7051
  const d = c3?.security?.dlpRedact;
7030
7052
  if (d && Array.isArray(d.patterns)) return { patterns: d.patterns, custom: Array.isArray(d.custom) ? d.custom : [] };
package/dist/shield.js CHANGED
@@ -3,7 +3,7 @@ import { createServer, request as httpRequest } from "http";
3
3
  import { request as httpsRequest } from "https";
4
4
  import { spawn } from "child_process";
5
5
  import { URL } from "url";
6
- import { readFileSync, existsSync } from "fs";
6
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
7
7
  import { resolve } from "path";
8
8
  import { homedir } from "os";
9
9
  var log = (...a) => process.stderr.write(`[SolonGate shield] ${a.map(String).join(" ")}
@@ -26,11 +26,33 @@ var DLP_PATTERNS = [
26
26
  { name: "Bearer token", re: /bearer\s+[A-Za-z0-9._-]{20,}/gi },
27
27
  { name: "secret assignment", re: /(api[_-]?key|secret|token|password|passwd|access[_-]?key)["']?\s*[:=]\s*["']?[A-Za-z0-9/+_.-]{12,}/gi }
28
28
  ];
29
+ function findCacheFile() {
30
+ const dir = resolve(homedir(), ".solongate");
31
+ const envSel = process.env.SOLONGATE_AGENT_ID;
32
+ if (envSel) {
33
+ const f = resolve(dir, ".policy-cache-" + envSel.replace(/[^a-zA-Z0-9_-]/g, "_") + ".json");
34
+ if (existsSync(f)) return f;
35
+ }
36
+ let best = null, bestTs = -1;
37
+ try {
38
+ for (const name of readdirSync(dir)) {
39
+ if (name.startsWith(".policy-cache-") && name.endsWith(".json")) {
40
+ const full = resolve(dir, name);
41
+ const ts = statSync(full).mtimeMs;
42
+ if (ts > bestTs) {
43
+ bestTs = ts;
44
+ best = full;
45
+ }
46
+ }
47
+ }
48
+ } catch {
49
+ }
50
+ return best;
51
+ }
29
52
  function loadCfg() {
30
53
  try {
31
- const sel = (process.env.SOLONGATE_AGENT_ID || "default").replace(/[^a-zA-Z0-9_-]/g, "_");
32
- const f = resolve(homedir(), ".solongate", ".policy-cache-" + sel + ".json");
33
- if (existsSync(f)) {
54
+ const f = findCacheFile();
55
+ if (f && existsSync(f)) {
34
56
  const c = JSON.parse(readFileSync(f, "utf-8"));
35
57
  const d = c?.security?.dlpRedact;
36
58
  if (d && Array.isArray(d.patterns)) return { patterns: d.patterns, custom: Array.isArray(d.custom) ? d.custom : [] };
package/hooks/audit.mjs CHANGED
@@ -127,13 +127,11 @@ function loadGhostPatterns() {
127
127
  function buildGhostOutput(toolName, toolInput, toolResponse, toolOutput, pats) {
128
128
  if (!Array.isArray(pats) || pats.length === 0) return null;
129
129
  const name = toolName || '';
130
- const getText = () => {
131
- if (typeof toolResponse === 'string') return toolResponse;
132
- if (toolResponse && typeof toolResponse.stdout === 'string') return toolResponse.stdout;
133
- if (toolResponse && typeof toolResponse.content === 'string') return toolResponse.content;
134
- if (typeof toolOutput === 'string' && toolOutput) return toolOutput;
135
- return null;
136
- };
130
+ // Use the SAME broad extraction as DLP redaction (Read file.content, array
131
+ // content, stdout, ). Previously this was narrower, so ghost-strip silently
132
+ // skipped tool shapes DLP still scanned a ghosted file then survived in a
133
+ // listing (e.g. the LS tool) even though names elsewhere got redacted.
134
+ const getText = () => extractOutputText(toolResponse, toolOutput);
137
135
  try {
138
136
  // Direct read of a hidden file → looks like it doesn't exist.
139
137
  if (name === 'Read' || name === 'NotebookRead') {
package/hooks/shield.mjs CHANGED
@@ -17,7 +17,7 @@
17
17
  import { createServer, request as httpRequest } from 'node:http';
18
18
  import { request as httpsRequest } from 'node:https';
19
19
  import { spawn } from 'node:child_process';
20
- import { readFileSync, existsSync } from 'node:fs';
20
+ import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
21
21
  import { resolve } from 'node:path';
22
22
  import { homedir } from 'node:os';
23
23
 
@@ -42,11 +42,35 @@ const DLP_PATTERNS = [
42
42
  { name: 'secret assignment', re: /(api[_-]?key|secret|token|password|passwd|access[_-]?key)["']?\s*[:=]\s*["']?[A-Za-z0-9/+_.-]{12,}/gi },
43
43
  ];
44
44
 
45
+ // Find the policy cache to read. The guard writes one per agent
46
+ // (.policy-cache-<agent>.json) - the shield wraps `claude` and doesn't know the
47
+ // agent id, so unless SOLONGATE_AGENT_ID is set it picks the MOST RECENTLY
48
+ // written cache (the active session's). A fixed 'default' missed the guard's
49
+ // real cache, so custom patterns never reached the shield.
50
+ function findCacheFile() {
51
+ const dir = resolve(homedir(), '.solongate');
52
+ const envSel = process.env.SOLONGATE_AGENT_ID;
53
+ if (envSel) {
54
+ const f = resolve(dir, '.policy-cache-' + envSel.replace(/[^a-zA-Z0-9_-]/g, '_') + '.json');
55
+ if (existsSync(f)) return f;
56
+ }
57
+ let best = null, bestTs = -1;
58
+ try {
59
+ for (const name of readdirSync(dir)) {
60
+ if (name.startsWith('.policy-cache-') && name.endsWith('.json')) {
61
+ const full = resolve(dir, name);
62
+ const ts = statSync(full).mtimeMs;
63
+ if (ts > bestTs) { bestTs = ts; best = full; }
64
+ }
65
+ }
66
+ } catch { /* dir missing */ }
67
+ return best;
68
+ }
69
+
45
70
  function loadCfg() {
46
71
  try {
47
- const sel = (process.env.SOLONGATE_AGENT_ID || 'default').replace(/[^a-zA-Z0-9_-]/g, '_');
48
- const f = resolve(homedir(), '.solongate', '.policy-cache-' + sel + '.json');
49
- if (existsSync(f)) {
72
+ const f = findCacheFile();
73
+ if (f && existsSync(f)) {
50
74
  const c = JSON.parse(readFileSync(f, 'utf-8'));
51
75
  const d = c && c.security && c.security.dlpRedact;
52
76
  if (d && Array.isArray(d.patterns)) return { patterns: d.patterns, custom: Array.isArray(d.custom) ? d.custom : [] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "description": "AI tool security proxy — protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
5
5
  "type": "module",
6
6
  "bin": {