Skip to content
Go back

The Tenant Boundary: Why Cedar and Rate Limits Are Not Data Isolation

Published: at 11:00 AM (10 min read)

A tenant A operator asks an agent for order B-482. The tool exists, the operator has the right role, and the request passes Gateway policy. If the backend looks up the order by its global identifier, the platform can still disclose tenant B’s data.

Tenant isolation is an identity-to-data invariant that every execution path must preserve. Cedar can restrict tool access, and rate limits can constrain consumption. Neither substitutes for binding authenticated tenant authority to backend reads and writes.

This second article in the series follows the order operations platform from caller identity to database access. The architectural goal is precise: a caller cannot select a different tenant by changing tool arguments, headers, cache keys, job identifiers, or entry points.

Table of contents

Open Table of contents

Separate the identities before propagating them

Gateway supports JWT-based and IAM-based inbound authorization. Those identify the caller using different mechanisms. AWS also documents offloaded authorization modes, so the presence of a Gateway endpoint alone does not establish which authorization checks are active. Source: Set up inbound authorization for your gateway.

Model four identities separately: the human operator, the tenant in which they are acting, the workload making the call, and the service credential used downstream. A shared workload role identifies the application; it does not automatically identify every customer using it.

Likewise, a user subject is not a tenant identifier. One user may belong to several tenants with different roles. Require a trusted membership decision when selecting an active tenant, and bind the selected context to the request. Do not infer customer authority from an email suffix, display name, or model-generated company label.

For delegated tasks, record who initiated the work and what authority the worker received. An overnight reconciliation job should not inherit an unrestricted platform credential merely because the initiating user once passed an interactive authentication check. Decide how membership changes, expiry, and cancellation affect already-queued work.

Verify before deployment

Inspect real request context for the selected authentication mode and target. Establish which claims are available, how issuer and audience are validated, and how IAM identities map to tenant membership. Do not assume an IAM role carries user-level claims.

Bind tenant context at a trusted boundary

Treat a model-provided tenant_id as a requested selector, never as proof of membership. Prefer excluding it from tools that operate within one authenticated tenant. Where cross-tenant support workflows need a selector, authorize that selection explicitly and audit the elevated operation.

A safe boundary derives an internal tenant identifier from validated identity and membership records. It rejects unknown membership, conflicting selectors, and missing context. It also defines freshness: a signed token can be cryptographically valid while representing membership that the business has revoked.

Choose that revocation window deliberately. Short-lived context can limit exposure, while sensitive writes can require a fresh membership check. Avoid promising immediate revocation unless every relevant cache, job, and backend actually implements it.

A caller credential is validated, mapped to tenant membership, bound to internal context, and consumed by a tenant-scoped backend operation; model arguments remain separate.

Figure 1. Recommended identity flow: tenant authority originates in validated context, not tool arguments.

Define the trusted context as a small contract: tenant, subject, workload, permitted operation scope, issued-at time, expiry, and correlation identifier. These are proposed application fields, not a claim that Gateway emits a universal tenant envelope.

Do not let the agent create this envelope. The trusted component must overwrite or reject conflicting client-supplied context. A missing field should produce a controlled authorization failure rather than fall back to a default tenant or global query.

Carry authority without inventing provenance

Gateway can propagate selected headers and query parameters. AWS assigns customers responsibility for choosing what is allowlisted, and documents interceptor-based context injection from authenticated claims. Source: Header propagation with Gateway.

Header transport and header trust are different properties. Forwarding x-tenant-id accurately does not establish who chose its value. If the original caller can set it, the backend must not treat it as authenticated tenancy simply because it arrived through Gateway.

For a supported HTTP integration, one possible design is a trusted adapter that derives context and authenticates its downstream request. Another is a short-lived, audience-bound application token that the backend validates. Select a pattern only after verifying the target’s actual authentication and propagation capabilities.

For MCP targets, a Gateway REQUEST interceptor is a natural place to bind context. It receives the JSON-RPC request (and the caller’s headers when passRequestHeaders is enabled), and returns either a transformed request or an immediate response. A sketch, where verify_bearer_token and resolve_active_tenant are your own functions:

AUTHORITY_FIELDS = {"tenant_id", "tenant", "user_id", "role", "_trusted_context"}


def lambda_handler(event, context):
    request = event["mcp"]["gatewayRequest"]
    body = request["body"]
    if body.get("method") != "tools/call":
        return {"interceptorOutputVersion": "1.0",
                "mcp": {"transformedGatewayRequest": {"body": body}}}

    claims = verify_bearer_token(request["headers"])  # signature, issuer, audience, expiry
    membership = resolve_active_tenant(claims)        # membership store, not the model
    if membership is None:
        return {"interceptorOutputVersion": "1.0", "mcp": {"transformedGatewayResponse": {
            "statusCode": 200,
            "body": {"jsonrpc": "2.0", "id": body.get("id"),
                     "error": {"code": -32001, "message": "no active tenant membership"}}}}}

    args = {k: v for k, v in body["params"].get("arguments", {}).items()
            if k not in AUTHORITY_FIELDS}                # model-supplied authority is dropped
    args["_trusted_context"] = {"tenant": membership.tenant_id, "subject": claims["sub"],
                                "role": membership.role, "expires_at": claims["exp"]}
    body["params"]["arguments"] = args
    return {"interceptorOutputVersion": "1.0",
            "mcp": {"transformedGatewayRequest": {"body": body}}}

