Beyond Tool Policies: Solving Fine-Grained Authorization in MCP Gateways

Share

A tool call is ultimately just a function name paired with a JSON payload. Deep within that JSON payload sits the true identity of the target resource. Your gateway's job is to extract that target, resolve it against a real-world asset, and decide whether to block the execution before the payload hits your upstream infrastructure.

Relying solely on tool call policies and OAuth scopes leaves you with coarse-grained access control for AI agents. Here is a breakdown of why this gap exists and how we solve it at Andromeda Security.

The Scenario: The Multi-Tenant Support Agent

Imagine an AI customer support agent integrated into a B2B SaaS platform. To answer customer tickets efficiently, the agent has access to a backend database tool: query_customer_data.

The Setup

  • OAuth Scopes: The agent authenticates to the data platform using an OAuth token granted the read:data_warehouse scope.
  • Tool Call Policy: The security policy engine has a broad rule: “The Support Agent role is authorized to invoke query_customer_data for read operations.”

On paper, the agent is fully authenticated, authorized, and compliant with your coarse-grained policy.

The Trigger

A Tier-1 support representative receives a ticket asking: "Can you confirm if our account migration from tenant ACME_CORP to ACME_RENEWAL went through yesterday?"

The representative prompts the agent: "Check the database and show me the details for ACME_CORP and ACME_RENEWAL."

The LLM formats its request and executes the following tool call:

JSON
{
  "name": "query_customer_data",
  "arguments": {
    "sql_statement": "SELECT * FROM public.tenants WHERE tenant_id IN ('ACME_CORP', 'ACME_RENEWAL') JOIN sensitive_financials ON public.tenants.id = sensitive_financials.tenant_id"
  }
}


Where the Model Breaks

  • The OAuth Scope checks out: The token carries read:data_warehouse. Pass.
  • The Tool Call Policy checks out: The function is query_customer_data, which matches the allowed tool list. Pass.
  • The Hidden Payload: Buried deep inside the argument's sql_statement is a JOIN with sensitive_financials—a table containing revenue figures, credit card tokens, and billing keys. Neither the support rep nor the agent acting on their behalf should ever view this table.

Because traditional security mechanisms only evaluate who is calling (OAuth scope) and what function is invoked (Tool Call Policy), this query sails straight through to the upstream database. The fine-grained target resource (sensitive_financials) remains invisible to coarse policy layers.

Why Is Target Extraction So Hard?

Extracting target resources at the gateway level introduces several engineering hurdles:

  1. Non-Standardized Parameter Names: Argument names vary even within the same vendor ecosystem. A warehouse table might appear as a fully qualified URN, an object ID, or split across three separate fields (database, schema, table). Miss one alias, and your policy either fails open (unsafe) or blocks legitimate traffic (broken).
  2. Unstructured & Free-Text Inputs: A massive subset of agent tools accept free text. Query tools take raw SQL, search tools accept natural language, and file tools take glob patterns. To understand what these tools touch, you would need to run and maintain shadow parsers capable of handling dialect shifts and adversarial prompts in real time.
  3. Multi-Resource References: A single call can touch dozens of resources via JOIN clauses, batch operations, or recursive wildcards. Target extraction requires resolving a set of resources, some of which are only known dynamically.
  4. Continuous Schema Drift: Tool servers version independently of your gateway. An argument gets renamed in a minor release, causing your extraction logic to silently degrade over time without throwing a compile error.

Move 1: Force the Call to Declare Its Target

Tool schemas are dynamic. Because your security layer brokers tool calls, it controls the schema presented to the LLM. You can leverage this by injecting a dedicated, reserved parameter into the schema whose explicit job is to capture a canonical identifier for the target resource.

Because the field is built into the tool definition, the LLM populates it automatically:

JSON
{
  "name": "query_warehouse",
  "arguments": {
    "sql_payload": "SELECT * FROM finance_prod.payroll JOIN hr.users...",
    "_target_resource": "urn:data:snowflake:acc123:finance_prod:payroll"
  }
}

This transforms target extraction from an $N \times M$ parsing problem into an $O(1)$ lookup.

⚠️ Critical Caveats

  • A declaration is a claim, not a proof: An LLM-declared identifier cannot be treated as absolute truth. It acts as a lookup key against a trusted system of record. If a declared target fails to resolve in your registry, the request is denied immediately.
  • Declaration is a floor, not a ceiling: Native schema parsing still provides valuable defense-in-depth. When the declared target and the parsed argument align, confidence is high. When they disagree, you have likely detected prompt drift or an adversarial injection attempt.

Move 2: Contextualize Targets via an Access Graph

Knowing that a call targets finance_prod.payroll is useless without security context. You cannot evaluate authorization policy against an opaque string.

