@anchrd/intel-api 0.17.0 → 0.18.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 CHANGED
@@ -348,10 +348,24 @@ both, every silent sign-in is refused and the Tools area shows "No access to the
348
348
  correct behaviour for somebody outside every policy, and a misleading one for a deployment that
349
349
  simply never enabled the setting.
350
350
 
351
- ⚠️ **Today, somebody without the Gate `mcp.connect` capability is sent into that sign-in anyway**
352
- and can land on the portal's own error page, outside Intel, with no way back but the browser's back
353
- button. Grant `mcp.connect` alongside the Intel capabilities to everyone who should reach Tools.
354
- Tracked as anchrd/intel#434.
351
+ ⚠️ **Somebody who carries the Gate capability `mcp.connect` at no service at all is not sent into
352
+ that sign-in.** Gate answers that question service-wide, Intel asks it before the first redirect,
353
+ and the person stays here reading a sentence that says whom to ask rather than landing on the
354
+ portal's own error page, outside Intel, with no way back but the browser's back button
355
+ (anchrd/intel#434). Grant `mcp.connect` alongside the Intel capabilities to everyone who should
356
+ reach Tools.
357
+
358
+ Two things that guard deliberately does not do. It **names no server**: the answer is one bit, so
359
+ somebody who may reach a different product's MCP server walks on and sees what the portal offers
360
+ them. And it **promises nothing** — this is the part worth knowing before you grant: the portal
361
+ decides on the `mcp` claim frozen into the ID token, and a running Cloudflare Access session carries
362
+ the old claim for up to 24 hours. ⚠️ **Inside that window the old dead end is still open.** Somebody
363
+ who was granted `mcp.connect` a minute ago is not refused here, walks on, and Access then decides on
364
+ the claim state that produced the error page in the first place. The guard closes the trip that is
365
+ certainly pointless; it does not close this one. The way out is the same as before — sign out of
366
+ Access and in again, so a new ID token carries the new claim. (Not measured against a running
367
+ installation; the claim lifetime is the documented one.) Against a Gate too old to answer the
368
+ question at all, nobody is refused here.
355
369
 
356
370
  The Tools screen distinguishes four situations rather than calling all of them "no access": a
357
371
  refusal that really came back, an expired connection, a sign-in that broke, and a sign-in that was
@@ -50,7 +50,32 @@ export function createIntel(deps) {
50
50
  if (!authorization) {
51
51
  return context.redirect(`/auth/login?returnTo=${connectReturnTo}`);
52
52
  }
53
- if (!authorization.can("tools", "read")) {
53
+ // ⚠️ Two questions, and both are answered BEFORE the first redirect leaves Intel (#434).
54
+ // `tools:read` is Intel's own door. `mcpPortalRefused()` is Gate's answer to a question Intel
55
+ // cannot ask itself: does this person carry `mcp.connect` at ANY service? Measured against the
56
+ // running installation, somebody without it was sent to `mcp.anchrd.sh/servers-callback` and
57
+ // landed on a white page saying "No allowed servers available, check your Zero trust policies"
58
+ // — a sentence written for an administrator, on a foreign domain, with not one clickable
59
+ // element on it.
60
+ //
61
+ // ⚠️ The helper, never the raw `mcpConnectAnywhere` beside it. The field has THREE states, and
62
+ // `undefined` means "this Gate is older than the question" (anchrd/gate#277), not "nowhere".
63
+ // `if (!authorization.mcpConnectAnywhere)` passes `tsc --noEmit` and `biome check` alike and
64
+ // would turn every reader of such an installation away without a word — the difference is a
65
+ // value, not a type, so nothing but this line prevents it (anchrd/gate#288).
66
+ //
67
+ // ⚠️ And it is deliberately NOT `can("mcp", "connect")`, which is what this guard was first
68
+ // built as: Gate trims `rules` to the ASKING service (GATE-64, least privilege), so that asks
69
+ // `intel.mcp.connect`. The portal is a door onto every server — somebody carrying
70
+ // `dwh.mcp.connect` reaches it and sees the dwh tools, and that spelling would have locked
71
+ // them out. The bit knows no service names, on purpose; it is one bit and carries no more.
72
+ //
73
+ // ⚠️ It spares the certainly wasted trip and promises no successful one: the portal decides
74
+ // on the `mcp` claim frozen into the ID token, and a running Access session carries the old
75
+ // claim for up to 24 hours. Somebody granted a minute ago is NOT refused here, walks on, and
76
+ // meets Access deciding on the very claim state this ticket is about — inside that window the
77
+ // old dead end is still open, and this guard does not close it. It closes the certain one.
78
+ if (!authorization.can("tools", "read") || authorization.mcpPortalRefused()) {
54
79
  return context.redirect("/tools?connectError=permission_required");
55
80
  }
56
81
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-api",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "typecheck": "tsc --noEmit"
43
43
  },
44
44
  "dependencies": {
45
- "@anchrd/gate-sdk": "^0.7.0",
45
+ "@anchrd/gate-sdk": "^0.15.0",
46
46
  "@anchrd/intel-contract": "^0.15.0",
47
47
  "@cfworker/json-schema": "^4.1.1",
48
48
  "@modelcontextprotocol/sdk": "^1.30.0",