@solongate/proxy 0.59.1 → 0.59.3

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.
@@ -34,9 +34,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  mod
35
35
  ));
36
36
 
37
- // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/json.js
37
+ // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/json.js
38
38
  var require_json = __commonJS({
39
- "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/json.js"(exports, module) {
39
+ "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/json.js"(exports, module) {
40
40
  function isValidJSON(str) {
41
41
  if (typeof str !== "string") {
42
42
  return;
@@ -57,9 +57,9 @@ var require_json = __commonJS({
57
57
  }
58
58
  });
59
59
 
60
- // ../../node_modules/.pnpm/sprintf-js@1.1.3/node_modules/sprintf-js/src/sprintf.js
60
+ // node_modules/.pnpm/sprintf-js@1.1.3/node_modules/sprintf-js/src/sprintf.js
61
61
  var require_sprintf = __commonJS({
62
- "../../node_modules/.pnpm/sprintf-js@1.1.3/node_modules/sprintf-js/src/sprintf.js"(exports) {
62
+ "node_modules/.pnpm/sprintf-js@1.1.3/node_modules/sprintf-js/src/sprintf.js"(exports) {
63
63
  !function() {
64
64
  "use strict";
65
65
  var re = {
@@ -260,26 +260,26 @@ var require_sprintf = __commonJS({
260
260
  }
261
261
  });
262
262
 
263
- // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/strings.js
263
+ // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/strings.js
264
264
  var require_strings = __commonJS({
265
- "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/strings.js"(exports, module) {
265
+ "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/strings.js"(exports, module) {
266
266
  var vsprintf = require_sprintf().vsprintf;
267
267
  var sprintf = (s, values) => vsprintf(s, values);
268
268
  module.exports = { sprintf };
269
269
  }
270
270
  });
271
271
 
272
- // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/regex.js
272
+ // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/regex.js
273
273
  var require_regex = __commonJS({
274
- "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/regex.js"(exports, module) {
274
+ "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/regex.js"(exports, module) {
275
275
  var regexSplit = (pattern, s) => s.split(RegExp(pattern));
276
276
  module.exports = { "regex.split": regexSplit };
277
277
  }
278
278
  });
279
279
 
280
- // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/PlainValue-516d5bc2.js
280
+ // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/PlainValue-516d5bc2.js
281
281
  var require_PlainValue_516d5bc2 = __commonJS({
282
- "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/PlainValue-516d5bc2.js"(exports) {
282
+ "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/PlainValue-516d5bc2.js"(exports) {
283
283
  "use strict";
284
284
  var Char = {
285
285
  ANCHOR: "&",
@@ -1042,9 +1042,9 @@ ${ctx}
1042
1042
  }
1043
1043
  });
1044
1044
 
1045
- // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/parse-cst.js
1045
+ // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/parse-cst.js
1046
1046
  var require_parse_cst = __commonJS({
1047
- "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/parse-cst.js"(exports) {
1047
+ "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/parse-cst.js"(exports) {
1048
1048
  "use strict";
1049
1049
  var PlainValue = require_PlainValue_516d5bc2();
1050
1050
  var BlankLine = class extends PlainValue.Node {
@@ -2535,9 +2535,9 @@ var require_parse_cst = __commonJS({
2535
2535
  }
2536
2536
  });
2537
2537
 
2538
- // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/resolveSeq-95613e94.js
2538
+ // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/resolveSeq-95613e94.js
2539
2539
  var require_resolveSeq_95613e94 = __commonJS({
2540
- "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/resolveSeq-95613e94.js"(exports) {
2540
+ "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/resolveSeq-95613e94.js"(exports) {
2541
2541
  "use strict";
2542
2542
  var PlainValue = require_PlainValue_516d5bc2();
2543
2543
  function addCommentBefore(str, indent, comment) {
@@ -4512,9 +4512,9 @@ ${ca}` : ca;
4512
4512
  }
4513
4513
  });
4514
4514
 
4515
- // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/warnings-793925ce.js
4515
+ // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/warnings-793925ce.js
4516
4516
  var require_warnings_793925ce = __commonJS({
4517
- "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/warnings-793925ce.js"(exports) {
4517
+ "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/warnings-793925ce.js"(exports) {
4518
4518
  "use strict";
4519
4519
  var PlainValue = require_PlainValue_516d5bc2();
4520
4520
  var resolveSeq = require_resolveSeq_95613e94();
@@ -4879,9 +4879,9 @@ ${pair.comment}` : item.comment;
4879
4879
  }
4880
4880
  });
4881
4881
 
4882
- // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Schema-bcc6c2d7.js
4882
+ // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Schema-bcc6c2d7.js
4883
4883
  var require_Schema_bcc6c2d7 = __commonJS({
4884
- "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Schema-bcc6c2d7.js"(exports) {
4884
+ "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Schema-bcc6c2d7.js"(exports) {
4885
4885
  "use strict";
4886
4886
  var PlainValue = require_PlainValue_516d5bc2();
4887
4887
  var resolveSeq = require_resolveSeq_95613e94();
@@ -5345,9 +5345,9 @@ var require_Schema_bcc6c2d7 = __commonJS({
5345
5345
  }
5346
5346
  });
5347
5347
 
5348
- // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Document-a8d0fbf9.js
5348
+ // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Document-a8d0fbf9.js
5349
5349
  var require_Document_a8d0fbf9 = __commonJS({
5350
- "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Document-a8d0fbf9.js"(exports) {
5350
+ "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Document-a8d0fbf9.js"(exports) {
5351
5351
  "use strict";
5352
5352
  var PlainValue = require_PlainValue_516d5bc2();
5353
5353
  var resolveSeq = require_resolveSeq_95613e94();
@@ -6028,9 +6028,9 @@ ${cbNode.commentBefore}` : cb;
6028
6028
  }
6029
6029
  });
6030
6030
 
6031
- // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/index.js
6031
+ // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/index.js
6032
6032
  var require_dist = __commonJS({
6033
- "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/index.js"(exports) {
6033
+ "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/index.js"(exports) {
6034
6034
  "use strict";
6035
6035
  var parseCst = require_parse_cst();
6036
6036
  var Document$1 = require_Document_a8d0fbf9();
@@ -6099,16 +6099,16 @@ var require_dist = __commonJS({
6099
6099
  }
6100
6100
  });
6101
6101
 
6102
- // ../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/index.js
6102
+ // node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/index.js
6103
6103
  var require_yaml = __commonJS({
6104
- "../../node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/index.js"(exports, module) {
6104
+ "node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/index.js"(exports, module) {
6105
6105
  module.exports = require_dist().YAML;
6106
6106
  }
6107
6107
  });
6108
6108
 
6109
- // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/yaml.js
6109
+ // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/yaml.js
6110
6110
  var require_yaml2 = __commonJS({
6111
- "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/yaml.js"(exports, module) {
6111
+ "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/yaml.js"(exports, module) {
6112
6112
  var yaml = require_yaml();
6113
6113
  var errors = /* @__PURE__ */ new Set([
6114
6114
  "YAMLReferenceError",
@@ -6143,9 +6143,9 @@ var require_yaml2 = __commonJS({
6143
6143
  }
6144
6144
  });
6145
6145
 
6146
- // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/index.js
6146
+ // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/index.js
6147
6147
  var require_builtins = __commonJS({
6148
- "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/index.js"(exports, module) {
6148
+ "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/builtins/index.js"(exports, module) {
6149
6149
  var json = require_json();
6150
6150
  var strings = require_strings();
6151
6151
  var regex = require_regex();
@@ -6159,9 +6159,9 @@ var require_builtins = __commonJS({
6159
6159
  }
6160
6160
  });
6161
6161
 
6162
- // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/opa.js
6162
+ // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/opa.js
6163
6163
  var require_opa = __commonJS({
6164
- "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/opa.js"(exports, module) {
6164
+ "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/opa.js"(exports, module) {
6165
6165
  var builtIns = require_builtins();
6166
6166
  function stringDecoder(mem) {
6167
6167
  return function(addr) {
@@ -6514,7 +6514,7 @@ var require_opa = __commonJS({
6514
6514
  }
6515
6515
  });
6516
6516
 
6517
- // ../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/index.mjs
6517
+ // node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/index.mjs
6518
6518
  var src_exports = {};
6519
6519
  __export(src_exports, {
6520
6520
  default: () => src_default,
@@ -6522,20 +6522,20 @@ __export(src_exports, {
6522
6522
  });
6523
6523
  var import_opa, loadPolicy, src_default;
6524
6524
  var init_src = __esm({
6525
- "../../node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/index.mjs"() {
6525
+ "node_modules/.pnpm/@open-policy-agent+opa-wasm@1.10.0/node_modules/@open-policy-agent/opa-wasm/src/index.mjs"() {
6526
6526
  import_opa = __toESM(require_opa(), 1);
6527
6527
  loadPolicy = import_opa.default.loadPolicy;
6528
6528
  src_default = import_opa.default;
6529
6529
  }
6530
6530
  });
6531
6531
 
6532
- // hooks/guard.mjs
6532
+ // packages/proxy/hooks/guard.mjs
6533
6533
  import { readFileSync, existsSync, statSync, writeFileSync, mkdirSync, chmodSync, renameSync, appendFileSync } from "node:fs";
6534
6534
  import { resolve, join, dirname, isAbsolute } from "node:path";
6535
6535
  import { homedir } from "node:os";
6536
6536
  import { gunzipSync } from "node:zlib";
6537
6537
  import { createHash } from "node:crypto";
6538
- var HOOK_VERSION = 30;
6538
+ var HOOK_VERSION = 32;
6539
6539
  function localLogsOnly(security) {
6540
6540
  const l = security && security.localLogs;
6541
6541
  return !!(l && l.enabled && typeof l.path === "string" && l.path.trim());
@@ -7646,16 +7646,18 @@ process.stdin.on("end", async () => {
7646
7646
  let securityCfg = null;
7647
7647
  const agentKey = (AGENT_ID || "default").replace(/[^a-zA-Z0-9_-]/g, "_");
7648
7648
  const policyCacheFile = join(resolve(homedir(), ".solongate"), ".policy-cache-" + agentKey + ".json");
7649
- const POLICY_TTL_MS = 6e4;
7649
+ const POLICY_TTL_MS = 1e4;
7650
7650
  try {
7651
7651
  let dashboardPolicy = null;
7652
7652
  let staleCache = null;
7653
+ let refreshDue = true;
7653
7654
  try {
7654
7655
  if (existsSync(policyCacheFile)) {
7655
7656
  const cached = JSON.parse(readFileSync(policyCacheFile, "utf-8"));
7656
7657
  if (cached && cached.policy)
7657
7658
  staleCache = cached;
7658
7659
  if (cached && cached._ts && Date.now() - cached._ts < POLICY_TTL_MS) {
7660
+ refreshDue = false;
7659
7661
  if (cached.policy)
7660
7662
  dashboardPolicy = cached.policy;
7661
7663
  if (typeof cached.selfProtect === "boolean")
@@ -7668,7 +7670,7 @@ process.stdin.on("end", async () => {
7668
7670
  }
7669
7671
  } catch {
7670
7672
  }
7671
- if (!dashboardPolicy) {
7673
+ if (refreshDue) {
7672
7674
  try {
7673
7675
  const res = await fetch(API_URL + "/api/v1/policies/active?agent_id=" + encodeURIComponent(AGENT_ID || "") + "&hv=" + HOOK_VERSION, { headers: AUTH_HEADERS, signal: AbortSignal.timeout(2e3) });
7674
7676
  if (res.ok) {
@@ -7681,10 +7683,6 @@ process.stdin.on("end", async () => {
7681
7683
  CLOUD_HOOK_VERSIONS = body.hook_versions;
7682
7684
  if (body && body.policy)
7683
7685
  dashboardPolicy = body.policy;
7684
- try {
7685
- writeFileSync(policyCacheFile, JSON.stringify({ _ts: Date.now(), policy: dashboardPolicy || null, selfProtect: selfProtectEnabled, security: securityCfg, hookVersions: CLOUD_HOOK_VERSIONS }));
7686
- } catch {
7687
- }
7688
7686
  }
7689
7687
  } catch {
7690
7688
  }
@@ -7697,6 +7695,11 @@ process.stdin.on("end", async () => {
7697
7695
  if (staleCache.hookVersions)
7698
7696
  CLOUD_HOOK_VERSIONS = staleCache.hookVersions;
7699
7697
  }
7698
+ const keepPolicy = dashboardPolicy || staleCache && staleCache.policy || null;
7699
+ try {
7700
+ writeFileSync(policyCacheFile, JSON.stringify({ _ts: Date.now(), policy: keepPolicy, selfProtect: selfProtectEnabled, security: securityCfg, hookVersions: CLOUD_HOOK_VERSIONS }));
7701
+ } catch {
7702
+ }
7700
7703
  }
7701
7704
  if (process.env.SOLONGATE_DEBUG) {
7702
7705
  }
package/hooks/guard.mjs CHANGED
@@ -31,7 +31,7 @@ import { createHash } from 'node:crypto';
31
31
  // the installed hook self-updates when the cloud version is higher (see
32
32
  // maybeSelfUpdate). This is what makes guard fixes propagate without a manual
33
33
  // reinstall — the same trust model as the OPA WASM this hook already runs.
34
- const HOOK_VERSION = 30;
34
+ const HOOK_VERSION = 32;
35
35
 
36
36
  // True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
37
37
  // nothing is sent to the cloud audit log.
@@ -1432,23 +1432,33 @@ process.stdin.on('end', async () => {
1432
1432
  const agentKey = (AGENT_ID || 'default').replace(/[^a-zA-Z0-9_-]/g, '_');
1433
1433
  const policyCacheFile = join(resolve(homedir(), '.solongate'), '.policy-cache-' + agentKey + '.json');
1434
1434
  // Serve the cached policy without any network for TTL seconds. This is a HOT
1435
- // PATH — it runs before every single tool call so a short TTL means we
1436
- // block on a cloud round-trip on nearly every action. A dashboard-managed
1437
- // policy changes rarely, so 60s freshness is plenty; the previous 3s made the
1438
- // cache useless and put an 8s-timeout fetch in front of most tool calls.
1439
- const POLICY_TTL_MS = 60_000;
1435
+ // PATH — it runs before every tool call. The original 3s TTL was slow not
1436
+ // because of the number but because it paired an 8s-timeout blocking fetch
1437
+ // with an every-3s cadence (and stacked behind the WASM fetch + a shared
1438
+ // rate limit). With those fixed 2s timeout, WASM decoupled (24h/fp),
1439
+ // last-known-good stale fallback — a short TTL is cheap again: one small
1440
+ // ~200ms GET on a single call per window, others read the cache with no
1441
+ // network. So keep freshness tight (10s propagation) at negligible cost.
1442
+ const POLICY_TTL_MS = 10_000;
1440
1443
  try {
1441
1444
  let dashboardPolicy = null;
1442
1445
  // Last-known-good cache, kept even past the TTL so we can fall back on it
1443
- // when the API is slow/unreachable instead of blocking or dropping the
1444
- // policy. `fresh` gates whether we skip the network entirely.
1446
+ // when the API is slow/unreachable instead of blocking or dropping the policy.
1445
1447
  let staleCache = null;
1446
- // Try cache first
1448
+ // Whether the TTL has elapsed since our last refresh ATTEMPT. Gating the
1449
+ // fetch on the attempt timestamp (not on "do we already have a policy") is
1450
+ // what makes the back-off actually work when the endpoint is slow: a failed
1451
+ // or timed-out attempt still advances _ts, so we don't re-hit it — and eat
1452
+ // the full 2s timeout — on every single call. Before this, _ts only moved on
1453
+ // a SUCCESSFUL fetch, so a slow /policies/active meant ~2s on EVERY call and
1454
+ // the 10s TTL never engaged (the cache never looked "fresh").
1455
+ let refreshDue = true;
1447
1456
  try {
1448
1457
  if (existsSync(policyCacheFile)) {
1449
1458
  const cached = JSON.parse(readFileSync(policyCacheFile, 'utf-8'));
1450
1459
  if (cached && cached.policy) staleCache = cached;
1451
1460
  if (cached && cached._ts && Date.now() - cached._ts < POLICY_TTL_MS) {
1461
+ refreshDue = false;
1452
1462
  if (cached.policy) dashboardPolicy = cached.policy;
1453
1463
  if (typeof cached.selfProtect === 'boolean') selfProtectEnabled = cached.selfProtect;
1454
1464
  if (cached.security !== undefined) securityCfg = cached.security;
@@ -1456,32 +1466,32 @@ process.stdin.on('end', async () => {
1456
1466
  }
1457
1467
  }
1458
1468
  } catch {}
1459
- // Refresh from API if cache expired
1460
- if (!dashboardPolicy) {
1469
+ // Refresh from the API at most once per TTL window.
1470
+ if (refreshDue) {
1461
1471
  try {
1462
- // Report our installed version (hv) so the dashboard can show whether
1463
- // this guard is on the latest build. Short timeout: a stale cached
1464
- // policy (below) is a safe fallback, so there's no reason to block the
1465
- // tool for 8s waiting on a slow cloud — fail fast to last-known-good.
1466
1472
  const res = await fetch(API_URL + '/api/v1/policies/active?agent_id=' + encodeURIComponent(AGENT_ID || '') + '&hv=' + HOOK_VERSION, { headers: AUTH_HEADERS, signal: AbortSignal.timeout(2000) });
1467
1473
  if (res.ok) {
1468
1474
  const body = await res.json();
1469
- // Capture the self-protection flag even when no cloud policy is set.
1470
1475
  if (typeof body?.self_protection_enabled === 'boolean') selfProtectEnabled = body.self_protection_enabled;
1471
1476
  if (body?.security !== undefined) securityCfg = body.security;
1472
1477
  if (body?.hook_versions && typeof body.hook_versions === 'object') CLOUD_HOOK_VERSIONS = body.hook_versions;
1473
1478
  if (body && body.policy) dashboardPolicy = body.policy;
1474
- try { writeFileSync(policyCacheFile, JSON.stringify({ _ts: Date.now(), policy: dashboardPolicy || null, selfProtect: selfProtectEnabled, security: securityCfg, hookVersions: CLOUD_HOOK_VERSIONS })); } catch {}
1475
1479
  }
1476
1480
  } catch {}
1477
- // API slow/unreachable → keep enforcing the last-known-good policy rather
1478
- // than blocking or silently falling through to the local default.
1481
+ // API slow/unreachable → keep enforcing the last-known-good policy.
1479
1482
  if (!dashboardPolicy && staleCache) {
1480
1483
  dashboardPolicy = staleCache.policy;
1481
1484
  if (typeof staleCache.selfProtect === 'boolean') selfProtectEnabled = staleCache.selfProtect;
1482
1485
  if (staleCache.security !== undefined) securityCfg = staleCache.security;
1483
1486
  if (staleCache.hookVersions) CLOUD_HOOK_VERSIONS = staleCache.hookVersions;
1484
1487
  }
1488
+ // ALWAYS advance _ts after an attempt — success, stale fallback, or even a
1489
+ // cold miss with no policy — so a slow/down endpoint is retried at most
1490
+ // once per TTL (≈2s per 10s worst case, not 2s per call). Keep the
1491
+ // last-known-good policy in the cache so a failed attempt never erases it;
1492
+ // when nothing is available, enforcement falls back to ~/.solongate/policy.json below.
1493
+ const keepPolicy = dashboardPolicy || (staleCache && staleCache.policy) || null;
1494
+ try { writeFileSync(policyCacheFile, JSON.stringify({ _ts: Date.now(), policy: keepPolicy, selfProtect: selfProtectEnabled, security: securityCfg, hookVersions: CLOUD_HOOK_VERSIONS })); } catch {}
1485
1495
  }
1486
1496
 
1487
1497
  if (process.env.SOLONGATE_DEBUG) {
package/hooks/shield.mjs CHANGED
@@ -23,7 +23,7 @@ import { homedir } from 'node:os';
23
23
 
24
24
  // Bump on every shield.mjs change. The cloud serves the newest version; the
25
25
  // guard hook installs it on its next run (no re-login needed).
26
- const HOOK_VERSION = 5;
26
+ const HOOK_VERSION = 7;
27
27
 
28
28
  const log = (...a) => process.stderr.write(`[SolonGate shield] ${a.map(String).join(' ')}\n`);
29
29
 
@@ -220,7 +220,23 @@ function startProxy(upstream) {
220
220
  try {
221
221
  if (body.length && String(req.headers['content-type'] || '').includes('json')) {
222
222
  const parsed = JSON.parse(body.toString('utf-8'));
223
- body = Buffer.from(JSON.stringify(redactDeep(parsed, cfg)), 'utf-8');
223
+ // Only the NEW turn needs scanning. The model re-sends the WHOLE
224
+ // conversation on every request, but every prior message was already
225
+ // redacted when it was itself the latest turn — so re-scanning all of
226
+ // history each call is pure waste that grows with the session (this was
227
+ // the source of the intermittent stalls). Redact ONLY the last message:
228
+ // the fresh user input + tool results (e.g. an `ls` result → ghost-strip
229
+ // hidden paths, DLP-redact secret patterns). We deliberately do NOT scan
230
+ // the model's own generated prose — only inputs and tool results matter.
231
+ const msgs = parsed && Array.isArray(parsed.messages) ? parsed.messages : null;
232
+ if (msgs && msgs.length > 0) {
233
+ const i = msgs.length - 1;
234
+ msgs[i] = redactDeep(msgs[i], cfg);
235
+ body = Buffer.from(JSON.stringify(parsed), 'utf-8');
236
+ } else {
237
+ // Non-messages payload (rare) → redact it once.
238
+ body = Buffer.from(JSON.stringify(redactDeep(parsed, cfg)), 'utf-8');
239
+ }
224
240
  }
225
241
  } catch { /* fail open */ }
226
242
  const headers = { ...req.headers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.59.1",
3
+ "version": "0.59.3",
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": {