Skip to content

Security Wave 2: MCP hardening (per-tool RBAC, dry-run, response shaping, description hygiene) #24

@jrosskopf

Description

@jrosskopf

Part of #21.

Goal: close the MCP-specific gaps that an external "runtime security gateway" would inspect. Build the inspection points in-product — they need access to the tool registry and result shape, which a sidecar cannot see without re-parsing config.

Tasks

  • W2.1 — Per-tool RBAC. Add to endpoint YAML:

    mcp-tool:
      allowed-roles: [analyst, read-only]   # optional; if absent and auth.required, inherit endpoint-level roles

    Enforce in MCPToolHandler::executeTool before SQL runs (src/mcp_tool_handler.cpp:15-89). Default for endpoints with auth.required: true but no explicit allowed-roles: deny-by-default with a clear error referencing the config key. Endpoints without auth keep working unchanged (ease-of-use principle).

  • W2.2 — Shadow / dry-run mode. Accept _dryRun: true in tool-call arguments (and ?_dryRun=1 for REST). When set: run validators + template expansion + DuckDB EXPLAIN, return rendered SQL + execution plan + estimated rows, but skip materialization. This is literally the "shadow mode + query inspection" pattern external gateways pitch.

  • W2.3 — Tool-description hygiene. At endpoint-config-load time (src/endpoint_config_parser.cpp:256), reject or warn on descriptions containing: control characters, JSON-breakout patterns, prompt-injection phrases ("ignore previous instructions"). Strict mode opt-in via mcp.strict-descriptions: true.

  • W2.4 — Response shaping per tool.

    mcp-tool:
      response:
        max-rows: 1000              # cap result size
        redact-columns: [ssn, salary]
        sample: false               # if true, return summary stats instead of rows

    Enforce in MCPToolHandler::formatResult (src/mcp_tool_handler.cpp:70-85).

  • W2.5 — Per-tool rate limits. Extend rate-limit config to support per-tool keys:

    mcp:
      tool-rate-limits:
        sensitive-tool: { rpm: 10 }

Critical files

src/mcp_tool_handler.cpp, src/mcp_route_handlers.cpp, src/endpoint_config_parser.cpp, src/include/endpoint_config.hpp, new src/mcp_response_shaper.{cpp,hpp}.

Verify

New test/integration/test_mcp_rbac.py, test_mcp_dryrun.py, test_mcp_response_shaping.py. End-to-end: configure two endpoints with different allowed-roles, issue MCP calls with two JWTs, assert exactly one succeeds; issue dry-run and assert no rows in result + SQL + plan present.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions