@solongate/proxy 0.82.17 → 0.82.19

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/dist/index.js CHANGED
@@ -13964,6 +13964,9 @@ var init_pull_push = __esm({
13964
13964
 
13965
13965
  // src/index.ts
13966
13966
  init_config();
13967
+ import { readFileSync as readFileSync15 } from "fs";
13968
+ import { fileURLToPath as fileURLToPath4 } from "url";
13969
+ import { dirname as dirname4, join as join17 } from "path";
13967
13970
 
13968
13971
  // src/proxy.ts
13969
13972
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -16987,7 +16990,8 @@ ${msg.content.text}`;
16987
16990
  // src/index.ts
16988
16991
  init_cli_utils();
16989
16992
  var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["shield", "create", "inject", "pull", "push", "list", "ls", "logs-server", "local-logs", "policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent", "doctor", "watch", "keys", "mcp", "alerts", "webhooks", "dataroom"]);
16990
- var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "");
16993
+ var CLI_INFO_ARGS = /* @__PURE__ */ new Set(["login", "help", "--help", "-h", "--version", "-v", "version"]);
16994
+ var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "") || CLI_INFO_ARGS.has(process.argv[2] ?? "");
16991
16995
  if (!IS_HUMAN_CLI) {
16992
16996
  console.log = (...args) => {
16993
16997
  process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
@@ -17002,24 +17006,76 @@ if (!IS_HUMAN_CLI) {
17002
17006
  `);
17003
17007
  };
17004
17008
  }
17009
+ var PKG_VERSION = (() => {
17010
+ try {
17011
+ const p = join17(dirname4(fileURLToPath4(import.meta.url)), "..", "package.json");
17012
+ return JSON.parse(readFileSync15(p, "utf-8")).version || "unknown";
17013
+ } catch {
17014
+ return "unknown";
17015
+ }
17016
+ })();
17005
17017
  function printWelcome() {
17006
17018
  console.log("");
17007
- console.log(` ${c.bold}${c.blue4}SolonGate${c.reset} ${c.dim}\u2014 secure gateway for your AI agents${c.reset}`);
17019
+ console.log(` ${c.bold}${c.blue4}SolonGate${c.reset} ${c.dim}secure gateway for your AI agents${c.reset}`);
17008
17020
  console.log("");
17009
17021
  console.log(" Get started with one command:");
17010
17022
  console.log("");
17011
- console.log(` ${c.cyan}solongate${c.reset}`);
17023
+ console.log(` ${c.cyan}solongate${c.reset} ${c.dim}open the dataroom (login, policies, audit, settings)${c.reset}`);
17024
+ console.log(` ${c.cyan}solongate --help${c.reset} ${c.dim}list every command${c.reset}`);
17012
17025
  console.log("");
17013
- console.log(` ${c.dim}This opens the dataroom; log in from the Accounts panel to pair this${c.reset}`);
17014
- console.log(` ${c.dim}device and protect every Claude Code session with your cloud policy.${c.reset}`);
17026
+ console.log(` ${c.dim}Open the dataroom, log in from the Accounts panel to pair this${c.reset}`);
17027
+ console.log(` ${c.dim}device, then the guard protects every AI session with your policy.${c.reset}`);
17015
17028
  console.log(` ${c.dim}Manage it at ${c.reset}${c.cyan}https://dashboard.solongate.com${c.reset}`);
17016
17029
  console.log("");
17017
- console.log(` ${c.dim}Using local log storage? Show it live in the dashboard with${c.reset}`);
17018
- console.log(` ${c.cyan}solongate logs-server${c.reset}`);
17030
+ }
17031
+ function printHelp3() {
17032
+ const row = (name, desc) => console.log(` ${c.cyan}${name.padEnd(13)}${c.reset}${c.dim}${desc}${c.reset}`);
17033
+ console.log("");
17034
+ console.log(` ${c.bold}${c.blue4}SolonGate${c.reset} ${c.dim}secure gateway for your AI agents${c.reset}`);
17035
+ console.log("");
17036
+ console.log(` ${c.dim}Usage:${c.reset} ${c.cyan}solongate${c.reset} ${c.dim}[command]${c.reset}`);
17037
+ console.log(` ${c.dim}With no command, opens the dataroom: accounts, policies, rate limits,${c.reset}`);
17038
+ console.log(` ${c.dim}DLP, audit and settings in one terminal UI.${c.reset}`);
17039
+ console.log("");
17040
+ console.log(` ${c.bold}Setup & status${c.reset}`);
17041
+ row("doctor", "health check: login, active policy, guard, local logs");
17042
+ row("create", "scaffold a guarded MCP server in the current folder");
17043
+ row("logs-server", "serve local audit logs to the dashboard");
17044
+ console.log("");
17045
+ console.log(` ${c.bold}Security config${c.reset}`);
17046
+ row("policy", "view and manage policies");
17047
+ row("ratelimit", "view and manage rate limits");
17048
+ row("dlp", "view and manage DLP (secret) rules");
17049
+ row("alerts", "view and manage alerts");
17050
+ row("webhooks", "view and manage webhooks");
17051
+ console.log("");
17052
+ console.log(` ${c.bold}Monitoring${c.reset}`);
17053
+ row("audit", "browse the audit log of tool-call decisions");
17054
+ row("stats", "usage statistics");
17055
+ row("watch", "live-tail tool calls as they happen");
17056
+ row("agents", "list agents and sessions");
17057
+ console.log("");
17058
+ console.log(` ${c.bold}Advanced${c.reset}`);
17059
+ row("keys", "manage API keys");
17060
+ row("mcp", "manage MCP servers");
17061
+ row("shield", "wrap a command with LLM-path secret redaction: shield -- <cmd>");
17062
+ row("pull", "pull a cloud policy to a local file");
17063
+ row("push", "push a local policy file to the cloud");
17064
+ row("list", "list cloud policies");
17065
+ console.log("");
17066
+ console.log(` ${c.dim}Dashboard: ${c.reset}${c.cyan}https://dashboard.solongate.com${c.reset}`);
17019
17067
  console.log("");
17020
17068
  }
