@solongate/proxy 0.83.8 → 0.83.10
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/audit.mjs +1 -1
- package/hooks/guard.bundled.mjs +3 -8
- package/hooks/guard.mjs +26 -15
- package/package.json +1 -1
package/hooks/audit.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { homedir } from 'node:os';
|
|
|
11
11
|
// Bump on every audit hook change. The cloud serves the newest version; the guard
|
|
12
12
|
// hook installs it on its next run (no re-login needed). See guard.mjs
|
|
13
13
|
// fetchAndInstallHook / maybeSelfUpdate.
|
|
14
|
-
const HOOK_VERSION =
|
|
14
|
+
const HOOK_VERSION = 26;
|
|
15
15
|
|
|
16
16
|
function loadEnvKey(dir) {
|
|
17
17
|
try {
|
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -6536,7 +6536,7 @@ import { resolve, join, dirname, isAbsolute } from "node:path";
|
|
|
6536
6536
|
import { homedir } from "node:os";
|
|
6537
6537
|
import { gunzipSync } from "node:zlib";
|
|
6538
6538
|
import { createHash } from "node:crypto";
|
|
6539
|
-
var HOOK_VERSION =
|
|
6539
|
+
var HOOK_VERSION = 75;
|
|
6540
6540
|
function localLogsOnly(security) {
|
|
6541
6541
|
if (security !== void 0) {
|
|
6542
6542
|
const l = security && security.localLogs;
|
|
@@ -8461,7 +8461,7 @@ if (!REFRESH_MODE) {
|
|
|
8461
8461
|
try {
|
|
8462
8462
|
if (existsSync(policyCacheFile)) {
|
|
8463
8463
|
const cached = JSON.parse(readFileSync(policyCacheFile, "utf-8"));
|
|
8464
|
-
if (cached
|
|
8464
|
+
if (cached)
|
|
8465
8465
|
staleCache = cached;
|
|
8466
8466
|
if (cached && cached._ts && Date.now() - cached._ts < POLICY_TTL_MS) {
|
|
8467
8467
|
refreshDue = false;
|
|
@@ -8566,15 +8566,12 @@ if (!REFRESH_MODE) {
|
|
|
8566
8566
|
});
|
|
8567
8567
|
} catch {
|
|
8568
8568
|
}
|
|
8569
|
-
await maybeSelfUpdate();
|
|
8570
8569
|
blockTool(ghostHit, true);
|
|
8571
8570
|
}
|
|
8572
8571
|
if (toolName === "Bash" || toolName === "run_command" || guessPermission(toolName) === "EXECUTE") {
|
|
8573
8572
|
const rw = ghostListingRewrite(args, securityCfg.ghost);
|
|
8574
|
-
if (rw)
|
|
8575
|
-
await maybeSelfUpdate();
|
|
8573
|
+
if (rw)
|
|
8576
8574
|
rewriteTool({ command: rw });
|
|
8577
|
-
}
|
|
8578
8575
|
}
|
|
8579
8576
|
}
|
|
8580
8577
|
if (!reason)
|
|
@@ -8608,7 +8605,6 @@ if (!REFRESH_MODE) {
|
|
|
8608
8605
|
reason = "Security layer (DLP): reading a file that contains a secret is blocked. Blocked by SolonGate.";
|
|
8609
8606
|
opaRoute = "black";
|
|
8610
8607
|
} else if (plan && plan.rewrite) {
|
|
8611
|
-
await maybeSelfUpdate();
|
|
8612
8608
|
rewriteTool(plan.rewrite);
|
|
8613
8609
|
}
|
|
8614
8610
|
}
|
|
@@ -8659,7 +8655,6 @@ if (!REFRESH_MODE) {
|
|
|
8659
8655
|
}
|
|
8660
8656
|
}
|
|
8661
8657
|
writeDenyFlag(toolName);
|
|
8662
|
-
await maybeSelfUpdate();
|
|
8663
8658
|
blockTool(reason);
|
|
8664
8659
|
}
|
|
8665
8660
|
} catch {
|
package/hooks/guard.mjs
CHANGED
|
@@ -32,10 +32,11 @@ import { createHash } from 'node:crypto';
|
|
|
32
32
|
// the installed hook self-updates when the cloud version is higher (see
|
|
33
33
|
// maybeSelfUpdate). This is what makes guard fixes propagate without a manual
|
|
34
34
|
// reinstall — the same trust model as the OPA WASM this hook already runs.
|
|
35
|
-
const HOOK_VERSION =
|
|
35
|
+
const HOOK_VERSION = 75;
|
|
36
36
|
|
|
37
37
|
// True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
|
|
38
|
-
// nothing is sent to the cloud audit log
|
|
38
|
+
// nothing is sent to the cloud audit log — local and cloud are one or the
|
|
39
|
+
// other, never both.
|
|
39
40
|
//
|
|
40
41
|
// `undefined` is the ONLY value that means "we do not know yet". `null` is an
|
|
41
42
|
// answer: it is what the refresh writes when the API returns no security block,
|
|
@@ -125,14 +126,13 @@ function writeLocalLog(security, entry) {
|
|
|
125
126
|
const mark = accountMark();
|
|
126
127
|
if (mark) entry = { ...entry, acct: mark };
|
|
127
128
|
const l = security && security.localLogs;
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
// never disappear, so fall back to the per-device default folder and keep it.
|
|
129
|
+
// No usable folder in the resolved config. The answer can still be "local is
|
|
130
|
+
// on" from the persisted marker alone, i.e. WITHOUT a resolved config (empty
|
|
131
|
+
// or cold policy cache, or a refresh that failed while the key was being
|
|
132
|
+
// rotated) — in which case keep the copy in the per-device default folder
|
|
133
|
+
// rather than dropping it.
|
|
134
134
|
if (!l || !l.enabled || typeof l.path !== 'string' || !l.path.trim()) {
|
|
135
|
-
if (!localLogsOnly(security)) return; //
|
|
135
|
+
if (!localLogsOnly(security)) return; // local logging is off — cloud only
|
|
136
136
|
const fallbackDir = resolve(homedir(), '.solongate', 'local-logs');
|
|
137
137
|
const fallbackLine = JSON.stringify(entry) + '\n';
|
|
138
138
|
const fallbackPayload = Buffer.from(JSON.stringify({ dir: fallbackDir, line: fallbackLine }), 'utf-8').toString('base64');
|
|
@@ -2484,7 +2484,13 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2484
2484
|
try {
|
|
2485
2485
|
if (existsSync(policyCacheFile)) {
|
|
2486
2486
|
const cached = JSON.parse(readFileSync(policyCacheFile, 'utf-8'));
|
|
2487
|
-
|
|
2487
|
+
// Kept whenever it parses, NOT only when it carries a policy. The
|
|
2488
|
+
// security layers (DLP, rate limit, ghost) are configured separately
|
|
2489
|
+
// from the policy, so gating the last-known-good on a policy existing
|
|
2490
|
+
// meant a project with DLP on and no policy lost DLP entirely the
|
|
2491
|
+
// moment this cache went past its 10s TTL — which is almost always,
|
|
2492
|
+
// since it only refreshes on activity.
|
|
2493
|
+
if (cached) staleCache = cached;
|
|
2488
2494
|
if (cached && cached._ts && Date.now() - cached._ts < POLICY_TTL_MS) {
|
|
2489
2495
|
refreshDue = false;
|
|
2490
2496
|
if (cached.policy) dashboardPolicy = cached.policy;
|
|
@@ -2585,7 +2591,7 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2585
2591
|
signal: AbortSignal.timeout(3000),
|
|
2586
2592
|
});
|
|
2587
2593
|
} catch {}
|
|
2588
|
-
|
|
2594
|
+
// NOT maybeSelfUpdate() first: see the deny at the end of this flow.
|
|
2589
2595
|
// Stealth deny: ghostHit is a bare "No such file or directory", and the
|
|
2590
2596
|
// stealth flag keeps the adapter from adding any SolonGate branding, so
|
|
2591
2597
|
// the path looks like it simply is not there on every client.
|
|
@@ -2597,7 +2603,7 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2597
2603
|
// PreToolUse `overwrite` replaces the command before it runs (hooks.md).
|
|
2598
2604
|
if (toolName === 'Bash' || toolName === 'run_command' || guessPermission(toolName) === 'EXECUTE') {
|
|
2599
2605
|
const rw = ghostListingRewrite(args, securityCfg.ghost);
|
|
2600
|
-
if (rw)
|
|
2606
|
+
if (rw) rewriteTool({ command: rw }); // verdict first, update later
|
|
2601
2607
|
}
|
|
2602
2608
|
}
|
|
2603
2609
|
// Extra security layers run after tamper, before policy. Block reason wins
|
|
@@ -2667,8 +2673,7 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2667
2673
|
reason = 'Security layer (DLP): reading a file that contains a secret is blocked. Blocked by SolonGate.';
|
|
2668
2674
|
opaRoute = 'black';
|
|
2669
2675
|
} else if (plan && plan.rewrite) {
|
|
2670
|
-
|
|
2671
|
-
rewriteTool(plan.rewrite);
|
|
2676
|
+
rewriteTool(plan.rewrite); // verdict first, update later
|
|
2672
2677
|
}
|
|
2673
2678
|
}
|
|
2674
2679
|
|
|
@@ -2723,7 +2728,13 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2723
2728
|
} catch {}
|
|
2724
2729
|
}
|
|
2725
2730
|
writeDenyFlag(toolName);
|
|
2726
|
-
|
|
2731
|
+
// The verdict is emitted BEFORE the hook self-update, never after.
|
|
2732
|
+
// maybeSelfUpdate() fetches three hooks at up to 5s each, and the
|
|
2733
|
+
// whole process carries an 8s backstop that force-exits with
|
|
2734
|
+
// `process.exitCode || 0` — i.e. ALLOW. So a slow network on the update
|
|
2735
|
+
// path turned a decided DENY into a permitted call. Measured: 8043ms,
|
|
2736
|
+
// exit 0, on a call DLP had already refused. Updating is a background
|
|
2737
|
+
// nicety; it still runs on every allow, which is nearly every call.
|
|
2727
2738
|
blockTool(reason);
|
|
2728
2739
|
}
|
|
2729
2740
|
} catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.83.
|
|
3
|
+
"version": "0.83.10",
|
|
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": {
|