@solongate/proxy 0.83.18 → 0.83.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/hooks/audit.mjs CHANGED
@@ -40,7 +40,7 @@ function projectFlagDir() {
40
40
  // Bump on every audit hook change. The cloud serves the newest version; the guard
41
41
  // hook installs it on its next run (no re-login needed). See guard.mjs
42
42
  // fetchAndInstallHook / maybeSelfUpdate.
43
- const HOOK_VERSION = 27;
43
+ const HOOK_VERSION = 28;
44
44
 
45
45
  function loadEnvKey(dir) {
46
46
  try {
@@ -724,13 +724,6 @@ try { input += readFileSync(0, 'utf-8'); } catch {}
724
724
  : v;
725
725
  }
726
726
 
727
- // Write flag so stop.mjs knows tool calls happened (skip text-only ALLOW)
728
- try {
729
- const flagDir = projectFlagDir();
730
- mkdirSync(flagDir, { recursive: true });
731
- writeFileSync(join(flagDir, '.last-tool-call'), Date.now().toString());
732
- } catch {}
733
-
734
727
  // Codex CANNOT rewrite tool output from PostToolUse: its output struct is
735
728
  // deny_unknown_fields and the only rewrite field it defines
736
729
  // (updatedMCPToolOutput) is explicitly rejected as unsupported — so emitting
@@ -6578,7 +6578,7 @@ function sweepLegacyFlagDir() {
6578
6578
  } catch {
6579
6579
  }
6580
6580
  }
6581
- var HOOK_VERSION = 78;
6581
+ var HOOK_VERSION = 79;
6582
6582
  function localLogsOnly(security) {
6583
6583
  if (security !== void 0) {
6584
6584
  const l = security && security.localLogs;
@@ -7096,7 +7096,6 @@ function writeDenyFlag(toolName) {
7096
7096
  try {
7097
7097
  const flagDir = projectFlagDir();
7098
7098
  mkdirSync(flagDir, { recursive: true });
7099
- writeFileSync(join(flagDir, ".last-tool-call"), Date.now().toString());
7100
7099
  writeFileSync(join(flagDir, ".last-deny"), JSON.stringify({
7101
7100
  tool: toolName,
7102
7101
  ts: Date.now(),
package/hooks/guard.mjs CHANGED
@@ -81,7 +81,7 @@ import { createHash } from 'node:crypto';
81
81
  // the installed hook self-updates when the cloud version is higher (see
82
82
  // maybeSelfUpdate). This is what makes guard fixes propagate without a manual
83
83
  // reinstall — the same trust model as the OPA WASM this hook already runs.
84
- const HOOK_VERSION = 78;
84
+ const HOOK_VERSION = 79;
85
85
 
86
86
  // True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
87
87
  // nothing is sent to the cloud audit log — local and cloud are one or the
@@ -826,12 +826,12 @@ function callFingerprint(s) {
826
826
  return h.toString(16);
827
827
  }
828
828
 
829
- // Write flag file so stop.mjs knows a tool call (DENY) happened and doesn't log extra ALLOW
829
+ // Records WHICH call was denied, so the PostToolUse audit hook does not log a
830
+ // second, ALLOW-looking entry for a call the guard already blocked.
830
831
  function writeDenyFlag(toolName) {
831
832
  try {
832
833
  const flagDir = projectFlagDir();
833
834
  mkdirSync(flagDir, { recursive: true });
834
- writeFileSync(join(flagDir, '.last-tool-call'), Date.now().toString());
835
835
  // Deny-specific flag: audit.mjs (PostToolUse) reads it to avoid logging a
836
836
  // second, ALLOW-looking entry for a call the guard already denied.
837
837
  //
package/hooks/stop.mjs CHANGED
@@ -1,91 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * SolonGate Stop Hook (Stop event)
4
- * Logs a single ALLOW when Claude responds WITHOUT any tool calls.
5
- * If tool calls were made, audit.mjs already logged them this hook skips.
6
- * Auto-installed by: npx @solongate/proxy login
3
+ * SolonGate Stop Hook (Stop event) — intentionally does nothing.
4
+ *
5
+ * It used to POST one synthetic audit row named `_response` whenever a turn
6
+ * ended with no tool calls, to record "the agent replied with text only". That
7
+ * was wrong twice over:
8
+ *
9
+ * - `_response` is not a tool, and it sat in the TOOL CALL log. It showed up
10
+ * as a row in the live stream and as an entry in the dashboard's top-tools
11
+ * list, and it inflated the call count with something no policy can act on.
12
+ * - it decided whether to write by looking for a flag file left by the audit
13
+ * hook, so any drift in where that flag lived turned "text-only turn" into
14
+ * "every turn" — which is exactly what happened when the per-call scratch
15
+ * moved out of the working directory.
16
+ *
17
+ * The registration is kept (it costs one process exit per turn and removing it
18
+ * would need every client's hook config rewritten), but nothing is logged. If a
19
+ * text-only turn is ever worth recording, it belongs in its own place, not in
20
+ * the record of what the agent was allowed to run.
7
21
  */
8
- import { readFileSync, existsSync, unlinkSync, writeFileSync } from 'node:fs';
9
- import { resolve, join } from 'node:path';
10
- import { homedir } from 'node:os';
11
-
12
- function loadEnvKey(dir) {
13
- try {
14
- const envPath = resolve(dir, '.env');
15
- if (!existsSync(envPath)) return {};
16
- const lines = readFileSync(envPath, 'utf-8').split('\n');
17
- const env = {};
18
- for (const line of lines) {
19
- const m = line.match(/^([A-Z_]+)=(.*)$/);
20
- if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, '').trim();
21
- }
22
- return env;
23
- } catch { return {}; }
24
- }
25
-
26
- // Global cloud config written at login (~/.solongate/cloud-guard.json). This hook
27
- // used to read ONLY the environment and a project env file, so on a normally
28
- // paired device it silently logged nothing — and where an env file did exist, a
29
- // stale key in it shadowed the login. Same order as the other hooks now:
30
- // process env -> login -> project env file.
31
- function loadGlobalCloudConfig() {
32
- try {
33
- const p = resolve(homedir(), '.solongate', 'cloud-guard.json');
34
- if (!existsSync(p)) return {};
35
- const cfg = JSON.parse(readFileSync(p, 'utf-8'));
36
- return (cfg && typeof cfg === 'object') ? cfg : {};
37
- } catch { return {}; }
38
- }
39
-
40
- const dotenv = loadEnvKey(process.cwd());
41
- const globalCfg = loadGlobalCloudConfig();
42
- const API_KEY = process.env.SOLONGATE_API_KEY || globalCfg.apiKey || dotenv.SOLONGATE_API_KEY || '';
43
- const API_URL = process.env.SOLONGATE_API_URL || globalCfg.apiUrl || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
44
-
45
- // Agent identity from CLI args: node stop.mjs <agent_id> <agent_name>
46
- const AGENT_ID = process.argv[2] || 'claude-code';
47
- const AGENT_NAME = process.argv[3] || 'Claude Code';
48
-
49
- if (!API_KEY || !API_KEY.startsWith('sg_live_')) process.exit(0);
50
-
51
- // Flag file: audit.mjs writes this when a tool call was logged in this turn.
52
- // If the flag exists, tool calls were made → skip (already logged).
53
- // If the flag doesn't exist, no tool calls → log 1 ALLOW for the text response.
54
- const flagDir = resolve('.solongate');
55
- const flagFile = join(flagDir, '.last-tool-call');
56
-
57
- let input = '';
58
- process.stdin.on('data', c => input += c);
59
- process.stdin.on('end', async () => {
60
- try {
61
- // Check if tool calls were made in this turn
62
- if (existsSync(flagFile)) {
63
- // Tool calls happened → audit.mjs already logged them. Clean up flag.
64
- try { unlinkSync(flagFile); } catch {}
65
- process.exit(0);
66
- }
67
-
68
- // No tool calls → log 1 ALLOW for the text-only response (fire-and-forget)
69
- fetch(`${API_URL}/api/v1/audit-logs`, {
70
- method: 'POST',
71
- headers: {
72
- 'Authorization': `Bearer ${API_KEY}`,
73
- 'Content-Type': 'application/json',
74
- },
75
- body: JSON.stringify({
76
- tool: '_response',
77
- arguments: {},
78
- decision: 'ALLOW',
79
- reason: 'text response (no tool calls)',
80
- source: `${AGENT_ID}-hook`,
81
- evaluationTimeMs: 0,
82
- agent_id: AGENT_ID,
83
- agent_name: AGENT_NAME,
84
- }),
85
- signal: AbortSignal.timeout(5000),
86
- }).catch(() => {}).finally(() => process.exit(0));
87
- setTimeout(() => process.exit(0), 3000);
88
- } catch {
89
- process.exit(0);
90
- }
91
- });
22
+ process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.83.18",
3
+ "version": "0.83.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": {