@solongate/proxy 0.83.51 → 0.83.52

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.
@@ -6579,7 +6579,7 @@ function sweepLegacyFlagDir() {
6579
6579
  } catch {
6580
6580
  }
6581
6581
  }
6582
- var HOOK_VERSION = 89;
6582
+ var HOOK_VERSION = 90;
6583
6583
  var SG_REFRESH_ARG = process.argv.includes("--sg-refresh-policy");
6584
6584
  var SG_STDIN = SG_REFRESH_ARG ? "" : (() => {
6585
6585
  try {
@@ -8813,6 +8813,7 @@ if (!REFRESH_MODE) {
8813
8813
  if (process.env.SOLONGATE_DEBUG) {
8814
8814
  }
8815
8815
  let reason = selfProtectEnabled ? tamperCheck(toolName, args) : null;
8816
+ let pendingGhostPatch = null;
8816
8817
  if (!reason && securityCfg && securityCfg.ghost) {
8817
8818
  const ghostHit = ghostBlock(toolName, args, securityCfg.ghost);
8818
8819
  if (ghostHit) {
@@ -8842,7 +8843,7 @@ if (!REFRESH_MODE) {
8842
8843
  if (toolName === "Bash" || toolName === "run_command" || guessPermission(toolName) === "EXECUTE") {
8843
8844
  const rw = ghostListingRewrite(args, securityCfg.ghost);
8844
8845
  if (rw)
8845
- rewriteTool({ command: rw });
8846
+ pendingGhostPatch = { command: rw };
8846
8847
  }
8847
8848
  }
8848
8849
  if (!reason)
@@ -8876,9 +8877,12 @@ if (!REFRESH_MODE) {
8876
8877
  reason = "Security layer (DLP): reading a file that contains a secret is blocked. Blocked by SolonGate.";
8877
8878
  opaRoute = "black";
8878
8879
  } else if (plan && plan.rewrite) {
8879
- rewriteTool(plan.rewrite);
8880
+ rewriteTool({ ...pendingGhostPatch || {}, ...plan.rewrite });
8881
+ pendingGhostPatch = null;
8880
8882
  }
8881
8883
  }
8884
+ if (!reason && pendingGhostPatch)
8885
+ rewriteTool(pendingGhostPatch);
8882
8886
  if (AGENT_TYPE !== "codex")
8883
8887
  process.stderr.write(`[SolonGate ROUTE] ${opaRoute.toUpperCase()} (${reason ? "block" : "allow"})
8884
8888
  `);
package/hooks/guard.mjs CHANGED
@@ -82,7 +82,7 @@ import { createHash } from 'node:crypto';
82
82
  // the installed hook self-updates when the cloud version is higher (see
83
83
  // maybeSelfUpdate). This is what makes guard fixes propagate without a manual
84
84
  // reinstall — the same trust model as the OPA WASM this hook already runs.
85
- const HOOK_VERSION = 89;
85
+ const HOOK_VERSION = 90;
86
86
 
87
87
  // ── The Go guard, when there is one and it is the right one ──────────────────
88
88
  //
@@ -2968,6 +2968,9 @@ if (!REFRESH_MODE) { input += SG_STDIN; }
2968
2968
  // Tamper / self-protection — runs before policy eval. ON by default; the
2969
2969
  // per-project cloud setting can disable it (fail safe: stays on if unread).
2970
2970
  let reason = selfProtectEnabled ? tamperCheck(toolName, args) : null;
2971
+ // A ghost listing rewrite decided BELOW but applied at the very end, so that
2972
+ // rewriting a call is never the same thing as approving it.
2973
+ let pendingGhostPatch = null;
2971
2974
  // Ghost paths — handled BEFORE the other layers and emitted as a STEALTH
2972
2975
  // block: a mutating op on a hidden path is denied with a bare OS-style
2973
2976
  // "No such file or directory" and NOTHING else (no ROUTE line, no SolonGate
@@ -3000,7 +3003,13 @@ if (!REFRESH_MODE) { input += SG_STDIN; }
3000
3003
  // PreToolUse `overwrite` replaces the command before it runs (hooks.md).
3001
3004
  if (toolName === 'Bash' || toolName === 'run_command' || guessPermission(toolName) === 'EXECUTE') {
3002
3005
  const rw = ghostListingRewrite(args, securityCfg.ghost);
3003
- if (rw) rewriteTool({ command: rw }); // verdict first, update later
3006
+ // HELD, not emitted. Emitting a rewrite ends the hook, so doing it here
3007
+ // meant the DLP argument scan, the rate limit and the POLICY never ran
3008
+ // for any call ghost touched — under a whitelist, `ls` in a project with
3009
+ // a ghost route was allowed whatever the policy said. Same reasoning as
3010
+ // the DLP read-redaction below, which was moved after policy eval for
3011
+ // exactly this failure. Applied at the end, once every layer has spoken.
3012
+ if (rw) pendingGhostPatch = { command: rw };
3004
3013
  }
3005
3014
  }
3006
3015
  // Extra security layers run after tamper, before policy. Block reason wins
@@ -3070,10 +3079,18 @@ if (!REFRESH_MODE) { input += SG_STDIN; }
3070
3079
  reason = 'Security layer (DLP): reading a file that contains a secret is blocked. Blocked by SolonGate.';
3071
3080
  opaRoute = 'black';
3072
3081
  } else if (plan && plan.rewrite) {
3073
- rewriteTool(plan.rewrite); // verdict first, update later
3082
+ // Both patches can be live at once (a listing ghost filters, pointed at a
3083
+ // redacted copy). Redaction wins any key they share: it is the one that
3084
+ // keeps a secret out of the agent's hands.
3085
+ rewriteTool({ ...(pendingGhostPatch || {}), ...plan.rewrite }); // verdict first, update later
3086
+ pendingGhostPatch = null;
3074
3087
  }
3075
3088
  }
3076
3089
 
3090
+ // The held ghost rewrite, applied only now that tamper, DLP, the rate limit
3091
+ // and the policy have all had their say and none of them blocked.
3092
+ if (!reason && pendingGhostPatch) rewriteTool(pendingGhostPatch);
3093
+
3077
3094
  // Diagnostic line for the terminal. NOT written on Codex: there the hook's
3078
3095
  // ENTIRE stderr becomes the block reason shown to the model (exit 2), so this
3079
3096
  // would prefix every denial with "[SolonGate ROUTE] BLACK (block)".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.83.51",
3
+ "version": "0.83.52",
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": {
@@ -61,12 +61,12 @@
61
61
  "node": ">=20.0.0"
62
62
  },
63
63
  "optionalDependencies": {
64
- "@solongate/guard-linux-x64": "0.83.51",
65
- "@solongate/guard-linux-arm64": "0.83.51",
66
- "@solongate/guard-darwin-x64": "0.83.51",
67
- "@solongate/guard-darwin-arm64": "0.83.51",
68
- "@solongate/guard-win32-x64": "0.83.51",
69
- "@solongate/guard-win32-arm64": "0.83.51"
64
+ "@solongate/guard-linux-x64": "0.83.52",
65
+ "@solongate/guard-linux-arm64": "0.83.52",
66
+ "@solongate/guard-darwin-x64": "0.83.52",
67
+ "@solongate/guard-darwin-arm64": "0.83.52",
68
+ "@solongate/guard-win32-x64": "0.83.52",
69
+ "@solongate/guard-win32-arm64": "0.83.52"
70
70
  },
71
71
  "dependencies": {
72
72
  "@modelcontextprotocol/sdk": "^1.26.0",