@solongate/proxy 0.15.5 → 0.16.1
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 +32 -4
- package/dist/init.js +28 -0
- package/hooks/guard.mjs +171 -53
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -306,6 +306,7 @@ var init_exports = {};
|
|
|
306
306
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
307
307
|
import { resolve as resolve2, join, dirname as dirname2 } from "path";
|
|
308
308
|
import { fileURLToPath } from "url";
|
|
309
|
+
import { execSync } from "child_process";
|
|
309
310
|
import { createInterface } from "readline";
|
|
310
311
|
function findConfigFile(explicitPath, createIfMissing = false) {
|
|
311
312
|
if (explicitPath) {
|
|
@@ -514,10 +515,37 @@ function installHooks(selectedTools = []) {
|
|
|
514
515
|
console.log(` Created ${settingsPath}`);
|
|
515
516
|
activatedNames.push(client.name);
|
|
516
517
|
}
|
|
518
|
+
const protectedDirs = [".solongate", ...clients.map((c3) => c3.dir)];
|
|
519
|
+
try {
|
|
520
|
+
if (process.platform === "win32") {
|
|
521
|
+
for (const dir of protectedDirs) {
|
|
522
|
+
const fullDir = resolve2(dir);
|
|
523
|
+
if (existsSync3(fullDir)) {
|
|
524
|
+
try {
|
|
525
|
+
execSync(`attrib +R /S /D "${fullDir}"`, { stdio: "ignore" });
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
} else {
|
|
531
|
+
for (const dir of protectedDirs) {
|
|
532
|
+
const fullDir = resolve2(dir);
|
|
533
|
+
if (existsSync3(fullDir)) {
|
|
534
|
+
try {
|
|
535
|
+
execSync(`chmod -R a-w "${fullDir}"`, { stdio: "ignore" });
|
|
536
|
+
} catch {
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
console.log(" OS-level read-only protection applied");
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
517
544
|
console.log("");
|
|
518
545
|
console.log(" Hooks installed:");
|
|
519
546
|
console.log(" guard.mjs \u2192 blocks policy-violating calls (pre-execution)");
|
|
520
547
|
console.log(" audit.mjs \u2192 logs all calls to dashboard (post-execution)");
|
|
548
|
+
console.log(" File system \u2192 read-only (OS-level protection)");
|
|
521
549
|
console.log(` Activated for: ${activatedNames.join(", ")}`);
|
|
522
550
|
}
|
|
523
551
|
function ensureEnvFile() {
|
|
@@ -804,7 +832,7 @@ var init_init = __esm({
|
|
|
804
832
|
var inject_exports = {};
|
|
805
833
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, copyFileSync } from "fs";
|
|
806
834
|
import { resolve as resolve3 } from "path";
|
|
807
|
-
import { execSync } from "child_process";
|
|
835
|
+
import { execSync as execSync2 } from "child_process";
|
|
808
836
|
function parseInjectArgs(argv) {
|
|
809
837
|
const args = argv.slice(2);
|
|
810
838
|
const opts = {
|
|
@@ -951,7 +979,7 @@ function installSdk() {
|
|
|
951
979
|
const cmd = pm === "yarn" ? "yarn add @solongate/sdk" : `${pm} install @solongate/sdk`;
|
|
952
980
|
log3(` Installing @solongate/sdk via ${pm}...`);
|
|
953
981
|
try {
|
|
954
|
-
|
|
982
|
+
execSync2(cmd, { stdio: "pipe", cwd: process.cwd() });
|
|
955
983
|
return true;
|
|
956
984
|
} catch (err) {
|
|
957
985
|
log3(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1176,7 +1204,7 @@ var init_inject = __esm({
|
|
|
1176
1204
|
var create_exports = {};
|
|
1177
1205
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
1178
1206
|
import { resolve as resolve4, join as join2 } from "path";
|
|
1179
|
-
import { execSync as
|
|
1207
|
+
import { execSync as execSync3 } from "child_process";
|
|
1180
1208
|
function log4(msg) {
|
|
1181
1209
|
process.stderr.write(msg + "\n");
|
|
1182
1210
|
}
|
|
@@ -1420,7 +1448,7 @@ async function main3() {
|
|
|
1420
1448
|
});
|
|
1421
1449
|
if (!opts.noInstall) {
|
|
1422
1450
|
withSpinner("Installing dependencies...", () => {
|
|
1423
|
-
|
|
1451
|
+
execSync3("npm install", { cwd: dir, stdio: "pipe" });
|
|
1424
1452
|
});
|
|
1425
1453
|
}
|
|
1426
1454
|
log4("");
|
package/dist/init.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
5
5
|
import { resolve, join, dirname } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
import { execSync } from "child_process";
|
|
7
8
|
import { createInterface } from "readline";
|
|
8
9
|
var SEARCH_PATHS = [
|
|
9
10
|
".mcp.json",
|
|
@@ -221,10 +222,37 @@ function installHooks(selectedTools = []) {
|
|
|
221
222
|
console.log(` Created ${settingsPath}`);
|
|
222
223
|
activatedNames.push(client.name);
|
|
223
224
|
}
|
|
225
|
+
const protectedDirs = [".solongate", ...clients.map((c) => c.dir)];
|
|
226
|
+
try {
|
|
227
|
+
if (process.platform === "win32") {
|
|
228
|
+
for (const dir of protectedDirs) {
|
|
229
|
+
const fullDir = resolve(dir);
|
|
230
|
+
if (existsSync(fullDir)) {
|
|
231
|
+
try {
|
|
232
|
+
execSync(`attrib +R /S /D "${fullDir}"`, { stdio: "ignore" });
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
for (const dir of protectedDirs) {
|
|
239
|
+
const fullDir = resolve(dir);
|
|
240
|
+
if (existsSync(fullDir)) {
|
|
241
|
+
try {
|
|
242
|
+
execSync(`chmod -R a-w "${fullDir}"`, { stdio: "ignore" });
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
console.log(" OS-level read-only protection applied");
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
224
251
|
console.log("");
|
|
225
252
|
console.log(" Hooks installed:");
|
|
226
253
|
console.log(" guard.mjs \u2192 blocks policy-violating calls (pre-execution)");
|
|
227
254
|
console.log(" audit.mjs \u2192 logs all calls to dashboard (post-execution)");
|
|
255
|
+
console.log(" File system \u2192 read-only (OS-level protection)");
|
|
228
256
|
console.log(` Activated for: ${activatedNames.join(", ")}`);
|
|
229
257
|
}
|
|
230
258
|
function ensureEnvFile() {
|
package/hooks/guard.mjs
CHANGED
|
@@ -296,21 +296,93 @@ process.stdin.on('end', async () => {
|
|
|
296
296
|
'policy.json', '.mcp.json',
|
|
297
297
|
];
|
|
298
298
|
|
|
299
|
-
//
|
|
299
|
+
// Block helper — logs to cloud + exits with code 2
|
|
300
|
+
async function blockSelfProtection(reason) {
|
|
301
|
+
if (API_KEY && API_KEY.startsWith('sg_live_')) {
|
|
302
|
+
try {
|
|
303
|
+
await fetch(API_URL + '/api/v1/audit-logs', {
|
|
304
|
+
method: 'POST',
|
|
305
|
+
headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
|
|
306
|
+
body: JSON.stringify({
|
|
307
|
+
tool: data.tool_name || '', arguments: args,
|
|
308
|
+
decision: 'DENY', reason,
|
|
309
|
+
source: 'claude-code-guard',
|
|
310
|
+
}),
|
|
311
|
+
signal: AbortSignal.timeout(3000),
|
|
312
|
+
});
|
|
313
|
+
} catch {}
|
|
314
|
+
}
|
|
315
|
+
process.stderr.write(reason);
|
|
316
|
+
process.exit(2);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ── Normalization layers ──
|
|
320
|
+
|
|
321
|
+
// 1. Decode ANSI-C quoting: $'\x72' → r, $'\162' → r, $'\n' → newline
|
|
322
|
+
function decodeAnsiC(s) {
|
|
323
|
+
return s.replace(/\$'([^']*)'/g, (_, content) => {
|
|
324
|
+
return content
|
|
325
|
+
.replace(/\\x([0-9a-fA-F]{2})/g, (__, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
326
|
+
.replace(/\\([0-7]{1,3})/g, (__, oct) => String.fromCharCode(parseInt(oct, 8)))
|
|
327
|
+
.replace(/\\u([0-9a-fA-F]{4})/g, (__, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
328
|
+
.replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r')
|
|
329
|
+
.replace(/\\(.)/g, '$1');
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 2. Strip all shell quoting: empty quotes, single, double, backslash escapes
|
|
300
334
|
function stripShellQuotes(s) {
|
|
301
|
-
|
|
335
|
+
let r = s;
|
|
336
|
+
r = r.replace(/""/g, ''); // empty double quotes
|
|
337
|
+
r = r.replace(/''/g, ''); // empty single quotes
|
|
338
|
+
r = r.replace(/\\(.)/g, '$1'); // backslash escapes
|
|
339
|
+
r = r.replace(/'/g, ''); // remaining single quotes
|
|
340
|
+
r = r.replace(/"/g, ''); // remaining double quotes
|
|
341
|
+
return r;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 3. Full normalization pipeline
|
|
345
|
+
function normalizeShell(s) {
|
|
346
|
+
return stripShellQuotes(decodeAnsiC(s));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 4. Extract inner commands from eval, bash -c, sh -c, pipe to bash/sh
|
|
350
|
+
function extractInnerCommands(s) {
|
|
351
|
+
const inner = [];
|
|
352
|
+
// eval "cmd" or eval 'cmd' or eval cmd
|
|
353
|
+
for (const m of s.matchAll(/\beval\s+["']([^"']+)["']/gi)) inner.push(m[1]);
|
|
354
|
+
for (const m of s.matchAll(/\beval\s+([^;"'|&]+)/gi)) inner.push(m[1]);
|
|
355
|
+
// bash -c "cmd" or sh -c "cmd"
|
|
356
|
+
for (const m of s.matchAll(/\b(?:bash|sh)\s+-c\s+["']([^"']+)["']/gi)) inner.push(m[1]);
|
|
357
|
+
// echo "cmd" | bash/sh or printf "cmd" | bash/sh
|
|
358
|
+
for (const m of s.matchAll(/(?:echo|printf)\s+["']([^"']+)["']\s*\|\s*(?:bash|sh)\b/gi)) inner.push(m[1]);
|
|
359
|
+
// find ... -name "pattern" ... -exec ...
|
|
360
|
+
for (const m of s.matchAll(/-name\s+["']?([^\s"']+)["']?/gi)) inner.push(m[1]);
|
|
361
|
+
return inner;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 5. Check variable assignments for protected path fragments
|
|
365
|
+
// e.g. X=".solon" && rm -rf ${X}gate → detects ".solon" as prefix of ".solongate"
|
|
366
|
+
function checkVarAssignments(s) {
|
|
367
|
+
const assignments = [...s.matchAll(/(\w+)=["']?([^"'\s&|;]+)["']?/g)];
|
|
368
|
+
for (const [, , value] of assignments) {
|
|
369
|
+
const v = value.toLowerCase();
|
|
370
|
+
if (v.length < 3) continue; // avoid false positives
|
|
371
|
+
for (const p of protectedPaths) {
|
|
372
|
+
if (p.startsWith(v) || p.includes(v)) return p;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
302
376
|
}
|
|
303
377
|
|
|
304
|
-
// Check if a glob/wildcard
|
|
305
|
-
// e.g. ".antig*" matches ".antigravity", "/path/.sol*" matches ".solongate"
|
|
378
|
+
// 6. Check if a glob/wildcard could match any protected path
|
|
306
379
|
function globMatchesProtected(s) {
|
|
307
380
|
if (!s.includes('*') && !s.includes('?')) return null;
|
|
308
|
-
// Extract all path segments and the full string to test
|
|
309
381
|
const segments = s.split('/').filter(Boolean);
|
|
310
382
|
const candidates = [s, ...segments];
|
|
311
383
|
for (const candidate of candidates) {
|
|
312
384
|
if (!candidate.includes('*') && !candidate.includes('?')) continue;
|
|
313
|
-
//
|
|
385
|
+
// Prefix match: ".antig*" → prefix ".antig"
|
|
314
386
|
const starIdx = candidate.indexOf('*');
|
|
315
387
|
const qIdx = candidate.indexOf('?');
|
|
316
388
|
const firstWild = starIdx === -1 ? qIdx : qIdx === -1 ? starIdx : Math.min(starIdx, qIdx);
|
|
@@ -320,79 +392,125 @@ process.stdin.on('end', async () => {
|
|
|
320
392
|
if (p.startsWith(prefix)) return p;
|
|
321
393
|
}
|
|
322
394
|
}
|
|
323
|
-
//
|
|
395
|
+
// Regex match for patterns like ".cl?ude"
|
|
324
396
|
try {
|
|
325
397
|
const escaped = candidate.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
|
|
326
398
|
const re = new RegExp('^' + escaped + '$', 'i');
|
|
327
399
|
for (const p of protectedPaths) {
|
|
328
400
|
if (re.test(p)) return p;
|
|
329
401
|
}
|
|
330
|
-
} catch {
|
|
402
|
+
} catch {}
|
|
331
403
|
}
|
|
332
404
|
return null;
|
|
333
405
|
}
|
|
334
406
|
|
|
335
|
-
//
|
|
407
|
+
// ── Build all candidate strings from tool input ──
|
|
336
408
|
const rawStrings = scanStrings(args).map(s => s.replace(/\\/g, '/').toLowerCase());
|
|
337
|
-
const allStrings =
|
|
409
|
+
const allStrings = new Set();
|
|
410
|
+
|
|
338
411
|
for (const s of rawStrings) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
412
|
+
// Raw string
|
|
413
|
+
allStrings.add(s);
|
|
414
|
+
// Normalized (ANSI-C decoded, quotes stripped)
|
|
415
|
+
const norm = normalizeShell(s);
|
|
416
|
+
allStrings.add(norm);
|
|
417
|
+
// Extract inner commands (eval, bash -c, pipe to bash, find -name)
|
|
418
|
+
for (const inner of extractInnerCommands(s)) {
|
|
419
|
+
allStrings.add(inner.toLowerCase());
|
|
420
|
+
allStrings.add(normalizeShell(inner.toLowerCase()));
|
|
421
|
+
}
|
|
422
|
+
// Split by spaces + shell operators for token-level checks
|
|
423
|
+
for (const tok of s.split(/[\s;&|]+/)) {
|
|
424
|
+
if (tok) {
|
|
425
|
+
allStrings.add(tok);
|
|
426
|
+
allStrings.add(normalizeShell(tok));
|
|
349
427
|
}
|
|
350
428
|
}
|
|
429
|
+
// Also split normalized version
|
|
430
|
+
for (const tok of norm.split(/[\s;&|]+/)) {
|
|
431
|
+
if (tok) allStrings.add(tok);
|
|
432
|
+
}
|
|
351
433
|
}
|
|
352
434
|
|
|
435
|
+
// ── Check all candidates ──
|
|
353
436
|
for (const s of allStrings) {
|
|
354
437
|
// Direct match
|
|
355
438
|
for (const p of protectedPaths) {
|
|
356
439
|
if (s.includes(p)) {
|
|
357
|
-
|
|
358
|
-
if (API_KEY && API_KEY.startsWith('sg_live_')) {
|
|
359
|
-
try {
|
|
360
|
-
await fetch(API_URL + '/api/v1/audit-logs', {
|
|
361
|
-
method: 'POST',
|
|
362
|
-
headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' },
|
|
363
|
-
body: JSON.stringify({
|
|
364
|
-
tool: data.tool_name || '', arguments: args,
|
|
365
|
-
decision: 'DENY', reason: msg,
|
|
366
|
-
source: 'claude-code-guard',
|
|
367
|
-
}),
|
|
368
|
-
signal: AbortSignal.timeout(3000),
|
|
369
|
-
});
|
|
370
|
-
} catch {}
|
|
371
|
-
}
|
|
372
|
-
process.stderr.write(msg);
|
|
373
|
-
process.exit(2);
|
|
440
|
+
await blockSelfProtection('SOLONGATE: Access to protected path "' + p + '" is blocked');
|
|
374
441
|
}
|
|
375
442
|
}
|
|
376
443
|
// Wildcard/glob match
|
|
377
444
|
const globHit = globMatchesProtected(s);
|
|
378
445
|
if (globHit) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
446
|
+
await blockSelfProtection('SOLONGATE: Wildcard "' + s + '" matches protected "' + globHit + '" — blocked');
|
|
447
|
+
}
|
|
448
|
+
// Variable assignment targeting protected paths
|
|
449
|
+
const varHit = checkVarAssignments(s);
|
|
450
|
+
if (varHit) {
|
|
451
|
+
await blockSelfProtection('SOLONGATE: Variable assignment targets protected "' + varHit + '" — blocked');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ── Layer 7: Dangerous execution pattern detection ──
|
|
456
|
+
// These can construct ANY string at runtime — block when touching protected dirs
|
|
457
|
+
const fullCmd = rawStrings.join(' ');
|
|
458
|
+
|
|
459
|
+
// 7a. Inline interpreter execution: node -e, python -c, perl -e, ruby -e
|
|
460
|
+
// Extract the -e/-c argument and scan it
|
|
461
|
+
const interpreterPatterns = [
|
|
462
|
+
/\bnode\s+(?:-e|--eval)\s+["']([^"']+)["']/gi,
|
|
463
|
+
/\bnode\s+(?:-e|--eval)\s+([^;&|"']+)/gi,
|
|
464
|
+
/\bpython[23]?\s+-c\s+["']([^"']+)["']/gi,
|
|
465
|
+
/\bperl\s+-e\s+["']([^"']+)["']/gi,
|
|
466
|
+
/\bruby\s+-e\s+["']([^"']+)["']/gi,
|
|
467
|
+
];
|
|
468
|
+
for (const pat of interpreterPatterns) {
|
|
469
|
+
for (const m of fullCmd.matchAll(pat)) {
|
|
470
|
+
const code = m[1].toLowerCase();
|
|
471
|
+
for (const p of protectedPaths) {
|
|
472
|
+
if (code.includes(p)) {
|
|
473
|
+
await blockSelfProtection('SOLONGATE: Interpreter code targets "' + p + '" — blocked');
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// Also check the normalized version
|
|
477
|
+
const normCode = normalizeShell(code);
|
|
478
|
+
for (const p of protectedPaths) {
|
|
479
|
+
if (normCode.includes(p)) {
|
|
480
|
+
await blockSelfProtection('SOLONGATE: Interpreter code targets "' + p + '" — blocked');
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// 7b. Base64 decode piped to execution — always block
|
|
487
|
+
if (/\bbase64\s+-d\b.*\|\s*(?:bash|sh|node|python|perl|ruby)\b/i.test(fullCmd) ||
|
|
488
|
+
/\bbase64\s+--decode\b.*\|\s*(?:bash|sh|node|python|perl|ruby)\b/i.test(fullCmd)) {
|
|
489
|
+
await blockSelfProtection('SOLONGATE: base64 decode piped to interpreter — blocked');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 7c. Temp script file execution: bash /path/file, sh /path/file
|
|
493
|
+
// If "bash <file>" or "sh <file>" and the file is not a well-known script
|
|
494
|
+
if (/\b(?:bash|sh)\s+(?:\/tmp\/|\/var\/tmp\/|~\/\.|\.\/[^.s])/i.test(fullCmd)) {
|
|
495
|
+
await blockSelfProtection('SOLONGATE: Temp script execution detected — blocked');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// 7d. Process substitution and here-strings that could construct protected paths
|
|
499
|
+
if (/>\s*\(\s*(?:rm|mv|cp|cat)\b/i.test(fullCmd) || /<<<.*(?:rm|mv|cp|cat)\b/i.test(fullCmd)) {
|
|
500
|
+
for (const p of protectedPaths) {
|
|
501
|
+
const prefix = p.slice(0, 4); // e.g. ".sol", ".cla"
|
|
502
|
+
if (fullCmd.includes(prefix)) {
|
|
503
|
+
await blockSelfProtection('SOLONGATE: Process substitution near protected path "' + p + '" — blocked');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// 7e. xargs with destructive operations
|
|
509
|
+
if (/\bxargs\b.*\b(?:rm|mv|cp|rmdir|unlink)\b/i.test(fullCmd)) {
|
|
510
|
+
for (const p of protectedPaths) {
|
|
511
|
+
if (fullCmd.includes(p.slice(0, 4))) {
|
|
512
|
+
await blockSelfProtection('SOLONGATE: xargs with destructive op near "' + p + '" — blocked');
|
|
393
513
|
}
|
|
394
|
-
process.stderr.write(msg);
|
|
395
|
-
process.exit(2);
|
|
396
514
|
}
|
|
397
515
|
}
|
|
398
516
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
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": {
|