AWS Open Source Blog

Introducing TOLAP: object-level access control for AI agent tools

Picture this: an AI agent is answering a question about a patient population. To do it, the agent calls a tool, and the tool queries a clinical database. IAM has already given the green light — the agent can invoke this tool. OAuth scopes checked out. The gateway logged the call.

But nobody in that path decided which rows and which columns this particular user should see through this particular call.

That’s the gap. The tool holds a connection to the data source, and the agent composes the query on its own. If the agent asks for a column the application layer never anticipated, the column comes back. Tool-Object Level Access Protocol (TOLAP) closes that gap. Today it’s available as open source under Apache-2.0 at github.com/awslabs/tolap: a versioned policy schema, enforcement SDKs for .NET, Python and TypeScript, a reference policy server with an authoring console, and fourteen worked integrations with popular agent frameworks.

Why the existing layers don’t close it

The instinct is to reach for an access-control model you already run. The trouble is, each one was designed for a different question.

Role-based access control answers whether a user can reach a resource. It has no vocabulary for which columns of that resource are visible, or which rows should be filtered away.

Attribute-based access control evaluates policy at a centralized engine. That works great when every request routes through the engine. But an agent whose tool holds a direct database connection composes a query that never passes through it.

Database row-level security is genuinely enforcement at the source — and it’s worth using. But it’s confined to databases. Your agent probably also reads from REST APIs, vector stores, and object storage.

Content guardrails are the layer most often assumed to cover this, and that assumption is where the risk concentrates. Guardrails constrain what the model says. They operate on output, which means the unrestricted data was already in the context window by the time they ran. Data in the context window is fair game for summarization, reasoning, follow-up questions, and extraction by prompt injection. Redacting a Social Security number from one response doesn’t remove it from the conversation the model is holding.

Agent frameworks like Amazon Bedrock Agents, Azure AI Agent Service, Google Vertex AI Agents, and LangChain authorize whether the agent may invoke a tool. Fine-grained control over what data the tool returns is typically the responsibility of the tool implementer. TOLAP is a specification and a set of libraries for that responsibility, so every team doesn’t have to solve it from scratch.

Figure 1. Both paths pass the same authorization check and differ at exactly one point: what the tool is permitted to return.

Figure 1. Both paths pass the same authorization check and differ at exactly one point: what the tool is permitted to return.

Three principles

Source-point enforcement. Policy is applied where the data originates, not in a layer above it. The tool wraps the data source and enforces before anything crosses the boundary. There’s no path to the data that skips enforcement.

Object granularity. Policies name individual data objects — columns, rows, fields, tags, endpoints, HTTP methods, similarity thresholds, storage prefixes, result limits. A single policy can say: this user may query the patients table, may not see the SSN column, sees only rows from their assigned regions, and receives the email field as a hash.

Agent transparency. The calling agent needs no security-aware code. Restricted data simply doesn’t appear in what it receives. The defense is architectural rather than behavioral, which is what makes it hold under prompt injection: there’s nothing in context to extract.

What is now open source

The repository contains five things.

A versioned policy schema, in three layers: the policy definition that declares what may be accessed, the assignment that links it to a user or group with scope and expiry, and the merged effective policy that actually gets enforced. One schema spans all four source categories — databases, APIs, knowledge bases, and object storage. Adding a connector means writing a wrapper, not redesigning the authorization model.

A normative specification defining canonical signing, the enforcement pipeline order, and the fail-closed rules. Cross-language behavior is pinned by shared test fixtures rather than by three independent readings of prose, because a behavioral difference between the SDKs would be a security defect rather than a rough edge.

Three SDKs — .NET, Python and TypeScript — each shipping a core, store and enforcement package. The core packages have zero external dependencies in all three languages. That matters because the enforcement engine needs to be embeddable in a Lambda function, an edge worker, or a plugin without dragging a dependency tree into your security path.

A reference policy server and authoring console, for teams who’d rather run a policy service than build one.

Fourteen integrations across the three languages — MCP SDK, Strands, LangChain, LangChain.js, Vercel AI SDK, Mastra, OpenAI Agents, Pydantic AI, Semantic Kernel, and Bedrock Agents — each tested in continuous integration to enforce the same policy identically.

One clarification: TOLAP is not an MCP server and does not speak the MCP protocol. It provides enforcement around the function your tool layer already calls. Your code fetches the data; TOLAP decides what may leave.

What a policy does

Consider a healthcare analyst policy that allows querying the patients, encounters and diagnoses tables while hiding the internal billing and audit tables. It hides the SSN and date of birth outright, returns the email as a hash and the name with only its first character visible, restricts rows to two named regions, and caps any result set.

What the agent receives under that policy is a table with no SSN column in it at all, a name that reads J*********, a hash where the email was, and rows from only the two permitted regions. An attempt to query the billing table gets refused before any query reaches the database. The agent isn’t told that some other version of this data exists, because from its perspective, it doesn’t.

