@solongate/proxy 0.6.3 → 0.6.5

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.
Files changed (3) hide show
  1. package/dist/index.js +65 -20
  2. package/dist/init.js +65 -20
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -955,13 +955,31 @@ var init_init = __esm({
955
955
  * SolonGate Policy Guard Hook (PreToolUse)
956
956
  * Reads policy.json and blocks tool calls that violate constraints.
957
957
  * Exit code 2 = BLOCK, exit code 0 = ALLOW.
958
+ * Logs ALL decisions (ALLOW + DENY) to SolonGate Cloud.
958
959
  * Auto-installed by: npx @solongate/proxy init
959
960
  */
960
- import { readFileSync } from 'node:fs';
961
+ import { readFileSync, existsSync } from 'node:fs';
961
962
  import { resolve } from 'node:path';
962
963
 
963
- const API_KEY = process.env.SOLONGATE_API_KEY || '';
964
- const API_URL = process.env.SOLONGATE_API_URL || 'https://api.solongate.com';
964
+ // \u2500\u2500 Load API key from .env file (Claude Code doesn't load .env into process.env) \u2500\u2500
965
+ function loadEnvKey(dir) {
966
+ try {
967
+ const envPath = resolve(dir, '.env');
968
+ if (!existsSync(envPath)) return {};
969
+ const lines = readFileSync(envPath, 'utf-8').split('\\n');
970
+ const env = {};
971
+ for (const line of lines) {
972
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
973
+ if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, '').trim();
974
+ }
975
+ return env;
976
+ } catch { return {}; }
977
+ }
978
+
979
+ const hookCwdEarly = process.cwd();
980
+ const dotenv = loadEnvKey(hookCwdEarly);
981
+ const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
982
+ const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
965
983
 
966
984
  // \u2500\u2500 Glob Matching \u2500\u2500
967
985
  function matchGlob(str, pattern) {
@@ -1056,7 +1074,13 @@ function extractCommands(args) {
1056
1074
  const fields = ['command', 'cmd', 'function', 'script', 'shell'];
1057
1075
  if (typeof args === 'object' && args) {
1058
1076
  for (const [k, v] of Object.entries(args)) {
1059
- if (fields.includes(k.toLowerCase()) && typeof v === 'string') cmds.push(v);
1077
+ if (fields.includes(k.toLowerCase()) && typeof v === 'string') {
1078
+ // Split chained commands: cd /path && npm install \u2192 [cd /path, npm install]
1079
+ for (const part of v.split(/\\s*(?:&&|\\|\\||;|\\|)\\s*/)) {
1080
+ const trimmed = part.trim();
1081
+ if (trimmed) cmds.push(trimmed);
1082
+ }
1083
+ }
1060
1084
  }
1061
1085
  }
1062
1086
  return cmds;
@@ -1151,21 +1175,25 @@ process.stdin.on('end', async () => {
1151
1175
  }
1152
1176
 
1153
1177
  const reason = evaluate(policy, args);
1178
+ const decision = reason ? 'DENY' : 'ALLOW';
1179
+
1180
+ // \u2500\u2500 Log ALL decisions to SolonGate Cloud \u2500\u2500
1181
+ if (API_KEY && API_KEY.startsWith('sg_live_')) {
1182
+ try {
1183
+ await fetch(API_URL + '/api/v1/audit-logs', {
1184
+ method: 'POST',
1185
+ headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
1186
+ body: JSON.stringify({
1187
+ tool: data.tool_name || '', arguments: args,
1188
+ decision, reason: reason || 'allowed by policy',
1189
+ source: 'claude-code-guard',
1190
+ }),
1191
+ signal: AbortSignal.timeout(3000),
1192
+ });
1193
+ } catch {}
1194
+ }
1154
1195
 
1155
1196
  if (reason) {
1156
- if (API_KEY && API_KEY.startsWith('sg_live_')) {
1157
- try {
1158
- await fetch(API_URL + '/api/v1/audit-logs', {
1159
- method: 'POST',
1160
- headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
1161
- body: JSON.stringify({
1162
- tool: data.tool_name || '', arguments: args,
1163
- decision: 'DENY', reason, source: 'claude-code-guard',
1164
- }),
1165
- signal: AbortSignal.timeout(3000),
1166
- });
1167
- } catch {}
1168
- }
1169
1197
  process.stderr.write(reason);
1170
1198
  process.exit(2);
1171
1199
  }
@@ -1176,12 +1204,29 @@ process.stdin.on('end', async () => {
1176
1204
  AUDIT_SCRIPT = `#!/usr/bin/env node
1177
1205
  /**
1178
1206
  * SolonGate Audit Hook for Claude Code (PostToolUse)
1179
- * Logs ALL tool calls to SolonGate Cloud after execution.
1207
+ * Logs tool execution results to SolonGate Cloud.
1180
1208
  * Auto-installed by: npx @solongate/proxy init
1181
1209
  */
1210
+ import { readFileSync, existsSync } from 'node:fs';
1211
+ import { resolve } from 'node:path';
1212
+
1213
+ function loadEnvKey(dir) {
1214
+ try {
1215
+ const envPath = resolve(dir, '.env');
1216
+ if (!existsSync(envPath)) return {};
1217
+ const lines = readFileSync(envPath, 'utf-8').split('\\n');
1218
+ const env = {};
1219
+ for (const line of lines) {
1220
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
1221
+ if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, '').trim();
1222
+ }
1223
+ return env;
1224
+ } catch { return {}; }
1225
+ }
1182
1226
 
1183
- const API_KEY = process.env.SOLONGATE_API_KEY || '';
1184
- const API_URL = process.env.SOLONGATE_API_URL || 'https://api.solongate.com';
1227
+ const dotenv = loadEnvKey(process.cwd());
1228
+ const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
1229
+ const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
1185
1230
 
1186
1231
  if (!API_KEY || !API_KEY.startsWith('sg_live_')) process.exit(0);
1187
1232
 
package/dist/init.js CHANGED
@@ -144,13 +144,31 @@ var GUARD_SCRIPT = `#!/usr/bin/env node
144
144
  * SolonGate Policy Guard Hook (PreToolUse)
145
145
  * Reads policy.json and blocks tool calls that violate constraints.
146
146
  * Exit code 2 = BLOCK, exit code 0 = ALLOW.
147
+ * Logs ALL decisions (ALLOW + DENY) to SolonGate Cloud.
147
148
  * Auto-installed by: npx @solongate/proxy init
148
149
  */
149
- import { readFileSync } from 'node:fs';
150
+ import { readFileSync, existsSync } from 'node:fs';
150
151
  import { resolve } from 'node:path';
151
152
 
152
- const API_KEY = process.env.SOLONGATE_API_KEY || '';
153
- const API_URL = process.env.SOLONGATE_API_URL || 'https://api.solongate.com';
153
+ // \u2500\u2500 Load API key from .env file (Claude Code doesn't load .env into process.env) \u2500\u2500
154
+ function loadEnvKey(dir) {
155
+ try {
156
+ const envPath = resolve(dir, '.env');
157
+ if (!existsSync(envPath)) return {};
158
+ const lines = readFileSync(envPath, 'utf-8').split('\\n');
159
+ const env = {};
160
+ for (const line of lines) {
161
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
162
+ if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, '').trim();
163
+ }
164
+ return env;
165
+ } catch { return {}; }
166
+ }
167
+
168
+ const hookCwdEarly = process.cwd();
169
+ const dotenv = loadEnvKey(hookCwdEarly);
170
+ const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
171
+ const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
154
172
 
155
173
  // \u2500\u2500 Glob Matching \u2500\u2500
156
174
  function matchGlob(str, pattern) {
@@ -245,7 +263,13 @@ function extractCommands(args) {
245
263
  const fields = ['command', 'cmd', 'function', 'script', 'shell'];
246
264
  if (typeof args === 'object' && args) {
247
265
  for (const [k, v] of Object.entries(args)) {
248
- if (fields.includes(k.toLowerCase()) && typeof v === 'string') cmds.push(v);
266
+ if (fields.includes(k.toLowerCase()) && typeof v === 'string') {
267
+ // Split chained commands: cd /path && npm install \u2192 [cd /path, npm install]
268
+ for (const part of v.split(/\\s*(?:&&|\\|\\||;|\\|)\\s*/)) {
269
+ const trimmed = part.trim();
270
+ if (trimmed) cmds.push(trimmed);
271
+ }
272
+ }
249
273
  }
250
274
  }
251
275
  return cmds;
@@ -340,21 +364,25 @@ process.stdin.on('end', async () => {
340
364
  }
341
365
 
342
366
  const reason = evaluate(policy, args);
367
+ const decision = reason ? 'DENY' : 'ALLOW';
368
+
369
+ // \u2500\u2500 Log ALL decisions to SolonGate Cloud \u2500\u2500
370
+ if (API_KEY && API_KEY.startsWith('sg_live_')) {
371
+ try {
372
+ await fetch(API_URL + '/api/v1/audit-logs', {
373
+ method: 'POST',
374
+ headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
375
+ body: JSON.stringify({
376
+ tool: data.tool_name || '', arguments: args,
377
+ decision, reason: reason || 'allowed by policy',
378
+ source: 'claude-code-guard',
379
+ }),
380
+ signal: AbortSignal.timeout(3000),
381
+ });
382
+ } catch {}
383
+ }
343
384
 
344
385
  if (reason) {
345
- if (API_KEY && API_KEY.startsWith('sg_live_')) {
346
- try {
347
- await fetch(API_URL + '/api/v1/audit-logs', {
348
- method: 'POST',
349
- headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
350
- body: JSON.stringify({
351
- tool: data.tool_name || '', arguments: args,
352
- decision: 'DENY', reason, source: 'claude-code-guard',
353
- }),
354
- signal: AbortSignal.timeout(3000),
355
- });
356
- } catch {}
357
- }
358
386
  process.stderr.write(reason);
359
387
  process.exit(2);
360
388
  }
@@ -365,12 +393,29 @@ process.stdin.on('end', async () => {
365
393
  var AUDIT_SCRIPT = `#!/usr/bin/env node
366
394
  /**
367
395
  * SolonGate Audit Hook for Claude Code (PostToolUse)
368
- * Logs ALL tool calls to SolonGate Cloud after execution.
396
+ * Logs tool execution results to SolonGate Cloud.
369
397
  * Auto-installed by: npx @solongate/proxy init
370
398
  */
399
+ import { readFileSync, existsSync } from 'node:fs';
400
+ import { resolve } from 'node:path';
401
+
402
+ function loadEnvKey(dir) {
403
+ try {
404
+ const envPath = resolve(dir, '.env');
405
+ if (!existsSync(envPath)) return {};
406
+ const lines = readFileSync(envPath, 'utf-8').split('\\n');
407
+ const env = {};
408
+ for (const line of lines) {
409
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
410
+ if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, '').trim();
411
+ }
412
+ return env;
413
+ } catch { return {}; }
414
+ }
371
415
 
372
- const API_KEY = process.env.SOLONGATE_API_KEY || '';
373
- const API_URL = process.env.SOLONGATE_API_URL || 'https://api.solongate.com';
416
+ const dotenv = loadEnvKey(process.cwd());
417
+ const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
418
+ const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
374
419
 
375
420
  if (!API_KEY || !API_KEY.startsWith('sg_live_')) process.exit(0);
376
421
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "MCP security proxy — protect any MCP server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
5
5
  "type": "module",
6
6
  "bin": {