@solongate/proxy 0.81.84 → 0.81.85

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.
@@ -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 = 41;
6539
+ var HOOK_VERSION = 42;
6540
6540
  function localLogsOnly(security) {
6541
6541
  if (security && typeof security === "object") {
6542
6542
  const l = security.localLogs;
@@ -6804,17 +6804,16 @@ function blockTool(reason) {
6804
6804
  }));
6805
6805
  sgFinish(0);
6806
6806
  } else {
6807
+ const msg = reason || "[SolonGate] Blocked by policy";
6807
6808
  process.stdout.write(JSON.stringify({
6808
6809
  hookSpecificOutput: {
6809
6810
  hookEventName: "PreToolUse",
6810
6811
  permissionDecision: "deny",
6811
- // Never empty: a deny with an empty reason becomes an is_error tool_result
6812
- // with empty content, which the API rejects and crashes the agent. Always
6813
- // carry a SolonGate-worded reason so the block says WHAT stopped it.
6814
- permissionDecisionReason: reason || "[SolonGate] Blocked by policy"
6812
+ permissionDecisionReason: msg
6815
6813
  }
6816
6814
  }));
6817
- sgFinish(0);
6815
+ process.stderr.write(msg + "\n");
6816
+ sgFinish(2);
6818
6817
  }
6819
6818
  }
6820
6819
  function allowTool() {
@@ -7924,7 +7923,8 @@ if (!REFRESH_MODE) {
7924
7923
  sgFinish(0);
7925
7924
  }
7926
7925
  process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: ghostHit } }));
7927
- sgFinish(0);
7926
+ process.stderr.write(ghostHit + "\n");
7927
+ sgFinish(2);
7928
7928
  }
7929
7929
  if (AGENT_TYPE !== "gemini-cli" && toolName === "Bash") {
7930
7930
  const rw = ghostListingRewrite(args, securityCfg.ghost);
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 = 41;
35
+ const HOOK_VERSION = 42;
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.
@@ -385,23 +385,25 @@ function blockTool(reason) {
385
385
  }));
386
386
  sgFinish(0);
387
387
  } else {
388
- // Claude Code — signal the block via the JSON PreToolUse protocol on stdout,
389
- // NOT exit code 2. On Windows the hook runs through PowerShell, which does not
390
- // reliably propagate a native process's exit code back to Claude Code, so an
391
- // `exit 2` block was being seen as a "non-blocking status code" and the tool ran
392
- // anyway. Claude only processes this JSON on exit 0 (JSON is ignored if you exit
393
- // 2), so we MUST exit 0 here. This is shell-independent and works everywhere.
388
+ // Claude Code — emit the JSON PreToolUse deny on stdout AND exit 2 (reason on
389
+ // stderr). Exit 2 is the ONLY block Claude Code enforces in EVERY permission
390
+ // mode: JSON + exit 0 goes through the NORMAL permission flow, which
391
+ // AUTO-ACCEPT ("auto mode") OVERRIDES the tool then runs despite the deny
392
+ // (observed: a DLP-blocked Write still landed on disk in auto mode). Exit 2
393
+ // hard-blocks BEFORE the permission system, in every mode. The JSON on stdout
394
+ // stays as the Windows/PowerShell fallback (there a native exit code may not
395
+ // propagate reliably; Claude Code parses the JSON instead). Reason is never
396
+ // empty — an empty deny becomes an is_error tool_result the API rejects.
397
+ const msg = reason || '[SolonGate] Blocked by policy';
394
398
  process.stdout.write(JSON.stringify({
395
399
  hookSpecificOutput: {
396
400
  hookEventName: 'PreToolUse',
397
401
  permissionDecision: 'deny',
398
- // Never empty: a deny with an empty reason becomes an is_error tool_result
399
- // with empty content, which the API rejects and crashes the agent. Always
400
- // carry a SolonGate-worded reason so the block says WHAT stopped it.
401
- permissionDecisionReason: reason || '[SolonGate] Blocked by policy',
402
+ permissionDecisionReason: msg,
402
403
  },
403
404
  }));
404
- sgFinish(0);
405
+ process.stderr.write(msg + '\n');
406
+ sgFinish(2);
405
407
  }
406
408
  }
407
409
 
@@ -1761,10 +1763,13 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
1761
1763
  } catch {}
1762
1764
  await maybeSelfUpdate();
1763
1765
  if (AGENT_TYPE === 'gemini-cli') { process.stdout.write(JSON.stringify({ decision: 'deny', reason: ghostHit })); sgFinish(0); }
1764
- // Claude Code: JSON deny on stdout + exit 0 (see blockTool exit codes are
1765
- // not reliable through PowerShell on Windows; JSON is ignored on exit 2).
1766
+ // Claude Code: JSON deny on stdout + exit 2 (reason on stderr). Exit 2 is
1767
+ // the only block enforced in auto-accept mode (see blockTool); the JSON is
1768
+ // the Windows fallback. ghostHit is a bare "No such file or directory", so
1769
+ // the stderr text keeps the stealth (looks like the path simply isn't there).
1766
1770
  process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ghostHit } }));
1767
- sgFinish(0);
1771
+ process.stderr.write(ghostHit + '\n');
1772
+ sgFinish(2);
1768
1773
  }
1769
1774
  // No direct hit: if this is a listing command, rewrite it so hidden
1770
1775
  // entries are filtered out of its output (Claude Code only).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.84",
3
+ "version": "0.81.85",
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": {