@solongate/proxy 0.81.96 → 0.81.98
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/README.md +50 -48
- package/dist/index.js +23 -16
- package/dist/tui/index.js +23 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,72 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://solongate.com/hero.png" alt="SolonGate. For AI agents to execute tools safely. Make agents secure again." width="900" />
|
|
3
|
+
</div>
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
>
|
|
5
|
-
> npm i -g @solongate/proxy
|
|
6
|
-
> solongate
|
|
7
|
-
> ```
|
|
5
|
+
<a href="https://github.com/Solongate"><img alt="GitHub" src="https://img.shields.io/badge/GitHub-111111?style=flat-square&logo=github&logoColor=white" height="24" /></a>
|
|
6
|
+
<a href="https://www.linkedin.com/company/solongate/"><img alt="LinkedIn" src="https://img.shields.io/badge/LinkedIn-111111?style=flat-square&logo=linkedin&logoColor=white" height="24" /></a>
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://nodejs.org"><img alt="Node.js" src="https://img.shields.io/node/v/@solongate/proxy?style=flat-square&label=Node.js&labelColor=111111&color=eeeeee&logo=nodedotjs&logoColor=white" /></a>
|
|
10
|
+
<a href="https://www.npmjs.com/package/@solongate/proxy"><img alt="npm" src="https://img.shields.io/npm/v/@solongate/proxy?style=flat-square&color=eeeeee&labelColor=111111&logo=npm&logoColor=white" /></a>
|
|
11
|
+
</p>
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
AI agent ──(tool call)──> SolonGate guard ──> Tool runs
|
|
13
|
-
│
|
|
14
|
-
[ policy check ]
|
|
15
|
-
[ allow / block / log ]
|
|
16
|
-
```
|
|
13
|
+
SolonGate is the guardrail for AI agents. It checks every action an agent takes (each shell command, file read or write, and network request) and allows, blocks, or logs it before it runs, based on a policy you control. No code changes.
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
**[solongate.com](https://solongate.com)** | [Documentation](https://solongate.com/docs) | [Dashboard](https://dashboard.solongate.com)
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
<div align="center">
|
|
18
|
+
<img src="https://solongate.com/flow.gif" alt="A tool call from an AI agent passes through SolonGate, which runs a policy check, DLP scan, and rate limit, then allows, blocks, or logs it before the tool runs" width="900" />
|
|
19
|
+
</div>
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
## Get started
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
> **Don't `npm i` this.** There's nothing to import. Run the two commands below to pair your machine.
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
You need a free [SolonGate account](https://auth.solongate.com) and Node.js 20+ on the machine you want to protect.
|
|
26
|
+
|
|
27
|
+
```sh
|
|
27
28
|
npm i -g @solongate/proxy
|
|
28
29
|
solongate
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
`solongate` opens your browser to authorize the device. Approve it and SolonGate installs a global guard hook that checks every tool call from every AI session on the machine against your active policy. No API keys to copy.
|
|
32
33
|
|
|
33
34
|
> Start a **new** terminal session afterwards. Hooks load when a session starts, so already-open terminals aren't guarded yet.
|
|
34
35
|
|
|
35
|
-
##
|
|
36
|
+
## Guarding a live Claude Code session
|
|
37
|
+
|
|
38
|
+
Drops in front of any MCP-speaking agent over stdio, SSE, or HTTP. Every tool call is checked in real time, and each allow or deny decision streams to your dashboard with the full arguments attached.
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
<img src="https://solongate.com/claudecode.gif" alt="SolonGate guarding a live Claude Code session" width="720" />
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
- **Whitelist:** nothing is allowed except what you permit.
|
|
42
|
+
## Command center for every decision area
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
Live analytics by tool, agent, and policy. Version your policies with cloud sync and one-click rollback. Every decision lands in a tamper-evident, searchable audit trail you can export as CSV.
|
|
45
|
+
|
|
46
|
+
<img src="https://solongate.com/solongatedashboard.gif" alt="SolonGate dashboard: analytics, policies, and audit trail" width="720" />
|
|
43
47
|
|
|
44
48
|
## What SolonGate can enforce
|
|
45
49
|
|
|
46
50
|
- **Policy rules:** allow or block tool calls by path, command, filename, or URL.
|
|
47
|
-
- **Ghost paths:** make chosen files and folders invisible to the agent
|
|
48
|
-
- **Data loss prevention (DLP):**
|
|
51
|
+
- **Ghost paths:** make chosen files and folders invisible to the agent, unlistable and unreadable.
|
|
52
|
+
- **Data loss prevention (DLP):** when a call carries a secret (API key, token, private key), block it or hide it from the model.
|
|
49
53
|
- **Rate limiting:** cap how many tool calls an agent can make per minute, hour, or day.
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
MIT
|
|
55
|
+
Manage everything from the **Policies**, **Audit**, and **Settings** pages in the [dashboard](https://dashboard.solongate.com). When a legitimate action is blocked, click **Whitelist this** to add a narrow exception. Keep logs local instead of in the cloud, and get alerted by Telegram, email, or webhook when blocks spike.
|
|
56
|
+
|
|
57
|
+
## Integrations
|
|
58
|
+
|
|
59
|
+
**Compatible with every tool.** SolonGate guards any MCP-speaking agent out of the box. Missing yours? [Reach out](https://solongate.com/contact) and we will consider it.
|
|
60
|
+
|
|
61
|
+
<table>
|
|
62
|
+
<tr>
|
|
63
|
+
<td align="center" width="140"><a href="https://claude.com/claude-code"><img src="https://solongate.com/platforms/claude.png" width="44" height="44" alt="Claude Code" /><br/>Claude Code</a></td>
|
|
64
|
+
<td align="center" width="140"><a href="https://github.com/google-gemini/gemini-cli"><img src="https://solongate.com/platforms/gemini.png" width="44" height="44" alt="Gemini CLI" /><br/>Gemini CLI</a></td>
|
|
65
|
+
<td align="center" width="140"><a href="https://openclaw.ai"><img src="https://solongate.com/platforms/openclaw.png" width="44" height="44" alt="OpenClaw" /><br/>OpenClaw<br/><sub>soon</sub></a></td>
|
|
66
|
+
<td align="center" width="140"><a href="https://openai.com/codex"><img src="https://solongate.com/platforms/codex.png" width="44" height="44" alt="Codex" /><br/>Codex<br/><sub>soon</sub></a></td>
|
|
67
|
+
</tr>
|
|
68
|
+
<tr>
|
|
69
|
+
<td align="center" width="140"><a href="https://hermes.nousresearch.com"><img src="https://solongate.com/platforms/hermes.png" width="44" height="44" alt="Hermes" /><br/>Hermes<br/><sub>soon</sub></a></td>
|
|
70
|
+
<td align="center" width="140"><a href="https://opencode.ai"><img src="https://solongate.com/platforms/opencode.png" width="44" height="44" alt="OpenCode" /><br/>OpenCode<br/><sub>soon</sub></a></td>
|
|
71
|
+
<td align="center" width="140"><a href="https://kilocode.ai"><img src="https://solongate.com/platforms/kilo.png" width="44" height="44" alt="Kilo" /><br/>Kilo<br/><sub>soon</sub></a></td>
|
|
72
|
+
<td align="center" width="140"><a href="https://solongate.com/contact"><b>+</b><br/>Your tool</a></td>
|
|
73
|
+
</tr>
|
|
74
|
+
</table>
|
package/dist/index.js
CHANGED
|
@@ -9111,9 +9111,8 @@ function RateLimitPanel({ focused }) {
|
|
|
9111
9111
|
const insightsQ = useLoader(() => api.stats.securityInsights(7));
|
|
9112
9112
|
const [draft, setDraft] = useState4(null);
|
|
9113
9113
|
const [dirty, setDirty] = useState4(false);
|
|
9114
|
-
const [
|
|
9114
|
+
const [sel, setSel] = useState4(0);
|
|
9115
9115
|
const [status, setStatus] = useState4(null);
|
|
9116
|
-
const [burstOff, setBurstOff] = useState4(0);
|
|
9117
9116
|
useEffect4(() => {
|
|
9118
9117
|
if (layersQ.data && !dirty) setDraft({ ...layersQ.data.layers.rateLimit });
|
|
9119
9118
|
}, [layersQ.data]);
|
|
@@ -9125,7 +9124,7 @@ function RateLimitPanel({ focused }) {
|
|
|
9125
9124
|
insightsQ.reloadQuiet();
|
|
9126
9125
|
}, 1e4);
|
|
9127
9126
|
const adjust = (dir, step) => {
|
|
9128
|
-
if (!draft) return;
|
|
9127
|
+
if (!draft || fi < 0) return;
|
|
9129
9128
|
const field = FIELDS2[fi];
|
|
9130
9129
|
if (field === "mode") setDraft({ ...draft, mode: MODES[(MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length] });
|
|
9131
9130
|
else setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
|
|
@@ -9156,14 +9155,19 @@ function RateLimitPanel({ focused }) {
|
|
|
9156
9155
|
const fixed = 1 + 4 + (showExtras ? (hasLoad ? 1 : 0) + 5 : 0) + 1 + 1 + 1 + 1;
|
|
9157
9156
|
const burstBudget = Math.max(1, rows - fixed);
|
|
9158
9157
|
const maxOff = Math.max(0, anomalies2.length - burstBudget);
|
|
9159
|
-
const
|
|
9158
|
+
const total = 4 + anomalies2.length;
|
|
9159
|
+
const selC = Math.min(sel, Math.max(0, total - 1));
|
|
9160
|
+
const onField = selC < 4;
|
|
9161
|
+
const fi = onField ? selC : -1;
|
|
9162
|
+
const bcur = onField ? -1 : selC - 4;
|
|
9163
|
+
const off = bcur < 0 ? 0 : Math.min(Math.max(0, bcur - Math.floor(burstBudget / 2)), maxOff);
|
|
9160
9164
|
useInput3(
|
|
9161
9165
|
(input, key) => {
|
|
9162
9166
|
const step = key.shift ? 10 : 1;
|
|
9163
|
-
if (key.
|
|
9164
|
-
else if (key.
|
|
9165
|
-
else if (key.
|
|
9166
|
-
else if (key.
|
|
9167
|
+
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
9168
|
+
else if (key.downArrow) setSel((n) => Math.min(total - 1, n + 1));
|
|
9169
|
+
else if (key.pageDown) setSel((n) => Math.min(total - 1, n + burstBudget));
|
|
9170
|
+
else if (key.pageUp) setSel((n) => Math.max(0, n - burstBudget));
|
|
9167
9171
|
else if (key.leftArrow) adjust(-1, step);
|
|
9168
9172
|
else if (key.rightArrow) adjust(1, step);
|
|
9169
9173
|
else if (input === "s") void save();
|
|
@@ -9184,7 +9188,7 @@ function RateLimitPanel({ focused }) {
|
|
|
9184
9188
|
const shown = anomalies2.slice(off, off + burstBudget);
|
|
9185
9189
|
const bw = Math.max(10, cols - 30);
|
|
9186
9190
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", height: rows, overflow: "hidden", children: [
|
|
9187
|
-
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ?
|
|
9191
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 move (fields + bursts) \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save \xB7 ^R refresh" : "press \u2192 to edit" }),
|
|
9188
9192
|
/* @__PURE__ */ jsxs4(FieldRow, { label: "Mode", active: focused && fi === 0, children: [
|
|
9189
9193
|
/* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), bold: true, children: draft.mode }),
|
|
9190
9194
|
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: draft.mode === "off" ? " no limit" : draft.mode === "detect" ? " flag bursts, never block" : " DENY calls over the limit" })
|
|
@@ -9220,13 +9224,16 @@ function RateLimitPanel({ focused }) {
|
|
|
9220
9224
|
] }) : /* @__PURE__ */ jsx4(
|
|
9221
9225
|
Table,
|
|
9222
9226
|
{
|
|
9223
|
-
columns: [{ header: "WHEN", width:
|
|
9224
|
-
rows: shown.map((a) =>
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9227
|
+
columns: [{ header: "WHEN", width: 15 }, { header: "RESULT", width: 9 }, { header: "CALLS", width: 7 }, { header: "AGENT", width: Math.max(8, bw - 31) }],
|
|
9228
|
+
rows: shown.map((a, i) => {
|
|
9229
|
+
const isSel = off + i === bcur;
|
|
9230
|
+
return [
|
|
9231
|
+
{ value: (isSel ? "\u25B8 " : " ") + when(new Date(a.minute).getTime()), color: isSel ? theme.accentBright : void 0, dim: !isSel },
|
|
9232
|
+
{ value: a.blocked ? "Blocked" : "Bypassed", color: a.blocked ? theme.bad : theme.warn },
|
|
9233
|
+
{ value: `${a.count}/${a.limit}`, color: theme.warn },
|
|
9234
|
+
{ value: truncate2(a.agent, Math.max(8, bw - 31)), dim: !isSel }
|
|
9235
|
+
];
|
|
9236
|
+
})
|
|
9230
9237
|
}
|
|
9231
9238
|
)
|
|
9232
9239
|
] }),
|
package/dist/tui/index.js
CHANGED
|
@@ -2199,9 +2199,8 @@ function RateLimitPanel({ focused }) {
|
|
|
2199
2199
|
const insightsQ = useLoader(() => api.stats.securityInsights(7));
|
|
2200
2200
|
const [draft, setDraft] = useState4(null);
|
|
2201
2201
|
const [dirty, setDirty] = useState4(false);
|
|
2202
|
-
const [
|
|
2202
|
+
const [sel, setSel] = useState4(0);
|
|
2203
2203
|
const [status, setStatus] = useState4(null);
|
|
2204
|
-
const [burstOff, setBurstOff] = useState4(0);
|
|
2205
2204
|
useEffect4(() => {
|
|
2206
2205
|
if (layersQ.data && !dirty) setDraft({ ...layersQ.data.layers.rateLimit });
|
|
2207
2206
|
}, [layersQ.data]);
|
|
@@ -2213,7 +2212,7 @@ function RateLimitPanel({ focused }) {
|
|
|
2213
2212
|
insightsQ.reloadQuiet();
|
|
2214
2213
|
}, 1e4);
|
|
2215
2214
|
const adjust = (dir, step) => {
|
|
2216
|
-
if (!draft) return;
|
|
2215
|
+
if (!draft || fi < 0) return;
|
|
2217
2216
|
const field = FIELDS2[fi];
|
|
2218
2217
|
if (field === "mode") setDraft({ ...draft, mode: MODES[(MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length] });
|
|
2219
2218
|
else setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
|
|
@@ -2244,14 +2243,19 @@ function RateLimitPanel({ focused }) {
|
|
|
2244
2243
|
const fixed = 1 + 4 + (showExtras ? (hasLoad ? 1 : 0) + 5 : 0) + 1 + 1 + 1 + 1;
|
|
2245
2244
|
const burstBudget = Math.max(1, rows - fixed);
|
|
2246
2245
|
const maxOff = Math.max(0, anomalies2.length - burstBudget);
|
|
2247
|
-
const
|
|
2246
|
+
const total = 4 + anomalies2.length;
|
|
2247
|
+
const selC = Math.min(sel, Math.max(0, total - 1));
|
|
2248
|
+
const onField = selC < 4;
|
|
2249
|
+
const fi = onField ? selC : -1;
|
|
2250
|
+
const bcur = onField ? -1 : selC - 4;
|
|
2251
|
+
const off = bcur < 0 ? 0 : Math.min(Math.max(0, bcur - Math.floor(burstBudget / 2)), maxOff);
|
|
2248
2252
|
useInput3(
|
|
2249
2253
|
(input, key) => {
|
|
2250
2254
|
const step = key.shift ? 10 : 1;
|
|
2251
|
-
if (key.
|
|
2252
|
-
else if (key.
|
|
2253
|
-
else if (key.
|
|
2254
|
-
else if (key.
|
|
2255
|
+
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
2256
|
+
else if (key.downArrow) setSel((n) => Math.min(total - 1, n + 1));
|
|
2257
|
+
else if (key.pageDown) setSel((n) => Math.min(total - 1, n + burstBudget));
|
|
2258
|
+
else if (key.pageUp) setSel((n) => Math.max(0, n - burstBudget));
|
|
2255
2259
|
else if (key.leftArrow) adjust(-1, step);
|
|
2256
2260
|
else if (key.rightArrow) adjust(1, step);
|
|
2257
2261
|
else if (input === "s") void save();
|
|
@@ -2272,7 +2276,7 @@ function RateLimitPanel({ focused }) {
|
|
|
2272
2276
|
const shown = anomalies2.slice(off, off + burstBudget);
|
|
2273
2277
|
const bw = Math.max(10, cols - 30);
|
|
2274
2278
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", height: rows, overflow: "hidden", children: [
|
|
2275
|
-
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ?
|
|
2279
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 move (fields + bursts) \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save \xB7 ^R refresh" : "press \u2192 to edit" }),
|
|
2276
2280
|
/* @__PURE__ */ jsxs4(FieldRow, { label: "Mode", active: focused && fi === 0, children: [
|
|
2277
2281
|
/* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), bold: true, children: draft.mode }),
|
|
2278
2282
|
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: draft.mode === "off" ? " no limit" : draft.mode === "detect" ? " flag bursts, never block" : " DENY calls over the limit" })
|
|
@@ -2308,13 +2312,16 @@ function RateLimitPanel({ focused }) {
|
|
|
2308
2312
|
] }) : /* @__PURE__ */ jsx4(
|
|
2309
2313
|
Table,
|
|
2310
2314
|
{
|
|
2311
|
-
columns: [{ header: "WHEN", width:
|
|
2312
|
-
rows: shown.map((a) =>
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2315
|
+
columns: [{ header: "WHEN", width: 15 }, { header: "RESULT", width: 9 }, { header: "CALLS", width: 7 }, { header: "AGENT", width: Math.max(8, bw - 31) }],
|
|
2316
|
+
rows: shown.map((a, i) => {
|
|
2317
|
+
const isSel = off + i === bcur;
|
|
2318
|
+
return [
|
|
2319
|
+
{ value: (isSel ? "\u25B8 " : " ") + when(new Date(a.minute).getTime()), color: isSel ? theme.accentBright : void 0, dim: !isSel },
|
|
2320
|
+
{ value: a.blocked ? "Blocked" : "Bypassed", color: a.blocked ? theme.bad : theme.warn },
|
|
2321
|
+
{ value: `${a.count}/${a.limit}`, color: theme.warn },
|
|
2322
|
+
{ value: truncate(a.agent, Math.max(8, bw - 31)), dim: !isSel }
|
|
2323
|
+
];
|
|
2324
|
+
})
|
|
2318
2325
|
}
|
|
2319
2326
|
)
|
|
2320
2327
|
] }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.81.
|
|
3
|
+
"version": "0.81.98",
|
|
4
4
|
"description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"url": "https://github.com/solongate/solongate"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
|
-
"node": ">=
|
|
59
|
+
"node": ">=20.0.0"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@modelcontextprotocol/sdk": "^1.26.0",
|