Write down the union
An agent’s power is not what its prompt says. It is the union of the permissions of every credential its tools hold. Write that union out as a list of plain sentences (can read all customer rows including password hashes; can send email as support@; can reach any host on the internet) and the design review usually finishes itself.
A read-only database role, concretely
CREATE ROLE agent_ro LOGIN PASSWORD 'use-a-generated-one';
GRANT CONNECT ON DATABASE app TO agent_ro;
GRANT USAGE ON SCHEMA public TO agent_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO agent_ro;
ALTER ROLE agent_ro SET statement_timeout = '10s';Two lines there are the ones people miss.
The default privileges line. Without it, agent_ro can read today’s tables and not tomorrow’s. Six weeks later somebody adds a table, the agent gets a permission error, and the quickest fix (granting a broader role) undoes the whole design. Set it now.
The statement timeout. A model can write a query with an accidental cross join. Ten seconds later it is cancelled, instead of holding your database down for four minutes.
And note what SELECT ON ALL TABLES still includes: password hashes, personal data, other customers’ rows. Least privilege on the verb is not least privilege on the data. Give the agent views that expose only the columns it needs, and grant on the views.
Scoped tokens and separate accounts
- A GitHub fine-grained personal access token, scoped to one repository with the two permissions it needs, rather than a classic token carrying full repository scope.
- A bot user in one channel.
- A dedicated email account with an empty contacts list, not a person’s mailbox.
- A cloud role with the four API actions it uses, not an administrator policy.
Never the founder’s personal credential. Beyond the obvious, it makes the audit log lie: every action the agent takes is attributed to a person, and six months later nobody can tell which is which.
Network egress
Deny by default; allowlist the hosts the tools need. This is one control doing two jobs: it stops a compromised dependency phoning home, and it is the same wall that stops the exfiltration in the previous lesson.
Spend limits at the provider
Set the cap in the provider’s own dashboard, not only in your counter. Your counter is code, and code has bugs in it; the provider’s limit holds even when your loop does not. Add an alert at half the cap, because you want to hear about it during the day.
Short lives, and rotation
If a run takes ten minutes, a credential valid for an hour is generous. Prefer short-lived tokens issued per run. Rotate the long-lived ones on a schedule you actually keep, and revoke whatever an abandoned project left behind.
The question to end a design review with
If this agent were fully controlled by whoever wrote the last document it read, what could it do?
The answer is your blast radius. Not the worst case you imagine: the list of permissions you just wrote down.
Every lesson here is open. Your progress is recorded once you pass the Context Engineering exam.
Check your understanding
An analytics agent is given a Postgres role with GRANT SELECT ON ALL TABLES IN SCHEMA public. Two months later it starts failing with permission errors on a newly added subscriptions table. What is the correct fix?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Mai, your AI tutor