@solongate/proxy 0.81.3 → 0.81.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/audit.mjs +16 -11
- package/hooks/guard.bundled.mjs +17 -25
- package/hooks/guard.mjs +18 -12
- package/package.json +1 -1
package/hooks/audit.mjs
CHANGED
|
@@ -399,6 +399,10 @@ let input = '';
|
|
|
399
399
|
// EOF avoids creating that handle, so the exits below tear down cleanly.
|
|
400
400
|
try { input += readFileSync(0, 'utf-8'); } catch {}
|
|
401
401
|
;(async () => {
|
|
402
|
+
// Safety backstop ONLY: the normal path exits by natural drain. Force-exit if the
|
|
403
|
+
// loop fails to drain — 8s > the 5s fetch timeout, so the threadpool is idle and
|
|
404
|
+
// exit() can't hit the Windows UV_HANDLE_CLOSING abort.
|
|
405
|
+
try { setTimeout(() => { try { process.exit(process.exitCode || 0); } catch {} }, 8000).unref(); } catch {}
|
|
402
406
|
try {
|
|
403
407
|
const data = JSON.parse(input);
|
|
404
408
|
let EMITTED_PAYLOAD = null;
|
|
@@ -531,12 +535,13 @@ try { input += readFileSync(0, 'utf-8'); } catch {}
|
|
|
531
535
|
// on the write's flush callback (and on the fire-and-forget audit POST).
|
|
532
536
|
let flushed = (typeof EMITTED_PAYLOAD !== 'string');
|
|
533
537
|
let fetchDone = false;
|
|
534
|
-
//
|
|
535
|
-
//
|
|
536
|
-
// `Assertion failed: !(handle->flags &
|
|
537
|
-
//
|
|
538
|
-
//
|
|
539
|
-
|
|
538
|
+
// Set the exit code and let the event loop drain naturally — do NOT call
|
|
539
|
+
// process.exit(). On Windows + Node 24, process.exit() in the same tick the
|
|
540
|
+
// audit-log fetch settled aborts with `Assertion failed: !(handle->flags &
|
|
541
|
+
// UV_HANDLE_CLOSING), file src\win\async.c` (a threadpool DNS worker is still
|
|
542
|
+
// mid-uv_async_send when exit() force-closes the loop's async handle). undici
|
|
543
|
+
// unrefs idle sockets, so Node exits on its own ~1ms after the fetch settles.
|
|
544
|
+
const maybeExit = () => { if (flushed && fetchDone) { process.exitCode = 0; } };
|
|
540
545
|
if (typeof EMITTED_PAYLOAD === 'string') {
|
|
541
546
|
try { process.stdout.write(EMITTED_PAYLOAD, () => { flushed = true; maybeExit(); }); }
|
|
542
547
|
catch { flushed = true; }
|
|
@@ -586,11 +591,11 @@ try { input += readFileSync(0, 'utf-8'); } catch {}
|
|
|
586
591
|
signal: AbortSignal.timeout(5000),
|
|
587
592
|
}).catch(() => {}).finally(() => { fetchDone = true; maybeExit(); });
|
|
588
593
|
}
|
|
589
|
-
//
|
|
590
|
-
//
|
|
591
|
-
|
|
594
|
+
// Safety backstop ONLY (unref'd, 8s > the 5s fetch timeout): the normal path
|
|
595
|
+
// exits by natural drain ~1ms after the fetch settles. This fires only if the
|
|
596
|
+
// loop somehow fails to drain — by 8s the threadpool is idle, so exit() is safe.
|
|
597
|
+
try { setTimeout(() => { try { process.exit(process.exitCode || 0); } catch {} }, 8000).unref(); } catch {}
|
|
592
598
|
} catch {
|
|
593
|
-
|
|
594
|
-
try { setImmediate(() => { try { process.exit(0); } catch {} }); } catch { try { process.exit(0); } catch {} }
|
|
599
|
+
process.exitCode = 0;
|
|
595
600
|
}
|
|
596
601
|
})();
|
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -6757,21 +6757,17 @@ async function refreshPolicyCache() {
|
|
|
6757
6757
|
}
|
|
6758
6758
|
}
|
|
6759
6759
|
if (REFRESH_MODE) {
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
try {
|
|
6763
|
-
setImmediate(() => {
|
|
6764
|
-
try {
|
|
6765
|
-
process.exit(0);
|
|
6766
|
-
} catch {
|
|
6767
|
-
}
|
|
6768
|
-
});
|
|
6769
|
-
} catch {
|
|
6760
|
+
try {
|
|
6761
|
+
setTimeout(() => {
|
|
6770
6762
|
try {
|
|
6771
|
-
process.exit(0);
|
|
6763
|
+
process.exit(process.exitCode || 0);
|
|
6772
6764
|
} catch {
|
|
6773
6765
|
}
|
|
6774
|
-
}
|
|
6766
|
+
}, 8e3).unref();
|
|
6767
|
+
} catch {
|
|
6768
|
+
}
|
|
6769
|
+
refreshPolicyCache().finally(() => {
|
|
6770
|
+
process.exitCode = 0;
|
|
6775
6771
|
});
|
|
6776
6772
|
}
|
|
6777
6773
|
var SG_DONE = Symbol("sg-done");
|
|
@@ -6780,19 +6776,6 @@ function sgFinish(code) {
|
|
|
6780
6776
|
if (!_sgDone) {
|
|
6781
6777
|
_sgDone = true;
|
|
6782
6778
|
process.exitCode = code;
|
|
6783
|
-
try {
|
|
6784
|
-
setImmediate(() => {
|
|
6785
|
-
try {
|
|
6786
|
-
process.exit(process.exitCode || 0);
|
|
6787
|
-
} catch {
|
|
6788
|
-
}
|
|
6789
|
-
});
|
|
6790
|
-
} catch {
|
|
6791
|
-
try {
|
|
6792
|
-
process.exit(code);
|
|
6793
|
-
} catch {
|
|
6794
|
-
}
|
|
6795
|
-
}
|
|
6796
6779
|
}
|
|
6797
6780
|
throw SG_DONE;
|
|
6798
6781
|
}
|
|
@@ -7698,6 +7681,15 @@ if (!REFRESH_MODE) {
|
|
|
7698
7681
|
}
|
|
7699
7682
|
}
|
|
7700
7683
|
(async () => {
|
|
7684
|
+
try {
|
|
7685
|
+
setTimeout(() => {
|
|
7686
|
+
try {
|
|
7687
|
+
process.exit(process.exitCode || 0);
|
|
7688
|
+
} catch {
|
|
7689
|
+
}
|
|
7690
|
+
}, 8e3).unref();
|
|
7691
|
+
} catch {
|
|
7692
|
+
}
|
|
7701
7693
|
try {
|
|
7702
7694
|
if (REFRESH_MODE)
|
|
7703
7695
|
return;
|
package/hooks/guard.mjs
CHANGED
|
@@ -281,7 +281,7 @@ async function refreshPolicyCache() {
|
|
|
281
281
|
try { writeFileSync(cacheFile, JSON.stringify({ _ts: Date.now(), policy, selfProtect, security, hookVersions })); } catch {}
|
|
282
282
|
} catch {}
|
|
283
283
|
}
|
|
284
|
-
if (REFRESH_MODE) {
|
|
284
|
+
if (REFRESH_MODE) { try { setTimeout(() => { try { process.exit(process.exitCode || 0); } catch {} }, 8000).unref(); } catch {} refreshPolicyCache().finally(() => { process.exitCode = 0; }); }
|
|
285
285
|
|
|
286
286
|
// ── Per-tool block/allow output ──
|
|
287
287
|
// Response format depends on the agent:
|
|
@@ -301,18 +301,20 @@ if (REFRESH_MODE) { refreshPolicyCache().finally(() => { process.exitCode = 0; t
|
|
|
301
301
|
// the threadpool has drained, so exit() is safe.
|
|
302
302
|
const SG_DONE = Symbol('sg-done');
|
|
303
303
|
let _sgDone = false;
|
|
304
|
+
// Terminate WITHOUT ever calling process.exit() on the hot path. On Windows + Node
|
|
305
|
+
// 24, process.exit() called in (or right after) the same tick a cloud fetch()
|
|
306
|
+
// settled aborts with `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING),
|
|
307
|
+
// file src\win\async.c`: a libuv threadpool worker (DNS) is still mid-uv_async_send
|
|
308
|
+
// when exit() force-closes the loop's async handle. The abort replaces exit code 2,
|
|
309
|
+
// so Claude Code sees a non-blocking hook failure and runs the tool anyway (guard
|
|
310
|
+
// logs DENY, never blocks). Instead we just set process.exitCode and let the event
|
|
311
|
+
// loop drain — undici unrefs idle sockets, so Node exits on its own within ~1ms of
|
|
312
|
+
// the work finishing, cleanly (no forced teardown → no abort). A latch preserves
|
|
313
|
+
// the FIRST code so a later allowTool() on fall-through can't overwrite a block; an
|
|
314
|
+
// unref'd backstop armed at the top of the handler is the only place exit() may run,
|
|
315
|
+
// and only long after every fetch has settled.
|
|
304
316
|
function sgFinish(code) {
|
|
305
|
-
if (!_sgDone) {
|
|
306
|
-
_sgDone = true;
|
|
307
|
-
process.exitCode = code;
|
|
308
|
-
// Defer the real exit by ONE loop tick. process.exit() called synchronously in
|
|
309
|
-
// the same tick a fetch just settled aborts on Windows: a threadpool worker is
|
|
310
|
-
// still mid-uv_async_send when exit() closes the loop's async handle. setImmediate
|
|
311
|
-
// lets libuv drain that completion first, then we exit — ~0ms, and it still hard-
|
|
312
|
-
// exits (killing undici keep-alive sockets) instead of waiting on them.
|
|
313
|
-
try { setImmediate(() => { try { process.exit(process.exitCode || 0); } catch {} }); }
|
|
314
|
-
catch { try { process.exit(code); } catch {} }
|
|
315
|
-
}
|
|
317
|
+
if (!_sgDone) { _sgDone = true; process.exitCode = code; }
|
|
316
318
|
throw SG_DONE;
|
|
317
319
|
}
|
|
318
320
|
|
|
@@ -1440,6 +1442,10 @@ function readReferencedFiles(args, cwd) {
|
|
|
1440
1442
|
// inside a stream callback, so the loop tears down cleanly.
|
|
1441
1443
|
if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
1442
1444
|
;(async () => {
|
|
1445
|
+
// Safety backstop ONLY: the normal path exits by natural drain (~1ms after work
|
|
1446
|
+
// finishes). If the loop somehow fails to drain, force-exit — 8s is well past every
|
|
1447
|
+
// fetch timeout (≤3s) so the threadpool is idle and exit() can't hit the abort.
|
|
1448
|
+
try { setTimeout(() => { try { process.exit(process.exitCode || 0); } catch {} }, 8000).unref(); } catch {}
|
|
1443
1449
|
try {
|
|
1444
1450
|
// Background-refresh invocation has no tool call to evaluate — it already ran
|
|
1445
1451
|
// refreshPolicyCache() at startup; do nothing on the (empty) stdin.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.81.
|
|
3
|
+
"version": "0.81.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": {
|