@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.
- package/dist/index.js +65 -20
- package/dist/init.js +65 -20
- 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
|
-
|
|
964
|
-
|
|
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')
|
|
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
|
|
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
|
|
1184
|
-
const
|
|
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
|
-
|
|
153
|
-
|
|
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')
|
|
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
|
|
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
|
|
373
|
-
const
|
|
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
|
+
"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": {
|