Skip to content

fix(mcp): gate describe_entities schema visibility by caller role and permissions - #3737

Merged
Anusha Kolan (anushakolan) merged 15 commits into
mainfrom
fix/msrc-mcp-describe-entities-authz
Aug 6, 2026
Merged

fix(mcp): gate describe_entities schema visibility by caller role and permissions#3737
Anusha Kolan (anushakolan) merged 15 commits into
mainfrom
fix/msrc-mcp-describe-entities-authz

Conversation

@anushakolan

@anushakolan Anusha Kolan (anushakolan) commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Why make this change?

describe_entities returned schema metadata — entity names, field names, parameters, and descriptions — for every configured entity without applying per-entity or per-column authorization. Callers with limited permissions could enumerate the full database surface, including entities and columns they had no right to access.

This change aligns describe_entities with REST, GraphQL, and OpenAPI: schema visibility is now gated by the caller's role, at both the entity level and the column level, using the same IAuthorizationResolver DAB uses everywhere else.

What changed?

Entity-level filter — an entity appears in the response only when the caller's role has at least one authorized operation on it.

  • HasAnyPermissionForEntity delegates to authResolver.AreRoleAndOperationDefinedForEntity, so anonymous → authenticated → named-role inheritance and wildcard All expansion are handled by the resolver, matching REST/GraphQL semantics exactly.

Column-level filter — for entities the caller can reach, only fields the caller is authorized to see (across all their permitted operations) are included.

  • ComputeAllowedFieldNames unions authResolver.GetAllowedExposedColumns across every operation the caller's role is authorized for.
  • BuildFieldMetadataInfo filters the emitted field list against that set.
  • Stored-procedure parameters are not filtered — SP access is gated by EXECUTE on the whole procedure; there is no column-level ACL on parameters.

BuildPermissionsInfo now returns a sorted, uppercased list of operations the caller's role is authorized for on each entity.

Single-role request model

X-MS-API-ROLE is treated as one atomic role at every MCP entry point. The value validated by IsValidRoleContext (via HttpContext.User.IsInRole(header)) is the role used for the entire request — no comma-splitting. This matches DAB's single-role model used by REST, GraphQL, and ClientRoleHeaderAuthorizationMiddleware. Multi-role headers like X-MS-API-ROLE: reader,admin are rejected with HTTP 403 before reaching any tool.

Applied in both DescribeEntitiesTool.ExecuteAsync and McpAuthorizationHelper.TryResolveAuthorizedRole (used by every DML MCP tool and DynamicCustomTool).

Testing

Unit tests — all pass ✅

Tests build a real DefaultHttpContext with the X-MS-API-ROLE header and a ClaimsPrincipal carrying Claim(ClaimTypes.Role, role). IsValidRoleContext is mocked with the production semantic — !string.IsNullOrWhiteSpace(header) && ctx.User.IsInRole(header) — so multi-role headers fail exactly as in production.

Notable coverage:

Test What it validates
DescribeEntities_RoleWithNoPermissions_ReturnsNoEntitiesError Role with zero permissions sees nothing
DescribeEntities_LowPrivRole_SeesOnlyAuthorizedEntities Low-privilege role sees only its own entities; permissions array asserted
DescribeEntities_NoRole_ReturnsNoEntitiesError Missing role header → empty result
DescribeEntities_AdminRole_SeesEveryAuthorizedEntityWithWildcardExpansion Admin sees Book with {CREATE,DELETE,READ,UPDATE} and GetBook with {EXECUTE}
DescribeEntities_HonorsRoleInheritance_AnonymousIntoAuthenticatedIntoNamedRole Full resolver-driven inheritance chain
DescribeEntities_ExcludesRestrictedColumnsFromFieldsArray Excluded column absent from fields[]; permitted columns present
DescribeEntities_StoredProcedure_DoesNotFilterFields SP result fields are never column-filtered
16 existing dml-tools / SP filtering tests No regression

dotnet format --verify-no-changes passes.

Manual end-to-end against SQL Server 2022 ✅

# Scenario Result
T1 No headers (anonymous default) 55 entities
T2–T3 role=anonymous 55 entities
T4 role=authenticated 58 entities (adds authenticated-only entities)
T5 X-MS-API-ROLE: authenticated,anonymous (multi-role) HTTP 403
T6 Low-priv role, entities=[Book]title excluded perms=[READ], fields=[id]
T7 entities=[Book,Stock] anonymous 2 entities
T8 Unknown named role ghost Inherits from authenticated → 58 entities
T9 Anonymous full Book perms=[CREATE,DELETE,READ,UPDATE], fields=[id,title]
T10 Low-priv role, no entity filter Restricted entity shows perms=[READ] fields=[id]; rest inherit from authenticated
T11 Role with READ restricted + UPDATE unrestricted on Book Union of fields across ops → perms=[READ,UPDATE], fields=[id,title]
T12 GetBook SP, anonymous perms=[EXECUTE], params=[id]
T13 Anonymous Book with wildcard * CRUD expansion → perms=[CREATE,DELETE,READ,UPDATE]
T14 entities=[DoesNotExist] EntitiesNotFound error

