Skip to content

docs: fix the authorization concept - #785

Open
kaisoz wants to merge 8 commits into
google:mainfrom
kaisoz:kaisoz/fix-docs-authorization
Open

kaisoz wants to merge 8 commits into
google:mainfrom
kaisoz:kaisoz/fix-docs-authorization

Conversation

@kaisoz

@kaisoz kaisoz commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Why

The authorization concept explains how the mesh decides who may call what. Seven statements there disagree with the code: what the catalog answers, what grants publishing, what the attenuation block holds, when a task block expires, where the required-labels header applies, which kind of rule the target check is, and which roles a bootstrap-token credential carries.

What changed

  • system://mesh.catalog lists what one node hosts. Discovery is the routers' directory.
  • An egress destination's served_by also grants publishing that destination.
  • The attenuation block holds rules, checks and policies, not facts.
  • A task block is valid at expire_time and expires after it.
  • The node honours X-Mesh-Required-Labels on the /v1 completion routes, on every /mesh/<peer-id>/... request, on /egress/ and on CONNECT. A node that serves the request itself checks its own labels.
  • The target check is a baseline check, not a policy.
  • A credential minted from a bootstrap token carries only the token's role. Other roles reach that node through the node rules, and a grant removed from those rules ends at the next policy sync.

Scope

  • One commit per corrected claim. Docs, plus the Go comment on api.HeaderMeshRequiredLabels.
  • Each commit also fixes the same claim on the other pages that repeat it: the policy and node API references, the identity concept, the exposing-services guide and two architecture pages.

Verification

  • Each claim was checked against the node's gate and middleware, the Datalog in api/, the TAR verifiers in Go, JS and Python, and the control plane's minting paths. An independent review confirmed the new text, and its corrections are in.
  • make lint reports 0 issues. The site builds with Hugo 0.147.5 and adds no broken link.

The authorization concept said system://mesh.catalog answers discovery
queries, the policy reference called it the built-in discovery service,
and the exposing-services guide called it discovery. The catalog is an
MCP server on the node that answers list_local_services for a peer
whose credential grants it (internal/node/gate.go:79-114,
internal/node/mcp.go:402-415). The routers' directory answers who
provides a name (internal/node/node.go:2263-2270). A lookup by type
asks the directory for the peers and then each peer's catalog for the
names (internal/node/node.go:2284-2318).

The guide's troubleshooting list also blamed a missing service on the
caller's lack of system://mesh.catalog. A lookup by name never opens
the catalog (internal/node/node.go:2166-2169). The list now names a
record that the router refused because the node's role does not grant
publishing it (internal/router/directory.go:216-218, 253-269).
The authorization concept and the policy reference said that without
allowed_publish, holders may publish nothing. An egress destination's
served_by selects the nodes that serve it, by role or by label. The
control plane adds egress://<name> to what those nodes may publish
when it mints their credential (api/egress.go:239-265, called from
internal/controlplane/server.go:2031-2037). The same grant travels in
the policy rules as granted_publish_exact("egress", <name>) <- role(r)
or <- label(k, v) (api/egress.go:277-300).
The authorization concept said the node's local rules add extra facts,
extra checks and policies. The attenuation block has three lists,
policies, checks and rules (api/policy.go:63-67), and the node parses
each with the matching Biscuit parser (internal/node/config.go:121-146).
A rule needs a body after "<-" (library biscuit-go v2.2.2:
~/go/pkg/mod/github.com/eclipse-biscuit/biscuit-go/v2@v2.2.2/parser/grammar.go:175-178),
so a bare fact is refused at start.
The authorization concept said the current time must be strictly
before every task block's expire_time. The policy reference, the
security architecture page and the agent architecture page said a
request must come before it. The verifier rejects a block only when
the current time is after expire_time, so a request at the instant of
expire_time still passes (api/tar.go:500,
internal/identity/biscuit.go:711). The JS and Python verifiers agree
(sdk/js/src/tar.ts:427, sdk/python/src/agent_mesh/tar.py:347).
The authorization concept said a caller sends X-Mesh-Required-Labels
on the /v1 inference endpoints or on a proxied A2A request. The node
API reference said the proxy path honours it, and so does /v1/*. The
Go comment on api.HeaderMeshRequiredLabels named only /v1/*. The node
reads the header on /v1/chat/completions and /v1/completions
(internal/node/openai_facade.go:357) and on every
/mesh/<peer-id>/<type>/<name>/ request (internal/node/sidecar.go:790,
with the A2A gate at internal/node/a2a_service.go:106). The node also
reads it on /egress/<destination>/ (internal/node/egress.go:481) and on
CONNECT (internal/node/egress_tunnel.go:349). call_remote_tool takes
the same requirement as required_labels
(internal/node/mcp_handlers.go:175).

/v1/models never reads the header
(internal/node/openai_facade.go:302-328). On /egress/<destination>/
the node serves the destination itself, so it compares the requirement
with its own labels (internal/node/egress.go:494-504). A CONNECT to a
destination the node serves does the same
(internal/node/egress_tunnel.go:376). This commit also rewrites the
comment on api.HeaderMeshRequiredLabels in api/network.go to name
these routes.
The authorization concept listed the target check under the baseline
policies. It is a Biscuit check, "check if allow_network_target($fact,
$val) or target_unrestricted(true)" (api/datalog.go:599). The node adds
it with AddCheck after its local rules and before the baseline policies
(internal/node/middleware.go:352-353). A check fails the request
whatever the policies say, so it belongs with the other baseline
checks (internal/node/middleware.go:303, 332).
The authorization concept said the control plane resolves the
identity's roles from the bindings and writes them into the authority
block. It said so for enrollment, refresh and token exchange. The
policy reference and the identity concept said the same. The control
plane resolves roles from the bindings only for an identity that the
identity provider authenticates
(internal/controlplane/server.go:1027-1074, 1417-1438,
internal/controlplane/sts.go:917-943). A member that enrolled with a
bootstrap token is minted with the token's role alone
(internal/identity/biscuit.go:683, called from
internal/controlplane/server.go:1449, 2405, 3019, 3299). A role bound
to node:<peer-id> or to mesh:system:authenticated reaches that member
through the policy rules the verifying node syncs
(api/policy_rules.go:154-171).

The authorization concept also said that removing a grant takes effect
through the credential. That holds for a grant compiled into a token.
A grant that reaches a member only through the policy rules ends when
the verifying node next fetches the policy.
@kaisoz
kaisoz requested a review from aojea as a code owner October 20, 2026 07:52

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates documentation and comments across several files to clarify authorization, identity, and policy evaluation behaviors. Key updates include detailing how system://mesh.catalog operates, clarifying that bootstrap enrollments only receive the bootstrap token's role, changing expire_time evaluation to "at or before" instead of "strictly before", and refining the description of where X-Mesh-Required-Labels is supported and processed. The review feedback suggests a minor phrasing improvement in the authorization documentation to enhance readability.

`inference://vllm-eu`, `a2a://triage`, `egress://bigquery.googleapis.com`, and
the built-in `system://mesh.catalog` that answers discovery queries.
the built-in `system://mesh.catalog`, which lists the services the node hosts
to a peer that asks. The routers' directory, not the catalog, tells a caller

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The phrase 'to a peer that asks' is slightly awkward. Using 'for any peer that asks' improves clarity and readability.

Suggested change
to a peer that asks. The routers' directory, not the catalog, tells a caller
for any peer that asks. The routers' directory, not the catalog, tells a caller

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant