Courses / Building AI Agents

Session 8 of 11

Isolation and Account Boundaries

Overview

Chapter 8 — Isolation and Account Boundaries

Course: Building AI Agents

Prerequisites: You can read and run a short Python script. From the previous chapter you need the idea that an agent’s approved actions end in real effects, such as refunds, which the chapter’s executed.tsv records. Blast radius, account, guardrail, effective permission, tenant, tenant isolation and sandbox are defined in the notes before they are used.

What the reader can do after this chapter:

  • Count what a compromised agent could reach under a single-account layout and under a layout with one account per environment or data domain.
  • Work out which requests a guardrail policy and an identity policy together allow, including the case that the guardrail does not cover.
  • Show, with a list of requests, that authenticating and authorising a user does not by itself keep one tenant out of another tenant’s records.

Core question this chapter answers: How do you place an agent that can take real actions so that a mistake or a compromise reaches as little as possible, and what does each boundary you draw actually stop?

Connections:

  • Builds on: the previous chapter’s executed effects. It opens with those effects and asks where they ran and what else that place could reach.
  • Leads into: Taking AI to Production, which asks how you prove afterwards what a system was asked and what it did. It opens with the requests your isolation rules refused, recorded in denied.tsv.

Your answers are scored

This chapter is assessed. The quiz is marked against a key held separately from your materials. Questions 1–8 are multiple choice and are marked by comparing your letter with the key. Questions 9–10 ask for a short answer in an exact format, and each one is marked by a rule printed with the question.

  • Nothing is judged by reading it for style. An answer either matches the rule or it does not.
  • All three project tasks are scored from an evidence file of key: value lines and one tab-separated file. Every accepted value follows from what the task prints.

Notes

Building AI Agents — Chapter 8: Isolation and Account Boundaries

Before you start

Nothing to install for the reading. For the project you need Python 3 and nothing beyond its standard library. You do not need an account with any cloud or model provider or an API key: every resource, policy and request the project uses is printed in the project file.

Keep one question in mind while you read: if this agent were misled tomorrow, what is the most it could reach?

Summary

An agent that can act on systems will sometimes be misled, by text it reads or by a mistake in its own tools, so the design question is how much a mistake can reach. A boundary around a group of resources limits that reach: separate accounts for separate environments and data domains, organisation-wide guardrail policies that cap what any identity inside them can do, checks that keep one customer’s records away from another customer’s users, and a sandbox that limits what generated code and tool calls can touch. Each boundary stops one kind of thing and leaves others alone, which is why a design names what each one covers. A defensible answer counts the reach before and after a boundary is drawn and says what the boundary does not stop.

Key concepts

Term Definition
Blast radius The set of resources a failure or a compromised identity could affect. A design that limits it keeps a problem inside one part of the system.
Account In one cloud provider’s terms, a container for resources that has its own access and billing boundary. Other platforms use different names for a similar unit, which this chapter does not cover.
Guardrail policy A policy that sets the most an identity may do, without granting anything. One provider’s service control policy is an example.
Effective permission What an identity can actually do once every layer is applied. With a guardrail and an identity policy, it is the actions that both allow.
Management account In one provider’s organisation service, the account that administers the organisation. Its guardrail policies do not apply to its own users and roles.
Tenant One customer, or one group of users, whose data a shared system must keep apart from the data of others.
Tenant isolation Controls that use the tenant of a request to limit which resources it can reach, applied in addition to authentication and authorisation.
Pooled resources Resources shared by several tenants, such as rows of different tenants in one table. Isolation here means checking the tenant on each access.
Sandbox A restricted environment in which code or a tool runs with minimal default privileges and limited access to files and network.
Egress Outbound network traffic from a workload. An egress rule limits where a workload may connect.

Explanations

The failure, before the explanation

