Hive Hive
Sign in

feat(kura): support project-scoped tokens over the REAPI gRPC surface

GitHub issue · Closed

Metadata
Source
tuist/tuist #11198
Updated
Jun 24, 2026
Domains
Kura
Details

What changed

The REAPI surface (Bazel/Buck2, gRPC) now authorizes at project scope using the namespace the request already carries, so a project-scoped token (e.g. a Tuist user token) works over gRPC the same way it does over the HTTP CAS protocol.

Three changes, all in kura/src/reapi/mod.rs (+ a #[cfg(test)] constructor in kura/src/extension/mod.rs):

  • ctx.tenant_id from metadata headers. grpc_extension_context now reads x-kura-tenant-id (falling back to x-tuist-account-handle) into ctx.tenant_id, mirroring the HTTP tenant_id / account_handle query aliases. This lets the extension enforce the same request-account-matches-server-tenant guard the HTTP path already has.
  • GetCapabilities namespace. It now derives its namespace from instance_name instead of dropping it (None).
  • ByteStream Write (deferred, single authorize per stream). Write captures the request metadata up front, then authorizes once per stream — on the first chunk, as soon as the namespace is known from its resource_name (authorize_request was split into a metadata-based authorize_metadata). Later chunks are not re-authorized; they’re only checked for a consistent resource_name, and a mid-stream change is rejected. Authorization runs before any blob data is written, and the temp file is removed on denial.

Why

Over gRPC, the extension previously authorized two RPCs — GetCapabilities and ByteStream Write — at account scope, because Kura handed them no namespace:

  • GetCapabilities hardcoded namespace_id: None (the request’s instance_name was dropped).
  • ByteStream Write authorized before reading the stream, but its namespace lives inside the first chunk’s resource_name, so it had to fall back to None → account scope.

GetCapabilities is Bazel’s first call and ByteStream Write handles large-blob uploads, so a project-scoped token was rejected on both and couldn’t drive a Bazel session at all — even though the same token works over HTTP CAS.

Why this approach

The namespace always comes from the REAPI instance_name / resource_name, never a client-supplied namespace header. That means the value authorized is exactly the value the blob is stored under (artifact_storage_id), so authorization and storage can’t diverge — no confused-deputy where a token authorized for project A writes into project B. The account half of the identifier is always the node’s KURA_TENANT_ID, never client-supplied, so a token granting a/ios can never satisfy a b/ios target.

tuist.lua needs no change: every gRPC RPC now hands the hook a non-nil project namespace, and the existing project-scope grant check + tenant guard do the rest. The tenant header is opt-in/non-breaking — clients that omit it keep the prior behavior.

Impact

  • Project-scoped (user) tokens now authorize the full Bazel REAPI surface: GetCapabilities, FindMissingBlobs, Batch{Update,Read}Blobs, action cache, ByteStream Read, and ByteStream Write.
  • Behavioral note: GetCapabilities with an empty instance_name resolves to namespace default and is denied unless a default project exists — Bazel sets --remote_instance_name when configured.

Validation

  • cargo build, cargo clippy --all-targets -- -D warnings, and the full reapi test suite pass.
  • New tests: header-extraction unit tests for ctx.tenant_id, plus extension-enabled end-to-end tests asserting GetCapabilities and ByteStream Write authorize a granted namespace and deny a non-granted one (the latter returns PermissionDenied before the blob is persisted).

🤖 Generated with Claude Code

Flights

Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.

New Flights are paused Configure model inference, GitHub, and a sandbox provider to start another Flight. Existing results remain available below.
No Flights yet

Start a Flight and preserve its objective, outcome, and session here.

Comments
TA
tuist-atlas[bot] Jun 11, 2026

Support for project-scoped tokens over the REAPI gRPC surface is now available in Kura 0.9.0. Update to kura@0.9.0 to use this feature.