When several policies apply to one user, they merge most-restrictive-wins. Allowed sets intersect, denied sets union, boolean permissions AND together, numeric limits resolve to the stricter value, and where two policies mask the same field differently, the more restrictive mask wins. The practical consequence: granting someone an additional policy can only ever reduce what they can see.

A concrete example helps. If Policy A allows columns [name, email, phone] and Policy B allows [name, email, address], the merged policy allows only [name, email]. If Policy A hides [ssn] and Policy B hides [dob], the merged policy hides both. The math is always the same: more policies means less visible data, never more.

How it is used

The pattern has three steps. You resolve a policy for a user against a data source, which merges every assignment that user holds into one effective policy. You sign it, producing a tamper-evident, time-bound envelope. Then the tool enforces it on every call.

Because the signature covers a canonical form of the whole envelope — including the identifier of the source it was issued for and its expiry — a context can’t be edited, can’t have its lifetime extended without the signing key, and can’t be replayed against a different data source. It also verifies across languages: a context signed by one SDK verifies in the other two.

For SQL sources, the .NET SDK can additionally push row filters into a WHERE clause and the limit into a LIMIT so the database returns less data. But that’s a resource optimization, not the enforcement boundary. The post-execution pass remains necessary because some policy constraints — field masking, hash transformations, and complex filters — can’t be reliably expressed in portable SQL. The post-execution pass is the security boundary; it always runs.

Getting started takes one install command. For Python: pip install tolap-core tolap-store tolap-mcp. For .NET: dotnet add package Tolap.Core. For TypeScript: npm install @tolap/core @tolap/store @tolap/mcp. Full instructions are in the repository’s readme.

A minimal example in Python shows the pattern:

# Resolve policy for this user + data source
policy = store.resolve_effective_policy(user_id, tenant_id, source_id)

# Tool executes query
raw_results = database.query("SELECT * FROM patients")

# Enforce before returning to agent
filtered = apply_row_filters(raw_results, policy)
masked = apply_field_masking(filtered, policy)
return apply_result_limit(masked, policy)
# SSN hidden, rows filtered, fields masked

What’s new: purpose-binding and delegation chains

Object-level enforcement answers which data an agent may see. It doesn’t answer a different question: why is this agent accessing this data right now, and on whose authority?

In practice, the distinction matters. An agent scoped to campaign-overlap analysis has legitimate access to customer segment tables. Under the original TOLAP model, it sees only the columns and rows its policy permits — which is correct. But it could still run queries that serve a different analytical purpose than what the human who launched it actually intended. The agent isn’t lying about what it can see; it’s drifting from what it was asked to do.

Three additions close that gap: purpose-binding, delegation chains, and an optional LLM judge for semantic alignment.

Purpose-binding adds a declared purpose to the security context. Policy assignments can now carry a purposeProfile tag, and the resolution engine includes a purpose-tagged policy only if the security context’s declaredPurpose matches. If the context declares no purpose, purpose-scoped policies are simply excluded and only purpose-agnostic policies resolve.

The obvious objection is a good one: how do you trust that the stated purpose is the real purpose? The answer is that purpose isn’t a trust claim. It’s a constraint selector. An administrator defines a set of purpose profiles, each mapping to a specific, locked-down policy — specific tables, columns, row filters, and result limits. The agent picks from that menu. If it selects “campaign-overlap” when it actually wants billing data, it gets campaign-overlap’s constraints, which don’t include billing tables. The lie is self-defeating because purpose determines what you can do, not what you say you’re doing.

Here’s a concrete example. An administrator defines three policies for a user: campaign-analyst scoped to purpose “campaign-x-overlap” with access to customer_segments and campaign_assignments; fraud-investigator scoped to purpose “fraud-detection” with access to transactions and fraud_flags; and base-readonly with no purpose profile, granting read-only and no-export permissions. When the agent declares purpose “campaign-x-overlap,” the resolution engine includes campaign-analyst plus base-readonly, merges them, and the agent gets exactly two tables. The fraud-investigator policy never resolves. From the agent’s perspective, the transactions table doesn’t exist.

Delegation chains

Delegation chains carry the authority path from human to agent. The security context now accepts a delegationChain: an ordered list of hops, each recording a principal identifier, a principal type (user, agent, or service), an optional declared purpose, and an optional scope-narrowing list. Each hop in the chain may only narrow scope — never widen it. The chain validator enforces this using the same glob-matching already in the SDK for source patterns.

This addresses a specific frustration with IAM-based systems. When an agent calls a tool, the service sees a single IAM principal — typically a service role — and the delegation path that produced it is invisible. A human delegated authority to an orchestrator, which delegated to an agent, which delegated to a sub-agent. The chain captures that path, the signing envelope covers it, and the enforcement engine can evaluate access decisions against the full provenance rather than just the terminal identity.

Action validation

