Skip to content

fix(authz): make Cedar URI entity IDs collision-free - #6239

Open
SashaMIT wants to merge 1 commit into
stacklok:mainfrom
SashaMIT:fix/cedar-uri-entity-id-collision
Open

fix(authz): make Cedar URI entity IDs collision-free#6239
SashaMIT wants to merge 1 commit into
stacklok:mainfrom
SashaMIT:fix/cedar-uri-entity-id-collision

Conversation

@SashaMIT

@SashaMIT SashaMIT commented Aug 7, 2026

Copy link
Copy Markdown

A permission granted to one resource URI can silently apply to a different, colliding URI.

Problem

authorizeResourceRead turned resource URIs into Cedar entity IDs with a lossy sanitizer that rewrote :, /, \, ?, &, =, #, space, and . to _. The mapping is many-to-one, so distinct URIs collide onto one entity ID:

  • file:///etc/passwd and file://_etc/passwd both became Resource::"file____etc_passwd"
  • mcp://srv/config:admin and mcp://srv/config/admin both became Resource::"mcp___srv_config_admin"

A policy granting Resource::"file____etc_passwd" therefore authorized reads of every URI in the collision class. The confused-deputy condition: an MCP server that serves both a permitted URI and a colliding sensitive URI (or one that lets users create resource URIs) gets the grant applied to a resource the policy author never named.

Severity framing, honestly stated: exploitation needs a policy author who grants by entity ID, plus a colliding pair where one side is permitted and the other is sensitive and server-distinguished. Attribute-based policies (resource.name == ..., resource.uri == ...) match on the exact URI and are unaffected.

Fix

Entities are built programmatically with cedar.NewEntityUID, which accepts any string, so no character rewriting is needed at all. The exact URI is now the entity ID and the sanitizer is removed. Policy authors can name the real URI (for example Resource::"file:///etc/passwd") instead of computing a mangled form.

Note for existing policies: a policy that references a sanitized ID (only possible for URIs containing the rewritten characters) must be updated to name the exact URI. Policies using plain IDs or attribute matching are unchanged.

Tests

Replaced TestSanitizeURIForCedar with TestAuthorizeResourceReadEntityIDsCollisionFree: for both collision pairs above, a grant on the exact URI authorizes that URI and denies the formerly colliding URI. The pkg/authz/... and pkg/vmcp/core suites pass.

Made with Cursor

authorizeResourceRead sanitized resource URIs into Cedar entity IDs
by rewriting reserved characters to "_". The mapping is many-to-one:
"file:///etc/passwd" and "file://_etc/passwd" both became
"file____etc_passwd", and "mcp://srv/config:admin" and
"mcp://srv/config/admin" both became "mcp___srv_config_admin". A
policy grant on one entity ID therefore authorized every URI in the
collision class.

Entities are built programmatically via cedar.NewEntityUID, which
accepts any string, so no rewriting is needed. Use the exact URI as
the entity ID and drop the sanitizer. Attribute-based policies
(resource.name / resource.uri) already matched on the exact URI and
are unaffected. Policies that referenced sanitized IDs must be
updated to name the exact URI.

Added TestAuthorizeResourceReadEntityIDsCollisionFree: a grant on an
exact URI authorizes that URI and denies the URI that used to
collide with it.

Signed-off-by: Sasha Mitchell <sash.t.mitchell@gmail.com>
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