@solongate/proxy 0.6.8 → 0.8.0
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/create.js +3 -3
- package/dist/index.js +973 -372
- package/dist/init.js +9 -14
- package/dist/pull-push.js +180 -36
- package/package.json +1 -1
package/dist/init.js
CHANGED
|
@@ -67,18 +67,14 @@ function isAlreadyProtected(server) {
|
|
|
67
67
|
function wrapServer(server, policy) {
|
|
68
68
|
const env = { ...server.env ?? {} };
|
|
69
69
|
env.SOLONGATE_API_KEY = "${SOLONGATE_API_KEY}";
|
|
70
|
+
const proxyArgs = ["-y", "@solongate/proxy"];
|
|
71
|
+
if (policy) {
|
|
72
|
+
proxyArgs.push("--policy", policy);
|
|
73
|
+
}
|
|
74
|
+
proxyArgs.push("--verbose", "--", server.command, ...server.args ?? []);
|
|
70
75
|
return {
|
|
71
76
|
command: "npx",
|
|
72
|
-
args:
|
|
73
|
-
"-y",
|
|
74
|
-
"@solongate/proxy",
|
|
75
|
-
"--policy",
|
|
76
|
-
policy,
|
|
77
|
-
"--verbose",
|
|
78
|
-
"--",
|
|
79
|
-
server.command,
|
|
80
|
-
...server.args ?? []
|
|
81
|
-
],
|
|
77
|
+
args: proxyArgs,
|
|
82
78
|
env
|
|
83
79
|
};
|
|
84
80
|
}
|
|
@@ -127,8 +123,7 @@ USAGE
|
|
|
127
123
|
|
|
128
124
|
OPTIONS
|
|
129
125
|
--config <path> Path to MCP config file (default: auto-detect)
|
|
130
|
-
--policy <file> Custom policy JSON file (
|
|
131
|
-
Auto-detects policy.json in current directory
|
|
126
|
+
--policy <file> Custom policy JSON file (auto-detects policy.json)
|
|
132
127
|
--api-key <key> SolonGate API key (sg_live_... or sg_test_...)
|
|
133
128
|
--all Protect all servers without prompting
|
|
134
129
|
-h, --help Show this help message
|
|
@@ -685,7 +680,7 @@ async function main() {
|
|
|
685
680
|
console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
|
|
686
681
|
process.exit(1);
|
|
687
682
|
}
|
|
688
|
-
let policyValue
|
|
683
|
+
let policyValue;
|
|
689
684
|
if (options.policy) {
|
|
690
685
|
const policyPath = resolve(options.policy);
|
|
691
686
|
if (existsSync(policyPath)) {
|
|
@@ -701,7 +696,7 @@ async function main() {
|
|
|
701
696
|
policyValue = "./policy.json";
|
|
702
697
|
console.log(` Policy: ${defaultPolicy} (auto-detected)`);
|
|
703
698
|
} else {
|
|
704
|
-
console.log(` Policy:
|
|
699
|
+
console.log(` Policy: cloud-managed (fetched via API key)`);
|
|
705
700
|
}
|
|
706
701
|
}
|
|
707
702
|
await sleep(300);
|
package/dist/pull-push.js
CHANGED
|
@@ -44,8 +44,15 @@ async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// src/pull-push.ts
|
|
47
|
-
var log = (...args) => process.stderr.write(
|
|
47
|
+
var log = (...args) => process.stderr.write(`${args.map(String).join(" ")}
|
|
48
48
|
`);
|
|
49
|
+
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
50
|
+
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
51
|
+
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
52
|
+
var red = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
53
|
+
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
54
|
+
var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
55
|
+
var magenta = (s) => `\x1B[35m${s}\x1B[0m`;
|
|
49
56
|
function loadEnv() {
|
|
50
57
|
if (process.env.SOLONGATE_API_KEY) return;
|
|
51
58
|
const envPath = resolve2(".env");
|
|
@@ -93,7 +100,7 @@ function parseCliArgs() {
|
|
|
93
100
|
}
|
|
94
101
|
}
|
|
95
102
|
if (!apiKey) {
|
|
96
|
-
log("ERROR: API key not found.");
|
|
103
|
+
log(red("ERROR: API key not found."));
|
|
97
104
|
log("");
|
|
98
105
|
log("Set it in .env file:");
|
|
99
106
|
log(" SOLONGATE_API_KEY=sg_live_...");
|
|
@@ -103,53 +110,170 @@ function parseCliArgs() {
|
|
|
103
110
|
process.exit(1);
|
|
104
111
|
}
|
|
105
112
|
if (!apiKey.startsWith("sg_live_")) {
|
|
106
|
-
log("ERROR: Pull/push requires a live API key (sg_live_...).");
|
|
113
|
+
log(red("ERROR: Pull/push/list requires a live API key (sg_live_...)."));
|
|
107
114
|
process.exit(1);
|
|
108
115
|
}
|
|
109
116
|
return { command, apiKey, file: resolve2(file), policyId };
|
|
110
117
|
}
|
|
118
|
+
var API_URL = "https://api.solongate.com";
|
|
111
119
|
async function listPolicies(apiKey) {
|
|
112
|
-
const res = await fetch(
|
|
120
|
+
const res = await fetch(`${API_URL}/api/v1/policies`, {
|
|
113
121
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
114
122
|
});
|
|
115
123
|
if (!res.ok) throw new Error(`Failed to list policies (${res.status})`);
|
|
116
124
|
const data = await res.json();
|
|
117
125
|
return data.policies ?? [];
|
|
118
126
|
}
|
|
127
|
+
async function list(apiKey, policyId) {
|
|
128
|
+
const policies = await listPolicies(apiKey);
|
|
129
|
+
if (policies.length === 0) {
|
|
130
|
+
log(yellow("No policies found. Create one in the dashboard first."));
|
|
131
|
+
log(dim(" https://dashboard.solongate.com/policies"));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (policyId) {
|
|
135
|
+
const match = policies.find((p) => p.id === policyId);
|
|
136
|
+
if (!match) {
|
|
137
|
+
log(red(`Policy not found: ${policyId}`));
|
|
138
|
+
log("");
|
|
139
|
+
log("Available policies:");
|
|
140
|
+
for (const p of policies) {
|
|
141
|
+
log(` ${dim("\u2022")} ${p.id}`);
|
|
142
|
+
}
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
const full = await fetchCloudPolicy(apiKey, API_URL, policyId);
|
|
146
|
+
printPolicyDetail(full);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
log("");
|
|
150
|
+
log(bold(` Policies (${policies.length})`));
|
|
151
|
+
log(dim(" \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"));
|
|
152
|
+
log("");
|
|
153
|
+
for (const p of policies) {
|
|
154
|
+
try {
|
|
155
|
+
const full = await fetchCloudPolicy(apiKey, API_URL, p.id);
|
|
156
|
+
printPolicySummary(p, full.rules);
|
|
157
|
+
} catch {
|
|
158
|
+
printPolicySummary(p, []);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
log(dim(" \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"));
|
|
162
|
+
log("");
|
|
163
|
+
log(` ${dim("View details:")} solongate-proxy list --policy-id <ID>`);
|
|
164
|
+
log(` ${dim("Pull policy:")} solongate-proxy pull --policy-id <ID>`);
|
|
165
|
+
log(` ${dim("Push policy:")} solongate-proxy push --policy-id <ID>`);
|
|
166
|
+
log("");
|
|
167
|
+
}
|
|
168
|
+
function printPolicySummary(p, rules) {
|
|
169
|
+
const ruleCount = rules.length;
|
|
170
|
+
const allowCount = rules.filter((r) => r.effect === "ALLOW").length;
|
|
171
|
+
const denyCount = rules.filter((r) => r.effect === "DENY").length;
|
|
172
|
+
log(` ${cyan(p.id)}`);
|
|
173
|
+
log(` ${bold(p.name)} ${dim(`v${p.version ?? "?"}`)}`);
|
|
174
|
+
log(` ${dim("Rules:")} ${ruleCount} ${green(`${allowCount} ALLOW`)} ${red(`${denyCount} DENY`)}`);
|
|
175
|
+
if (p.created_at) {
|
|
176
|
+
log(` ${dim("Updated:")} ${new Date(p.created_at).toLocaleString()}`);
|
|
177
|
+
}
|
|
178
|
+
log("");
|
|
179
|
+
}
|
|
180
|
+
function printPolicyDetail(policy) {
|
|
181
|
+
log("");
|
|
182
|
+
log(bold(` ${policy.name}`));
|
|
183
|
+
log(` ${dim("ID:")} ${cyan(policy.id)} ${dim("Version:")} ${policy.version} ${dim("Rules:")} ${policy.rules.length}`);
|
|
184
|
+
log("");
|
|
185
|
+
if (policy.rules.length === 0) {
|
|
186
|
+
log(yellow(" No rules defined."));
|
|
187
|
+
log("");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
log(dim(" \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"));
|
|
191
|
+
for (const rule of policy.rules) {
|
|
192
|
+
const effectColor = rule.effect === "ALLOW" ? green : red;
|
|
193
|
+
log("");
|
|
194
|
+
log(` ${effectColor(rule.effect.padEnd(5))} ${bold(rule.toolPattern)} ${dim(`P:${rule.priority}`)}`);
|
|
195
|
+
if (rule.description) {
|
|
196
|
+
log(` ${dim(rule.description)}`);
|
|
197
|
+
}
|
|
198
|
+
log(` ${dim(`${rule.permission} trust:${rule.minimumTrustLevel || "UNTRUSTED"}`)}`);
|
|
199
|
+
if (rule.pathConstraints) {
|
|
200
|
+
const pc = rule.pathConstraints;
|
|
201
|
+
if (pc.rootDirectory) log(` ${magenta("ROOT")} ${pc.rootDirectory}`);
|
|
202
|
+
if (pc.allowed?.length) log(` ${green("PATHS")} ${pc.allowed.join(", ")}`);
|
|
203
|
+
if (pc.denied?.length) log(` ${red("DENY")} ${pc.denied.join(", ")}`);
|
|
204
|
+
}
|
|
205
|
+
if (rule.commandConstraints) {
|
|
206
|
+
const cc = rule.commandConstraints;
|
|
207
|
+
if (cc.allowed?.length) log(` ${green("CMDS")} ${cc.allowed.join(", ")}`);
|
|
208
|
+
if (cc.denied?.length) log(` ${red("DENY")} ${cc.denied.join(", ")}`);
|
|
209
|
+
}
|
|
210
|
+
if (rule.filenameConstraints) {
|
|
211
|
+
const fc = rule.filenameConstraints;
|
|
212
|
+
if (fc.allowed?.length) log(` ${green("FILES")} ${fc.allowed.join(", ")}`);
|
|
213
|
+
if (fc.denied?.length) log(` ${red("DENY")} ${fc.denied.join(", ")}`);
|
|
214
|
+
}
|
|
215
|
+
if (rule.urlConstraints) {
|
|
216
|
+
const uc = rule.urlConstraints;
|
|
217
|
+
if (uc.allowed?.length) log(` ${green("URLS")} ${uc.allowed.join(", ")}`);
|
|
218
|
+
if (uc.denied?.length) log(` ${red("DENY")} ${uc.denied.join(", ")}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
log("");
|
|
222
|
+
log(dim(" \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"));
|
|
223
|
+
log("");
|
|
224
|
+
}
|
|
119
225
|
async function pull(apiKey, file, policyId) {
|
|
120
|
-
const apiUrl = "https://api.solongate.com";
|
|
121
226
|
if (!policyId) {
|
|
122
227
|
const policies = await listPolicies(apiKey);
|
|
123
228
|
if (policies.length === 0) {
|
|
124
|
-
log("No policies found. Create one in the dashboard first.");
|
|
229
|
+
log(red("No policies found. Create one in the dashboard first."));
|
|
125
230
|
process.exit(1);
|
|
126
231
|
}
|
|
127
|
-
if (policies.length
|
|
128
|
-
|
|
129
|
-
`);
|
|
232
|
+
if (policies.length === 1) {
|
|
233
|
+
policyId = policies[0].id;
|
|
234
|
+
log(dim(`Auto-selecting only policy: ${policyId}`));
|
|
235
|
+
} else {
|
|
236
|
+
log(yellow(`Found ${policies.length} policies:`));
|
|
237
|
+
log("");
|
|
130
238
|
for (const p of policies) {
|
|
131
|
-
log(` ${p.id} ${p.name} (${
|
|
239
|
+
log(` ${cyan(p.id)} ${p.name} ${dim(`v${p.version ?? "?"}`)}`);
|
|
132
240
|
}
|
|
133
241
|
log("");
|
|
134
242
|
log("Use --policy-id <ID> to specify which one to pull.");
|
|
135
243
|
process.exit(1);
|
|
136
244
|
}
|
|
137
245
|
}
|
|
138
|
-
log(`Pulling
|
|
139
|
-
const policy = await fetchCloudPolicy(apiKey,
|
|
140
|
-
const
|
|
246
|
+
log(`Pulling ${cyan(policyId)} from dashboard...`);
|
|
247
|
+
const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
|
|
248
|
+
const { id: _id, ...policyWithoutId } = policy;
|
|
249
|
+
const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
|
|
141
250
|
writeFileSync(file, json, "utf-8");
|
|
142
|
-
log(`Saved: ${file}`);
|
|
143
|
-
log(` Name: ${policy.name}`);
|
|
144
|
-
log(` Version: ${policy.version}`);
|
|
145
|
-
log(` Rules: ${policy.rules.length}`);
|
|
146
251
|
log("");
|
|
147
|
-
log("
|
|
252
|
+
log(green(" Saved to: ") + file);
|
|
253
|
+
log(` ${dim("Name:")} ${policy.name}`);
|
|
254
|
+
log(` ${dim("Version:")} ${policy.version}`);
|
|
255
|
+
log(` ${dim("Rules:")} ${policy.rules.length}`);
|
|
256
|
+
log("");
|
|
257
|
+
log(dim("The policy file does not contain an ID."));
|
|
258
|
+
log(dim("Use --policy-id to specify the target when pushing/pulling."));
|
|
259
|
+
log("");
|
|
148
260
|
}
|
|
149
|
-
async function push(apiKey, file) {
|
|
150
|
-
const apiUrl = "https://api.solongate.com";
|
|
261
|
+
async function push(apiKey, file, policyId) {
|
|
151
262
|
if (!existsSync2(file)) {
|
|
152
|
-
log(`ERROR: File not found: ${file}`);
|
|
263
|
+
log(red(`ERROR: File not found: ${file}`));
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
if (!policyId) {
|
|
267
|
+
log(red("ERROR: --policy-id is required for push."));
|
|
268
|
+
log("");
|
|
269
|
+
log("This determines which cloud policy to update.");
|
|
270
|
+
log("");
|
|
271
|
+
log("Usage:");
|
|
272
|
+
log(" solongate-proxy push --policy-id my-policy");
|
|
273
|
+
log(" solongate-proxy push --policy-id my-policy --file custom.json");
|
|
274
|
+
log("");
|
|
275
|
+
log("List your policies:");
|
|
276
|
+
log(" solongate-proxy list");
|
|
153
277
|
process.exit(1);
|
|
154
278
|
}
|
|
155
279
|
const content = readFileSync2(file, "utf-8");
|
|
@@ -157,21 +281,26 @@ async function push(apiKey, file) {
|
|
|
157
281
|
try {
|
|
158
282
|
policy = JSON.parse(content);
|
|
159
283
|
} catch {
|
|
160
|
-
log(`ERROR: Invalid JSON in ${file}`);
|
|
284
|
+
log(red(`ERROR: Invalid JSON in ${file}`));
|
|
161
285
|
process.exit(1);
|
|
162
286
|
}
|
|
163
|
-
log(`Pushing
|
|
164
|
-
log(` File:
|
|
165
|
-
log(` Name:
|
|
166
|
-
log(` Rules: ${(policy.rules || []).length}`);
|
|
167
|
-
const
|
|
168
|
-
|
|
287
|
+
log(`Pushing to ${cyan(policyId)}...`);
|
|
288
|
+
log(` ${dim("File:")} ${file}`);
|
|
289
|
+
log(` ${dim("Name:")} ${policy.name || "Unnamed"}`);
|
|
290
|
+
log(` ${dim("Rules:")} ${(policy.rules || []).length}`);
|
|
291
|
+
const checkRes = await fetch(`${API_URL}/api/v1/policies/${policyId}`, {
|
|
292
|
+
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
293
|
+
});
|
|
294
|
+
const method = checkRes.ok ? "PUT" : "POST";
|
|
295
|
+
const url = checkRes.ok ? `${API_URL}/api/v1/policies/${policyId}` : `${API_URL}/api/v1/policies`;
|
|
296
|
+
const res = await fetch(url, {
|
|
297
|
+
method,
|
|
169
298
|
headers: {
|
|
170
299
|
"Authorization": `Bearer ${apiKey}`,
|
|
171
300
|
"Content-Type": "application/json"
|
|
172
301
|
},
|
|
173
302
|
body: JSON.stringify({
|
|
174
|
-
id:
|
|
303
|
+
id: policyId,
|
|
175
304
|
name: policy.name || "Local Policy",
|
|
176
305
|
description: policy.description || "Pushed from local file",
|
|
177
306
|
version: policy.version || 1,
|
|
@@ -180,13 +309,15 @@ async function push(apiKey, file) {
|
|
|
180
309
|
});
|
|
181
310
|
if (!res.ok) {
|
|
182
311
|
const body = await res.text().catch(() => "");
|
|
183
|
-
log(`ERROR: Push failed (${res.status}): ${body}`);
|
|
312
|
+
log(red(`ERROR: Push failed (${res.status}): ${body}`));
|
|
184
313
|
process.exit(1);
|
|
185
314
|
}
|
|
186
315
|
const data = await res.json();
|
|
187
|
-
log(` Cloud version: ${data._version ?? "created"}`);
|
|
188
316
|
log("");
|
|
189
|
-
log(
|
|
317
|
+
log(green(` Pushed to cloud: v${data._version ?? "created"}`));
|
|
318
|
+
log(` ${dim("Policy ID:")} ${policyId}`);
|
|
319
|
+
log(` ${dim("Method:")} ${method === "PUT" ? "Updated existing" : "Created new"}`);
|
|
320
|
+
log("");
|
|
190
321
|
}
|
|
191
322
|
async function main() {
|
|
192
323
|
const { command, apiKey, file, policyId } = parseCliArgs();
|
|
@@ -194,14 +325,27 @@ async function main() {
|
|
|
194
325
|
if (command === "pull") {
|
|
195
326
|
await pull(apiKey, file, policyId);
|
|
196
327
|
} else if (command === "push") {
|
|
197
|
-
await push(apiKey, file);
|
|
328
|
+
await push(apiKey, file, policyId);
|
|
329
|
+
} else if (command === "list" || command === "ls") {
|
|
330
|
+
await list(apiKey, policyId);
|
|
198
331
|
} else {
|
|
199
|
-
log(`Unknown command: ${command}`);
|
|
200
|
-
log("
|
|
332
|
+
log(red(`Unknown command: ${command}`));
|
|
333
|
+
log("");
|
|
334
|
+
log(bold("Usage:"));
|
|
335
|
+
log(" solongate-proxy list List all policies");
|
|
336
|
+
log(" solongate-proxy list --policy-id <ID> Show policy details");
|
|
337
|
+
log(" solongate-proxy pull --policy-id <ID> Pull policy to local file");
|
|
338
|
+
log(" solongate-proxy push --policy-id <ID> Push local file to cloud");
|
|
339
|
+
log("");
|
|
340
|
+
log(bold("Flags:"));
|
|
341
|
+
log(" --policy-id, --id <ID> Cloud policy ID (required for push)");
|
|
342
|
+
log(" --file, -f <path> Local file path (default: policy.json)");
|
|
343
|
+
log(" --api-key <key> API key (or set SOLONGATE_API_KEY)");
|
|
344
|
+
log("");
|
|
201
345
|
process.exit(1);
|
|
202
346
|
}
|
|
203
347
|
} catch (err) {
|
|
204
|
-
log(`ERROR: ${err instanceof Error ? err.message : String(err)}`);
|
|
348
|
+
log(red(`ERROR: ${err instanceof Error ? err.message : String(err)}`));
|
|
205
349
|
process.exit(1);
|
|
206
350
|
}
|
|
207
351
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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": {
|