@solongate/proxy 0.59.0 → 0.59.1

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.
@@ -7443,7 +7443,7 @@ function extractWasmFromBundle(buf) {
7443
7443
  }
7444
7444
  throw new Error("Unknown OPA bundle format");
7445
7445
  }
7446
- var OPA_WASM_TTL_MS = 3e4;
7446
+ var OPA_WASM_TTL_MS = 24 * 60 * 60 * 1e3;
7447
7447
  async function getOpaWasmBytes(policy) {
7448
7448
  if (!policy || !policy.id)
7449
7449
  return null;
@@ -7466,7 +7466,7 @@ async function getOpaWasmBytes(policy) {
7466
7466
  try {
7467
7467
  const res = await fetch(
7468
7468
  API_URL + "/api/v1/policies/" + encodeURIComponent(policy.id) + "/wasm",
7469
- { headers: AUTH_HEADERS, signal: AbortSignal.timeout(8e3) }
7469
+ { headers: AUTH_HEADERS, signal: AbortSignal.timeout(2500) }
7470
7470
  );
7471
7471
  if (!res.ok)
7472
7472
  return stale;
@@ -7646,12 +7646,15 @@ 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 = 3e3;
7649
+ const POLICY_TTL_MS = 6e4;
7650
7650
  try {
7651
7651
  let dashboardPolicy = null;
7652
+ let staleCache = null;
7652
7653
  try {
7653
7654
  if (existsSync(policyCacheFile)) {
7654
7655
  const cached = JSON.parse(readFileSync(policyCacheFile, "utf-8"));
7656
+ if (cached && cached.policy)
7657
+ staleCache = cached;
7655
7658
  if (cached && cached._ts && Date.now() - cached._ts < POLICY_TTL_MS) {
7656
7659
  if (cached.policy)
7657
7660
  dashboardPolicy = cached.policy;
@@ -7667,7 +7670,7 @@ process.stdin.on("end", async () => {
7667
7670
  }
7668
7671
  if (!dashboardPolicy) {
7669
7672
  try {
7670
- const res = await fetch(API_URL + "/api/v1/policies/active?agent_id=" + encodeURIComponent(AGENT_ID || "") + "&hv=" + HOOK_VERSION, { headers: AUTH_HEADERS, signal: AbortSignal.timeout(8e3) });
7673
+ 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) });
7671
7674
  if (res.ok) {
7672
7675
  const body = await res.json();
7673
7676
  if (typeof body?.self_protection_enabled === "boolean")
@@ -7685,6 +7688,15 @@ process.stdin.on("end", async () => {
7685
7688
  }
7686
7689
  } catch {
7687
7690
  }
7691
+ if (!dashboardPolicy && staleCache) {
7692
+ dashboardPolicy = staleCache.policy;
7693
+ if (typeof staleCache.selfProtect === "boolean")
7694
+ selfProtectEnabled = staleCache.selfProtect;
7695
+ if (staleCache.security !== void 0)
7696
+ securityCfg = staleCache.security;
7697
+ if (staleCache.hookVersions)
7698
+ CLOUD_HOOK_VERSIONS = staleCache.hookVersions;
7699
+ }
7688
7700
  }
7689
7701
  if (process.env.SOLONGATE_DEBUG) {
7690
7702
  }
package/hooks/guard.mjs CHANGED
@@ -1138,7 +1138,12 @@ function extractWasmFromBundle(buf) {
1138
1138
  // Fetches the compiled WASM for this policy from the API, with a local cache
1139
1139
  // keyed by a fingerprint of the policy so updates propagate. Returns the raw
1140
1140
  // policy.wasm bytes (Uint8Array) or null when unavailable.
1141
- const OPA_WASM_TTL_MS = 30_000;
1141
+ // The WASM cache is keyed by `fp`, a fingerprint of the policy rules+mode, so a
1142
+ // real policy change invalidates it immediately regardless of age. The TTL is
1143
+ // therefore only a backstop for a server-side recompile that keeps the same
1144
+ // rules — a rare event — so we keep it long (24h) instead of re-downloading the
1145
+ // bundle every 30s on the hot path.
1146
+ const OPA_WASM_TTL_MS = 24 * 60 * 60 * 1000;
1142
1147
  async function getOpaWasmBytes(policy) {
1143
1148
  if (!policy || !policy.id) return null;
1144
1149
  const fp = djb2(JSON.stringify(policy.rules || []) + '|' + (policy.mode || ''));
@@ -1165,7 +1170,7 @@ async function getOpaWasmBytes(policy) {
1165
1170
  try {
1166
1171
  const res = await fetch(
1167
1172
  API_URL + '/api/v1/policies/' + encodeURIComponent(policy.id) + '/wasm',
1168
- { headers: AUTH_HEADERS, signal: AbortSignal.timeout(8000) },
1173
+ { headers: AUTH_HEADERS, signal: AbortSignal.timeout(2500) },
1169
1174
  );
1170
1175
  if (!res.ok) return stale; // API has no compiled WASM right now → last known good
1171
1176
  const bundle = Buffer.from(await res.arrayBuffer());
@@ -1426,13 +1431,23 @@ process.stdin.on('end', async () => {
1426
1431
  // don't share a stale cached policy.
1427
1432
  const agentKey = (AGENT_ID || 'default').replace(/[^a-zA-Z0-9_-]/g, '_');
1428
1433
  const policyCacheFile = join(resolve(homedir(), '.solongate'), '.policy-cache-' + agentKey + '.json');
1429
- const POLICY_TTL_MS = 3_000;
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;
1430
1440
  try {
1431
1441
  let dashboardPolicy = null;
1442
+ // 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.
1445
+ let staleCache = null;
1432
1446
  // Try cache first
1433
1447
  try {
1434
1448
  if (existsSync(policyCacheFile)) {
1435
1449
  const cached = JSON.parse(readFileSync(policyCacheFile, 'utf-8'));
1450
+ if (cached && cached.policy) staleCache = cached;
1436
1451
  if (cached && cached._ts && Date.now() - cached._ts < POLICY_TTL_MS) {
1437
1452
  if (cached.policy) dashboardPolicy = cached.policy;
1438
1453
  if (typeof cached.selfProtect === 'boolean') selfProtectEnabled = cached.selfProtect;
@@ -1445,8 +1460,10 @@ process.stdin.on('end', async () => {
1445
1460
  if (!dashboardPolicy) {
1446
1461
  try {
1447
1462
  // Report our installed version (hv) so the dashboard can show whether
1448
- // this guard is on the latest build.
1449
- const res = await fetch(API_URL + '/api/v1/policies/active?agent_id=' + encodeURIComponent(AGENT_ID || '') + '&hv=' + HOOK_VERSION, { headers: AUTH_HEADERS, signal: AbortSignal.timeout(8000) });
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
+ 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) });
1450
1467
  if (res.ok) {
1451
1468
  const body = await res.json();
1452
1469
  // Capture the self-protection flag even when no cloud policy is set.
@@ -1457,6 +1474,14 @@ process.stdin.on('end', async () => {
1457
1474
  try { writeFileSync(policyCacheFile, JSON.stringify({ _ts: Date.now(), policy: dashboardPolicy || null, selfProtect: selfProtectEnabled, security: securityCfg, hookVersions: CLOUD_HOOK_VERSIONS })); } catch {}
1458
1475
  }
1459
1476
  } catch {}
1477
+ // API slow/unreachable → keep enforcing the last-known-good policy rather
1478
+ // than blocking or silently falling through to the local default.
1479
+ if (!dashboardPolicy && staleCache) {
1480
+ dashboardPolicy = staleCache.policy;
1481
+ if (typeof staleCache.selfProtect === 'boolean') selfProtectEnabled = staleCache.selfProtect;
1482
+ if (staleCache.security !== undefined) securityCfg = staleCache.security;
1483
+ if (staleCache.hookVersions) CLOUD_HOOK_VERSIONS = staleCache.hookVersions;
1484
+ }
1460
1485
  }
1461
1486
 
1462
1487
  if (process.env.SOLONGATE_DEBUG) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.59.0",
3
+ "version": "0.59.1",
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": {