@solongate/proxy 0.59.0 → 0.59.2
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 +17 -5
- package/hooks/guard.mjs +34 -6
- package/package.json +1 -1
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -6535,7 +6535,7 @@ 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 =
|
|
6538
|
+
var HOOK_VERSION = 31;
|
|
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());
|
|
@@ -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 = 1e4;
|
|
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
|
@@ -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 =
|
|
34
|
+
const HOOK_VERSION = 31;
|
|
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.
|
|
@@ -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,26 @@ 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 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;
|
|
1430
1443
|
try {
|
|
1431
1444
|
let dashboardPolicy = null;
|
|
1445
|
+
// Last-known-good cache, kept even past the TTL so we can fall back on it
|
|
1446
|
+
// when the API is slow/unreachable instead of blocking or dropping the
|
|
1447
|
+
// policy. `fresh` gates whether we skip the network entirely.
|
|
1448
|
+
let staleCache = null;
|
|
1432
1449
|
// Try cache first
|
|
1433
1450
|
try {
|
|
1434
1451
|
if (existsSync(policyCacheFile)) {
|
|
1435
1452
|
const cached = JSON.parse(readFileSync(policyCacheFile, 'utf-8'));
|
|
1453
|
+
if (cached && cached.policy) staleCache = cached;
|
|
1436
1454
|
if (cached && cached._ts && Date.now() - cached._ts < POLICY_TTL_MS) {
|
|
1437
1455
|
if (cached.policy) dashboardPolicy = cached.policy;
|
|
1438
1456
|
if (typeof cached.selfProtect === 'boolean') selfProtectEnabled = cached.selfProtect;
|
|
@@ -1445,8 +1463,10 @@ process.stdin.on('end', async () => {
|
|
|
1445
1463
|
if (!dashboardPolicy) {
|
|
1446
1464
|
try {
|
|
1447
1465
|
// Report our installed version (hv) so the dashboard can show whether
|
|
1448
|
-
// this guard is on the latest build.
|
|
1449
|
-
|
|
1466
|
+
// this guard is on the latest build. Short timeout: a stale cached
|
|
1467
|
+
// policy (below) is a safe fallback, so there's no reason to block the
|
|
1468
|
+
// tool for 8s waiting on a slow cloud — fail fast to last-known-good.
|
|
1469
|
+
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
1470
|
if (res.ok) {
|
|
1451
1471
|
const body = await res.json();
|
|
1452
1472
|
// Capture the self-protection flag even when no cloud policy is set.
|
|
@@ -1457,6 +1477,14 @@ process.stdin.on('end', async () => {
|
|
|
1457
1477
|
try { writeFileSync(policyCacheFile, JSON.stringify({ _ts: Date.now(), policy: dashboardPolicy || null, selfProtect: selfProtectEnabled, security: securityCfg, hookVersions: CLOUD_HOOK_VERSIONS })); } catch {}
|
|
1458
1478
|
}
|
|
1459
1479
|
} catch {}
|
|
1480
|
+
// API slow/unreachable → keep enforcing the last-known-good policy rather
|
|
1481
|
+
// than blocking or silently falling through to the local default.
|
|
1482
|
+
if (!dashboardPolicy && staleCache) {
|
|
1483
|
+
dashboardPolicy = staleCache.policy;
|
|
1484
|
+
if (typeof staleCache.selfProtect === 'boolean') selfProtectEnabled = staleCache.selfProtect;
|
|
1485
|
+
if (staleCache.security !== undefined) securityCfg = staleCache.security;
|
|
1486
|
+
if (staleCache.hookVersions) CLOUD_HOOK_VERSIONS = staleCache.hookVersions;
|
|
1487
|
+
}
|
|
1460
1488
|
}
|
|
1461
1489
|
|
|
1462
1490
|
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.2",
|
|
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": {
|