nameOnly=true and entity-filter arguments are unaffected.

Copilot AI 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.

Pull request overview

This pull request addresses an information disclosure vulnerability in the MCP describe_entities tool by introducing per-entity authorization filtering, so schema metadata is only returned for entities the caller is permitted to access (bringing MCP discovery behavior closer to other DAB surfaces).

Changes:

  • Added authorization-based entity filtering to describe_entities via a new HasAnyPermissionForEntity helper.
  • Added unit tests covering “no permissions”, “low privilege”, and “no role” scenarios for describe_entities filtering.
  • Updated MCP test harness to allow specifying (or omitting) the request role header in the mocked HttpContext.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Azure.DataApiBuilder.Mcp/BuiltInTools/DescribeEntitiesTool.cs Adds per-entity authorization filtering to prevent disclosure of metadata for unauthorized entities.
src/Service.Tests/Mcp/DescribeEntitiesFilteringTests.cs Adds new tests for role-based filtering and updates helpers to simulate different role contexts.
Comments suppressed due to low confidence (1)

src/Service.Tests/Mcp/DescribeEntitiesFilteringTests.cs:305

  • This test name says "ReturnsEmptyList", but the assertion expects an error result (NoEntitiesConfigured). Renaming to match the actual expectation will make the test intent clearer and avoid confusion for future readers.
        /// <summary>
        /// Verifies that a null/empty role (unauthenticated caller)
        /// receives no entities, even if some entities have "anonymous" permissions.
        /// describe_entities requires a valid role to be included in the response.
        /// </summary>
        [TestMethod]
        public async Task DescribeEntities_NoRole_ReturnsEmptyList()
        {
            // Arrange - Config with entities
            RuntimeConfig config = CreateConfigWithMixedEntityTypes();
            IServiceProvider serviceProvider = CreateServiceProvider(config, role: null);
            DescribeEntitiesTool tool = new();

            // Act
            CallToolResult result = await tool.ExecuteAsync(null, serviceProvider, CancellationToken.None);

            // Assert - No role should result in empty entity list
            AssertErrorResult(result, "NoEntitiesConfigured");
        }

Comment thread src/Azure.DataApiBuilder.Mcp/BuiltInTools/DescribeEntitiesTool.cs Outdated
Comment thread src/Service.Tests/Mcp/DescribeEntitiesFilteringTests.cs
Comment thread src/Azure.DataApiBuilder.Mcp/BuiltInTools/DescribeEntitiesTool.cs
Comment thread src/Azure.DataApiBuilder.Mcp/BuiltInTools/DescribeEntitiesTool.cs Outdated
Comment thread src/Azure.DataApiBuilder.Mcp/BuiltInTools/DescribeEntitiesTool.cs Outdated
Comment thread src/Service.Tests/Mcp/DescribeEntitiesFilteringTests.cs Outdated
Comment thread src/Service.Tests/Mcp/DescribeEntitiesFilteringTests.cs
@aaronburtle

Copy link
Copy Markdown
Contributor

Looks good, just one suggestion:

Could we add an automated regression test for this field-level filtering path?

The production implementation now uses GetAllowedExposedColumns(), which addresses the previous disclosure concern, but the current DescribeEntitiesFilteringTests entities all use Fields: null and the resolver mock does not provide an allowed-column set. As a result, no test currently proves that fields.include / fields.exclude actually remove unauthorized field names and descriptions from describe_entities.

Because field metadata disclosure is part of the MSRC issue, could we add a test with real FieldMetadata entries and a restricted allowed-column seta adn ideally it should also cover an alias and the union across two authorized operations.

@aaronburtle aaronburtle 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.

Looks good, just one comment about a regression test.

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.

Approving. Please address the comments and also update the PR title and description

Comment thread src/Service.Tests/Mcp/DescribeEntitiesFilteringTests.cs
@anushakolan Anusha Kolan (anushakolan) changed the title fix: MSRC Incident-31000000666371 - add authorization filtering to de… fix(mcp): gate describe_entities schema visibility by caller role and permissions Aug 6, 2026
@anushakolan
Anusha Kolan (anushakolan) enabled auto-merge (squash) August 6, 2026 21:14
@anushakolan
Anusha Kolan (anushakolan) merged commit 0c7d7c5 into main Aug 6, 2026
14 checks passed
@anushakolan
Anusha Kolan (anushakolan) deleted the fix/msrc-mcp-describe-entities-authz branch August 6, 2026 22:43
@github-project-automation github-project-automation Bot moved this from Todo to Done in Data API builder Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working mcp-server security

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants