@solongate/proxy 0.59.2 → 0.59.4
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 +99 -54
- package/hooks/guard.mjs +68 -24
- package/hooks/shield.mjs +18 -2
- package/package.json +1 -1
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -34,9 +34,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
34
|
mod
|
|
35
35
|
));
|
|
36
36
|
|
|
37
|
-
//
|
|
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
|
-
"
|
|
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
|
-
//
|
|
60
|
+
// node_modules/.pnpm/sprintf-js@1.1.3/node_modules/sprintf-js/src/sprintf.js
|
|
61
61
|
var require_sprintf = __commonJS({
|
|
62
|
-
"
|
|
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
|
-
//
|
|
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
|
-
"
|
|
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
|
-
//
|
|
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
|
-
"
|
|
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
|
-
//
|
|
280
|
+
// node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/PlainValue-516d5bc2.js
|
|
281
281
|
var require_PlainValue_516d5bc2 = __commonJS({
|
|
282
|
-
"
|
|
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
|
-
//
|
|
1045
|
+
// node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/parse-cst.js
|
|
1046
1046
|
var require_parse_cst = __commonJS({
|
|
1047
|
-
"
|
|
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
|
-
//
|
|
2538
|
+
// node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/resolveSeq-95613e94.js
|
|
2539
2539
|
var require_resolveSeq_95613e94 = __commonJS({
|
|
2540
|
-
"
|
|
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
|
-
//
|
|
4515
|
+
// node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/warnings-793925ce.js
|
|
4516
4516
|
var require_warnings_793925ce = __commonJS({
|
|
4517
|
-
"
|
|
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
|
-
//
|
|
4882
|
+
// node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Schema-bcc6c2d7.js
|
|
4883
4883
|
var require_Schema_bcc6c2d7 = __commonJS({
|
|
4884
|
-
"
|
|
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
|
-
//
|
|
5348
|
+
// node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/Document-a8d0fbf9.js
|
|
5349
5349
|
var require_Document_a8d0fbf9 = __commonJS({
|
|
5350
|
-
"
|
|
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
|
-
//
|
|
6031
|
+
// node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/dist/index.js
|
|
6032
6032
|
var require_dist = __commonJS({
|
|
6033
|
-
"
|
|
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
|
-
//
|
|
6102
|
+
// node_modules/.pnpm/yaml@1.10.3/node_modules/yaml/index.js
|
|
6103
6103
|
var require_yaml = __commonJS({
|
|
6104
|
-
"
|
|
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
|
-
//
|
|
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
|
-
"
|
|
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
|
-
//
|
|
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
|
-
"
|
|
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
|
-
//
|
|
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
|
-
"
|
|
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
|
-
//
|
|
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,21 @@ __export(src_exports, {
|
|
|
6522
6522
|
});
|
|
6523
6523
|
var import_opa, loadPolicy, src_default;
|
|
6524
6524
|
var init_src = __esm({
|
|
6525
|
-
"
|
|
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
|
+
import { spawn } from "node:child_process";
|
|
6534
6535
|
import { resolve, join, dirname, isAbsolute } from "node:path";
|
|
6535
6536
|
import { homedir } from "node:os";
|
|
6536
6537
|
import { gunzipSync } from "node:zlib";
|
|
6537
6538
|
import { createHash } from "node:crypto";
|
|
6538
|
-
var HOOK_VERSION =
|
|
6539
|
+
var HOOK_VERSION = 33;
|
|
6539
6540
|
function localLogsOnly(security) {
|
|
6540
6541
|
const l = security && security.localLogs;
|
|
6541
6542
|
return !!(l && l.enabled && typeof l.path === "string" && l.path.trim());
|
|
@@ -6713,6 +6714,51 @@ var AGENT_TYPE = process.argv[2] || "claude-code";
|
|
|
6713
6714
|
var POLICY_SELECTOR = process.env.SOLONGATE_AGENT_ID || "";
|
|
6714
6715
|
var AGENT_ID = POLICY_SELECTOR || AGENT_TYPE;
|
|
6715
6716
|
var AGENT_NAME = process.env.SOLONGATE_AGENT_NAME || process.argv[3] || AGENT_TYPE;
|
|
6717
|
+
var REFRESH_MODE = process.argv.includes("--sg-refresh-policy");
|
|
6718
|
+
async function refreshPolicyCache() {
|
|
6719
|
+
try {
|
|
6720
|
+
const agentKey = (AGENT_ID || "default").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
6721
|
+
const cacheFile = join(resolve(homedir(), ".solongate"), ".policy-cache-" + agentKey + ".json");
|
|
6722
|
+
let selfProtect = true, security = null, hookVersions = null, policy = null;
|
|
6723
|
+
try {
|
|
6724
|
+
if (existsSync(cacheFile)) {
|
|
6725
|
+
const c = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
6726
|
+
if (c) {
|
|
6727
|
+
policy = c.policy ?? null;
|
|
6728
|
+
if (typeof c.selfProtect === "boolean")
|
|
6729
|
+
selfProtect = c.selfProtect;
|
|
6730
|
+
if (c.security !== void 0)
|
|
6731
|
+
security = c.security;
|
|
6732
|
+
if (c.hookVersions)
|
|
6733
|
+
hookVersions = c.hookVersions;
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
} catch {
|
|
6737
|
+
}
|
|
6738
|
+
try {
|
|
6739
|
+
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) });
|
|
6740
|
+
if (res.ok) {
|
|
6741
|
+
const body = await res.json();
|
|
6742
|
+
if (typeof body?.self_protection_enabled === "boolean")
|
|
6743
|
+
selfProtect = body.self_protection_enabled;
|
|
6744
|
+
if (body?.security !== void 0)
|
|
6745
|
+
security = body.security;
|
|
6746
|
+
if (body?.hook_versions && typeof body.hook_versions === "object")
|
|
6747
|
+
hookVersions = body.hook_versions;
|
|
6748
|
+
policy = body && body.policy ? body.policy : null;
|
|
6749
|
+
}
|
|
6750
|
+
} catch {
|
|
6751
|
+
}
|
|
6752
|
+
try {
|
|
6753
|
+
writeFileSync(cacheFile, JSON.stringify({ _ts: Date.now(), policy, selfProtect, security, hookVersions }));
|
|
6754
|
+
} catch {
|
|
6755
|
+
}
|
|
6756
|
+
} catch {
|
|
6757
|
+
}
|
|
6758
|
+
}
|
|
6759
|
+
if (REFRESH_MODE) {
|
|
6760
|
+
refreshPolicyCache().finally(() => process.exit(0));
|
|
6761
|
+
}
|
|
6716
6762
|
function blockTool(reason) {
|
|
6717
6763
|
if (AGENT_TYPE === "gemini-cli") {
|
|
6718
6764
|
process.stdout.write(JSON.stringify({
|
|
@@ -7610,6 +7656,8 @@ function readReferencedFiles(args, cwd) {
|
|
|
7610
7656
|
}
|
|
7611
7657
|
process.stdin.on("data", (c) => input += c);
|
|
7612
7658
|
process.stdin.on("end", async () => {
|
|
7659
|
+
if (REFRESH_MODE)
|
|
7660
|
+
return;
|
|
7613
7661
|
if (process.env.SOLONGATE_DEBUG) {
|
|
7614
7662
|
}
|
|
7615
7663
|
if (!API_KEY) {
|
|
@@ -7650,12 +7698,14 @@ process.stdin.on("end", async () => {
|
|
|
7650
7698
|
try {
|
|
7651
7699
|
let dashboardPolicy = null;
|
|
7652
7700
|
let staleCache = null;
|
|
7701
|
+
let refreshDue = true;
|
|
7653
7702
|
try {
|
|
7654
7703
|
if (existsSync(policyCacheFile)) {
|
|
7655
7704
|
const cached = JSON.parse(readFileSync(policyCacheFile, "utf-8"));
|
|
7656
7705
|
if (cached && cached.policy)
|
|
7657
7706
|
staleCache = cached;
|
|
7658
7707
|
if (cached && cached._ts && Date.now() - cached._ts < POLICY_TTL_MS) {
|
|
7708
|
+
refreshDue = false;
|
|
7659
7709
|
if (cached.policy)
|
|
7660
7710
|
dashboardPolicy = cached.policy;
|
|
7661
7711
|
if (typeof cached.selfProtect === "boolean")
|
|
@@ -7668,26 +7718,7 @@ process.stdin.on("end", async () => {
|
|
|
7668
7718
|
}
|
|
7669
7719
|
} catch {
|
|
7670
7720
|
}
|
|
7671
|
-
if (
|
|
7672
|
-
try {
|
|
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) });
|
|
7674
|
-
if (res.ok) {
|
|
7675
|
-
const body = await res.json();
|
|
7676
|
-
if (typeof body?.self_protection_enabled === "boolean")
|
|
7677
|
-
selfProtectEnabled = body.self_protection_enabled;
|
|
7678
|
-
if (body?.security !== void 0)
|
|
7679
|
-
securityCfg = body.security;
|
|
7680
|
-
if (body?.hook_versions && typeof body.hook_versions === "object")
|
|
7681
|
-
CLOUD_HOOK_VERSIONS = body.hook_versions;
|
|
7682
|
-
if (body && body.policy)
|
|
7683
|
-
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
|
-
}
|
|
7689
|
-
} catch {
|
|
7690
|
-
}
|
|
7721
|
+
if (refreshDue) {
|
|
7691
7722
|
if (!dashboardPolicy && staleCache) {
|
|
7692
7723
|
dashboardPolicy = staleCache.policy;
|
|
7693
7724
|
if (typeof staleCache.selfProtect === "boolean")
|
|
@@ -7697,6 +7728,20 @@ process.stdin.on("end", async () => {
|
|
|
7697
7728
|
if (staleCache.hookVersions)
|
|
7698
7729
|
CLOUD_HOOK_VERSIONS = staleCache.hookVersions;
|
|
7699
7730
|
}
|
|
7731
|
+
if (API_KEY) {
|
|
7732
|
+
try {
|
|
7733
|
+
const lock = join(resolve(homedir(), ".solongate"), ".policy-refresh-" + agentKey + ".lock");
|
|
7734
|
+
const due = !existsSync(lock) || Date.now() - statSync(lock).mtimeMs > 3e3;
|
|
7735
|
+
if (due) {
|
|
7736
|
+
try {
|
|
7737
|
+
writeFileSync(lock, String(Date.now()));
|
|
7738
|
+
} catch {
|
|
7739
|
+
}
|
|
7740
|
+
spawn(process.execPath, [process.argv[1], AGENT_TYPE, AGENT_NAME, "--sg-refresh-policy"], { detached: true, stdio: "ignore", env: process.env }).unref();
|
|
7741
|
+
}
|
|
7742
|
+
} catch {
|
|
7743
|
+
}
|
|
7744
|
+
}
|
|
7700
7745
|
}
|
|
7701
7746
|
if (process.env.SOLONGATE_DEBUG) {
|
|
7702
7747
|
}
|
package/hooks/guard.mjs
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* Auto-installed by: npx @solongate/proxy init --global
|
|
23
23
|
*/
|
|
24
24
|
import { readFileSync, existsSync, statSync, writeFileSync, mkdirSync, chmodSync, renameSync, appendFileSync } from 'node:fs';
|
|
25
|
+
import { spawn } from 'node:child_process';
|
|
25
26
|
import { resolve, join, dirname, isAbsolute } from 'node:path';
|
|
26
27
|
import { homedir } from 'node:os';
|
|
27
28
|
import { gunzipSync } from 'node:zlib';
|
|
@@ -31,7 +32,7 @@ import { createHash } from 'node:crypto';
|
|
|
31
32
|
// the installed hook self-updates when the cloud version is higher (see
|
|
32
33
|
// maybeSelfUpdate). This is what makes guard fixes propagate without a manual
|
|
33
34
|
// reinstall — the same trust model as the OPA WASM this hook already runs.
|
|
34
|
-
const HOOK_VERSION =
|
|
35
|
+
const HOOK_VERSION = 33;
|
|
35
36
|
|
|
36
37
|
// True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
|
|
37
38
|
// nothing is sent to the cloud audit log.
|
|
@@ -248,6 +249,40 @@ const POLICY_SELECTOR = process.env.SOLONGATE_AGENT_ID || '';
|
|
|
248
249
|
const AGENT_ID = POLICY_SELECTOR || AGENT_TYPE;
|
|
249
250
|
const AGENT_NAME = process.env.SOLONGATE_AGENT_NAME || process.argv[3] || AGENT_TYPE;
|
|
250
251
|
|
|
252
|
+
// ── Background policy refresh (stale-while-revalidate) ──
|
|
253
|
+
// The hot path NEVER blocks on the network: when the policy cache is stale it
|
|
254
|
+
// serves the cached (or local) policy INSTANTLY and spawns this detached mode to
|
|
255
|
+
// fetch a fresh policy and rewrite the cache for the NEXT call. Result: fast tool
|
|
256
|
+
// calls AND ~one-call propagation of policy changes — no long-TTL tradeoff.
|
|
257
|
+
const REFRESH_MODE = process.argv.includes('--sg-refresh-policy');
|
|
258
|
+
async function refreshPolicyCache() {
|
|
259
|
+
try {
|
|
260
|
+
const agentKey = (AGENT_ID || 'default').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
261
|
+
const cacheFile = join(resolve(homedir(), '.solongate'), '.policy-cache-' + agentKey + '.json');
|
|
262
|
+
// Preserve existing fields so a failed fetch never erases the last-known-good.
|
|
263
|
+
let selfProtect = true, security = null, hookVersions = null, policy = null;
|
|
264
|
+
try {
|
|
265
|
+
if (existsSync(cacheFile)) {
|
|
266
|
+
const c = JSON.parse(readFileSync(cacheFile, 'utf-8'));
|
|
267
|
+
if (c) { policy = c.policy ?? null; if (typeof c.selfProtect === 'boolean') selfProtect = c.selfProtect; if (c.security !== undefined) security = c.security; if (c.hookVersions) hookVersions = c.hookVersions; }
|
|
268
|
+
}
|
|
269
|
+
} catch {}
|
|
270
|
+
try {
|
|
271
|
+
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) });
|
|
272
|
+
if (res.ok) {
|
|
273
|
+
const body = await res.json();
|
|
274
|
+
if (typeof body?.self_protection_enabled === 'boolean') selfProtect = body.self_protection_enabled;
|
|
275
|
+
if (body?.security !== undefined) security = body.security;
|
|
276
|
+
if (body?.hook_versions && typeof body.hook_versions === 'object') hookVersions = body.hook_versions;
|
|
277
|
+
policy = (body && body.policy) ? body.policy : null;
|
|
278
|
+
}
|
|
279
|
+
} catch {}
|
|
280
|
+
// Always advance _ts (success or failure) so the hot path backs off between refreshes.
|
|
281
|
+
try { writeFileSync(cacheFile, JSON.stringify({ _ts: Date.now(), policy, selfProtect, security, hookVersions })); } catch {}
|
|
282
|
+
} catch {}
|
|
283
|
+
}
|
|
284
|
+
if (REFRESH_MODE) { refreshPolicyCache().finally(() => process.exit(0)); }
|
|
285
|
+
|
|
251
286
|
// ── Per-tool block/allow output ──
|
|
252
287
|
// Response format depends on the agent:
|
|
253
288
|
// Claude Code: exit 2 + stderr = BLOCK, exit 0 = ALLOW
|
|
@@ -1369,6 +1404,9 @@ function readReferencedFiles(args, cwd) {
|
|
|
1369
1404
|
|
|
1370
1405
|
process.stdin.on('data', c => input += c);
|
|
1371
1406
|
process.stdin.on('end', async () => {
|
|
1407
|
+
// Background-refresh invocation has no tool call to evaluate — it already ran
|
|
1408
|
+
// refreshPolicyCache() at startup; do nothing on the (empty) stdin.
|
|
1409
|
+
if (REFRESH_MODE) return;
|
|
1372
1410
|
// No policy selected => no enforcement. A plain launch (no SOLONGATE_AGENT_ID)
|
|
1373
1411
|
// is intentionally unrestricted.
|
|
1374
1412
|
if (process.env.SOLONGATE_DEBUG) {
|
|
@@ -1443,15 +1481,22 @@ process.stdin.on('end', async () => {
|
|
|
1443
1481
|
try {
|
|
1444
1482
|
let dashboardPolicy = null;
|
|
1445
1483
|
// 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.
|
|
1484
|
+
// when the API is slow/unreachable instead of blocking or dropping the policy.
|
|
1448
1485
|
let staleCache = null;
|
|
1449
|
-
//
|
|
1486
|
+
// Whether the TTL has elapsed since our last refresh ATTEMPT. Gating the
|
|
1487
|
+
// fetch on the attempt timestamp (not on "do we already have a policy") is
|
|
1488
|
+
// what makes the back-off actually work when the endpoint is slow: a failed
|
|
1489
|
+
// or timed-out attempt still advances _ts, so we don't re-hit it — and eat
|
|
1490
|
+
// the full 2s timeout — on every single call. Before this, _ts only moved on
|
|
1491
|
+
// a SUCCESSFUL fetch, so a slow /policies/active meant ~2s on EVERY call and
|
|
1492
|
+
// the 10s TTL never engaged (the cache never looked "fresh").
|
|
1493
|
+
let refreshDue = true;
|
|
1450
1494
|
try {
|
|
1451
1495
|
if (existsSync(policyCacheFile)) {
|
|
1452
1496
|
const cached = JSON.parse(readFileSync(policyCacheFile, 'utf-8'));
|
|
1453
1497
|
if (cached && cached.policy) staleCache = cached;
|
|
1454
1498
|
if (cached && cached._ts && Date.now() - cached._ts < POLICY_TTL_MS) {
|
|
1499
|
+
refreshDue = false;
|
|
1455
1500
|
if (cached.policy) dashboardPolicy = cached.policy;
|
|
1456
1501
|
if (typeof cached.selfProtect === 'boolean') selfProtectEnabled = cached.selfProtect;
|
|
1457
1502
|
if (cached.security !== undefined) securityCfg = cached.security;
|
|
@@ -1459,32 +1504,31 @@ process.stdin.on('end', async () => {
|
|
|
1459
1504
|
}
|
|
1460
1505
|
}
|
|
1461
1506
|
} catch {}
|
|
1462
|
-
//
|
|
1463
|
-
if
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
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) });
|
|
1470
|
-
if (res.ok) {
|
|
1471
|
-
const body = await res.json();
|
|
1472
|
-
// Capture the self-protection flag even when no cloud policy is set.
|
|
1473
|
-
if (typeof body?.self_protection_enabled === 'boolean') selfProtectEnabled = body.self_protection_enabled;
|
|
1474
|
-
if (body?.security !== undefined) securityCfg = body.security;
|
|
1475
|
-
if (body?.hook_versions && typeof body.hook_versions === 'object') CLOUD_HOOK_VERSIONS = body.hook_versions;
|
|
1476
|
-
if (body && body.policy) dashboardPolicy = body.policy;
|
|
1477
|
-
try { writeFileSync(policyCacheFile, JSON.stringify({ _ts: Date.now(), policy: dashboardPolicy || null, selfProtect: selfProtectEnabled, security: securityCfg, hookVersions: CLOUD_HOOK_VERSIONS })); } catch {}
|
|
1478
|
-
}
|
|
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.
|
|
1507
|
+
// Cache stale → DO NOT block the tool on the network. Serve the last-known-good
|
|
1508
|
+
// policy right now (or the local policy.json fallback below if there is none)
|
|
1509
|
+
// and kick off a DETACHED background refresh that rewrites the cache for the
|
|
1510
|
+
// NEXT call. Stale-while-revalidate: tool calls stay instant (~ms), yet a
|
|
1511
|
+
// policy change still lands within ~one call, because the fetch runs in
|
|
1512
|
+
// parallel instead of on the hot path.
|
|
1513
|
+
if (refreshDue) {
|
|
1482
1514
|
if (!dashboardPolicy && staleCache) {
|
|
1483
1515
|
dashboardPolicy = staleCache.policy;
|
|
1484
1516
|
if (typeof staleCache.selfProtect === 'boolean') selfProtectEnabled = staleCache.selfProtect;
|
|
1485
1517
|
if (staleCache.security !== undefined) securityCfg = staleCache.security;
|
|
1486
1518
|
if (staleCache.hookVersions) CLOUD_HOOK_VERSIONS = staleCache.hookVersions;
|
|
1487
1519
|
}
|
|
1520
|
+
// Debounce: at most one background refresh in flight per ~3s, so a burst of
|
|
1521
|
+
// stale calls (or several agents sharing this cache) doesn't spawn a swarm.
|
|
1522
|
+
if (API_KEY) {
|
|
1523
|
+
try {
|
|
1524
|
+
const lock = join(resolve(homedir(), '.solongate'), '.policy-refresh-' + agentKey + '.lock');
|
|
1525
|
+
const due = !existsSync(lock) || (Date.now() - statSync(lock).mtimeMs) > 3000;
|
|
1526
|
+
if (due) {
|
|
1527
|
+
try { writeFileSync(lock, String(Date.now())); } catch {}
|
|
1528
|
+
spawn(process.execPath, [process.argv[1], AGENT_TYPE, AGENT_NAME, '--sg-refresh-policy'], { detached: true, stdio: 'ignore', env: process.env }).unref();
|
|
1529
|
+
}
|
|
1530
|
+
} catch {}
|
|
1531
|
+
}
|
|
1488
1532
|
}
|
|
1489
1533
|
|
|
1490
1534
|
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 =
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.59.4",
|
|
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": {
|