A team runs a refund agent in one cloud account. The same account holds the support database, a bucket of exported customer records, the keys for the payment service, a reporting job and a developer’s test database, because that is where the project started. The agent reads a ticket that carries a hidden instruction, of the kind the prompt-injection chapter described, and it calls tools with the role it was given. The role was written broadly so that nobody had to revisit it, and it reaches everything in the account.

The refund tool is fine, the approval gate worked and the audit log shows nothing unusual about the model. What failed was placement: everything the agent’s role could reach sat in one container, so one misled call had everything in the account to land on. The rest of this chapter is about drawing boundaries so that the same mistake has fewer.

Accounts as a boundary: blast radius

Two examples first. A kitchen with one room, where the raw-chicken board, the salad board and the till are on the same counter, spreads any contamination to all three. A building with fire doors lets a fire in one room burn that room. A second way to see it is a ship with watertight compartments: a hole floods one compartment, not the hull.

One provider’s guidance on organising environments says that the “inherent isolation provided by an AWS account can be leveraged to limit the scope of impact from issues within your cloud environment”, and that an application-level problem, misconfiguration or malicious activity within one account can be contained and prevented “from impacting workloads in other accounts” (AWS, Organizing Your AWS Environment Using Multiple Accounts, “Benefits of using multiple AWS accounts”). The same page names more uses of accounts: separate non-production and production environments “by default” keep the resources and data of a workload environment separate from other environments and workloads; dedicated accounts for sensitive data let you restrict permissions at the account level; and quotas and request rate limits are allocated per account, so separate accounts distribute their impact. These are claims about one provider, so a reader on another platform checks that platform’s equivalent.

For an agent the practical question is: what can this identity reach? Count the resources in the accounts its role can act in. Put the refund tool, its data and nothing else in one account, give the role access to that account and to any account it has to call on purpose, and the count falls from everything to a few.

  • Applied use. List the agent’s resources and data domains, give each environment and each sensitive data domain its own account, and grant the agent’s role access only to the accounts its task needs.
  • Signal. Wording such as “limit the damage”, “contain”, “keep the test agent away from production data” and “regulated data in a dedicated place” points toward separate accounts. “Everything in one account for simplicity” is the layout the question is probing.
  • Practical consequence. More accounts mean more places to configure and cross-account access to design on purpose. Reach is counted by account, so a cross-account trust you add widens it again, and each trust is a decision to record.
  • Versus the distractor. The tempting wrong answer is to rely on a narrow permission list inside one broad account. A permission list that is later widened, or a role that is mistyped, leaves no second boundary. The account boundary is a second layer that does not depend on the first being right.

Guardrails that cap but do not grant

A guardrail is easiest to see as a fence around a field and a ticket for a ride. The fence says where you may not go. It does not hand out tickets. A second way is a speed limiter on a van: it caps the speed whatever the driver wants, and it does not make the van move.

One provider’s organisation service has such a policy, the service control policy. Its documentation says SCPs “offer central control over the maximum available permissions for the IAM users and IAM roles in your organization”, that “no permissions are granted by an SCP”, and that to grant permissions an administrator attaches identity-based or resource-based policies. The effective permissions are “the logical intersection” between what the SCPs allow and what those other policies allow. If an action is blocked at any level above the account, a user or role in the account “can’t use that permission, even if the account administrator attaches the AdministratorAccess IAM policy”. The same page lists limits: SCPs “affect only member accounts” and have no effect on users or roles in the management account, and they do not affect service-linked roles (AWS, Service control policies (SCPs)).

