You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of stacklok/stacklok-enterprise-platform#stacklok/stacklok-enterprise-platform#376
Description
Replace the FeatureType synthetic entity with the MCP server entity as the resource for list operations when serverName is configured on the Cedar authorizer. Currently, authorizeFeatureList uses FeatureType::<feature> (e.g., FeatureType::tool) as the resource entity for list operations. This synthetic type is not part of the enterprise Cedar schema and will never match server-scoped policies like resource in MCP::"github". By switching to MCP::<server> when serverName is set, both Shape 1 (unrestricted grant) and Shape 2 (restricted grant with list/item split) Cedar policies correctly cover list operations.
The change is gated on serverName being non-empty, so existing standalone deployments that use FeatureType::<feature> in hand-written policies are completely unaffected.
Context
The Platform Authorization design compiles CRD-based role bindings into Cedar policies that use resource in MCP::"<server>" for server scoping. Cedar's in operator is reflexive -- MCP::"github" in MCP::"github" evaluates to true -- so a list operation whose resource entity is MCP::"github" naturally matches policies scoped to that server. The current FeatureType::tool resource can never satisfy resource in MCP::"github" because FeatureType is not in the resource hierarchy under MCP.
Making FeatureType a child of MCP was evaluated and rejected: FeatureType::tool is a singleton entity shared across all servers, so in a vMCP deployment with a shared authorizer it cannot simultaneously be a child of multiple MCP parents without breaking server isolation.
This change depends on #4764 which stores serverName on the Authorizer struct, making it available to authorizeFeatureList.
Dependencies: #4764 (serverName on Authorizer) Blocks: #4771 (end-to-end integration test)
Acceptance Criteria
authorizeFeatureList uses MCP::<serverName> as the resource entity when a.serverName != ""
authorizeFeatureList preserves FeatureType::<feature> as the resource entity when a.serverName == "" (backward compatibility)
List authorization with serverName set evaluates correctly against a policy using resource in MCP::"<server>"
List authorization without serverName evaluates correctly against existing FeatureType-based policies
All existing tests pass with no behavior change
Code reviewed and approved
Technical Approach
Recommended Implementation
A single conditional change in authorizeFeatureList in pkg/authz/authorizers/cedar/core.go. The method currently constructs the resource string unconditionally as FeatureType::<feature>. After this change, it checks a.serverName and uses MCP::<server> when set.
The resource variable construction should use the immutable-assignment-with-anonymous-function pattern preferred by the project:
func (a*Authorizer) authorizeFeatureList(
clientIDstring,
feature authorizers.MCPFeature,
claimsMapmap[string]interface{},
attrsMapmap[string]interface{},
) (bool, error) {
principal:=fmt.Sprintf("Client::%s", clientID)
action:=fmt.Sprintf("Action::list_%ss", feature)
// Use MCP server entity when serverName is configured (enterprise),// fall back to FeatureType for backward compatibility (standalone)resource:=func() string {
ifa.serverName!="" {
returnfmt.Sprintf("MCP::%s", a.serverName)
}
returnfmt.Sprintf("FeatureType::%s", feature)
}()
// ... rest of the method unchanged ...
}
No other files need modification. The rest of the method (attributes map construction, entity creation, context merging, IsAuthorized call) remains unchanged.
Patterns & Frameworks
Immutable variable assignment with anonymous function (project Go style convention)
Table-driven tests with testify assertions and t.Parallel() per project testing standards
Backward-compatible gating on serverName (empty = current behavior) per design invariant from 00-invariants.md
Code Pointers
pkg/authz/authorizers/cedar/core.go lines 499-534 -- authorizeFeatureList method; the resource variable on line 515 (fmt.Sprintf("FeatureType::%s", feature)) is the line to change
pkg/authz/authorizers/cedar/core_test.go lines 246-305 -- Existing list authorization tests ("User can list tools", "User can list prompts", "User can list resources"); these use FeatureType-based policies and must continue to pass (backward compat verification)
pkg/authz/authorizers/cedar/entity.go lines 88-125 -- CreateEntitiesForRequest; creates entities for the resource string, no change needed here
Component Interfaces
No interface changes. The authorizeFeatureList method signature is unchanged; only the internal resource string construction changes:
// Before (always FeatureType)resource:=fmt.Sprintf("FeatureType::%s", feature)
// After (conditional on serverName)resource:=func() string {
ifa.serverName!="" {
returnfmt.Sprintf("MCP::%s", a.serverName)
}
returnfmt.Sprintf("FeatureType::%s", feature)
}()
Testing Strategy
Unit Tests
authorizeFeatureList with serverName set to "github" and a policy permit(principal, action == Action::"list_tools", resource in MCP::"github") returns authorized (verifies MCP resource is used)
authorizeFeatureList with serverName set to "github" and a policy permit(principal, action == Action::"list_tools", resource == FeatureType::"tool") returns denied (verifies FeatureType is NOT used when serverName is set)
authorizeFeatureList with empty serverName and a policy permit(principal, action == Action::"list_tools", resource == FeatureType::"tool") returns authorized (backward compat)
authorizeFeatureList with empty serverName and a policy permit(principal, action == Action::"list_prompts", resource == FeatureType::"prompt") returns authorized (backward compat, different feature type)
authorizeFeatureList with empty serverName and a policy permit(principal, action == Action::"list_resources", resource == FeatureType::"resource") returns authorized (backward compat, different feature type)
authorizeFeatureList with serverName set and all three feature types (tool, prompt, resource) each use MCP::<server> as the resource
Integration Tests
End-to-end via AuthorizeWithJWTClaims with MCPOperationList and serverName set verifies the complete path from JWT context to MCP-based list authorization
Edge Cases
serverName with special characters (e.g., "my-server.example.com") produces a valid Cedar entity ID and authorization succeeds
Existing tests in core_test.go for list operations ("User can list tools", "User can list prompts", "User can list resources") continue to pass unchanged (they use authorizers created without serverName)
Part of stacklok/stacklok-enterprise-platform#stacklok/stacklok-enterprise-platform#376
Description
Replace the
FeatureTypesynthetic entity with theMCPserver entity as the resource for list operations whenserverNameis configured on the Cedar authorizer. Currently,authorizeFeatureListusesFeatureType::<feature>(e.g.,FeatureType::tool) as the resource entity for list operations. This synthetic type is not part of the enterprise Cedar schema and will never match server-scoped policies likeresource in MCP::"github". By switching toMCP::<server>whenserverNameis set, both Shape 1 (unrestricted grant) and Shape 2 (restricted grant with list/item split) Cedar policies correctly cover list operations.The change is gated on
serverNamebeing non-empty, so existing standalone deployments that useFeatureType::<feature>in hand-written policies are completely unaffected.Context
The Platform Authorization design compiles CRD-based role bindings into Cedar policies that use
resource in MCP::"<server>"for server scoping. Cedar'sinoperator is reflexive --MCP::"github" in MCP::"github"evaluates to true -- so a list operation whose resource entity isMCP::"github"naturally matches policies scoped to that server. The currentFeatureType::toolresource can never satisfyresource in MCP::"github"becauseFeatureTypeis not in the resource hierarchy underMCP.Making
FeatureTypea child ofMCPwas evaluated and rejected:FeatureType::toolis a singleton entity shared across all servers, so in a vMCP deployment with a shared authorizer it cannot simultaneously be a child of multipleMCPparents without breaking server isolation.This change depends on #4764 which stores
serverNameon theAuthorizerstruct, making it available toauthorizeFeatureList.Dependencies: #4764 (serverName on Authorizer)
Blocks: #4771 (end-to-end integration test)
Acceptance Criteria
authorizeFeatureListusesMCP::<serverName>as the resource entity whena.serverName != ""authorizeFeatureListpreservesFeatureType::<feature>as the resource entity whena.serverName == ""(backward compatibility)serverNameset evaluates correctly against a policy usingresource in MCP::"<server>"serverNameevaluates correctly against existingFeatureType-based policiesTechnical Approach
Recommended Implementation
A single conditional change in
authorizeFeatureListinpkg/authz/authorizers/cedar/core.go. The method currently constructs the resource string unconditionally asFeatureType::<feature>. After this change, it checksa.serverNameand usesMCP::<server>when set.The resource variable construction should use the immutable-assignment-with-anonymous-function pattern preferred by the project:
No other files need modification. The rest of the method (attributes map construction, entity creation, context merging,
IsAuthorizedcall) remains unchanged.Patterns & Frameworks
testifyassertions andt.Parallel()per project testing standardsserverName(empty = current behavior) per design invariant from00-invariants.mdCode Pointers
pkg/authz/authorizers/cedar/core.golines 499-534 --authorizeFeatureListmethod; theresourcevariable on line 515 (fmt.Sprintf("FeatureType::%s", feature)) is the line to changepkg/authz/authorizers/cedar/core.golines 116-125 --Authorizerstruct;serverNamefield will be available here after Store serverName on Authorizer and update NewCedarAuthorizer #4764pkg/authz/authorizers/cedar/core_test.golines 246-305 -- Existing list authorization tests ("User can list tools","User can list prompts","User can list resources"); these useFeatureType-based policies and must continue to pass (backward compat verification)pkg/authz/authorizers/cedar/entity.golines 88-125 --CreateEntitiesForRequest; creates entities for the resource string, no change needed hereComponent Interfaces
No interface changes. The
authorizeFeatureListmethod signature is unchanged; only the internal resource string construction changes:Testing Strategy
Unit Tests
authorizeFeatureListwithserverNameset to"github"and a policypermit(principal, action == Action::"list_tools", resource in MCP::"github")returns authorized (verifies MCP resource is used)authorizeFeatureListwithserverNameset to"github"and a policypermit(principal, action == Action::"list_tools", resource == FeatureType::"tool")returns denied (verifies FeatureType is NOT used when serverName is set)authorizeFeatureListwith emptyserverNameand a policypermit(principal, action == Action::"list_tools", resource == FeatureType::"tool")returns authorized (backward compat)authorizeFeatureListwith emptyserverNameand a policypermit(principal, action == Action::"list_prompts", resource == FeatureType::"prompt")returns authorized (backward compat, different feature type)authorizeFeatureListwith emptyserverNameand a policypermit(principal, action == Action::"list_resources", resource == FeatureType::"resource")returns authorized (backward compat, different feature type)authorizeFeatureListwithserverNameset and all three feature types (tool,prompt,resource) each useMCP::<server>as the resourceIntegration Tests
AuthorizeWithJWTClaimswithMCPOperationListandserverNameset verifies the complete path from JWT context to MCP-based list authorizationEdge Cases
serverNamewith special characters (e.g.,"my-server.example.com") produces a valid Cedar entity ID and authorization succeedscore_test.gofor list operations ("User can list tools","User can list prompts","User can list resources") continue to pass unchanged (they use authorizers created withoutserverName)Out of Scope
serverNameto theAuthorizerstruct (Store serverName on Authorizer and update NewCedarAuthorizer #4764)call_tool(Set MCP parent on resource entities #4769)CreateEntitiesForRequestor entity factory (Replace groups parameter with variadic parents on entity factory #4765, Extend group extraction with dual-claim, dot-notation, and merge-order fix #4768)FeatureTypeentity type from existing standalone policiesReferences