@solongate/proxy 0.82.20 → 0.82.22

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/inject.js DELETED
@@ -1,375 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/inject.ts
4
- import { readFileSync, writeFileSync, existsSync, copyFileSync } from "fs";
5
- import { resolve } from "path";
6
- import { execSync } from "child_process";
7
-
8
- // src/cli-utils.ts
9
- var c = {
10
- reset: "\x1B[0m",
11
- bold: "\x1B[1m",
12
- dim: "\x1B[2m",
13
- italic: "\x1B[3m",
14
- white: "\x1B[97m",
15
- gray: "\x1B[90m",
16
- blue1: "\x1B[38;2;20;50;160m",
17
- blue2: "\x1B[38;2;40;80;190m",
18
- blue3: "\x1B[38;2;60;110;215m",
19
- blue4: "\x1B[38;2;90;140;230m",
20
- blue5: "\x1B[38;2;130;170;240m",
21
- blue6: "\x1B[38;2;170;200;250m",
22
- green: "\x1B[38;2;80;200;120m",
23
- red: "\x1B[38;2;220;80;80m",
24
- cyan: "\x1B[38;2;100;200;220m",
25
- yellow: "\x1B[38;2;220;200;80m",
26
- bgBlue: "\x1B[48;2;20;50;160m"
27
- };
28
- function log(msg) {
29
- process.stderr.write(msg + "\n");
30
- }
31
- var BANNER_FULL = [
32
- " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
33
- " \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
34
- " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 ",
35
- " \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D ",
36
- " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
37
- " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
38
- ];
39
- var BANNER_COLORS = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
40
- function printBanner(subtitle) {
41
- log("");
42
- for (let i = 0; i < BANNER_FULL.length; i++) {
43
- log(`${c.bold}${BANNER_COLORS[i]}${BANNER_FULL[i]}${c.reset}`);
44
- }
45
- log("");
46
- log(` ${c.dim}${c.italic}${subtitle}${c.reset}`);
47
- log("");
48
- }
49
-
50
- // src/inject.ts
51
- function parseInjectArgs(argv) {
52
- const args = argv.slice(2);
53
- const opts = {
54
- dryRun: false,
55
- restore: false,
56
- skipInstall: false
57
- };
58
- for (let i = 0; i < args.length; i++) {
59
- switch (args[i]) {
60
- case "--file":
61
- opts.file = args[++i];
62
- break;
63
- case "--dry-run":
64
- opts.dryRun = true;
65
- break;
66
- case "--restore":
67
- opts.restore = true;
68
- break;
69
- case "--skip-install":
70
- opts.skipInstall = true;
71
- break;
72
- case "--help":
73
- case "-h":
74
- printHelp();
75
- process.exit(0);
76
- }
77
- }
78
- return opts;
79
- }
80
- function printHelp() {
81
- log(`
82
- SolonGate Inject \u2014 Add security to your MCP server in seconds
83
-
84
- USAGE
85
- npx @solongate/proxy inject [options]
86
-
87
- OPTIONS
88
- --file <path> Entry file to modify (default: auto-detect)
89
- --dry-run Preview changes without writing
90
- --restore Restore original file from backup
91
- --skip-install Don't install SDK package
92
- -h, --help Show this help message
93
-
94
- EXAMPLES
95
- npx @solongate/proxy inject # Auto-detect and inject
96
- npx @solongate/proxy inject --file src/main.ts # Specify entry file
97
- npx @solongate/proxy inject --dry-run # Preview changes
98
- npx @solongate/proxy inject --restore # Undo injection
99
-
100
- WHAT IT DOES
101
- Replaces McpServer with SecureMcpServer (2 lines changed)
102
- All tool() calls are automatically protected by SolonGate.
103
- `);
104
- }
105
- function detectProject() {
106
- if (!existsSync(resolve("package.json"))) return false;
107
- try {
108
- const pkg = JSON.parse(readFileSync(resolve("package.json"), "utf-8"));
109
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
110
- return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
111
- } catch {
112
- return false;
113
- }
114
- }
115
- function findTsEntryFile() {
116
- try {
117
- const pkg = JSON.parse(readFileSync(resolve("package.json"), "utf-8"));
118
- if (pkg.bin) {
119
- const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
120
- if (typeof binPath === "string") {
121
- const srcPath = binPath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
122
- if (existsSync(resolve(srcPath))) return resolve(srcPath);
123
- if (existsSync(resolve(binPath))) return resolve(binPath);
124
- }
125
- }
126
- if (pkg.main) {
127
- const srcPath = pkg.main.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
128
- if (existsSync(resolve(srcPath))) return resolve(srcPath);
129
- }
130
- } catch {
131
- }
132
- const candidates = [
133
- "src/index.ts",
134
- "src/server.ts",
135
- "src/main.ts",
136
- "index.ts",
137
- "server.ts",
138
- "main.ts"
139
- ];
140
- for (const c2 of candidates) {
141
- const full = resolve(c2);
142
- if (existsSync(full)) {
143
- try {
144
- const content = readFileSync(full, "utf-8");
145
- if (content.includes("McpServer") || content.includes("McpServer")) {
146
- return full;
147
- }
148
- } catch {
149
- }
150
- }
151
- }
152
- for (const c2 of candidates) {
153
- if (existsSync(resolve(c2))) return resolve(c2);
154
- }
155
- return null;
156
- }
157
- function detectPackageManager() {
158
- if (existsSync(resolve("pnpm-lock.yaml"))) return "pnpm";
159
- if (existsSync(resolve("yarn.lock"))) return "yarn";
160
- return "npm";
161
- }
162
- function installSdk() {
163
- try {
164
- const pkg = JSON.parse(readFileSync(resolve("package.json"), "utf-8"));
165
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
166
- if (allDeps["@solongate/proxy"]) {
167
- log(" @solongate/proxy already installed");
168
- return true;
169
- }
170
- } catch {
171
- }
172
- const pm = detectPackageManager();
173
- const cmd = pm === "yarn" ? "yarn add @solongate/proxy" : `${pm} install @solongate/proxy`;
174
- log(` Installing @solongate/proxy via ${pm}...`);
175
- try {
176
- execSync(cmd, { stdio: "pipe", cwd: process.cwd() });
177
- return true;
178
- } catch (err) {
179
- log(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
180
- log(" You can install manually: npm install @solongate/proxy");
181
- return false;
182
- }
183
- }
184
- function injectTypeScript(filePath) {
185
- const original = readFileSync(filePath, "utf-8");
186
- const changes = [];
187
- let modified = original;
188
- if (modified.includes("SecureMcpServer")) {
189
- return { file: filePath, changes: ["Already injected \u2014 skipping"], original, modified };
190
- }
191
- const mcpImportPatterns = [
192
- // Solo import: import { McpServer } from '...'
193
- /import\s*\{\s*McpServer\s*\}\s*from\s*['"][^'"]+['"]/,
194
- // Import with others: import { McpServer, ... } from '...'
195
- /import\s*\{[^}]*McpServer[^}]*\}\s*from\s*['"][^'"]+['"]/
196
- ];
197
- let importReplaced = false;
198
- for (const pattern of mcpImportPatterns) {
199
- const match = modified.match(pattern);
200
- if (match) {
201
- const importLine = match[0];
202
- const namedImports = importLine.match(/\{([^}]+)\}/)?.[1] ?? "";
203
- const importNames = namedImports.split(",").map((s) => s.trim()).filter(Boolean);
204
- if (importNames.length === 1 && importNames[0] === "McpServer") {
205
- modified = modified.replace(
206
- importLine,
207
- `import { SecureMcpServer } from '@solongate/proxy'`
208
- );
209
- changes.push("Replaced McpServer import with SecureMcpServer from @solongate/proxy");
210
- } else {
211
- const otherImports = importNames.filter((n) => n !== "McpServer");
212
- const fromModule = importLine.match(/from\s*['"]([^'"]+)['"]/)?.[1] ?? "";
213
- modified = modified.replace(
214
- importLine,
215
- `import { ${otherImports.join(", ")} } from '${fromModule}';
216
- import { SecureMcpServer } from '@solongate/proxy'`
217
- );
218
- changes.push("Removed McpServer from existing import, added SecureMcpServer import from @solongate/proxy");
219
- }
220
- importReplaced = true;
221
- break;
222
- }
223
- }
224
- if (!importReplaced) {
225
- const insertPos = findImportInsertPosition(modified);
226
- modified = modified.slice(0, insertPos) + `import { SecureMcpServer } from '@solongate/proxy';
227
- ` + modified.slice(insertPos);
228
- changes.push("Added SecureMcpServer import from @solongate/proxy");
229
- }
230
- const constructorPattern = /new\s+McpServer\s*\(/g;
231
- const constructorCount = (modified.match(constructorPattern) || []).length;
232
- if (constructorCount > 0) {
233
- modified = modified.replace(constructorPattern, "new SecureMcpServer(");
234
- changes.push(`Replaced ${constructorCount} McpServer constructor(s) with SecureMcpServer`);
235
- }
236
- return { file: filePath, changes, original, modified };
237
- }
238
- function findImportInsertPosition(content) {
239
- let pos = 0;
240
- if (content.startsWith("#!")) {
241
- pos = content.indexOf("\n") + 1;
242
- }
243
- const lines = content.slice(pos).split("\n");
244
- let offset = pos;
245
- for (const line of lines) {
246
- const trimmed = line.trim();
247
- if (trimmed === "" || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || trimmed.startsWith("*/")) {
248
- offset += line.length + 1;
249
- } else {
250
- break;
251
- }
252
- }
253
- const importRegex = /^import\s+.+$/gm;
254
- let lastImportEnd = offset;
255
- let importMatch;
256
- while ((importMatch = importRegex.exec(content)) !== null) {
257
- lastImportEnd = importMatch.index + importMatch[0].length + 1;
258
- }
259
- return lastImportEnd > offset ? lastImportEnd : offset;
260
- }
261
- function showDiff(result) {
262
- if (result.original === result.modified) {
263
- log(" No changes needed.");
264
- return;
265
- }
266
- const origLines = result.original.split("\n");
267
- const modLines = result.modified.split("\n");
268
- log("");
269
- log(` File: ${result.file}`);
270
- log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
271
- const maxLines = Math.max(origLines.length, modLines.length);
272
- let diffCount = 0;
273
- for (let i = 0; i < maxLines; i++) {
274
- const orig = origLines[i];
275
- const mod = modLines[i];
276
- if (orig !== mod) {
277
- if (diffCount < 30) {
278
- if (orig !== void 0) log(` - ${orig}`);
279
- if (mod !== void 0) log(` + ${mod}`);
280
- }
281
- diffCount++;
282
- }
283
- }
284
- if (diffCount > 30) {
285
- log(` ... and ${diffCount - 30} more line changes`);
286
- }
287
- log("");
288
- }
289
- async function main() {
290
- const opts = parseInjectArgs(process.argv);
291
- printBanner("Inject SDK");
292
- if (!detectProject()) {
293
- log(" Could not detect a TypeScript MCP server project.");
294
- log(" Make sure you are in a project directory with:");
295
- log(" package.json + @modelcontextprotocol/sdk in dependencies");
296
- log("");
297
- log(" To create a new MCP server: npx @solongate/proxy create <name>");
298
- process.exit(1);
299
- }
300
- log(" Language: TypeScript");
301
- const entryFile = opts.file ? resolve(opts.file) : findTsEntryFile();
302
- if (!entryFile || !existsSync(entryFile)) {
303
- log(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
304
- log("");
305
- log(" Specify it manually: --file <path>");
306
- log("");
307
- log(" Common entry points:");
308
- log(" src/index.ts, src/server.ts, index.ts");
309
- process.exit(1);
310
- }
311
- log(` Entry: ${entryFile}`);
312
- log("");
313
- const backupPath = entryFile + ".solongate-backup";
314
- if (opts.restore) {
315
- if (!existsSync(backupPath)) {
316
- log(" No backup found. Nothing to restore.");
317
- process.exit(1);
318
- }
319
- copyFileSync(backupPath, entryFile);
320
- log(` Restored original file from backup.`);
321
- log(` Backup: ${backupPath}`);
322
- process.exit(0);
323
- }
324
- if (!opts.skipInstall && !opts.dryRun) {
325
- installSdk();
326
- log("");
327
- }
328
- const result = injectTypeScript(entryFile);
329
- log(` Changes (${result.changes.length}):`);
330
- for (const change of result.changes) {
331
- log(` - ${change}`);
332
- }
333
- if (result.changes.length === 1 && result.changes[0].includes("Already injected")) {
334
- log("");
335
- log(" Your MCP server is already protected by SolonGate!");
336
- process.exit(0);
337
- }
338
- if (result.original === result.modified) {
339
- log("");
340
- log(" No changes were made. The file may not contain recognizable MCP patterns.");
341
- log(" See docs: https://solongate.com/docs/integration");
342
- process.exit(0);
343
- }
344
- if (opts.dryRun) {
345
- log("");
346
- log(" --- DRY RUN (no changes written) ---");
347
- showDiff(result);
348
- log(" To apply: npx @solongate/proxy inject");
349
- process.exit(0);
350
- }
351
- if (!existsSync(backupPath)) {
352
- copyFileSync(entryFile, backupPath);
353
- log("");
354
- log(` Backup: ${backupPath}`);
355
- }
356
- writeFileSync(entryFile, result.modified);
357
- log("");
358
- log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
359
- log(" \u2502 SolonGate SDK injected successfully! \u2502");
360
- log(" \u2502 \u2502");
361
- log(" \u2502 McpServer \u2192 SecureMcpServer \u2502");
362
- log(" \u2502 All tool() calls are now auto-protected. \u2502");
363
- log(" \u2502 \u2502");
364
- log(" \u2502 Set your API key: \u2502");
365
- log(" \u2502 export SOLONGATE_API_KEY=sg_live_xxx \u2502");
366
- log(" \u2502 \u2502");
367
- log(" \u2502 To undo: \u2502");
368
- log(" \u2502 npx @solongate/proxy inject --restore \u2502");
369
- log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
370
- log("");
371
- }
372
- main().catch((err) => {
373
- log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
374
- process.exit(1);
375
- });
package/dist/shield.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function runShield(): Promise<void>;
package/dist/shield.js DELETED
@@ -1,314 +0,0 @@
1
- // src/shield.ts
2
- import { createServer, request as httpRequest } from "http";
3
- import { request as httpsRequest } from "https";
4
- import { spawn } from "child_process";
5
- import { URL } from "url";
6
- import { readFileSync, existsSync, readdirSync, statSync } from "fs";
7
- import { resolve } from "path";
8
- import { homedir } from "os";
9
- var log = (...a) => process.stderr.write(`[SolonGate shield] ${a.map(String).join(" ")}
10
- `);
11
- var DLP_PATTERNS = [
12
- { name: "AWS access key", re: /AKIA[0-9A-Z]{16}/g },
13
- { name: "Private key block", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----|-----BEGIN [A-Z ]*PRIVATE KEY-----/g },
14
- { name: "Anthropic key", re: /sk-ant-[A-Za-z0-9_-]{20,}/g },
15
- { name: "OpenAI key", re: /sk-(proj-)?[A-Za-z0-9_-]{20,}/g },
16
- { name: "GitHub token", re: /gh[pousr]_[A-Za-z0-9]{20,}/g },
17
- { name: "GitHub fine-grained PAT", re: /github_pat_[A-Za-z0-9_]{20,}/g },
18
- { name: "GitLab token", re: /glpat-[A-Za-z0-9_-]{20,}/g },
19
- { name: "Slack token", re: /xox[baprs]-[A-Za-z0-9-]{10,}/g },
20
- { name: "Stripe key", re: /[sr]k_(live|test)_[A-Za-z0-9]{20,}/g },
21
- { name: "SendGrid key", re: /SG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g },
22
- { name: "Twilio key", re: /SK[0-9a-fA-F]{32}/g },
23
- { name: "npm token", re: /npm_[A-Za-z0-9]{36}/g },
24
- { name: "JWT", re: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g },
25
- { name: "Bearer token", re: /bearer\s+[A-Za-z0-9._-]{20,}/gi }
26
- ];
27
- function findCacheFile() {
28
- const dir = resolve(homedir(), ".solongate");
29
- const envSel = process.env.SOLONGATE_AGENT_ID;
30
- if (envSel) {
31
- const f = resolve(dir, ".policy-cache-" + envSel.replace(/[^a-zA-Z0-9_-]/g, "_") + ".json");
32
- if (existsSync(f)) return f;
33
- }
34
- let best = null, bestTs = -1;
35
- try {
36
- for (const name of readdirSync(dir)) {
37
- if (name.startsWith(".policy-cache-") && name.endsWith(".json")) {
38
- const full = resolve(dir, name);
39
- const ts = statSync(full).mtimeMs;
40
- if (ts > bestTs) {
41
- bestTs = ts;
42
- best = full;
43
- }
44
- }
45
- }
46
- } catch {
47
- }
48
- return best;
49
- }
50
- function loadCfg() {
51
- try {
52
- const f = findCacheFile();
53
- if (f && existsSync(f)) {
54
- const c = JSON.parse(readFileSync(f, "utf-8"));
55
- const d = c?.security?.dlpRedact;
56
- const g = c?.security?.ghost;
57
- const ghost = g && Array.isArray(g.patterns) ? g.patterns : [];
58
- if (d && Array.isArray(d.patterns)) return { patterns: d.patterns, custom: Array.isArray(d.custom) ? d.custom : [], ghost };
59
- return { patterns: DLP_PATTERNS.map((p) => p.name), custom: [], ghost };
60
- }
61
- } catch {
62
- }
63
- return { patterns: DLP_PATTERNS.map((p) => p.name), custom: [], ghost: [] };
64
- }
65
- function ghostGlobToRegExp(glob) {
66
- let re = "";
67
- for (let i = 0; i < glob.length; i++) {
68
- const c = glob[i];
69
- if (c === "*") {
70
- if (glob[i + 1] === "*") {
71
- re += ".*";
72
- i++;
73
- } else re += "[^/]*";
74
- } else if (c === "?") re += "[^/]";
75
- else if ("\\^$.|+()[]{}".indexOf(c) !== -1) re += "\\" + c;
76
- else re += c;
77
- }
78
- try {
79
- return new RegExp("^" + re + "$");
80
- } catch {
81
- return null;
82
- }
83
- }
84
- function ghostMatch(targetPath, patterns) {
85
- if (!targetPath || !Array.isArray(patterns) || patterns.length === 0) return false;
86
- const norm = String(targetPath).replace(/\\/g, "/").replace(/\/+$/, "");
87
- if (!norm) return false;
88
- const segments = norm.split("/").filter(Boolean);
89
- const base = segments.length ? segments[segments.length - 1] : norm;
90
- for (let pat of patterns) {
91
- pat = String(pat || "").trim();
92
- if (!pat) continue;
93
- let dirOnly = false;
94
- if (pat.endsWith("/")) {
95
- dirOnly = true;
96
- pat = pat.slice(0, -1);
97
- }
98
- if (!pat) continue;
99
- const hasSlash = pat.indexOf("/") !== -1;
100
- const hasWild = /[*?]/.test(pat);
101
- const re = ghostGlobToRegExp(pat);
102
- if (!re) continue;
103
- if (dirOnly) {
104
- if (!hasSlash && !hasWild) {
105
- if (segments.indexOf(pat) !== -1) return true;
106
- continue;
107
- }
108
- let acc = "";
109
- for (const s of segments) {
110
- acc = acc ? acc + "/" + s : s;
111
- if (re.test(acc) || re.test(s)) return true;
112
- }
113
- continue;
114
- }
115
- if (!hasSlash) {
116
- if (re.test(base)) return true;
117
- if (segments.some((s) => re.test(s))) return true;
118
- continue;
119
- }
120
- if (re.test(norm)) return true;
121
- }
122
- return false;
123
- }
124
- function ghostCleanToken(tok) {
125
- let t = String(tok || "").trim();
126
- t = t.replace(/^[<>|;&(]+/, "").replace(/[);&|]+$/, "");
127
- t = t.replace(/^['"]+/, "").replace(/['"]+$/, "");
128
- t = t.replace(/^\d*>>?/, "");
129
- return t.trim();
130
- }
131
- function ghostStripLines(text, pats) {
132
- if (!Array.isArray(pats) || pats.length === 0) return text;
133
- const lines = String(text).split("\n");
134
- const kept = [];
135
- for (const line of lines) {
136
- const trimmed = line.trim();
137
- if (!trimmed) {
138
- kept.push(line);
139
- continue;
140
- }
141
- if (ghostMatch(trimmed, pats)) continue;
142
- const toks = trimmed.split(/\s+/);
143
- const anyHit = toks.some((t) => ghostMatch(ghostCleanToken(t), pats));
144
- if (!anyHit) {
145
- kept.push(line);
146
- continue;
147
- }
148
- if (toks.length > 3) continue;
149
- const remaining = toks.filter((t) => !ghostMatch(ghostCleanToken(t), pats));
150
- if (remaining.length === 0) continue;
151
- kept.push(remaining.join(" "));
152
- }
153
- return kept.join("\n");
154
- }
155
- function dlpGlobToRe(glob, flags) {
156
- let re = "";
157
- for (const ch of String(glob || "")) {
158
- if (ch === "*") re += "[^\\s]*";
159
- else if (".+?^${}()|[]\\".indexOf(ch) !== -1) re += "\\" + ch;
160
- else re += ch;
161
- }
162
- return new RegExp(re, flags);
163
- }
164
- function redactString(s, cfg) {
165
- if (!cfg || typeof s !== "string" || !s) return s;
166
- const allow = new Set(cfg.patterns);
167
- const NUL = String.fromCharCode(0);
168
- const labels = [];
169
- const stash = (label) => NUL + (labels.push(label) - 1) + NUL;
170
- let out = s;
171
- for (const p of DLP_PATTERNS) if (allow.has(p.name)) out = out.replace(p.re, () => stash(`[REDACTED: ${p.name}]`));
172
- for (const c of cfg.custom) {
173
- try {
174
- out = out.replace(dlpGlobToRe(c.re, "gi"), () => stash(`[REDACTED: ${c.name || "custom"}]`));
175
- } catch {
176
- }
177
- }
178
- return out.replace(new RegExp(NUL + "(\\d+)" + NUL, "g"), (_, i) => labels[+i] || "");
179
- }
180
- function redactDeep(value, cfg) {
181
- const ghost = cfg && Array.isArray(cfg.ghost) ? cfg.ghost : null;
182
- if (typeof value === "string") {
183
- let out = redactString(value, cfg);
184
- if (ghost && ghost.length) out = ghostStripLines(out, ghost);
185
- if (out === "" && value !== "") out = "\n";
186
- return out;
187
- }
188
- if (Array.isArray(value)) {
189
- const arr = ghost && ghost.length ? value.filter((v) => !(typeof v === "string" && ghostMatch(v.trim(), ghost))) : value;
190
- return arr.map((v) => redactDeep(v, cfg));
191
- }
192
- if (value && typeof value === "object") {
193
- const out = {};
194
- for (const [k, v] of Object.entries(value)) out[k] = redactDeep(v, cfg);
195
- return out;
196
- }
197
- return value;
198
- }
199
- function pickUpstream() {
200
- const raw = process.env.SOLONGATE_SHIELD_UPSTREAM || process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
201
- try {
202
- return new URL(raw);
203
- } catch {
204
- return new URL("https://api.anthropic.com");
205
- }
206
- }
207
- function startProxy(upstream) {
208
- const forward = upstream.protocol === "https:" ? httpsRequest : httpRequest;
209
- const server = createServer((req, res) => {
210
- const cfg = loadCfg();
211
- const chunks = [];
212
- req.on("data", (c) => chunks.push(c));
213
- req.on("end", () => {
214
- let body = Buffer.concat(chunks);
215
- try {
216
- if (body.length && (req.headers["content-type"] || "").includes("json")) {
217
- const parsed = JSON.parse(body.toString("utf-8"));
218
- if (parsed && typeof parsed === "object" && Array.isArray(parsed["messages"])) {
219
- if (parsed["system"] !== void 0) parsed["system"] = redactDeep(parsed["system"], cfg);
220
- const msgs = parsed["messages"];
221
- for (let i = 0; i < msgs.length; i++) {
222
- const m = msgs[i];
223
- if (m && m["role"] !== "assistant") msgs[i] = redactDeep(m, cfg);
224
- }
225
- body = Buffer.from(JSON.stringify(parsed), "utf-8");
226
- } else {
227
- body = Buffer.from(JSON.stringify(redactDeep(parsed, cfg)), "utf-8");
228
- }
229
- }
230
- } catch {
231
- }
232
- const headers = { ...req.headers };
233
- delete headers["host"];
234
- delete headers["content-length"];
235
- delete headers["accept-encoding"];
236
- headers["content-length"] = String(body.length);
237
- const upstreamReq = forward(
238
- {
239
- protocol: upstream.protocol,
240
- hostname: upstream.hostname,
241
- port: upstream.port || (upstream.protocol === "https:" ? 443 : 80),
242
- method: req.method,
243
- path: req.url,
244
- headers: { ...headers, host: upstream.host }
245
- },
246
- (upRes) => {
247
- res.writeHead(upRes.statusCode || 502, upRes.headers);
248
- upRes.pipe(res);
249
- }
250
- );
251
- upstreamReq.on("error", (e) => {
252
- log("upstream error:", e.message);
253
- if (!res.headersSent) res.writeHead(502, { "content-type": "text/plain" });
254
- res.end("shield upstream error");
255
- });
256
- upstreamReq.end(body);
257
- });
258
- req.on("error", () => {
259
- try {
260
- res.destroy();
261
- } catch {
262
- }
263
- });
264
- });
265
- return new Promise((resolveP) => {
266
- server.listen(0, "127.0.0.1", () => {
267
- const addr = server.address();
268
- const port = typeof addr === "object" && addr ? addr.port : 0;
269
- resolveP({ port, close: () => server.close() });
270
- });
271
- });
272
- }
273
- async function runShield() {
274
- const sep = process.argv.indexOf("--");
275
- const cmd = sep !== -1 ? process.argv.slice(sep + 1) : [];
276
- if (cmd.length === 0) {
277
- log("usage: npx @solongate/proxy shield -- <command> [args...] (e.g. shield -- claude)");
278
- process.exit(1);
279
- }
280
- const upstream = pickUpstream();
281
- const { port, close } = await startProxy(upstream);
282
- log(`redacting secrets on the LLM path \u2192 masking before ${upstream.host} (127.0.0.1:${port})`);
283
- const child = spawn(cmd[0], cmd.slice(1), {
284
- stdio: "inherit",
285
- env: { ...process.env, ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}` },
286
- shell: process.platform === "win32"
287
- // resolve `claude.cmd` etc. on Windows
288
- });
289
- const shutdown = () => {
290
- try {
291
- close();
292
- } catch {
293
- }
294
- };
295
- child.on("exit", (code, signal) => {
296
- shutdown();
297
- if (signal) process.kill(process.pid, signal);
298
- else process.exit(code ?? 0);
299
- });
300
- child.on("error", (e) => {
301
- log("failed to launch command:", e.message);
302
- shutdown();
303
- process.exit(1);
304
- });
305
- for (const sig of ["SIGINT", "SIGTERM"]) process.on(sig, () => {
306
- try {
307
- child.kill(sig);
308
- } catch {
309
- }
310
- });
311
- }
312
- export {
313
- runShield
314
- };