Put numbers on it. Suppose the guardrail for an account allows three actions: tickets:read, tickets:write and refunds:create. A role’s identity policy grants tickets:read, refunds:create and users:delete. The role can use tickets:read and refunds:create, the two in both. It cannot use users:delete, which the guardrail does not allow, and it cannot use tickets:write, which the guardrail allows and the identity policy does not grant.

  • Applied use. Use a guardrail for rules that must hold whatever a team writes in its own policies, such as “no deleting users in the refund accounts”, and write the narrow grants separately.
  • Signal. “The administrator has full access but the call is denied” points toward a guardrail above the account. “The guardrail allows it, so the agent can do it” is the misreading, since an allow in a guardrail grants nothing.
  • Practical consequence. To find why a call is denied, check both layers. A guardrail applied at the top of an organisation without testing can lock people out, and the provider’s page says it strongly recommends not attaching SCPs to the root of the organisation without thoroughly testing their effect on accounts.
  • Versus the distractor. The tempting wrong answer is to treat the guardrail as the agent’s permission list. It is a ceiling. The agent still needs its own grant, and an action that the ceiling allows but nothing grants stays unavailable. A second wrong answer assumes the ceiling reaches the management account. The page says it does not, so the management account is not where an agent should run.

Tenants: authenticated is not isolated

A hotel gives two examples. A guest with a valid key card is authenticated, and the card may be set to open only that guest’s room. A card that opens every room on the floor identifies the guest and lets the guest into rooms that are not theirs. A second way is a library with a membership card: showing the card proves who you are, and a rule about which shelves are yours is a separate rule.

An AWS SaaS architecture paper makes the point directly: “the fact that a tenant user is authenticated does not mean that your system has achieved isolation”, and “a user could be authenticated and authorized, and still access the resources of another tenant”, because nothing about authentication and authorisation “will necessarily block this access”. It describes tenant isolation as using tenant context to limit access to resources. In some designs whole stacks are dedicated to one tenant, with network-level policies preventing cross-tenant access, and in others resources are pooled, for example items in one database table, and finer-grained policies control access (AWS, SaaS Architecture Fundamentals, “Tenant isolation”; the page is marked “for historical reference only”, and it is used here for its definition, not for current service names).

A related pattern appears in the Model Context Protocol’s security guidance, for servers that hand out a handle for state, such as a cart or workflow identifier. It says servers “MUST NOT treat possession of a state handle as authentication” and “SHOULD bind handles server-side to the authenticated user”, for example by keying stored state as <user_id>:<handle>, where the user identifier comes from the verified token and is not supplied by the client (Model Context Protocol, Security Best Practices). For an agent this matters because the agent acts for a user, and a handle or record identifier it receives in text could name another tenant’s data.

  • Applied use. Carry the tenant on every request, derive it from the verified identity, and check it against the tenant that owns each record. Test with a request that is authenticated, has the right role, and names another tenant’s record.
  • Signal. “Valid login, correct role, but saw another customer’s data” points toward missing tenant isolation. “A guessed identifier returned someone else’s record” points toward a handle used as authentication.
  • Practical consequence. In pooled storage the check is on every access and is the only boundary, so it needs a test per access path. Dedicated stacks per tenant add cost and operations in return for a boundary that does not depend on that check.
  • Versus the distractor. The tempting wrong answer is to say the role check already covers it. A role says what a user may do. It does not say whose records the user may do it to.

The sandbox and the egress rule

A contractor given a key to the whole building shows the failure; the contractor given one room and a visitor’s badge shows the fix. A second way is a lab glovebox: the experiment happens inside, and what comes out is decided at the door.

When an agent runs generated code or starts a local tool server, that code runs with the privileges of whatever launched it. The Model Context Protocol security guidance lists, for local servers, running them “in a sandboxed environment with minimal default privileges”, launching them “with restricted access to the file system, network, and other system resources”, and providing a way to “explicitly grant additional privileges” when needed. For outbound connections, the same document recommends that clients block requests to private and reserved address ranges, including link-local 169.254.0.0/16, which it says includes cloud metadata endpoints, and that operators consider an egress proxy that enforces network policy. Its reason is that a metadata endpoint can expose credentials. It also says to avoid writing the address checks by hand, because attackers use encoding tricks that custom parsers miss (Model Context Protocol, Security Best Practices, sections on local server compromise and server-side request forgery).

  • Applied use. Start tool code with no file or network access and add what the task needs, and put an egress rule in front of anything that fetches a URL an outside party can influence.
  • Signal. “Generated code runs next to production credentials” and “the agent fetches any URL in a ticket” point toward a sandbox and an egress rule. “The agent only calls two named internal services” points toward an allow-list.
  • Practical consequence. A sandbox adds work every time a legitimate tool needs more access, and a deny-list check written by hand can be bypassed, so use the platform’s tested mechanism.
  • Versus the distractor. The tempting wrong answer is to tell the agent in its prompt not to touch certain files or addresses. The prompt is the thing a misled agent no longer follows; a boundary outside the model does not depend on it.