To make accurate decisions, the gateway requires metadata sourced directly from the downstream provider:

  • Classification & Metadata: Is the resource tagged as PII, PHI, or SOX-in-scope?
  • Resource Hierarchy: Where does it sit in the object tree? A policy defined at the database level must automatically govern all nested schemas and tables.
  • Standing Entitlements: Which service accounts, roles, and security groups hold active or inherited grants?
  • Caller Context: What identity is the agent presenting upstream, and what is its exact permission footprint?

This requires an Access Graph—a synchronized model of inventory and entitlement data continuously ingested from downstream providers like Snowflake, AWS, GitHub, and Salesforce.

Because gateway decisions operate under strict single-digit millisecond latency budgets, this graph must be precomputed. Querying provider APIs synchronously on every tool call introduces unacceptable latency; target metadata must be indexed before the request arrives.

The Core Principle: Least-Privilege Intersection

With a real-time Access Graph, your gateway can enforce Least-Privilege Intersection:

$$\text{Effective Access} = \text{Policy-Allowed Sets} \cap \text{Provider Entitlements}$$

  • If a policy permits an agent to read a table, but the underlying service account lacks database-level access, the gateway denies the call.
  • If the underlying credential has access to 500 tables, but the agent's policy explicitly limits it to 2, the effective access is strictly 2 tables.

Without an Access Graph, a tool gateway can only layer static rules on top of existing credentials—it cannot validate what those credentials actually permit. A gateway that adds permissions without understanding provider context isn't a security control; it’s just a proxy with opinions.

Real-World Considerations

Building this in production requires addressing key operational trade-offs:

  • Provider Heterogeneity: Authorization models vary widely across platforms. AWS IAM bears little resemblance to Snowflake RBAC or Salesforce sharing rules. Integrations must be modeled natively per provider.
  • Schema Drift & False Positives: Unmatched or malformed target declarations result in blocked requests. Tighter pipeline resolution—rather than policy exceptions—is required to prevent security bypasses.
  • The Free-Text Frontier: Arbitrary SQL execution and open-ended shell commands will always test the limits of pre-call inspection. For these patterns, secondary defenses (such as tightly scoping the underlying credential using the Access Graph) ensure executing roles lack destructive reach.

Summary

Moving the trust boundary from the broad application layer down to individual tool calls is essential for safe agent deployment.

However, defining policies is only half the solution. Real protection requires robust target resolution: accurately identifying what a tool call touches without relying on fragile, per-tool parsers, and validating those targets against a precomputed Access Graph.

How is your team currently approaching fine-grained authorization for agentic tool calls? Are you relying on schema sidecars, custom gateways, or scoping down upstream credentials?

A tool call is ultimately just a function name paired with a JSON payload. Deep within that JSON payload sits the true identity of the target resource. Your gateway's job is to extract that target, resolve it against a real-world asset, and decide whether to block the execution before the payload hits your upstream infrastructure.

Relying solely on tool call policies and OAuth scopes leaves you with coarse-grained access control for AI agents. Here is a breakdown of why this gap exists and how we solve it at Andromeda Security.

The Scenario: The Multi-Tenant Support Agent

Imagine an AI customer support agent integrated into a B2B SaaS platform. To answer customer tickets efficiently, the agent has access to a backend database tool: query_customer_data.

The Setup

  • OAuth Scopes: The agent authenticates to the data platform using an OAuth token granted the read:data_warehouse scope.
  • Tool Call Policy: The security policy engine has a broad rule: “The Support Agent role is authorized to invoke query_customer_data for read operations.”

On paper, the agent is fully authenticated, authorized, and compliant with your coarse-grained policy.

The Trigger

A Tier-1 support representative receives a ticket asking: "Can you confirm if our account migration from tenant ACME_CORP to ACME_RENEWAL went through yesterday?"

The representative prompts the agent: "Check the database and show me the details for ACME_CORP and ACME_RENEWAL."

The LLM formats its request and executes the following tool call:

JSON
{
  "name": "query_customer_data",
  "arguments": {
    "sql_statement": "SELECT * FROM public.tenants WHERE tenant_id IN ('ACME_CORP', 'ACME_RENEWAL') JOIN sensitive_financials ON public.tenants.id = sensitive_financials.tenant_id"
  }
}


Where the Model Breaks

  • The OAuth Scope checks out: The token carries read:data_warehouse. Pass.
  • The Tool Call Policy checks out: The function is query_customer_data, which matches the allowed tool list. Pass.
  • The Hidden Payload: Buried deep inside the argument's sql_statement is a JOIN with sensitive_financials—a table containing revenue figures, credit card tokens, and billing keys. Neither the support rep nor the agent acting on their behalf should ever view this table.

Because traditional security mechanisms only evaluate who is calling (OAuth scope) and what function is invoked (Tool Call Policy), this query sails straight through to the upstream database. The fine-grained target resource (sensitive_financials) remains invisible to coarse policy layers.

Why Is Target Extraction So Hard?

