@solongate/proxy 0.83.18 → 0.83.20
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 -8
- package/hooks/guard.bundled.mjs +47 -30
- package/hooks/guard.mjs +56 -27
- package/hooks/stop.mjs +19 -88
- package/package.json +1 -1
package/hooks/audit.mjs
CHANGED
|
@@ -40,7 +40,7 @@ function projectFlagDir() {
|
|
|
40
40
|
// Bump on every audit hook change. The cloud serves the newest version; the guard
|
|
41
41
|
// hook installs it on its next run (no re-login needed). See guard.mjs
|
|
42
42
|
// fetchAndInstallHook / maybeSelfUpdate.
|
|
43
|
-
const HOOK_VERSION =
|
|
43
|
+
const HOOK_VERSION = 28;
|
|
44
44
|
|
|
45
45
|
function loadEnvKey(dir) {
|
|
46
46
|
try {
|
|
@@ -724,13 +724,6 @@ try { input += readFileSync(0, 'utf-8'); } catch {}
|
|
|
724
724
|
: v;
|
|
725
725
|
}
|
|
726
726
|
|
|
727
|
-
// Write flag so stop.mjs knows tool calls happened (skip text-only ALLOW)
|
|
728
|
-
try {
|
|
729
|
-
const flagDir = projectFlagDir();
|
|
730
|
-
mkdirSync(flagDir, { recursive: true });
|
|
731
|
-
writeFileSync(join(flagDir, '.last-tool-call'), Date.now().toString());
|
|
732
|
-
} catch {}
|
|
733
|
-
|
|
734
727
|
// Codex CANNOT rewrite tool output from PostToolUse: its output struct is
|
|
735
728
|
// deny_unknown_fields and the only rewrite field it defines
|
|
736
729
|
// (updatedMCPToolOutput) is explicitly rejected as unsupported — so emitting
|
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -6578,7 +6578,7 @@ function sweepLegacyFlagDir() {
|
|
|
6578
6578
|
} catch {
|
|
6579
6579
|
}
|
|
6580
6580
|
}
|
|
6581
|
-
var HOOK_VERSION =
|
|
6581
|
+
var HOOK_VERSION = 80;
|
|
6582
6582
|
function localLogsOnly(security) {
|
|
6583
6583
|
if (security !== void 0) {
|
|
6584
6584
|
const l = security && security.localLogs;
|
|
@@ -6606,6 +6606,17 @@ function accountMark() {
|
|
|
6606
6606
|
return "";
|
|
6607
6607
|
}
|
|
6608
6608
|
}
|
|
6609
|
+
function postAuditDetached(entry) {
|
|
6610
|
+
try {
|
|
6611
|
+
const payload = Buffer.from(JSON.stringify({
|
|
6612
|
+
url: API_URL + "/api/v1/audit-logs",
|
|
6613
|
+
headers: AUTH_HEADERS,
|
|
6614
|
+
body: entry
|
|
6615
|
+
}), "utf-8").toString("base64");
|
|
6616
|
+
spawn(process.execPath, [process.argv[1], "--sg-audit-post", payload], { detached: true, stdio: "ignore" }).unref();
|
|
6617
|
+
} catch {
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6609
6620
|
function writeLocalLog(security, entry) {
|
|
6610
6621
|
try {
|
|
6611
6622
|
const mark = accountMark();
|
|
@@ -6818,6 +6829,28 @@ var AGENT_TYPE = process.argv[2] || "claude-code";
|
|
|
6818
6829
|
var POLICY_SELECTOR = process.env.SOLONGATE_AGENT_ID || "";
|
|
6819
6830
|
var AGENT_ID = POLICY_SELECTOR || AGENT_TYPE;
|
|
6820
6831
|
var AGENT_NAME = process.env.SOLONGATE_AGENT_NAME || process.argv[3] || AGENT_TYPE;
|
|
6832
|
+
{
|
|
6833
|
+
const _ai = process.argv.indexOf("--sg-audit-post");
|
|
6834
|
+
if (_ai !== -1) {
|
|
6835
|
+
try {
|
|
6836
|
+
const _raw = Buffer.from(process.argv[_ai + 1] || "", "base64").toString("utf-8");
|
|
6837
|
+
const _p = JSON.parse(_raw);
|
|
6838
|
+
if (_p && _p.url && _p.body) {
|
|
6839
|
+
fetch(_p.url, {
|
|
6840
|
+
method: "POST",
|
|
6841
|
+
headers: { "Content-Type": "application/json", ..._p.headers || {} },
|
|
6842
|
+
body: JSON.stringify(_p.body),
|
|
6843
|
+
signal: AbortSignal.timeout(1e4)
|
|
6844
|
+
}).catch(() => {
|
|
6845
|
+
}).finally(() => process.exit(0));
|
|
6846
|
+
setTimeout(() => process.exit(0), 11e3).unref();
|
|
6847
|
+
} else
|
|
6848
|
+
process.exit(0);
|
|
6849
|
+
} catch {
|
|
6850
|
+
process.exit(0);
|
|
6851
|
+
}
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6821
6854
|
{
|
|
6822
6855
|
const _wi = process.argv.indexOf("--sg-log-write");
|
|
6823
6856
|
if (_wi !== -1) {
|
|
@@ -7096,7 +7129,6 @@ function writeDenyFlag(toolName) {
|
|
|
7096
7129
|
try {
|
|
7097
7130
|
const flagDir = projectFlagDir();
|
|
7098
7131
|
mkdirSync(flagDir, { recursive: true });
|
|
7099
|
-
writeFileSync(join(flagDir, ".last-tool-call"), Date.now().toString());
|
|
7100
7132
|
writeFileSync(join(flagDir, ".last-deny"), JSON.stringify({
|
|
7101
7133
|
tool: toolName,
|
|
7102
7134
|
ts: Date.now(),
|
|
@@ -8512,12 +8544,7 @@ if (!REFRESH_MODE) {
|
|
|
8512
8544
|
}
|
|
8513
8545
|
try {
|
|
8514
8546
|
if (!localLogsOnly(_sec))
|
|
8515
|
-
|
|
8516
|
-
method: "POST",
|
|
8517
|
-
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
8518
|
-
body: JSON.stringify(_logEntry),
|
|
8519
|
-
signal: AbortSignal.timeout(3e3)
|
|
8520
|
-
});
|
|
8547
|
+
postAuditDetached(_logEntry);
|
|
8521
8548
|
} catch {
|
|
8522
8549
|
}
|
|
8523
8550
|
if (AGENT_TYPE !== "codex")
|
|
@@ -8630,22 +8657,17 @@ if (!REFRESH_MODE) {
|
|
|
8630
8657
|
writeLocalLog(securityCfg, { ts: (/* @__PURE__ */ new Date()).toISOString(), tool: toolName, arguments: args, decision: "DENY", reason: "ghost path (hidden from agent)", permission: guessPermission(toolName), source: `${AGENT_TYPE}-guard`, agent_id: AGENT_TYPE, agent_name: AGENT_NAME, session_id: call.sessionId, evaluation_time_ms: Date.now() - _evalStart });
|
|
8631
8658
|
try {
|
|
8632
8659
|
if (!localLogsOnly(securityCfg))
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
agent_name: AGENT_NAME,
|
|
8645
|
-
session_id: call.sessionId,
|
|
8646
|
-
evaluation_time_ms: Date.now() - _evalStart
|
|
8647
|
-
}),
|
|
8648
|
-
signal: AbortSignal.timeout(3e3)
|
|
8660
|
+
postAuditDetached({
|
|
8661
|
+
tool: toolName,
|
|
8662
|
+
arguments: args,
|
|
8663
|
+
decision: "DENY",
|
|
8664
|
+
reason: "ghost path (hidden from agent)",
|
|
8665
|
+
permission: guessPermission(toolName),
|
|
8666
|
+
source: `${AGENT_TYPE}-guard`,
|
|
8667
|
+
agent_id: AGENT_TYPE,
|
|
8668
|
+
agent_name: AGENT_NAME,
|
|
8669
|
+
session_id: call.sessionId,
|
|
8670
|
+
evaluation_time_ms: Date.now() - _evalStart
|
|
8649
8671
|
});
|
|
8650
8672
|
} catch {
|
|
8651
8673
|
}
|
|
@@ -8729,12 +8751,7 @@ if (!REFRESH_MODE) {
|
|
|
8729
8751
|
};
|
|
8730
8752
|
writeLocalLog(securityCfg, { ts: (/* @__PURE__ */ new Date()).toISOString(), ...logEntry });
|
|
8731
8753
|
if (!localLogsOnly(securityCfg))
|
|
8732
|
-
|
|
8733
|
-
method: "POST",
|
|
8734
|
-
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
8735
|
-
body: JSON.stringify(logEntry),
|
|
8736
|
-
signal: AbortSignal.timeout(3e3)
|
|
8737
|
-
});
|
|
8754
|
+
postAuditDetached(logEntry);
|
|
8738
8755
|
} catch {
|
|
8739
8756
|
}
|
|
8740
8757
|
}
|
package/hooks/guard.mjs
CHANGED
|
@@ -81,7 +81,7 @@ import { createHash } from 'node:crypto';
|
|
|
81
81
|
// the installed hook self-updates when the cloud version is higher (see
|
|
82
82
|
// maybeSelfUpdate). This is what makes guard fixes propagate without a manual
|
|
83
83
|
// reinstall — the same trust model as the OPA WASM this hook already runs.
|
|
84
|
-
const HOOK_VERSION =
|
|
84
|
+
const HOOK_VERSION = 80;
|
|
85
85
|
|
|
86
86
|
// True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
|
|
87
87
|
// nothing is sent to the cloud audit log — local and cloud are one or the
|
|
@@ -170,6 +170,25 @@ function accountMark() {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Record a denial in the cloud WITHOUT making the agent wait for it.
|
|
175
|
+
*
|
|
176
|
+
* The verdict is the product; the audit line is bookkeeping. Awaiting the POST
|
|
177
|
+
* put a full network round trip between "blocked" and the agent hearing it —
|
|
178
|
+
* measured at 1643ms per denial, against 78ms with the API unreachable. Handing
|
|
179
|
+
* it to a detached child keeps the record and gives the time back.
|
|
180
|
+
*/
|
|
181
|
+
function postAuditDetached(entry) {
|
|
182
|
+
try {
|
|
183
|
+
const payload = Buffer.from(JSON.stringify({
|
|
184
|
+
url: API_URL + '/api/v1/audit-logs',
|
|
185
|
+
headers: AUTH_HEADERS,
|
|
186
|
+
body: entry,
|
|
187
|
+
}), 'utf-8').toString('base64');
|
|
188
|
+
spawn(process.execPath, [process.argv[1], '--sg-audit-post', payload], { detached: true, stdio: 'ignore' }).unref();
|
|
189
|
+
} catch { /* the denial still stands; only the record is at risk */ }
|
|
190
|
+
}
|
|
191
|
+
|
|
173
192
|
function writeLocalLog(security, entry) {
|
|
174
193
|
try {
|
|
175
194
|
const mark = accountMark();
|
|
@@ -460,6 +479,31 @@ const AGENT_NAME = process.env.SOLONGATE_AGENT_NAME || process.argv[3] || AGENT_
|
|
|
460
479
|
// lands even if Claude Code kills the parent hook mid-write during a concurrent
|
|
461
480
|
// denial burst. Handled FIRST and exits immediately — the child never loads the
|
|
462
481
|
// heavy guard logic below.
|
|
482
|
+
// Survivable CLOUD writer, same idea. A denial used to `await` its audit POST
|
|
483
|
+
// before telling the agent it was blocked, so the agent sat waiting on a network
|
|
484
|
+
// round trip to be told "no": measured at 1643ms per denial against 78ms with
|
|
485
|
+
// the API unreachable. The record still has to land, so it is handed to a
|
|
486
|
+
// detached child rather than dropped — the verdict goes out at once and the POST
|
|
487
|
+
// finishes on its own.
|
|
488
|
+
{
|
|
489
|
+
const _ai = process.argv.indexOf('--sg-audit-post');
|
|
490
|
+
if (_ai !== -1) {
|
|
491
|
+
try {
|
|
492
|
+
const _raw = Buffer.from(process.argv[_ai + 1] || '', 'base64').toString('utf-8');
|
|
493
|
+
const _p = JSON.parse(_raw);
|
|
494
|
+
if (_p && _p.url && _p.body) {
|
|
495
|
+
fetch(_p.url, {
|
|
496
|
+
method: 'POST',
|
|
497
|
+
headers: { 'Content-Type': 'application/json', ...(_p.headers || {}) },
|
|
498
|
+
body: JSON.stringify(_p.body),
|
|
499
|
+
signal: AbortSignal.timeout(10000),
|
|
500
|
+
}).catch(() => {}).finally(() => process.exit(0));
|
|
501
|
+
setTimeout(() => process.exit(0), 11000).unref();
|
|
502
|
+
} else process.exit(0);
|
|
503
|
+
} catch { process.exit(0); }
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
463
507
|
{
|
|
464
508
|
const _wi = process.argv.indexOf('--sg-log-write');
|
|
465
509
|
if (_wi !== -1) {
|
|
@@ -826,12 +870,12 @@ function callFingerprint(s) {
|
|
|
826
870
|
return h.toString(16);
|
|
827
871
|
}
|
|
828
872
|
|
|
829
|
-
//
|
|
873
|
+
// Records WHICH call was denied, so the PostToolUse audit hook does not log a
|
|
874
|
+
// second, ALLOW-looking entry for a call the guard already blocked.
|
|
830
875
|
function writeDenyFlag(toolName) {
|
|
831
876
|
try {
|
|
832
877
|
const flagDir = projectFlagDir();
|
|
833
878
|
mkdirSync(flagDir, { recursive: true });
|
|
834
|
-
writeFileSync(join(flagDir, '.last-tool-call'), Date.now().toString());
|
|
835
879
|
// Deny-specific flag: audit.mjs (PostToolUse) reads it to avoid logging a
|
|
836
880
|
// second, ALLOW-looking entry for a call the guard already denied.
|
|
837
881
|
//
|
|
@@ -2523,12 +2567,7 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2523
2567
|
};
|
|
2524
2568
|
try { writeLocalLog(_sec, { ts: new Date().toISOString(), ..._logEntry }); } catch {}
|
|
2525
2569
|
try {
|
|
2526
|
-
if (!localLogsOnly(_sec))
|
|
2527
|
-
method: 'POST',
|
|
2528
|
-
headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
|
|
2529
|
-
body: JSON.stringify(_logEntry),
|
|
2530
|
-
signal: AbortSignal.timeout(3000),
|
|
2531
|
-
});
|
|
2570
|
+
if (!localLogsOnly(_sec)) postAuditDetached(_logEntry);
|
|
2532
2571
|
} catch {}
|
|
2533
2572
|
// Not on Codex: its block reason IS the hook's stderr (see the ROUTE
|
|
2534
2573
|
// write at the end of the slow path for the full rationale).
|
|
@@ -2676,18 +2715,13 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2676
2715
|
try { writeDenyFlag(toolName); } catch {}
|
|
2677
2716
|
writeLocalLog(securityCfg, { ts: new Date().toISOString(), tool: toolName, arguments: args, decision: 'DENY', reason: 'ghost path (hidden from agent)', permission: guessPermission(toolName), source: `${AGENT_TYPE}-guard`, agent_id: AGENT_TYPE, agent_name: AGENT_NAME, session_id: call.sessionId, evaluation_time_ms: Date.now() - _evalStart });
|
|
2678
2717
|
try {
|
|
2679
|
-
if (!localLogsOnly(securityCfg))
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
source: `${AGENT_TYPE}-guard`, agent_id: AGENT_TYPE, agent_name: AGENT_NAME,
|
|
2687
|
-
session_id: call.sessionId,
|
|
2688
|
-
evaluation_time_ms: Date.now() - _evalStart,
|
|
2689
|
-
}),
|
|
2690
|
-
signal: AbortSignal.timeout(3000),
|
|
2718
|
+
if (!localLogsOnly(securityCfg)) postAuditDetached({
|
|
2719
|
+
tool: toolName, arguments: args, decision: 'DENY',
|
|
2720
|
+
reason: 'ghost path (hidden from agent)',
|
|
2721
|
+
permission: guessPermission(toolName),
|
|
2722
|
+
source: `${AGENT_TYPE}-guard`, agent_id: AGENT_TYPE, agent_name: AGENT_NAME,
|
|
2723
|
+
session_id: call.sessionId,
|
|
2724
|
+
evaluation_time_ms: Date.now() - _evalStart,
|
|
2691
2725
|
});
|
|
2692
2726
|
} catch {}
|
|
2693
2727
|
// NOT maybeSelfUpdate() first: see the deny at the end of this flow.
|
|
@@ -2819,12 +2853,7 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2819
2853
|
writeLocalLog(securityCfg, { ts: new Date().toISOString(), ...logEntry });
|
|
2820
2854
|
// PI hook layer removed — piResult fields no longer attached.
|
|
2821
2855
|
// Local-only mode: keep the log on the user's machine, skip the cloud.
|
|
2822
|
-
if (!localLogsOnly(securityCfg))
|
|
2823
|
-
method: 'POST',
|
|
2824
|
-
headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
|
|
2825
|
-
body: JSON.stringify(logEntry),
|
|
2826
|
-
signal: AbortSignal.timeout(3000),
|
|
2827
|
-
});
|
|
2856
|
+
if (!localLogsOnly(securityCfg)) postAuditDetached(logEntry);
|
|
2828
2857
|
} catch {}
|
|
2829
2858
|
}
|
|
2830
2859
|
writeDenyFlag(toolName);
|
package/hooks/stop.mjs
CHANGED
|
@@ -1,91 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* SolonGate Stop Hook (Stop event)
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* SolonGate Stop Hook (Stop event) — intentionally does nothing.
|
|
4
|
+
*
|
|
5
|
+
* It used to POST one synthetic audit row named `_response` whenever a turn
|
|
6
|
+
* ended with no tool calls, to record "the agent replied with text only". That
|
|
7
|
+
* was wrong twice over:
|
|
8
|
+
*
|
|
9
|
+
* - `_response` is not a tool, and it sat in the TOOL CALL log. It showed up
|
|
10
|
+
* as a row in the live stream and as an entry in the dashboard's top-tools
|
|
11
|
+
* list, and it inflated the call count with something no policy can act on.
|
|
12
|
+
* - it decided whether to write by looking for a flag file left by the audit
|
|
13
|
+
* hook, so any drift in where that flag lived turned "text-only turn" into
|
|
14
|
+
* "every turn" — which is exactly what happened when the per-call scratch
|
|
15
|
+
* moved out of the working directory.
|
|
16
|
+
*
|
|
17
|
+
* The registration is kept (it costs one process exit per turn and removing it
|
|
18
|
+
* would need every client's hook config rewritten), but nothing is logged. If a
|
|
19
|
+
* text-only turn is ever worth recording, it belongs in its own place, not in
|
|
20
|
+
* the record of what the agent was allowed to run.
|
|
7
21
|
*/
|
|
8
|
-
|
|
9
|
-
import { resolve, join } from 'node:path';
|
|
10
|
-
import { homedir } from 'node:os';
|
|
11
|
-
|
|
12
|
-
function loadEnvKey(dir) {
|
|
13
|
-
try {
|
|
14
|
-
const envPath = resolve(dir, '.env');
|
|
15
|
-
if (!existsSync(envPath)) return {};
|
|
16
|
-
const lines = readFileSync(envPath, 'utf-8').split('\n');
|
|
17
|
-
const env = {};
|
|
18
|
-
for (const line of lines) {
|
|
19
|
-
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
20
|
-
if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, '').trim();
|
|
21
|
-
}
|
|
22
|
-
return env;
|
|
23
|
-
} catch { return {}; }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Global cloud config written at login (~/.solongate/cloud-guard.json). This hook
|
|
27
|
-
// used to read ONLY the environment and a project env file, so on a normally
|
|
28
|
-
// paired device it silently logged nothing — and where an env file did exist, a
|
|
29
|
-
// stale key in it shadowed the login. Same order as the other hooks now:
|
|
30
|
-
// process env -> login -> project env file.
|
|
31
|
-
function loadGlobalCloudConfig() {
|
|
32
|
-
try {
|
|
33
|
-
const p = resolve(homedir(), '.solongate', 'cloud-guard.json');
|
|
34
|
-
if (!existsSync(p)) return {};
|
|
35
|
-
const cfg = JSON.parse(readFileSync(p, 'utf-8'));
|
|
36
|
-
return (cfg && typeof cfg === 'object') ? cfg : {};
|
|
37
|
-
} catch { return {}; }
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const dotenv = loadEnvKey(process.cwd());
|
|
41
|
-
const globalCfg = loadGlobalCloudConfig();
|
|
42
|
-
const API_KEY = process.env.SOLONGATE_API_KEY || globalCfg.apiKey || dotenv.SOLONGATE_API_KEY || '';
|
|
43
|
-
const API_URL = process.env.SOLONGATE_API_URL || globalCfg.apiUrl || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
|
|
44
|
-
|
|
45
|
-
// Agent identity from CLI args: node stop.mjs <agent_id> <agent_name>
|
|
46
|
-
const AGENT_ID = process.argv[2] || 'claude-code';
|
|
47
|
-
const AGENT_NAME = process.argv[3] || 'Claude Code';
|
|
48
|
-
|
|
49
|
-
if (!API_KEY || !API_KEY.startsWith('sg_live_')) process.exit(0);
|
|
50
|
-
|
|
51
|
-
// Flag file: audit.mjs writes this when a tool call was logged in this turn.
|
|
52
|
-
// If the flag exists, tool calls were made → skip (already logged).
|
|
53
|
-
// If the flag doesn't exist, no tool calls → log 1 ALLOW for the text response.
|
|
54
|
-
const flagDir = resolve('.solongate');
|
|
55
|
-
const flagFile = join(flagDir, '.last-tool-call');
|
|
56
|
-
|
|
57
|
-
let input = '';
|
|
58
|
-
process.stdin.on('data', c => input += c);
|
|
59
|
-
process.stdin.on('end', async () => {
|
|
60
|
-
try {
|
|
61
|
-
// Check if tool calls were made in this turn
|
|
62
|
-
if (existsSync(flagFile)) {
|
|
63
|
-
// Tool calls happened → audit.mjs already logged them. Clean up flag.
|
|
64
|
-
try { unlinkSync(flagFile); } catch {}
|
|
65
|
-
process.exit(0);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// No tool calls → log 1 ALLOW for the text-only response (fire-and-forget)
|
|
69
|
-
fetch(`${API_URL}/api/v1/audit-logs`, {
|
|
70
|
-
method: 'POST',
|
|
71
|
-
headers: {
|
|
72
|
-
'Authorization': `Bearer ${API_KEY}`,
|
|
73
|
-
'Content-Type': 'application/json',
|
|
74
|
-
},
|
|
75
|
-
body: JSON.stringify({
|
|
76
|
-
tool: '_response',
|
|
77
|
-
arguments: {},
|
|
78
|
-
decision: 'ALLOW',
|
|
79
|
-
reason: 'text response (no tool calls)',
|
|
80
|
-
source: `${AGENT_ID}-hook`,
|
|
81
|
-
evaluationTimeMs: 0,
|
|
82
|
-
agent_id: AGENT_ID,
|
|
83
|
-
agent_name: AGENT_NAME,
|
|
84
|
-
}),
|
|
85
|
-
signal: AbortSignal.timeout(5000),
|
|
86
|
-
}).catch(() => {}).finally(() => process.exit(0));
|
|
87
|
-
setTimeout(() => process.exit(0), 3000);
|
|
88
|
-
} catch {
|
|
89
|
-
process.exit(0);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
22
|
+
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.83.
|
|
3
|
+
"version": "0.83.20",
|
|
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": {
|