Three situations you will meet, and what they mean for your design

  1. A support product is moving from a pilot to customers, and one shared account holds the pilot’s test data and the real support database.
  2. An engineer asks why an administrator’s call to delete users is denied in a refunds account.
  3. A customer reports seeing another customer’s ticket, though the login and role were both valid.
What you see What to do Why
Test and real data in one account (situation 1) Separate the environments into accounts and give the agent access only to what its task needs A misled test agent no longer has the real data in its reach
An admin denied despite full access (situation 2) Check the guardrail above the account for the action The effective permission is what both layers allow
Valid login and role, wrong customer’s record (situation 3) Add a tenant check against the record’s owner Authentication and role do not decide whose record it is

Flashcards

Say the answer aloud before revealing it. Speaking it is what exposes the gaps that reading past a written answer hides.

What is blast radius, and how does an account boundary limit it?

The resources a failure or compromised identity could affect. One provider’s guidance says account-level isolation can contain an application problem, misconfiguration or malicious activity within one account, so it does not affect workloads in other accounts.

Name two benefits of separate accounts that the AWS guidance lists besides containing impact.

Separate production and non-production environments, and dedicated accounts for sensitive data that allow permissions to be restricted at account level. The same page also lists per-account quotas and cost allocation.

Does a guardrail policy grant permissions?

No. One provider’s documentation says no permissions are granted by a service control policy. It sets the maximum, and an identity or resource policy has to grant the action.

How do you compute the effective permission with a guardrail?

Take the actions the guardrail allows and the actions the identity policy grants, and keep those in both. An action in only one of them is not available.

What does the AWS page say about the management account?

Service control policies do not affect users or roles in the management account, and they affect only member accounts. An agent should not run there if the ceiling is meant to apply to it.

Why is an authenticated, authorised user not necessarily isolated from other tenants?

Authentication says who the user is and authorisation says what the role may do. Neither says whose records the user may reach, so a tenant check against each record’s owner is needed.

What does MCP guidance say about a state handle?

Possession of a handle must not be treated as authentication, and handles should be bound server-side to the authenticated user, for example by keying state as the user id from the verified token plus the handle.

What does a sandbox for agent tools start from?

Minimal default privileges with restricted access to the file system, network and other resources, and an explicit way to grant more when a task needs it, following the MCP guidance for local servers.

Why block link-local addresses in an agent's egress?

The MCP guidance names 169.254.0.0/16 as including cloud metadata endpoints, which can expose credentials. A request the agent was induced to make would otherwise reach them.

Why is "tell the agent in the prompt" a weak boundary?

A misled agent may no longer follow the prompt. A boundary outside the model, such as an account, a guardrail, a tenant check or a sandbox, holds whether or not the model behaves.

Model answer

The question: “We are putting an agent that can take real actions into production. How do you limit what a mistake or a compromise can reach?” An architect is asked this in a security review. A strong answer hits five beats, in this order:

  1. Reach — count what the agent’s identity can reach today.
  2. Accounts — separate environments and sensitive data domains into accounts, with deliberate cross-account access only.
  3. Ceiling — add a guardrail policy above the accounts, and check effective permission as the intersection.
  4. Tenants — check the tenant against each record, in addition to authentication and role.
  5. Sandbox — run generated code and tool calls with minimal privileges and an egress rule.