The backend then reads tenant and subject only from _trusted_context, and accepts calls only from the Gateway’s execution role, so nothing else can supply that field. Treat the interceptor as one verified link, not the whole chain: still test what each target type actually receives. Source: Types of interceptors.

Bind any application token to the intended service and scope. Give it a short expiry, protect its signing keys, and avoid logging bearer credentials. If requests can be replayed, design replay controls appropriate to the effect; a tenant claim alone does not make a write unique.

Lambda and Smithy targets use the attached execution role for outbound calls; other target families have their own credential configuration. Source: Core concepts for Amazon Bedrock AgentCore Gateway.

Verify before deployment

Demonstrate which identity and context the backend receives for each target. Do not assume the inbound JWT becomes the outbound credential, or that a shared service role establishes end-user authority. Fail the integration review if its tenant provenance cannot be explained and tested.

Make the data operation enforce ownership

The order service must combine the trusted tenant with the requested order identifier in its data access. A later application comparison can be useful defense, but it should not be the only protection after an unrestricted row has already entered memory, logs, or caches.

The core predicate is simple:

SELECT order_id, status, refundable_minor_units
FROM orders
WHERE tenant_id = :trusted_tenant
  AND order_id = :requested_order;

Both values are bound parameters, but they have different provenance. The order identifier is untrusted input. The tenant value comes from authenticated application context. Parameterization prevents query injection; it does not establish tenant authority.

Apply the same ownership condition to updates and deletes. Where supported, database row-level security can add another enforcement layer, provided connection pooling cannot retain the previous request’s tenant context. Test the actual database roles, including maintenance and background workers.

For writes, tenant ownership is only the first predicate. The order might already be refunded, its remaining refundable balance might have changed, or a concurrent worker might hold the same operation. Validate those conditions atomically with the state transition.

A signed object URL or cache hit can bypass the database entirely. Partition cache keys by trusted tenant and relevant authorization scope. Authorize object references before issuing download access. Bind asynchronous job records, polling, callbacks, and stored results to the originating tenant.

Close the path around Gateway

Gateway private egress depends on the target type; supported private endpoints can use VPC Lattice. These connectivity mechanisms describe routes to services, not tenant-specific database permissions. Source: Configure Amazon Bedrock AgentCore Gateway VPC Egress for Gateway Targets.

Inventory every way the same backend effect can occur: direct Lambda invocation, an API endpoint, a console action, an internal worker, a maintenance role, and a public or private function URL. A private address can still be reachable by an overprivileged workload inside the network.

An approved identity-bound path reaches the backend, while a separate direct caller path is rejected before it can bypass tenant checks.

Figure 2. Recommended bypass prevention: every admitted path preserves identity binding and backend authorization.

Restrict invocation to approved principals using the controls supported by the target. Remove unused public exposure and broad invocation grants. Where other applications legitimately share the backend, require those paths to establish equivalent trusted context rather than asserting that all traffic must use Gateway.

Do not add a universal shared secret and call the bypass closed. Compromise of any holder would recreate platform-wide authority. Scope credentials and backend permissions to their required operations, and retain tenant checks even for admitted service identities.

Verify before deployment

Attempt access from an unauthorized workload inside the same VPC and from an identity with broad legacy permissions. Verify resource policies, identity policies, endpoint configuration, security groups, and actual invocation results. A network diagram is insufficient evidence.

Test isolation across every data surface

Gateway rate limits can fail open when the rate-limit service is unavailable or a grouping dimension cannot be resolved. Source: Add rate limits to a gateway.

Therefore, cross-tenant denial must remain true without a successful rate-limit decision. A per-tenant bucket is consumption accounting, not evidence that the request owns the selected data.

Tenant A and tenant B test identities are checked against rows, caches, objects, and jobs, with denied cross-tenant access required on every surface.

Figure 3. Recommended test coverage: isolation must survive each alternate data access mechanism.

Use at least two tenants with realistic, distinct fixtures. Exercise valid identifiers from the other tenant rather than only malformed values. Otherwise, a not-found response may prove input validation while leaving ownership enforcement untested.

SurfaceNegative testPassing condition
RowsSubstitute another tenant’s valid orderNo data or mutation
CacheWarm with tenant B, request as ANo shared result
ObjectsReuse another tenant’s object referenceNo download authority
JobsPoll or cancel another tenant’s valid jobNo result or state change
ContextOmit or forge tenant metadataRejected before data access
Entry pointsInvoke the backend through an alternate routeDenied or equivalently authorized

Test membership revocation and tenant switching on persistent clients and pooled connections. Include delayed jobs created before revocation. Inspect error bodies and telemetry for foreign identifiers or payloads; preventing a successful response does not excuse a tenant leak into an operator-visible log.

What this means in practice

Isolation is credible when a reviewer can trace authenticated authority into every data operation and reproduce its negative tests. Gateway access policy remains valuable, but backend ownership predicates and bypass prevention carry the tenant guarantee.

Choose one order lookup today. Trace its tenant value from identity validation to the query, cache, object store, and job paths it touches. Replace every ambiguous provenance step with an explicit contract, then try a valid identifier from another tenant.