@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.
- package/hooks/guard.bundled.mjs +16 -4
- package/hooks/guard.mjs +30 -5
- package/package.json +1 -1
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|