“I would start by counting what the agent’s role can reach, because that is the blast radius. I would put each environment and each sensitive data domain in its own account and grant the role only the accounts its task needs, so that a misled call has a few places to land and not all of them. Above the accounts I would put a guardrail policy for the rules that must hold whatever teams write, remembering that it only caps: the agent still needs its own grant, effective permission is what both allow, and in one provider’s service the management account is outside it. For a shared system I would add a tenant check on every access, since a valid login and role do not say whose record it is. Finally I would run generated code and tool servers in a sandbox with minimal privileges and put an egress rule in front of anything that fetches outside URLs. I would say that each of these stops one thing, and name what remains.”

Missing a beat is a failure state. Without reach the answer has no measure of improvement. Without accounts a narrow permission list is the only layer. Without ceiling a team’s own policy can widen anything. Without tenants one customer can read another’s records. Without sandbox generated code runs with the host’s privileges.

Why this matters

  • Reviews ask for it. A security review of an agent asks what a compromised identity could reach, and answers that count resources are stronger than answers that describe intentions.
  • Isolation is a second layer. A permission list can be widened by mistake, and a boundary that does not depend on it still holds.
  • Customer data. A report of one customer seeing another’s data is the incident that tenant isolation exists to prevent, and a valid login does not rule it out.
  • Talking to customers. When a customer asks whether the agent could reach their data from another customer’s session, an architect who can say which boundary applies, and what it does not cover, gives a better answer than “it is secure”.

After this chapter

The project for this chapter is Draw the Boundaries (project.md). You will count the reach of two agents under two layouts, work out the effective permission of eight requests under a guardrail, and check seven requests against tenant isolation, finishing by writing denied.tsv.

Taking AI to Production opens with the requests recorded in your denied.tsv. It asks what a system must have written down to prove, afterwards, what it was asked and why it refused.

Project

Hands-on Project — Draw the Boundaries

Objective

Count what two agents could reach under two layouts, work out which requests a guardrail and an identity policy allow together, and check requests against tenant isolation, so that where a boundary goes rests on counted reach and not on intention.

What this project needs: Python 3 and its standard library only. No task needs a cloud account, a model, an API key or an internet connection. The resources, policies and requests are printed below, and every check is an exact comparison. All three tasks are required, and they can be done in any order.

Your evidence file. Keep one plain-text file, chapter8-evidence.txt. Each task tells you which lines to add. Every line has the form key: value, one per line, with the key in lower case exactly as printed. Write what your code printed, not what you expected it to print. Where a value is a list, write its items comma-separated with no spaces, in the order given.

The data and the rules for all tasks

Copy this into your Python file.

EFFECTS = [("p1", 250), ("p4", 500), ("p3", 100)]   # the previous chapter's executed.tsv: (key, amount)

# (resource, account in the separated layout, sensitive)
RESOURCES = [
    ("refund-api",    "refunds",   False),
    ("ticket-db",     "support",   False),
    ("customer-pii",  "support",   True),
    ("payment-keys",  "payments",  True),
    ("report-bucket", "analytics", False),
    ("model-logs",    "analytics", False),
    ("sandbox-db",    "dev",       False),
]
# The accounts each agent's role can act in.
SINGLE = {"refund-agent": {"main"}, "test-agent": {"main"}}
SEPARATED = {"refund-agent": {"refunds", "support"}, "test-agent": {"dev"}}

# Guardrail: the actions allowed in each member account. "root" is the management account.
GUARDRAIL = {
    "refunds": {"refunds:create", "tickets:read"},
    "support": {"tickets:read", "tickets:write"},
    "dev":     {"sandbox:read", "sandbox:write"},
}

