@solongate/proxy 0.82.17 → 0.82.18
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 +39 -20
- package/hooks/guard.mjs +43 -20
- package/package.json +1 -1
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 = 46;
|
|
6540
6540
|
function localLogsOnly(security) {
|
|
6541
6541
|
if (security && typeof security === "object") {
|
|
6542
6542
|
const l = security.localLogs;
|
|
@@ -6789,6 +6789,7 @@ if (REFRESH_MODE) {
|
|
|
6789
6789
|
}
|
|
6790
6790
|
var SG_DONE = Symbol("sg-done");
|
|
6791
6791
|
var _sgDone = false;
|
|
6792
|
+
var _decisionEmitted = false;
|
|
6792
6793
|
function sgFinish(code) {
|
|
6793
6794
|
if (!_sgDone) {
|
|
6794
6795
|
_sgDone = true;
|
|
@@ -6798,34 +6799,46 @@ function sgFinish(code) {
|
|
|
6798
6799
|
}
|
|
6799
6800
|
function blockTool(reason) {
|
|
6800
6801
|
if (AGENT_TYPE === "antigravity") {
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6802
|
+
if (!_decisionEmitted) {
|
|
6803
|
+
_decisionEmitted = true;
|
|
6804
|
+
process.stdout.write(JSON.stringify({
|
|
6805
|
+
allow_tool: false,
|
|
6806
|
+
deny_reason: `[SolonGate] ${reason}`
|
|
6807
|
+
}));
|
|
6808
|
+
}
|
|
6805
6809
|
sgFinish(0);
|
|
6806
6810
|
} else {
|
|
6807
6811
|
const msg = reason || "[SolonGate] Blocked by policy";
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6812
|
+
if (!_decisionEmitted) {
|
|
6813
|
+
_decisionEmitted = true;
|
|
6814
|
+
process.stdout.write(JSON.stringify({
|
|
6815
|
+
hookSpecificOutput: {
|
|
6816
|
+
hookEventName: "PreToolUse",
|
|
6817
|
+
permissionDecision: "deny",
|
|
6818
|
+
permissionDecisionReason: msg
|
|
6819
|
+
}
|
|
6820
|
+
}));
|
|
6821
|
+
process.stderr.write(msg + "\n");
|
|
6822
|
+
}
|
|
6816
6823
|
sgFinish(2);
|
|
6817
6824
|
}
|
|
6818
6825
|
}
|
|
6819
6826
|
function allowTool() {
|
|
6820
6827
|
if (AGENT_TYPE === "antigravity") {
|
|
6821
|
-
|
|
6828
|
+
if (!_decisionEmitted) {
|
|
6829
|
+
_decisionEmitted = true;
|
|
6830
|
+
process.stdout.write(JSON.stringify({ allow_tool: true }));
|
|
6831
|
+
}
|
|
6822
6832
|
}
|
|
6823
6833
|
sgFinish(0);
|
|
6824
6834
|
}
|
|
6825
6835
|
function rewriteTool(updatedInput) {
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6836
|
+
if (!_decisionEmitted) {
|
|
6837
|
+
_decisionEmitted = true;
|
|
6838
|
+
process.stdout.write(JSON.stringify({
|
|
6839
|
+
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", updatedInput }
|
|
6840
|
+
}));
|
|
6841
|
+
}
|
|
6829
6842
|
sgFinish(0);
|
|
6830
6843
|
}
|
|
6831
6844
|
function writeDenyFlag(toolName) {
|
|
@@ -7966,11 +7979,17 @@ if (!REFRESH_MODE) {
|
|
|
7966
7979
|
}
|
|
7967
7980
|
await maybeSelfUpdate();
|
|
7968
7981
|
if (AGENT_TYPE === "antigravity") {
|
|
7969
|
-
|
|
7982
|
+
if (!_decisionEmitted) {
|
|
7983
|
+
_decisionEmitted = true;
|
|
7984
|
+
process.stdout.write(JSON.stringify({ allow_tool: false, deny_reason: ghostHit }));
|
|
7985
|
+
}
|
|
7970
7986
|
sgFinish(0);
|
|
7971
7987
|
}
|
|
7972
|
-
|
|
7973
|
-
|
|
7988
|
+
if (!_decisionEmitted) {
|
|
7989
|
+
_decisionEmitted = true;
|
|
7990
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: ghostHit } }));
|
|
7991
|
+
process.stderr.write(ghostHit + "\n");
|
|
7992
|
+
}
|
|
7974
7993
|
sgFinish(2);
|
|
7975
7994
|
}
|
|
7976
7995
|
if (AGENT_TYPE !== "antigravity" && toolName === "Bash") {
|
package/hooks/guard.mjs
CHANGED
|
@@ -32,7 +32,7 @@ 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 = 46;
|
|
36
36
|
|
|
37
37
|
// True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
|
|
38
38
|
// nothing is sent to the cloud audit log.
|
|
@@ -365,6 +365,14 @@ if (REFRESH_MODE) { try { setTimeout(() => { try { process.exit(process.exitCode
|
|
|
365
365
|
// the threadpool has drained, so exit() is safe.
|
|
366
366
|
const SG_DONE = Symbol('sg-done');
|
|
367
367
|
let _sgDone = false;
|
|
368
|
+
// The decision JSON must be written to stdout AT MOST ONCE. The handler can reach
|
|
369
|
+
// a terminal allowTool() even after a block already emitted its deny (the SG_DONE
|
|
370
|
+
// unwind is swallowed on some paths), which for Claude Code was harmless
|
|
371
|
+
// (allowTool writes nothing there) but for Antigravity appended a second
|
|
372
|
+
// {"allow_tool":true} right after {"allow_tool":false} — agy then parsed the
|
|
373
|
+
// trailing allow (or failed on the double object) and ran the blocked tool. This
|
|
374
|
+
// latch guarantees the FIRST decision is the only one on stdout.
|
|
375
|
+
let _decisionEmitted = false;
|
|
368
376
|
// Terminate WITHOUT ever calling process.exit() on the hot path. On Windows + Node
|
|
369
377
|
// 24, process.exit() called in (or right after) the same tick a cloud fetch()
|
|
370
378
|
// settled aborts with `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING),
|
|
@@ -384,10 +392,13 @@ function sgFinish(code) {
|
|
|
384
392
|
|
|
385
393
|
function blockTool(reason) {
|
|
386
394
|
if (AGENT_TYPE === 'antigravity') {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
395
|
+
if (!_decisionEmitted) {
|
|
396
|
+
_decisionEmitted = true;
|
|
397
|
+
process.stdout.write(JSON.stringify({
|
|
398
|
+
allow_tool: false,
|
|
399
|
+
deny_reason: `[SolonGate] ${reason}`,
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
391
402
|
sgFinish(0);
|
|
392
403
|
} else {
|
|
393
404
|
// Claude Code — emit the JSON PreToolUse deny on stdout AND exit 2 (reason on
|
|
@@ -400,21 +411,27 @@ function blockTool(reason) {
|
|
|
400
411
|
// propagate reliably; Claude Code parses the JSON instead). Reason is never
|
|
401
412
|
// empty — an empty deny becomes an is_error tool_result the API rejects.
|
|
402
413
|
const msg = reason || '[SolonGate] Blocked by policy';
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
414
|
+
if (!_decisionEmitted) {
|
|
415
|
+
_decisionEmitted = true;
|
|
416
|
+
process.stdout.write(JSON.stringify({
|
|
417
|
+
hookSpecificOutput: {
|
|
418
|
+
hookEventName: 'PreToolUse',
|
|
419
|
+
permissionDecision: 'deny',
|
|
420
|
+
permissionDecisionReason: msg,
|
|
421
|
+
},
|
|
422
|
+
}));
|
|
423
|
+
process.stderr.write(msg + '\n');
|
|
424
|
+
}
|
|
411
425
|
sgFinish(2);
|
|
412
426
|
}
|
|
413
427
|
}
|
|
414
428
|
|
|
415
429
|
function allowTool() {
|
|
416
430
|
if (AGENT_TYPE === 'antigravity') {
|
|
417
|
-
|
|
431
|
+
if (!_decisionEmitted) {
|
|
432
|
+
_decisionEmitted = true;
|
|
433
|
+
process.stdout.write(JSON.stringify({ allow_tool: true }));
|
|
434
|
+
}
|
|
418
435
|
}
|
|
419
436
|
sgFinish(0);
|
|
420
437
|
}
|
|
@@ -423,9 +440,12 @@ function allowTool() {
|
|
|
423
440
|
// ghost layer to rewrite a listing command so hidden entries are filtered out of
|
|
424
441
|
// its output — the agent never sees them. Claude Code only.
|
|
425
442
|
function rewriteTool(updatedInput) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
443
|
+
if (!_decisionEmitted) {
|
|
444
|
+
_decisionEmitted = true;
|
|
445
|
+
process.stdout.write(JSON.stringify({
|
|
446
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow', updatedInput },
|
|
447
|
+
}));
|
|
448
|
+
}
|
|
429
449
|
sgFinish(0);
|
|
430
450
|
}
|
|
431
451
|
|
|
@@ -1821,13 +1841,16 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
1821
1841
|
});
|
|
1822
1842
|
} catch {}
|
|
1823
1843
|
await maybeSelfUpdate();
|
|
1824
|
-
if (AGENT_TYPE === 'antigravity') { process.stdout.write(JSON.stringify({ allow_tool: false, deny_reason: ghostHit })); sgFinish(0); }
|
|
1844
|
+
if (AGENT_TYPE === 'antigravity') { if (!_decisionEmitted) { _decisionEmitted = true; process.stdout.write(JSON.stringify({ allow_tool: false, deny_reason: ghostHit })); } sgFinish(0); }
|
|
1825
1845
|
// Claude Code: JSON deny on stdout + exit 2 (reason on stderr). Exit 2 is
|
|
1826
1846
|
// the only block enforced in auto-accept mode (see blockTool); the JSON is
|
|
1827
1847
|
// the Windows fallback. ghostHit is a bare "No such file or directory", so
|
|
1828
1848
|
// the stderr text keeps the stealth (looks like the path simply isn't there).
|
|
1829
|
-
|
|
1830
|
-
|
|
1849
|
+
if (!_decisionEmitted) {
|
|
1850
|
+
_decisionEmitted = true;
|
|
1851
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ghostHit } }));
|
|
1852
|
+
process.stderr.write(ghostHit + '\n');
|
|
1853
|
+
}
|
|
1831
1854
|
sgFinish(2);
|
|
1832
1855
|
}
|
|
1833
1856
|
// No direct hit: if this is a listing command, rewrite it so hidden
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.82.
|
|
3
|
+
"version": "0.82.18",
|
|
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": {
|