@anchrd/intel-api 0.18.0 → 0.19.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 +15 -0
- package/dist/intel/intel.js +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -263,6 +263,21 @@ grants them.
|
|
|
263
263
|
|
|
264
264
|
A missing capability answers **`403`**, and the screen says a permission is missing.
|
|
265
265
|
|
|
266
|
+
⚠️ **`mcp.connect` is the one that gates a whole endpoint rather than an action.** Intel's own MCP
|
|
267
|
+
endpoint (`POST /mcp`, the one an MCP client or a portal connects to) checks it **on every request**
|
|
268
|
+
and answers `403` without it — before any tool runs and before anything is read. Two things follow
|
|
269
|
+
for an operator:
|
|
270
|
+
|
|
271
|
+
- **Somebody who should use Intel through an MCP client needs `mcp.connect` in Intel**, beside their
|
|
272
|
+
`nodes.*` capabilities. Without it the client authenticates successfully and every call is refused.
|
|
273
|
+
- **Revoking it takes effect on the next call**, with no new sign-in and nothing to press. If a
|
|
274
|
+
Cloudflare Access policy in front of the endpoint also compares the `mcp` claim, that layer is the
|
|
275
|
+
slower of the two — the claim is written once per sign-in and a running session carries it for up
|
|
276
|
+
to 24 hours, while this check reads the live grant rows every time.
|
|
277
|
+
|
|
278
|
+
The web UI is unaffected: it speaks the HTTP surface, which is gated by `nodes.*`, `flows.*` and
|
|
279
|
+
`tools.*` as before.
|
|
280
|
+
|
|
266
281
|
### Layer two: the grant on the node
|
|
267
282
|
|
|
268
283
|
What somebody may reach *in this tree*, granted in Intel on a node. Four verbs — `read`, `write`,
|
package/dist/intel/intel.js
CHANGED
|
@@ -116,6 +116,32 @@ export function createIntel(deps) {
|
|
|
116
116
|
context.header("WWW-Authenticate", `Bearer resource_metadata="${resourceMetadataUrl}"`);
|
|
117
117
|
return context.json({ jsonrpc: "2.0", id: null, error: { code: -32003, message: "authentication required" } }, 401);
|
|
118
118
|
}
|
|
119
|
+
// ⚠️ Intel's OWN portal door, asked per request — and it is deliberately the service-trimmed
|
|
120
|
+
// `can`, the exact spelling the guard on `/auth/connect` above must never use (anchrd/gate#293).
|
|
121
|
+
// The two questions are different and both are needed:
|
|
122
|
+
//
|
|
123
|
+
// /auth/connect "is anything in the SHARED portal for this person" mcpPortalRefused()
|
|
124
|
+
// /mcp "may they reach THIS server" can("mcp", "connect")
|
|
125
|
+
//
|
|
126
|
+
// Until now only Cloudflare Access asked the second one, comparing the `mcp` claim of the Access
|
|
127
|
+
// application against the service name. That claim is collected when the person signs in and a
|
|
128
|
+
// running Access session carries it for up to 24 hours, so a revoked grant kept working for a
|
|
129
|
+
// day while `rules` — the same rows, read live on every call — already said no. This line moves
|
|
130
|
+
// the decision to where the answer is: a revocation lands on the NEXT call, with no new sign-in,
|
|
131
|
+
// no session revocation and nobody pressing anything. It also lets the Access policy go coarse
|
|
132
|
+
// ("is a Gate user") without opening the server to everyone.
|
|
133
|
+
//
|
|
134
|
+
// ⚠️ NOT `permits(...)`: `intel.admin` is the bootstrap escape for Intel's own resource ACLs
|
|
135
|
+
// (ADR-0004 §6), and there is no bootstrap here — this grant is set in Gate, not in Intel, so no
|
|
136
|
+
// admin has to be able to hand it to themselves through this door. Widening it would let people
|
|
137
|
+
// in whom the Access policy turns away today, which is the opposite of moving a check.
|
|
138
|
+
//
|
|
139
|
+
// ⚠️ Fails CLOSED, and that is the whole reason a `403` and not a `401`: the caller IS
|
|
140
|
+
// authenticated and merely may not, so a `WWW-Authenticate` challenge would send an MCP client
|
|
141
|
+
// round the OAuth loop it just completed — the certain dead end of #434 in another costume.
|
|
142
|
+
if (!authorization.can("mcp", "connect")) {
|
|
143
|
+
return context.json({ jsonrpc: "2.0", id: null, error: { code: -32003, message: "permission required" } }, 403);
|
|
144
|
+
}
|
|
119
145
|
return await handleMcp(context.req.raw, {
|
|
120
146
|
authorization,
|
|
121
147
|
// ⚠️ The very token this call was authorized with, so a tool that has to speak to Gate does
|