# (request, account, is_management, action, identity_grants_it)
POLICY_REQUESTS = [
    ("q1", "refunds", False, "refunds:create", True),
    ("q2", "refunds", False, "refunds:delete", True),
    ("q3", "refunds", False, "tickets:read",   False),
    ("q4", "support", False, "tickets:write",  True),
    ("q5", "support", False, "users:delete",   True),
    ("q6", "root",    True,  "users:delete",   True),
    ("q7", "dev",     False, "sandbox:write",  True),
    ("q8", "dev",     False, "refunds:create", True),
]

RECORDS = {"r1": "acme", "r2": "acme", "r3": "globex", "r4": "globex", "r5": "initech"}

# (request, user, user's tenant, authenticated, role can read, record)
TENANT_REQUESTS = [
    ("t1", "ann", "acme",    True,  True,  "r1"),
    ("t2", "ann", "acme",    True,  True,  "r3"),
    ("t3", "gus", "globex",  True,  True,  "r3"),
    ("t4", "gus", "globex",  True,  False, "r4"),
    ("t5", "ivy", "initech", False, True,  "r5"),
    ("t6", "gus", "globex",  True,  True,  "r1"),
    ("t7", "ivy", "initech", True,  True,  "r5"),
]

Task 1 — Count the reach

First, a carry-in. Add up the amounts in EFFECTS.

In the single layout every resource is in one account, main. In the separated layout each resource is in the account named in RESOURCES. An agent can reach a resource when the resource’s account is in the set given for that agent in that layout. Write reach(layout, agent) to return the resources an agent can reach.

Add to your evidence file:

t1_carried_total: <the sum of the amounts in EFFECTS>
t1_single_refund: <number of resources refund-agent reaches in the single layout>
t1_separated_refund: <number of resources refund-agent reaches in the separated layout>
t1_separated_test: <number of resources test-agent reaches in the separated layout>
t1_sensitive_separated_refund: <number of sensitive resources refund-agent reaches in the separated layout>

Output: one Python file with reach, and five evidence lines.

Task 2 — Work out the effective permission

For each request in POLICY_REQUESTS, write effective(request), which returns the first of these that applies:

  1. the identity does not grant the action: no_grant;
  2. the request is not from the management account and its action is not in GUARDRAIL for its account: guardrail_deny;
  3. otherwise: allowed.

The management account has no entry in GUARDRAIL, so do not look it up for a request from it.

Add to your evidence file:

t2_results: <the eight results in order q1..q8, comma-separated, no spaces>
t2_allowed_ids: <request ids whose result is allowed, in order, comma-separated, no spaces>
t2_guardrail_denied: <number of requests whose result is guardrail_deny>
t2_no_grant: <number of requests whose result is no_grant>

Output: one effective function, and four evidence lines.

Task 3 — Check the tenant, and write the denials

Evaluate each request in TENANT_REQUESTS under two designs.

  • Design A, login and role only: allowed when the request is authenticated and its role can read.
  • Design B, with tenant isolation: allowed when it is allowed under A and the user’s tenant equals the tenant that owns the record, found in RECORDS.

For design B, the reason for a denial is the first that applies: not_authenticated when the request is not authenticated, no_role when its role cannot read, other_tenant when the tenants differ.

Write denied.tsv: one line per request that design B denies, in request order, with two fields separated by a single tab character: the request id and its reason.

Add to your evidence file:

t3_allowed_a: <number of requests allowed under design A>
t3_allowed_b: <number of requests allowed under design B>
t3_cross_tenant_ids: <ids allowed under A and denied under B, in order, comma-separated, no spaces>

Output: a denied.tsv, and three evidence lines.

What carries forward

Keep denied.tsv and chapter8-evidence.txt. The next chapter opens with the requests in your denied.tsv: the ones your isolation rules refused. It asks what a system must have written down to prove afterwards what it was asked, and why it refused.

TechEazy Consulting training material — not affiliated with or endorsed by OpenAI, Anthropic or any other company named in this course.