17021
17069
  async function main4() {
17022
17070
  const subcommand = process.argv[2];
17071
+ if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
17072
+ printHelp3();
17073
+ return;
17074
+ }
17075
+ if (subcommand === "--version" || subcommand === "-v" || subcommand === "version") {
17076
+ console.log(PKG_VERSION);
17077
+ return;
17078
+ }
17023
17079
  if (IS_HUMAN_CLI) {
17024
17080
  const tuiBound = subcommand === "dataroom" || process.argv.length <= 2 && process.stdout.isTTY && process.stdin.isTTY;
17025
17081
  if (!tuiBound) {
@@ -17054,7 +17110,7 @@ async function main4() {
17054
17110
  if (subcommand === "login") {
17055
17111
  console.log("");
17056
17112
  console.log(` ${c.dim}\`solongate login\` was removed.${c.reset} Run ${c.cyan}solongate${c.reset} and add your`);
17057
- console.log(` account from the Accounts panel \u2014 the guard installs and updates from there.`);
17113
+ console.log(` account from the Accounts panel. The guard installs and updates from there.`);
17058
17114
  console.log("");
17059
17115
  return;
17060
17116
  }
@@ -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 = 45;
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
- process.stdout.write(JSON.stringify({
6802
- allow_tool: false,
6803
- deny_reason: `[SolonGate] ${reason}`
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
- process.stdout.write(JSON.stringify({
6809
- hookSpecificOutput: {
6810
- hookEventName: "PreToolUse",
6811
- permissionDecision: "deny",
6812
- permissionDecisionReason: msg
6813
- }
6814
- }));
6815
- process.stderr.write(msg + "\n");
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
- process.stdout.write(JSON.stringify({ allow_tool: true }));
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
- process.stdout.write(JSON.stringify({
6827
- hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", updatedInput }
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
- process.stdout.write(JSON.stringify({ allow_tool: false, deny_reason: ghostHit }));
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
- process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: ghostHit } }));
7973
- process.stderr.write(ghostHit + "\n");
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 = 45;
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
- process.stdout.write(JSON.stringify({
388
- allow_tool: false,
389
- deny_reason: `[SolonGate] ${reason}`,
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
- process.stdout.write(JSON.stringify({
404
- hookSpecificOutput: {
405
- hookEventName: 'PreToolUse',
406
- permissionDecision: 'deny',
407
- permissionDecisionReason: msg,
408
- },
409
- }));
410
- process.stderr.write(msg + '\n');
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
- process.stdout.write(JSON.stringify({ allow_tool: true }));
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
- process.stdout.write(JSON.stringify({
427
- hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow', updatedInput },
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
- process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ghostHit } }));
1830
- process.stderr.write(ghostHit + '\n');
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.17",
3
+ "version": "0.82.19",
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": {