Extracting target resources at the gateway level introduces several engineering hurdles:

  1. Non-Standardized Parameter Names: Argument names vary even within the same vendor ecosystem. A warehouse table might appear as a fully qualified URN, an object ID, or split across three separate fields (database, schema, table). Miss one alias, and your policy either fails open (unsafe) or blocks legitimate traffic (broken).
  2. Unstructured & Free-Text Inputs: A massive subset of agent tools accept free text. Query tools take raw SQL, search tools accept natural language, and file tools take glob patterns. To understand what these tools touch, you would need to run and maintain shadow parsers capable of handling dialect shifts and adversarial prompts in real time.
  3. Multi-Resource References: A single call can touch dozens of resources via JOIN clauses, batch operations, or recursive wildcards. Target extraction requires resolving a set of resources, some of which are only known dynamically.
  4. Continuous Schema Drift: Tool servers version independently of your gateway. An argument gets renamed in a minor release, causing your extraction logic to silently degrade over time without throwing a compile error.

Move 1: Force the Call to Declare Its Target

Tool schemas are dynamic. Because your security layer brokers tool calls, it controls the schema presented to the LLM. You can leverage this by injecting a dedicated, reserved parameter into the schema whose explicit job is to capture a canonical identifier for the target resource.

Because the field is built into the tool definition, the LLM populates it automatically:

JSON
{
  "name": "query_warehouse",
  "arguments": {
    "sql_payload": "SELECT * FROM finance_prod.payroll JOIN hr.users...",
    "_target_resource": "urn:data:snowflake:acc123:finance_prod:payroll"
  }
}

This transforms target extraction from an $N \times M$ parsing problem into an $O(1)$ lookup.

⚠️ Critical Caveats

  • A declaration is a claim, not a proof: An LLM-declared identifier cannot be treated as absolute truth. It acts as a lookup key against a trusted system of record. If a declared target fails to resolve in your registry, the request is denied immediately.
  • Declaration is a floor, not a ceiling: Native schema parsing still provides valuable defense-in-depth. When the declared target and the parsed argument align, confidence is high. When they disagree, you have likely detected prompt drift or an adversarial injection attempt.

Move 2: Contextualize Targets via an Access Graph

Knowing that a call targets finance_prod.payroll is useless without security context. You cannot evaluate authorization policy against an opaque string.

To make accurate decisions, the gateway requires metadata sourced directly from the downstream provider:

  • Classification & Metadata: Is the resource tagged as PII, PHI, or SOX-in-scope?
  • Resource Hierarchy: Where does it sit in the object tree? A policy defined at the database level must automatically govern all nested schemas and tables.
  • Standing Entitlements: Which service accounts, roles, and security groups hold active or inherited grants?
  • Caller Context: What identity is the agent presenting upstream, and what is its exact permission footprint?

This requires an Access Graph—a synchronized model of inventory and entitlement data continuously ingested from downstream providers like Snowflake, AWS, GitHub, and Salesforce.

Because gateway decisions operate under strict single-digit millisecond latency budgets, this graph must be precomputed. Querying provider APIs synchronously on every tool call introduces unacceptable latency; target metadata must be indexed before the request arrives.

The Core Principle: Least-Privilege Intersection

With a real-time Access Graph, your gateway can enforce Least-Privilege Intersection:

$$\text{Effective Access} = \text{Policy-Allowed Sets} \cap \text{Provider Entitlements}$$

  • If a policy permits an agent to read a table, but the underlying service account lacks database-level access, the gateway denies the call.
  • If the underlying credential has access to 500 tables, but the agent's policy explicitly limits it to 2, the effective access is strictly 2 tables.

Without an Access Graph, a tool gateway can only layer static rules on top of existing credentials—it cannot validate what those credentials actually permit. A gateway that adds permissions without understanding provider context isn't a security control; it’s just a proxy with opinions.

Real-World Considerations

Building this in production requires addressing key operational trade-offs:

  • Provider Heterogeneity: Authorization models vary widely across platforms. AWS IAM bears little resemblance to Snowflake RBAC or Salesforce sharing rules. Integrations must be modeled natively per provider.
  • Schema Drift & False Positives: Unmatched or malformed target declarations result in blocked requests. Tighter pipeline resolution—rather than policy exceptions—is required to prevent security bypasses.
  • The Free-Text Frontier: Arbitrary SQL execution and open-ended shell commands will always test the limits of pre-call inspection. For these patterns, secondary defenses (such as tightly scoping the underlying credential using the Access Graph) ensure executing roles lack destructive reach.

Summary

Moving the trust boundary from the broad application layer down to individual tool calls is essential for safe agent deployment.

However, defining policies is only half the solution. Real protection requires robust target resolution: accurately identifying what a tool call touches without relying on fragile, per-tool parsers, and validating those targets against a precomputed Access Graph.

How is your team currently approaching fine-grained authorization for agentic tool calls? Are you relying on schema sidecars, custom gateways, or scoping down upstream credentials?