Action validation adds a second enforcement check after resolution. Once the effective policy is resolved and the purpose profile is active, each tool call is validated against the purpose’s allowedActions and prohibitedActions lists. A prohibited action is rejected unconditionally. An action not in the allowed list (when one is defined) is rejected. This is deterministic, fast, and independent of the object-level enforcement that runs afterward.

The three additions compose. Resolution-time purpose filtering determines which policy applies. Action validation confirms the tool call is within scope. Delegation chain validation confirms the authority path is legitimate and narrowing. Object-level enforcement — the original TOLAP behavior — then runs as before: hiding columns, filtering rows, masking fields, capping results. Each layer is independently testable, and each fails closed.

Optional: semantic alignment with an LLM judge

Deterministic rules catch structural violations — wrong table, wrong action category, widening delegation. What they can’t catch is semantic drift within the allowed scope. An agent with legitimate access to customer segment tables can run queries that individually comply with every rule but collectively form a re-identification pattern, or serve an analytical purpose the human never intended.

The SDK now defines a judge interface for this. A purpose profile can optionally enable an LLM judge with a model identifier, a sliding history window, a confidence threshold, and an escalation threshold. When enabled, queries that pass all deterministic enforcement are also evaluated by the judge, which receives the declared purpose description, the current tool call, and the recent tool call history.

The judge returns an alignment assessment with a confidence score. Above the confidence threshold, the assessment is final — allow or block. Below the escalation threshold, the query routes to human review rather than being decided automatically. Ambiguity doesn’t resolve itself.

Two design decisions are deliberate. First, the judge is layer two, not layer one. It evaluates only queries that already passed deterministic checks, so an agent can’t talk its way past column restrictions. Second, the judge prompt is admin-controlled. The agent never sees it, can’t influence it, and has no path to modify it. The judge is off by default and adds zero overhead to tool calls that don’t enable it.

Running it beyond one process

The SDKs ship an in-memory policy store, which is the right choice for development, tests, and a single process. In production you generally want one place where policies live.

The repository includes a working one. The policy server provides a PostgreSQL store, schema validation, immutable policy versions with publish and rollback, an audit trail, Cognito-authenticated administration, and signing-key rotation with an overlap window. Deployment definitions using the AWS CDK are included for CloudFront, WAF, Aurora PostgreSQL Serverless v2, and Fargate.

If you’d rather implement a store against a different backend — DynamoDB for serverless, Redis as a cache, or a REST service shared across teams — the SDKs define a store interface for exactly that, with PostgreSQL implementations in all three languages as a starting point.

Getting started

The packages are published for .NET, Python and TypeScript, and the repository’s readme lists the install command for each. The architecture guide covers how the components fit together, and the examples directory is probably the fastest way in: find the framework closest to yours and read the substitution it makes.

Two documents are worth reading even if you never adopt TOLAP. The threat model is a STRIDE analysis per trust boundary, including defects it found, what was fixed, and what remains open. A companion document records six defects that shipped while the test suite was green, with the smell to grep for in each.

TOLAP’s security boundaries. A few things to understand up front:

  • Enforcement only applies where the TOLAP wrapper is used. Direct database access bypasses it entirely — TOLAP can’t protect a path it doesn’t sit on.
  • Signed contexts are replay-bounded rather than replay-proof. The expiry is inside the signature and can’t be extended, but without the optional replay guard, a valid context is replayable until it expires. Keep the lifetime short.
  • Hash-based masking prevents rainbow table attacks only when you configure a salt, which ensures each installation produces different hashes for the same input. For stronger protection, consider using a keyed hash (HMAC) which requires the signing key to verify hash correctness.
  • The LLM judge is non-deterministic by nature — the same query may receive different alignment scores on successive evaluations. That’s appropriate for an advisory layer and inappropriate for a compliance gate.

See the full known limitations section in the repository for complete details.

Acknowledgments

TOLAP integrates with and is inspired by excellent work from the open source community, including LangChain, Model Context Protocol, Pydantic AI, Semantic Kernel, Vercel AI SDK, Mastra, and OpenAI Agents. We’re grateful to these communities and their maintainers for building the agent frameworks that make this work possible.

Contributing

TOLAP is protocol-agnostic. It enforces around the function your tool layer calls, so it works with MCP servers, Semantic Kernel plugins, LangChain tools, Bedrock Agents, or any other tool-based architecture. The project is Apache-2.0 licensed at github.com/awslabs/tolap. Issues and pull requests are welcome.

Three questions to take back to your own architecture. First, where does data-object policy enforcement live today, and if the answer is the gateway or the prompt, what stops a query the application never anticipated? Second, would your enforcement hold under prompt injection if the tool received unfiltered data? Third, when an autonomous agent accesses data on behalf of a user, does anyone record why and on whose authority — and could the agent drift from that purpose without detection?

Phillip Spies

Phillip Spies

Phillip Spies is a senior solutions architect on the Amazon Web Services (AWS) Federal Civilian team with 20 years of development and platform architecture experience. He builds production-grade generative AI prototypes with government agencies, accelerating the path from "art of the possible" to deployed capability.