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.
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.
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.
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.
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.
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.
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.
| Surface | Negative test | Passing condition |
|---|---|---|
| Rows | Substitute another tenant’s valid order | No data or mutation |
| Cache | Warm with tenant B, request as A | No shared result |
| Objects | Reuse another tenant’s object reference | No download authority |
| Jobs | Poll or cancel another tenant’s valid job | No result or state change |
| Context | Omit or forge tenant metadata | Rejected before data access |
| Entry points | Invoke the backend through an alternate route | Denied 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.