@guardbee/mcp-security-proxy 1.1.1 → 1.3.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/README.md +45 -0
- package/TR.md +45 -0
- package/ZH.md +45 -0
- package/dist/audit/dashboard-sink.d.ts +26 -0
- package/dist/audit/dashboard-sink.d.ts.map +1 -0
- package/dist/audit/dashboard-sink.js +88 -0
- package/dist/audit/dashboard-sink.js.map +1 -0
- package/dist/audit/logger.d.ts +4 -2
- package/dist/audit/logger.d.ts.map +1 -1
- package/dist/audit/logger.js +12 -4
- package/dist/audit/logger.js.map +1 -1
- package/dist/gateway/approval.d.ts +16 -0
- package/dist/gateway/approval.d.ts.map +1 -1
- package/dist/gateway/approval.js +74 -0
- package/dist/gateway/approval.js.map +1 -1
- package/dist/gateway/config.d.ts +22 -3
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +79 -15
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/gateway.d.ts +12 -1
- package/dist/gateway/gateway.d.ts.map +1 -1
- package/dist/gateway/gateway.js +26 -5
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/upstream.d.ts +9 -3
- package/dist/gateway/upstream.d.ts.map +1 -1
- package/dist/gateway/upstream.js +23 -1
- package/dist/gateway/upstream.js.map +1 -1
- package/dist/http-server.d.ts +20 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +146 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +3 -2
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +21 -6
- package/dist/init.js.map +1 -1
- package/dist/proxy.d.ts +3 -1
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +68 -7
- package/dist/proxy.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,6 +121,7 @@ audit:
|
|
|
121
121
|
- **Toxic flow (lethal trifecta):** each tool is labeled `untrusted`, `sensitive`, `egress` or `destructive` (name/description heuristics, overridable under `labels`). Once a session has read untrusted content (an `untrusted` tool or any resource) and sensitive data (a `sensitive` tool or any PII in a result), an `egress` call is blocked in `strict` mode and only logged in `warn` mode. The check spans servers: an issue read from GitHub plus a customer record from a CRM blocks a webhook on a third server.
|
|
122
122
|
- **Rules** match on `tool` (glob), `upstream`, `label`, `session` (`clean` | `tainted`) and `args`. `mask` forces PII masking for that tool even when masking is off, and `mask.fields` also blanks the named JSON keys in text and `structuredContent`. A rule's `allow` does not skip the toxic-flow check; relabel the tool instead.
|
|
123
123
|
- **Approval:** `approve` (a rule action, or `taint.mode: approve` for toxic flows) shows the person a yes/no form through MCP elicitation, with the tool, the reason and the arguments. Anything but an explicit yes — decline, cancel, no answer within `approval.timeoutSeconds` (default 120) — blocks the call. A client without elicitation support cannot approve, so the call is blocked with a message saying so.
|
|
124
|
+
- **Approval in the dashboard:** with `approval.channels: [elicitation, dashboard]` and `audit.dashboard` set, a client that cannot show a prompt no longer blocks the call outright. The request goes to the GuardBee dashboard (MCP Gateway page) with the arguments PII-masked, workspace owners and admins get a notification, and the proxy holds the call until someone approves or denies it there or `timeoutSeconds` passes. Channels are tried in order; the first one that can ask gives the answer.
|
|
124
125
|
- **Tokenize:** with `interceptors.piiMasking.mode: tokenize` the model sees `<pii:tc_kimlik:7f3a9b21>` instead of the value and can still pass it to another tool: the proxy puts the real value back on the way to that server. Tokens live in memory for the session only, and are not turned back into values for an `egress` tool unless `piiMasking.detokenizeForEgress: true`.
|
|
125
126
|
- **Prompts** (`prompts/get`) get the same injection scan and PII masking as tool results.
|
|
126
127
|
- `interceptors.definitionDrift.recheck: on-change` skips the per-call `tools/list` and re-checks only after a server sends `tools/list_changed`, which the proxy also passes on to the agent.
|
|
@@ -132,6 +133,50 @@ The single-server `--` form above keeps its 0.x behavior: tool names are not pre
|
|
|
132
133
|
|
|
133
134
|
---
|
|
134
135
|
|
|
136
|
+
## Remote Mode (HTTP)
|
|
137
|
+
|
|
138
|
+
The proxy can run as a shared network service instead of a subprocess of one client. Agents connect over Streamable HTTP, and upstreams can be remote Streamable HTTP servers too.
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
version: 1
|
|
142
|
+
listen:
|
|
143
|
+
transport: http # stdio (default) | http
|
|
144
|
+
host: 0.0.0.0 # 0.0.0.0 in a container; 127.0.0.1 on a laptop
|
|
145
|
+
port: 8787
|
|
146
|
+
path: /mcp
|
|
147
|
+
apiKeys: ["${GUARDBEE_PROXY_KEY}"] # agents send Authorization: Bearer <key>
|
|
148
|
+
maxSessions: 100
|
|
149
|
+
upstreams:
|
|
150
|
+
github:
|
|
151
|
+
command: npx
|
|
152
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
153
|
+
linear:
|
|
154
|
+
url: https://mcp.linear.app/mcp # a Streamable HTTP upstream
|
|
155
|
+
headers: { Authorization: "Bearer ${LINEAR_TOKEN}" }
|
|
156
|
+
audit:
|
|
157
|
+
sink: file
|
|
158
|
+
filePath: /var/log/guardbee/audit.jsonl
|
|
159
|
+
dashboard:
|
|
160
|
+
url: https://app.guardbee.ai/api/v1/gateway/events
|
|
161
|
+
apiKeyEnv: GUARDBEE_API_KEY # workspace API key with the gateway.write scope
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **Sessions are isolated.** Each MCP session gets its own taint state, PII tokens and tool pins, so one agent's toxic flow never blocks another. All sessions share the upstream connections. Audit events carry the `sessionId`.
|
|
165
|
+
- **No key, no network.** Listening on anything other than loopback without `listen.apiKeys` is a config error. Keys are compared in constant time, and a wrong or missing key gets `401`.
|
|
166
|
+
- `GET /healthz` returns `{"ok": true, "sessions": N}`. Sessions above `maxSessions` get `503`.
|
|
167
|
+
- **Dashboard:** `audit.dashboard` sends every audit event, batched, to the GuardBee dashboard with a workspace API key taken from `apiKeyEnv`. The key never sits in the file. If the dashboard is unreachable, events wait in memory (up to 10,000) and go out later. A rejected key is reported once on stderr. Tool calls never wait for the dashboard.
|
|
168
|
+
- `init` now also moves Streamable HTTP servers (`url` + `headers`) behind the proxy. Legacy SSE servers stay in the client config and are reported.
|
|
169
|
+
|
|
170
|
+
Docker:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
docker build -t guardbee/mcp-security-proxy packages/security-proxy
|
|
174
|
+
docker run -p 8787:8787 -e GUARDBEE_PROXY_KEY=... -e GUARDBEE_API_KEY=... \
|
|
175
|
+
-v "$PWD/guardbee-proxy.yaml:/etc/guardbee/guardbee-proxy.yaml:ro" guardbee/mcp-security-proxy
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
135
180
|
## MCP Tools
|
|
136
181
|
|
|
137
182
|
The proxy does not register tools of its own. It forwards the target server's tools and applies the interceptor chain on every call.
|
package/TR.md
CHANGED
|
@@ -121,6 +121,7 @@ audit:
|
|
|
121
121
|
- **Toxic flow (lethal trifecta):** her tool `untrusted`, `sensitive`, `egress` veya `destructive` olarak etiketlenir (ad/açıklama sezgileriyle; `labels` altında ezilebilir). Oturum güvenilmeyen içerik (`untrusted` bir tool veya herhangi bir resource) ve hassas veri (`sensitive` bir tool veya bir sonuçtaki herhangi bir PII) gördükten sonra, `egress` çağrısı `strict` modda engellenir, `warn` modda sadece loglanır. Kontrol sunucular arasında çalışır: GitHub'dan okunan bir issue ile CRM'den gelen bir müşteri kaydı, üçüncü bir sunucudaki webhook çağrısını engeller.
|
|
122
122
|
- **Kurallar** `tool` (glob), `upstream`, `label`, `session` (`clean` | `tainted`) ve `args` alanlarıyla eşleşir. `mask`, maskeleme kapalı olsa bile o tool için PII maskelemeyi zorunlu kılar; `mask.fields` ayrıca adı verilen JSON anahtarlarını metinde ve `structuredContent`'te boşaltır. Bir kuraldaki `allow` toxic-flow kontrolünü atlatmaz; bunun yerine tool'un etiketini değiştirin.
|
|
123
123
|
- **Onay:** `approve` (kural aksiyonu olarak ya da toxic flow için `taint.mode: approve`) kişiye MCP elicitation üzerinden tool'u, gerekçeyi ve argümanları gösteren bir evet/hayır formu açar. Açık bir "evet" dışındaki her cevap (red, iptal, `approval.timeoutSeconds` içinde cevap gelmemesi; varsayılan 120) çağrıyı engeller. Elicitation desteklemeyen istemci onay veremez; çağrı bunu söyleyen bir mesajla engellenir.
|
|
124
|
+
- **Dashboard'dan onay:** `approval.channels: [elicitation, dashboard]` ve `audit.dashboard` ayarlıysa, onay formu gösteremeyen istemcide çağrı artık doğrudan engellenmez. İstek, argümanlardaki kişisel veriler maskelenmiş olarak GuardBee dashboard'una (MCP Gateway sayfası) gider; workspace sahipleri ve yöneticilerine bildirim düşer; proxy çağrıyı biri oradan onaylayana ya da reddedene veya `timeoutSeconds` dolana kadar bekletir. Kanallar sırayla denenir; soru sorabilen ilk kanal cevabı verir.
|
|
124
125
|
- **Tokenize:** `interceptors.piiMasking.mode: tokenize` ile model değerin yerine `<pii:tc_kimlik:7f3a9b21>` görür ve bu token'ı başka bir tool'a yine verebilir; proxy gerçek değeri o sunucuya giderken geri koyar. Token'lar sadece oturum boyunca bellekte tutulur ve `piiMasking.detokenizeForEgress: true` olmadıkça `egress` tool'lar için geri çevrilmez.
|
|
125
126
|
- **Prompt'lar** (`prompts/get`) tool sonuçlarıyla aynı injection taramasından ve PII maskelemeden geçer.
|
|
126
127
|
- `interceptors.definitionDrift.recheck: on-change` her çağrıdaki `tools/list`'i atlar ve sadece sunucu `tools/list_changed` gönderdiğinde yeniden kontrol eder; proxy bu bildirimi ajana da iletir.
|
|
@@ -132,6 +133,50 @@ Yukarıdaki tek sunuculu `--` kullanımı 0.x davranışını korur: tool adlar
|
|
|
132
133
|
|
|
133
134
|
---
|
|
134
135
|
|
|
136
|
+
## Remote Mod (HTTP)
|
|
137
|
+
|
|
138
|
+
Proxy, tek bir istemcinin alt süreci olmak yerine paylaşılan bir ağ servisi olarak da çalışabilir. Ajanlar Streamable HTTP ile bağlanır; upstream'ler de uzak Streamable HTTP server'lar olabilir.
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
version: 1
|
|
142
|
+
listen:
|
|
143
|
+
transport: http # stdio (default) | http
|
|
144
|
+
host: 0.0.0.0 # konteynerde 0.0.0.0; dizüstünde 127.0.0.1
|
|
145
|
+
port: 8787
|
|
146
|
+
path: /mcp
|
|
147
|
+
apiKeys: ["${GUARDBEE_PROXY_KEY}"] # ajanlar Authorization: Bearer <key> gönderir
|
|
148
|
+
maxSessions: 100
|
|
149
|
+
upstreams:
|
|
150
|
+
github:
|
|
151
|
+
command: npx
|
|
152
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
153
|
+
linear:
|
|
154
|
+
url: https://mcp.linear.app/mcp # bir Streamable HTTP upstream
|
|
155
|
+
headers: { Authorization: "Bearer ${LINEAR_TOKEN}" }
|
|
156
|
+
audit:
|
|
157
|
+
sink: file
|
|
158
|
+
filePath: /var/log/guardbee/audit.jsonl
|
|
159
|
+
dashboard:
|
|
160
|
+
url: https://app.guardbee.ai/api/v1/gateway/events
|
|
161
|
+
apiKeyEnv: GUARDBEE_API_KEY # gateway.write scope'lu workspace API key'i
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **Oturumlar birbirinden ayrık.** Her MCP oturumunun kendi taint durumu, PII token'ları ve tool pin'leri vardır; bir ajanın toxic flow'u başka bir ajanı asla engellemez. Tüm oturumlar upstream bağlantılarını paylaşır. Audit olayları `sessionId` taşır.
|
|
165
|
+
- **Key yoksa ağ da yok.** `listen.apiKeys` olmadan loopback dışında dinlemek config hatasıdır. Key'ler sabit zamanda karşılaştırılır; yanlış veya eksik key `401` alır.
|
|
166
|
+
- `GET /healthz` `{"ok": true, "sessions": N}` döner. `maxSessions` üstündeki oturumlar `503` alır.
|
|
167
|
+
- **Dashboard:** `audit.dashboard` her audit olayını toplu halde, `apiKeyEnv`'den alınan workspace API key'iyle GuardBee dashboard'una gönderir; key dosyada durmaz. Dashboard'a ulaşılamazsa olaylar bellekte bekler (en fazla 10.000) ve sonra gönderilir. Reddedilen key stderr'e bir kez raporlanır. Tool çağrıları dashboard'u asla beklemez.
|
|
168
|
+
- `init` artık Streamable HTTP server'ları (`url` + `headers`) da proxy'nin arkasına taşır. Eski SSE server'lar istemci config'inde kalır ve raporlanır.
|
|
169
|
+
|
|
170
|
+
Docker:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
docker build -t guardbee/mcp-security-proxy packages/security-proxy
|
|
174
|
+
docker run -p 8787:8787 -e GUARDBEE_PROXY_KEY=... -e GUARDBEE_API_KEY=... \
|
|
175
|
+
-v "$PWD/guardbee-proxy.yaml:/etc/guardbee/guardbee-proxy.yaml:ro" guardbee/mcp-security-proxy
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
135
180
|
## MCP Tools
|
|
136
181
|
|
|
137
182
|
Proxy kendi tool'unu kaydetmez. Hedef sunucunun tool'larını iletir ve her çağrıda interceptor zincirini uygular.
|
package/ZH.md
CHANGED
|
@@ -121,6 +121,7 @@ audit:
|
|
|
121
121
|
- **有毒数据流(lethal trifecta):** 每个工具会被标记为 `untrusted`、`sensitive`、`egress` 或 `destructive`(基于名称/描述的启发式,可在 `labels` 中覆盖)。当一个会话既读取过不可信内容(`untrusted` 工具或任何 resource),又读取过敏感数据(`sensitive` 工具,或结果中出现任何 PII)之后,`egress` 调用在 `strict` 模式下会被拦截,在 `warn` 模式下只记录日志。该检查跨服务器生效:从 GitHub 读取的 issue 加上从 CRM 读取的客户记录,会拦截第三个服务器上的 webhook 调用。
|
|
122
122
|
- **规则**可按 `tool`(glob)、`upstream`、`label`、`session`(`clean` | `tainted`)和 `args` 匹配。即使全局脱敏关闭,`mask` 也会对该工具强制进行 PII 脱敏;`mask.fields` 还会清空文本和 `structuredContent` 中指定的 JSON 键。规则中的 `allow` 不会跳过有毒数据流检查;如需放行,请修改该工具的标签。
|
|
123
123
|
- **审批:** `approve`(规则动作,或针对有毒数据流的 `taint.mode: approve`)会通过 MCP elicitation 向用户展示一个是/否表单,其中包含工具、原因和参数。除明确的"是"之外的任何回答——拒绝、取消、在 `approval.timeoutSeconds`(默认 120 秒)内未作答——都会拦截该调用。不支持 elicitation 的客户端无法审批,调用会被拦截并附带说明。
|
|
124
|
+
- **在仪表盘中审批:** 设置 `approval.channels: [elicitation, dashboard]` 和 `audit.dashboard` 后,无法显示表单的客户端不会再直接拦截调用。请求会以 PII 已脱敏的参数发送到 GuardBee 仪表盘(MCP Gateway 页面),工作区所有者和管理员会收到通知;代理会一直保留该调用,直到有人在那里批准或拒绝,或 `timeoutSeconds` 到期。各渠道按顺序尝试,第一个能够发起询问的渠道给出答案。
|
|
124
125
|
- **令牌化(Tokenize):** 设置 `interceptors.piiMasking.mode: tokenize` 后,模型看到的是 `<pii:tc_kimlik:7f3a9b21>` 而不是真实值,并且仍然可以把它传给另一个工具:代理会在发往该服务器时把真实值放回去。令牌只在会话期间保存在内存中;除非设置 `piiMasking.detokenizeForEgress: true`,否则不会为 `egress` 工具还原成真实值。
|
|
125
126
|
- **Prompt**(`prompts/get`)与工具结果一样,经过注入扫描和 PII 脱敏。
|
|
126
127
|
- `interceptors.definitionDrift.recheck: on-change` 会跳过每次调用前的 `tools/list`,只在服务器发送 `tools/list_changed` 后重新检查;代理也会把该通知转发给智能体。
|
|
@@ -132,6 +133,50 @@ audit:
|
|
|
132
133
|
|
|
133
134
|
---
|
|
134
135
|
|
|
136
|
+
## 远程模式(HTTP)
|
|
137
|
+
|
|
138
|
+
代理也可以作为共享的网络服务运行,而不只是某个客户端的子进程。智能体通过 Streamable HTTP 连接;上游也可以是远程的 Streamable HTTP 服务器。
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
version: 1
|
|
142
|
+
listen:
|
|
143
|
+
transport: http # stdio (default) | http
|
|
144
|
+
host: 0.0.0.0 # 容器中用 0.0.0.0;笔记本上用 127.0.0.1
|
|
145
|
+
port: 8787
|
|
146
|
+
path: /mcp
|
|
147
|
+
apiKeys: ["${GUARDBEE_PROXY_KEY}"] # 智能体发送 Authorization: Bearer <key>
|
|
148
|
+
maxSessions: 100
|
|
149
|
+
upstreams:
|
|
150
|
+
github:
|
|
151
|
+
command: npx
|
|
152
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
153
|
+
linear:
|
|
154
|
+
url: https://mcp.linear.app/mcp # 一个 Streamable HTTP 上游
|
|
155
|
+
headers: { Authorization: "Bearer ${LINEAR_TOKEN}" }
|
|
156
|
+
audit:
|
|
157
|
+
sink: file
|
|
158
|
+
filePath: /var/log/guardbee/audit.jsonl
|
|
159
|
+
dashboard:
|
|
160
|
+
url: https://app.guardbee.ai/api/v1/gateway/events
|
|
161
|
+
apiKeyEnv: GUARDBEE_API_KEY # 带 gateway.write 权限的工作区 API 密钥
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **会话相互隔离。** 每个 MCP 会话都有自己的污点状态、PII 令牌和工具固定;一个智能体的有毒数据流绝不会拦截另一个智能体。所有会话共享上游连接。审计事件带有 `sessionId`。
|
|
165
|
+
- **没有密钥就不能监听网络。** 未配置 `listen.apiKeys` 就在回环地址以外监听属于配置错误。密钥以恒定时间比较;错误或缺失的密钥返回 `401`。
|
|
166
|
+
- `GET /healthz` 返回 `{"ok": true, "sessions": N}`。超过 `maxSessions` 的会话返回 `503`。
|
|
167
|
+
- **仪表盘:** `audit.dashboard` 会用从 `apiKeyEnv` 读取的工作区 API 密钥,把每个审计事件批量发送到 GuardBee 仪表盘;密钥不写在文件中。仪表盘不可达时,事件会在内存中等待(最多 10,000 条)并稍后发送。被拒绝的密钥只会在 stderr 报告一次。工具调用从不等待仪表盘。
|
|
168
|
+
- `init` 现在也会把 Streamable HTTP 服务器(`url` + `headers`)移到代理之后。旧的 SSE 服务器保留在客户端配置中并会被报告。
|
|
169
|
+
|
|
170
|
+
Docker:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
docker build -t guardbee/mcp-security-proxy packages/security-proxy
|
|
174
|
+
docker run -p 8787:8787 -e GUARDBEE_PROXY_KEY=... -e GUARDBEE_API_KEY=... \
|
|
175
|
+
-v "$PWD/guardbee-proxy.yaml:/etc/guardbee/guardbee-proxy.yaml:ro" guardbee/mcp-security-proxy
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
135
180
|
## MCP 工具
|
|
136
181
|
|
|
137
182
|
代理本身不注册任何工具。它转发目标服务器的工具,并在每次调用时应用拦截器链。
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AuditEvent, DashboardSinkConfig } from "../types.js";
|
|
2
|
+
type Fetch = typeof fetch;
|
|
3
|
+
/**
|
|
4
|
+
* Sends audit events to the GuardBee dashboard in batches, off the request
|
|
5
|
+
* path: a tool call never waits for it. Events that could not be sent stay
|
|
6
|
+
* queued (up to MAX_QUEUE, oldest dropped first) and go out with the next flush.
|
|
7
|
+
*/
|
|
8
|
+
export declare class DashboardSink {
|
|
9
|
+
private readonly config;
|
|
10
|
+
private readonly fetchImpl;
|
|
11
|
+
private queue;
|
|
12
|
+
private inFlight;
|
|
13
|
+
private readonly timer;
|
|
14
|
+
private dropped;
|
|
15
|
+
private warnedStatus;
|
|
16
|
+
constructor(config: DashboardSinkConfig, fetchImpl?: Fetch);
|
|
17
|
+
private get batchSize();
|
|
18
|
+
push(event: AuditEvent): void;
|
|
19
|
+
/** Send everything queued; resolves when done or when the endpoint is unreachable. */
|
|
20
|
+
flush(): Promise<void>;
|
|
21
|
+
private drain;
|
|
22
|
+
get pending(): number;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=dashboard-sink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-sink.d.ts","sourceRoot":"","sources":["../../src/audit/dashboard-sink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAOnE,KAAK,KAAK,GAAG,OAAO,KAAK,CAAC;AAE1B;;;;GAIG;AACH,qBAAa,aAAa;IAQtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAR5B,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,YAAY,CAAuB;gBAGxB,MAAM,EAAE,mBAAmB,EAC3B,SAAS,GAAE,KAAa;IAM3C,OAAO,KAAK,SAAS,GAEpB;IAED,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAU7B,sFAAsF;IACtF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAOR,KAAK;IAqCnB,IAAI,OAAO,IAAI,MAAM,CAEpB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DashboardSink = void 0;
|
|
4
|
+
const MAX_QUEUE = 10000;
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 10000;
|
|
6
|
+
/** Retrying these cannot succeed: a bad payload or a bad / under-scoped key. */
|
|
7
|
+
const PERMANENT = new Set([400, 401, 403, 404, 413, 422]);
|
|
8
|
+
/**
|
|
9
|
+
* Sends audit events to the GuardBee dashboard in batches, off the request
|
|
10
|
+
* path: a tool call never waits for it. Events that could not be sent stay
|
|
11
|
+
* queued (up to MAX_QUEUE, oldest dropped first) and go out with the next flush.
|
|
12
|
+
*/
|
|
13
|
+
class DashboardSink {
|
|
14
|
+
constructor(config, fetchImpl = fetch) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.fetchImpl = fetchImpl;
|
|
17
|
+
this.queue = [];
|
|
18
|
+
this.inFlight = null;
|
|
19
|
+
this.dropped = 0;
|
|
20
|
+
this.warnedStatus = null;
|
|
21
|
+
this.timer = setInterval(() => void this.flush(), config.flushIntervalMs ?? 5000);
|
|
22
|
+
this.timer.unref?.();
|
|
23
|
+
}
|
|
24
|
+
get batchSize() {
|
|
25
|
+
return this.config.batchSize ?? 100;
|
|
26
|
+
}
|
|
27
|
+
push(event) {
|
|
28
|
+
this.queue.push(event);
|
|
29
|
+
if (this.queue.length > MAX_QUEUE) {
|
|
30
|
+
const overflow = this.queue.length - MAX_QUEUE;
|
|
31
|
+
this.queue.splice(0, overflow);
|
|
32
|
+
this.dropped += overflow;
|
|
33
|
+
}
|
|
34
|
+
if (this.queue.length >= this.batchSize)
|
|
35
|
+
void this.flush();
|
|
36
|
+
}
|
|
37
|
+
/** Send everything queued; resolves when done or when the endpoint is unreachable. */
|
|
38
|
+
flush() {
|
|
39
|
+
this.inFlight ?? (this.inFlight = this.drain().finally(() => {
|
|
40
|
+
this.inFlight = null;
|
|
41
|
+
}));
|
|
42
|
+
return this.inFlight;
|
|
43
|
+
}
|
|
44
|
+
async drain() {
|
|
45
|
+
while (this.queue.length > 0) {
|
|
46
|
+
const batch = this.queue.slice(0, this.batchSize);
|
|
47
|
+
let status;
|
|
48
|
+
try {
|
|
49
|
+
const response = await this.fetchImpl(this.config.url, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { authorization: `Bearer ${this.config.apiKey}`, "content-type": "application/json" },
|
|
52
|
+
body: JSON.stringify({ source: this.config.source, dropped: this.dropped, events: batch }),
|
|
53
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
54
|
+
});
|
|
55
|
+
status = response.status;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return; // offline or timed out: keep the batch for the next flush
|
|
59
|
+
}
|
|
60
|
+
if (status >= 200 && status < 300) {
|
|
61
|
+
this.queue.splice(0, batch.length);
|
|
62
|
+
this.dropped = 0;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (PERMANENT.has(status)) {
|
|
66
|
+
this.queue.splice(0, batch.length);
|
|
67
|
+
this.dropped += batch.length;
|
|
68
|
+
if (this.warnedStatus !== status) {
|
|
69
|
+
this.warnedStatus = status;
|
|
70
|
+
process.stderr.write(`[guardbee-proxy] dashboard rejected audit events with HTTP ${status}` +
|
|
71
|
+
(status === 401 || status === 403 ? " — check the API key and its gateway.write scope" : "") +
|
|
72
|
+
"; those events stay only in the local audit log\n");
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
return; // 429 / 5xx: try again later
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
get pending() {
|
|
80
|
+
return this.queue.length;
|
|
81
|
+
}
|
|
82
|
+
async close() {
|
|
83
|
+
clearInterval(this.timer);
|
|
84
|
+
await this.flush();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.DashboardSink = DashboardSink;
|
|
88
|
+
//# sourceMappingURL=dashboard-sink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-sink.js","sourceRoot":"","sources":["../../src/audit/dashboard-sink.ts"],"names":[],"mappings":"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"}
|
package/dist/audit/logger.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ export declare class AuditLogger {
|
|
|
3
3
|
private config;
|
|
4
4
|
private stream;
|
|
5
5
|
private lastHash;
|
|
6
|
-
|
|
6
|
+
private readonly dashboard;
|
|
7
|
+
constructor(config: AuditConfig, fetchImpl?: typeof fetch);
|
|
7
8
|
log(event: AuditEvent): void;
|
|
8
|
-
close
|
|
9
|
+
/** Flush the dashboard queue and close the file. */
|
|
10
|
+
close(): Promise<void>;
|
|
9
11
|
private redact;
|
|
10
12
|
}
|
|
11
13
|
export type ChainVerification = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/audit/logger.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/audit/logger.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAkC3D,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;gBAE5C,MAAM,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK;IASzD,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAe5B,oDAAoD;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,OAAO,CAAC,MAAM;CAKf;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhD,mGAAmG;AACnG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAwBjE"}
|
package/dist/audit/logger.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.verifyAuditChain = verifyAuditChain;
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const crypto_1 = require("crypto");
|
|
10
10
|
const definition_drift_js_1 = require("../interceptors/definition-drift.js");
|
|
11
|
+
const dashboard_sink_js_1 = require("./dashboard-sink.js");
|
|
11
12
|
const GENESIS = "";
|
|
12
13
|
function sha256(text) {
|
|
13
14
|
return (0, crypto_1.createHash)("sha256").update(text).digest("hex");
|
|
@@ -38,14 +39,17 @@ function lastHashIn(filePath) {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
class AuditLogger {
|
|
41
|
-
constructor(config) {
|
|
42
|
+
constructor(config, fetchImpl) {
|
|
42
43
|
this.stream = null;
|
|
43
44
|
this.lastHash = GENESIS;
|
|
45
|
+
this.dashboard = null;
|
|
44
46
|
this.config = config;
|
|
45
47
|
if (config.sink === "file" && config.filePath) {
|
|
46
48
|
this.lastHash = lastHashIn(config.filePath);
|
|
47
49
|
this.stream = fs_1.default.createWriteStream(config.filePath, { flags: "a" });
|
|
48
50
|
}
|
|
51
|
+
if (config.enabled && config.dashboard)
|
|
52
|
+
this.dashboard = new dashboard_sink_js_1.DashboardSink(config.dashboard, fetchImpl);
|
|
49
53
|
}
|
|
50
54
|
log(event) {
|
|
51
55
|
if (!this.config.enabled)
|
|
@@ -53,7 +57,9 @@ class AuditLogger {
|
|
|
53
57
|
const unhashed = { ...this.redact(event), prevHash: this.lastHash };
|
|
54
58
|
const hash = chainHash(unhashed);
|
|
55
59
|
this.lastHash = hash;
|
|
56
|
-
const
|
|
60
|
+
const hashed = { ...unhashed, hash };
|
|
61
|
+
this.dashboard?.push(hashed);
|
|
62
|
+
const line = JSON.stringify(hashed);
|
|
57
63
|
if (this.config.sink === "file" && this.stream) {
|
|
58
64
|
this.stream.write(line + "\n");
|
|
59
65
|
}
|
|
@@ -61,8 +67,10 @@ class AuditLogger {
|
|
|
61
67
|
process.stderr.write("[guardbee-proxy] " + line + "\n");
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
|
-
close
|
|
65
|
-
|
|
70
|
+
/** Flush the dashboard queue and close the file. */
|
|
71
|
+
async close() {
|
|
72
|
+
await this.dashboard?.close();
|
|
73
|
+
await new Promise((resolve) => (this.stream ? this.stream.end(resolve) : resolve()));
|
|
66
74
|
}
|
|
67
75
|
redact(event) {
|
|
68
76
|
if (this.config.includePayloads !== false)
|
package/dist/audit/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/audit/logger.ts"],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/audit/logger.ts"],"names":[],"mappings":";;;;;;AAoFA,4CAwBC;AA5GD,4CAAoB;AACpB,mCAAoC;AAEpC,6EAA6D;AAC7D,2DAAoD;AAEpD,MAAM,OAAO,GAAG,EAAE,CAAC;AAEnB,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,iFAAiF;AACjF,SAAS,SAAS,CAAC,QAAkC;IACnD,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,uFAAuF;AACvF,SAAS,UAAU,CAAC,QAAgB;IAClC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrC,IAAI,CAAC,IAAI;QAAE,OAAO,OAAO,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,IAAI,GAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAgB,CAAC,IAAI,CAAC;QACnD,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAa,WAAW;IAMtB,YAAY,MAAmB,EAAE,SAAwB;QAJjD,WAAM,GAA0B,IAAI,CAAC;QACrC,aAAQ,GAAG,OAAO,CAAC;QACV,cAAS,GAAyB,IAAI,CAAC;QAGtD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YAC9C,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,GAAG,YAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QACtE,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,IAAI,iCAAa,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC1G,CAAC;IAED,GAAG,CAAC,KAAiB;QACnB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO;QACjC,MAAM,QAAQ,GAA6B,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9F,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;QACrC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC/C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,oDAAoD;IACpD,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC;QAC9B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC7F,CAAC;IAEO,MAAM,CAAC,KAAiB;QAC9B,IAAI,IAAI,CAAC,MAAM,CAAC,eAAe,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QACxD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;QAClD,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,IAAA,4BAAM,EAAC,KAAK,CAAC,CAAC,EAAE,CAAC;IACpF,CAAC;CACF;AAzCD,kCAyCC;AAMD,mGAAmG;AACnG,SAAgB,gBAAgB,CAAC,KAAa;IAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,YAAY,GAAG,OAAO,CAAC;IAC3B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QAC5C,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,SAAS;QACjC,MAAM,MAAM,GAAG,KAAK,GAAG,CAAC,CAAC;QACzB,IAAI,KAAiB,CAAC;QACtB,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;QAC/D,CAAC;QACD,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAC;QACpC,IAAI,QAAQ,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;YACvC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,8EAA8E,EAAE,CAAC;QAC7H,CAAC;QACD,IAAI,IAAI,KAAK,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,8DAA8D,EAAE,CAAC;QAC7G,CAAC;QACD,YAAY,GAAG,IAAI,CAAC;QACpB,MAAM,EAAE,CAAC;IACX,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import type { DashboardSinkConfig } from "../types.js";
|
|
2
3
|
export interface ApprovalRequest {
|
|
3
4
|
tool: string;
|
|
4
5
|
upstream: string;
|
|
5
6
|
args: Record<string, unknown>;
|
|
6
7
|
reason: string;
|
|
8
|
+
/** MCP session (HTTP mode), shown to whoever approves in the dashboard. */
|
|
9
|
+
sessionId?: string;
|
|
7
10
|
}
|
|
11
|
+
export type ApprovalChannel = "elicitation" | "dashboard";
|
|
8
12
|
/** `unavailable`: the client cannot show a prompt, so nobody could approve. */
|
|
9
13
|
export type ApprovalOutcome = "approved" | "declined" | "timed-out" | "unavailable";
|
|
10
14
|
export type Approver = (request: ApprovalRequest) => Promise<ApprovalOutcome>;
|
|
@@ -13,4 +17,16 @@ export type Approver = (request: ApprovalRequest) => Promise<ApprovalOutcome>;
|
|
|
13
17
|
* other than an explicit yes — decline, cancel, timeout — counts as no.
|
|
14
18
|
*/
|
|
15
19
|
export declare function elicitationApprover(server: Server, timeoutSeconds: number): Approver;
|
|
20
|
+
type Fetch = typeof fetch;
|
|
21
|
+
/**
|
|
22
|
+
* Ask in the GuardBee dashboard: create an approval request, then poll until
|
|
23
|
+
* someone approves or denies it, it expires, or the timeout passes. The
|
|
24
|
+
* dashboard sees the arguments with PII masked, never the raw values.
|
|
25
|
+
* `unavailable` when the dashboard cannot be reached or rejects the key, so
|
|
26
|
+
* a chain can fall through to the next channel.
|
|
27
|
+
*/
|
|
28
|
+
export declare function dashboardApprover(dashboard: DashboardSinkConfig, timeoutSeconds: number, fetchImpl?: Fetch, pollMs?: number): Approver;
|
|
29
|
+
/** Try each channel in order; the first one that can actually ask gives the answer. */
|
|
30
|
+
export declare function chainApprovers(approvers: readonly Approver[]): Approver;
|
|
31
|
+
export {};
|
|
16
32
|
//# sourceMappingURL=approval.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../src/gateway/approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAExE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../src/gateway/approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAExE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,WAAW,CAAC;AAE1D,+EAA+E;AAC/E,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,CAAC;AAEpF,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAS9E;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,QAAQ,CAwBpF;AAGD,KAAK,KAAK,GAAG,OAAO,KAAK,CAAC;AAE1B;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,mBAAmB,EAC9B,cAAc,EAAE,MAAM,EACtB,SAAS,GAAE,KAAa,EACxB,MAAM,SAAO,GACZ,QAAQ,CA6CV;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,CAQvE"}
|
package/dist/gateway/approval.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.elicitationApprover = elicitationApprover;
|
|
4
|
+
exports.dashboardApprover = dashboardApprover;
|
|
5
|
+
exports.chainApprovers = chainApprovers;
|
|
6
|
+
const guard_core_1 = require("@guardbee/guard-core");
|
|
4
7
|
const MAX_ARGS_SHOWN = 600;
|
|
5
8
|
function describeArgs(args) {
|
|
6
9
|
const json = JSON.stringify(args, null, 2);
|
|
@@ -33,4 +36,75 @@ function elicitationApprover(server, timeoutSeconds) {
|
|
|
33
36
|
}
|
|
34
37
|
};
|
|
35
38
|
}
|
|
39
|
+
const MAX_PREVIEW = 2000;
|
|
40
|
+
/**
|
|
41
|
+
* Ask in the GuardBee dashboard: create an approval request, then poll until
|
|
42
|
+
* someone approves or denies it, it expires, or the timeout passes. The
|
|
43
|
+
* dashboard sees the arguments with PII masked, never the raw values.
|
|
44
|
+
* `unavailable` when the dashboard cannot be reached or rejects the key, so
|
|
45
|
+
* a chain can fall through to the next channel.
|
|
46
|
+
*/
|
|
47
|
+
function dashboardApprover(dashboard, timeoutSeconds, fetchImpl = fetch, pollMs = 2000) {
|
|
48
|
+
const endpoint = new URL("approvals", dashboard.url).toString();
|
|
49
|
+
const headers = { authorization: `Bearer ${dashboard.apiKey}`, "content-type": "application/json" };
|
|
50
|
+
return async (request) => {
|
|
51
|
+
const preview = JSON.stringify((0, guard_core_1.maskPiiInValue)(request.args), null, 2);
|
|
52
|
+
let id;
|
|
53
|
+
try {
|
|
54
|
+
const created = await fetchImpl(endpoint, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers,
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
source: dashboard.source,
|
|
59
|
+
sessionId: request.sessionId,
|
|
60
|
+
tool: request.tool,
|
|
61
|
+
upstream: request.upstream,
|
|
62
|
+
reason: request.reason,
|
|
63
|
+
argsPreview: preview.length > MAX_PREVIEW ? `${preview.slice(0, MAX_PREVIEW)}\n…` : preview,
|
|
64
|
+
timeoutSeconds,
|
|
65
|
+
}),
|
|
66
|
+
signal: AbortSignal.timeout(10000),
|
|
67
|
+
});
|
|
68
|
+
if (!created.ok)
|
|
69
|
+
return "unavailable";
|
|
70
|
+
id = (await created.json()).data?.id;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return "unavailable";
|
|
74
|
+
}
|
|
75
|
+
if (!id)
|
|
76
|
+
return "unavailable";
|
|
77
|
+
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
78
|
+
while (Date.now() < deadline) {
|
|
79
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
80
|
+
try {
|
|
81
|
+
const res = await fetchImpl(`${endpoint}/${encodeURIComponent(id)}`, { headers, signal: AbortSignal.timeout(10000) });
|
|
82
|
+
if (!res.ok)
|
|
83
|
+
continue; // a transient error must not count as an answer
|
|
84
|
+
const status = (await res.json()).data?.status;
|
|
85
|
+
if (status === "APPROVED")
|
|
86
|
+
return "approved";
|
|
87
|
+
if (status === "DENIED")
|
|
88
|
+
return "declined";
|
|
89
|
+
if (status === "EXPIRED")
|
|
90
|
+
return "timed-out";
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// keep polling until the deadline
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return "timed-out";
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Try each channel in order; the first one that can actually ask gives the answer. */
|
|
100
|
+
function chainApprovers(approvers) {
|
|
101
|
+
return async (request) => {
|
|
102
|
+
for (const approver of approvers) {
|
|
103
|
+
const outcome = await approver(request);
|
|
104
|
+
if (outcome !== "unavailable")
|
|
105
|
+
return outcome;
|
|
106
|
+
}
|
|
107
|
+
return "unavailable";
|
|
108
|
+
};
|
|
109
|
+
}
|
|
36
110
|
//# sourceMappingURL=approval.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/gateway/approval.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/gateway/approval.ts"],"names":[],"mappings":";;AA+BA,kDAwBC;AAYD,8CAkDC;AAGD,wCAQC;AA/HD,qDAAsD;AAmBtD,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,SAAS,YAAY,CAAC,IAA6B;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,QAAQ,IAAI,CAAC,MAAM,GAAG,cAAc,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC;AACvI,CAAC;AAED;;;GAGG;AACH,SAAgB,mBAAmB,CAAC,MAAc,EAAE,cAAsB;IACxE,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,IAAI,CAAC,MAAM,CAAC,qBAAqB,EAAE,EAAE,WAAW;YAAE,OAAO,aAAa,CAAC;QACvE,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CACrC;gBACE,OAAO,EACL,uCAAuC;oBACvC,SAAS,OAAO,CAAC,IAAI,6BAA6B,OAAO,CAAC,MAAM,mBAAmB,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;gBACjH,eAAe,EAAE;oBACf,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,KAAK,EAAE;qBACzE;oBACD,QAAQ,EAAE,CAAC,SAAS,CAAC;iBACtB;aACF,EACD,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,EAAE,CACnC,CAAC;YACF,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC;QACtG,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,GAAG,YAAY,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC;QAC7F,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,WAAW,GAAG,IAAI,CAAC;AAGzB;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAC/B,SAA8B,EAC9B,cAAsB,EACtB,YAAmB,KAAK,EACxB,MAAM,GAAG,IAAI;IAEb,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;IAChE,MAAM,OAAO,GAAG,EAAE,aAAa,EAAE,UAAU,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;IAEpG,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAA,2BAAc,EAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACtE,IAAI,EAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE;gBACxC,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,MAAM,EAAE,SAAS,CAAC,MAAM;oBACxB,SAAS,EAAE,OAAO,CAAC,SAAS;oBAC5B,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,WAAW,EAAE,OAAO,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO;oBAC3F,cAAc;iBACf,CAAC;gBACF,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAM,CAAC;aACpC,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,EAAE;gBAAE,OAAO,aAAa,CAAC;YACtC,EAAE,GAAI,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,CAAgC,CAAC,IAAI,EAAE,EAAE,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,aAAa,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,EAAE;YAAE,OAAO,aAAa,CAAC;QAE9B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,GAAG,IAAI,CAAC;QACpD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;YAC5D,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,QAAQ,IAAI,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAM,CAAC,EAAE,CAAC,CAAC;gBACvH,IAAI,CAAC,GAAG,CAAC,EAAE;oBAAE,SAAS,CAAC,gDAAgD;gBACvE,MAAM,MAAM,GAAI,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAoC,CAAC,IAAI,EAAE,MAAM,CAAC;gBACnF,IAAI,MAAM,KAAK,UAAU;oBAAE,OAAO,UAAU,CAAC;gBAC7C,IAAI,MAAM,KAAK,QAAQ;oBAAE,OAAO,UAAU,CAAC;gBAC3C,IAAI,MAAM,KAAK,SAAS;oBAAE,OAAO,WAAW,CAAC;YAC/C,CAAC;YAAC,MAAM,CAAC;gBACP,kCAAkC;YACpC,CAAC;QACH,CAAC;QACD,OAAO,WAAW,CAAC;IACrB,CAAC,CAAC;AACJ,CAAC;AAED,uFAAuF;AACvF,SAAgB,cAAc,CAAC,SAA8B;IAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;YACxC,IAAI,OAAO,KAAK,aAAa;gBAAE,OAAO,OAAO,CAAC;QAChD,CAAC;QACD,OAAO,aAAa,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/gateway/config.d.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
import type { AuditConfig,
|
|
1
|
+
import type { AuditConfig, ProxyConfig, UpstreamConfig } from "../types.js";
|
|
2
2
|
import { type Label } from "./labels.js";
|
|
3
3
|
import type { PolicyAction, PolicyRule } from "./policy.js";
|
|
4
4
|
import type { TaintMode } from "./taint.js";
|
|
5
|
+
import type { ApprovalChannel } from "./approval.js";
|
|
6
|
+
/** How the agent reaches the proxy. */
|
|
7
|
+
export type ListenConfig = {
|
|
8
|
+
transport: "stdio";
|
|
9
|
+
} | {
|
|
10
|
+
transport: "http";
|
|
11
|
+
host: string;
|
|
12
|
+
port: number;
|
|
13
|
+
path: string;
|
|
14
|
+
/** Accepted Bearer keys; required unless the host is loopback. */
|
|
15
|
+
apiKeys: string[];
|
|
16
|
+
maxSessions: number;
|
|
17
|
+
};
|
|
5
18
|
export interface GatewayConfig {
|
|
6
|
-
upstreams: Record<string,
|
|
19
|
+
upstreams: Record<string, UpstreamConfig>;
|
|
20
|
+
listen: ListenConfig;
|
|
7
21
|
/** Prefix tool and prompt names with `<upstream>__`. Off only for the legacy single-server config. */
|
|
8
22
|
namespaced: boolean;
|
|
9
23
|
/** Label overrides by the tool name the agent sees; replaces the heuristic labels entirely. */
|
|
@@ -12,9 +26,14 @@ export interface GatewayConfig {
|
|
|
12
26
|
taint: {
|
|
13
27
|
mode: TaintMode;
|
|
14
28
|
};
|
|
15
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* How long an `approve` decision waits before it becomes a deny, and where
|
|
31
|
+
* to ask, in order: the client's own prompt (MCP elicitation) and/or the
|
|
32
|
+
* GuardBee dashboard (needs audit.dashboard).
|
|
33
|
+
*/
|
|
16
34
|
approval: {
|
|
17
35
|
timeoutSeconds: number;
|
|
36
|
+
channels: ApprovalChannel[];
|
|
18
37
|
};
|
|
19
38
|
defaults: {
|
|
20
39
|
action: PolicyAction;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/gateway/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/gateway/config.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAmB,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7F,OAAO,EAAU,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,uCAAuC;AACvC,MAAM,MAAM,YAAY,GACpB;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,GACtB;IACE,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEN,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,MAAM,EAAE,YAAY,CAAC;IACrB,sGAAsG;IACtG,UAAU,EAAE,OAAO,CAAC;IACpB,+FAA+F;IAC/F,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAChC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,eAAe,EAAE,CAAA;KAAE,CAAC;IAClE,QAAQ,EAAE;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;IACnC,KAAK,EAAE,WAAW,CAAC;IACnB,YAAY,EAAE,WAAW,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC;CACxD;AAsLD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,SAAwB,GAAG,aAAa,CA2BjG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,CAanE;AAgBD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,MAAM,EAAiB,EAAE,GAAG,GAAE,MAAsB,GAAG,aAAa,CAmB3G"}
|