Codex vs Claude

What the Agent Inherits: Sessions You Are Already Logged Into

David Guzenburg/ / 11 min read

It does not need to read a key. It needs to run a command, and the authentication has already happened.

credentialsleast privilegeaccess controlblast radius

The agent does not need your secrets; it inherits your sessions

Discussions of agent credentials focus on secrets as objects: an API key in a file, a token in an environment variable, what must never reach context. That framing is right and it misses the larger surface, because on a developer's machine most access is not held as a secret the agent could read. It is held as an authenticated session the agent can simply use.

Your cloud CLI has a cached token. Your container registry login is in a config file. Your Kubernetes context points at a cluster with credentials already resolved. Your SSH agent holds an unlocked key. Your version control CLI is authenticated. None of that requires the agent to find a secret. It requires the agent to run a command.

So the question in credential boundaries for agents — what may the agent see versus use — has an uncomfortable answer here: it can use a great deal without ever seeing anything.

Take the inventory once

Most people have never enumerated this, and the list is longer than the guess. It is worth ten minutes.

#!/usr/bin/env bash
# Every line that succeeds is access the agent inherits.
echo "== cloud =="
aws sts get-caller-identity 2>/dev/null | jq -r '.Arn' || echo "aws: none"
gcloud config get-value account 2>/dev/null || echo "gcloud: none"
az account show --query user.name -o tsv 2>/dev/null || echo "az: none"

echo "== clusters =="
kubectl config current-context 2>/dev/null || echo "kubectl: none"

echo "== registries =="
jq -r '.auths | keys[]' ~/.docker/config.json 2>/dev/null || echo "docker: none"

echo "== code hosts =="
gh auth status 2>&1 | grep -E 'Logged in|not logged' || true

echo "== ssh agent =="
ssh-add -l 2>/dev/null || echo "ssh-agent: empty"

echo "== databases =="
[ -f ~/.pgpass ] && echo "pgpass: $(wc -l < ~/.pgpass) entries"

Run it and read the output as a list of things an agent in your shell can do without asking anyone. For most developers it includes at least one production-adjacent system, and the discovery is usually the kubectl context.

The kubectl context is the sharpest edge

A single ambient setting decides which cluster every command applies to. The command that lists pods in your local development cluster and the command that deletes a deployment in production differ by a value nobody typed and nothing displays.

An agent asked to "clean up the failing pods" will operate against whatever the current context is. It has no way to know that the context changed yesterday during an incident and was never changed back — and neither do you, because there is nothing in the terminal that says so.

Never leave a production context current

Make the default context something harmless, and make selecting a production one an explicit per-command flag rather than a mode you enter. Ambient state that decides the blast radius of every subsequent command is exactly the wrong shape of configuration to have around an agent.

Agent forwarding turns one machine into many

SSH agent forwarding means a session on a remote host can use your local key. It is convenient and it means the boundary is not your machine: an agent that connects to a bastion inherits the ability to authenticate onward to everything that key opens.

The narrower alternatives are worth the small inconvenience. Jump-host configuration achieves the same result without exposing the key to the intermediate machine, and short-lived certificates limit the window in which anything inherited is useful. Neither is agent specific; both matter more when something automated is composing the commands.

Short-lived beats scoped, and scoped beats neither

Two independent dimensions, and time is the one people neglect. A credential scoped to read-only on one bucket is good. A credential that expires in an hour is good in a different way, because it bounds the damage from a leak you have not noticed rather than the damage from a leak you have.

Where your provider supports short-lived sessions, use them and let them expire. The friction — re-authenticating a few times a day — is the mechanism working: it means the window in which an inherited session is useful is measured in hours, and a stray token in a log has probably already stopped mattering by the time anyone finds it.

The blast radius question, asked concretely

For each item on your inventory: if a command ran against it right now with no further authentication, what is the worst outcome? Not the likely one — the worst.

Read-only access to a staging bucket is a paragraph. Write access to a production database, or a cluster context with delete permissions, is an incident. The answers sort your inventory into things that can stay ambient and things that need to be absent by default and present only deliberately, which is a more useful classification than any general policy about credentials.

Absence is the only reliable control

The instinct is to write a rule saying the agent must not touch production. That is an instruction, and instructions are requests. The control that holds is that the credential is not there: not logged in, not in the environment, context not selected, key not loaded.

This is why the profile-based approach works better than the policy-based one. A shell for agent work that has no cloud session, no production context and no loaded key cannot do the thing regardless of what any text says. Work requiring those is done deliberately, in a session opened for the purpose, and closed afterwards.

Per-project environments make this practical

The reason people keep everything ambient is that scoping it is annoying. Directory-scoped environment tooling removes most of that: the credentials relevant to a project load when you enter it and unload when you leave, so the ambient set is small and correct rather than large and historical.

It also makes the inventory self-maintaining. When credentials are declared per project, the answer to "what does this session have access to" is a file you can read rather than an accumulation of every login you have ever performed on this machine.

Rotation is the part nobody does

A token that appeared in a transcript is disclosed. Not probably, not depending on retention — disclosed, and the correct response is to rotate it.

Almost nobody does, because rotation is inconvenient and the exposure feels theoretical. The way to make it happen is to make it cheap in advance: know where each credential is issued, have the rotation command written down, and practise it once while nothing is wrong. A rotation procedure that has been run once is a five-minute task; one that has never been run is an afternoon and a reason to talk yourself out of it.

Detection, since prevention will not be complete

A scanner over transcripts and logs for credential-shaped strings costs nothing to run and occasionally finds something. It will not catch everything — a session cookie does not look like a key — and it catches the common cases, which is the majority of real incidents.

Pair it with the audit record from your hooks, so that when something does turn up you can answer the question that immediately follows: what else happened in that session, and which systems were reachable at the time. An inventory taken after the fact is guesswork; one recorded per session is evidence.

What I would change today

Run the inventory script and read it honestly. Set the default cluster context to something harmless. Stop forwarding the SSH agent by default. Move cloud sessions to short-lived credentials wherever the provider supports it. Open agent sessions from a shell that has none of the above loaded, and load what a specific task needs, deliberately, for that task.

That is an afternoon and it changes the answer to "what could this session do" from an unbounded list to a short one you chose. Nothing in it restricts what an agent can do for you; it restricts what it can do without you having decided.

Databases are the quiet entry on the list

Connection strings live in more places than anyone tracks: a password file in your home directory, a saved connection in a GUI client, an environment variable set by a project hook, a URL in a config file that was supposed to be local-only and points somewhere shared.

The specific hazard is that database access does not feel like production access. Running a query is reading, and reading feels safe — until a query is against a table with customer records and the results are now in an agent's context and therefore in a request to a model provider. That is a disclosure with no destructive step anywhere in it.

Treat read access to anything containing real data as a credential worth scoping, not as a convenience. A connection to a seeded local database costs nothing and removes the entire question.

The version control CLI is more powerful than it looks

An authenticated code-host CLI can usually do considerably more than clone and push. Depending on your permissions: create and merge pull requests, modify repository settings, manage branch protection, publish releases, edit issues, and trigger workflows — which in turn run with whatever secrets those workflows hold.

That last one is the interesting chain. A session that can trigger a workflow can cause code to run in an environment holding deployment credentials, without ever touching a deployment credential itself. Enumerating what your token can do is worth doing once; the scopes attached to a personal token created three years ago are rarely the scopes you would grant today.

What this looks like on a well-set-up machine

The inventory script prints almost nothing. No cloud session by default. The cluster context points at a local development cluster. The SSH agent is empty until a task needs a key, and it is unloaded afterwards. Database connections in project environments point at seeded local instances. The code-host token has scopes matching what daily work requires and nothing else.

Getting a production credential involves a deliberate action — a command, in a shell opened for it, that expires. That is one extra step per occasion, a handful of times a week, and in exchange the answer to "what could that session have done" is short enough to state.

Why this framing is worth adopting

Thinking in secrets leads to a checklist about files and environment variables, and that checklist is fine as far as it goes. Thinking in inherited sessions leads to a different and more useful question: not what can this process read, but what is it already allowed to do.

That question has a concrete answer you can produce in ten minutes, it changes with every login you perform during a day, and it maps directly onto the outcomes you actually care about. It is also the framing that makes the right fix obvious — you do not protect a session by writing a rule about it, you protect it by not being in it.

And keep the inventory script somewhere you will actually run it — a make target, a shell alias, whatever you already reach for. The value is not in having run it once while reading an article about it. It is in running it on a Tuesday six months from now and noticing an entry that was not there before, which is the only way this stays true rather than becoming a snapshot of a machine you no longer have.

Takeaway

An agent in your shell does not need to find a secret — it inherits every session you are already authenticated into: cloud CLIs, registries, the current cluster context, a loaded SSH key, an authenticated code-host CLI. Enumerate them, because the list is longer than you think and usually includes something production-adjacent. Then make the dangerous ones absent by default rather than forbidden by instruction, prefer short-lived credentials, and stop leaving a production context current.

Keep reading
Security Engineering

Gating Irreversible Actions: Controls That Don't Depend on the Model

Sorting agent actions by reversibility and visibility, why the strongest control is simply not granting the credential, and the limits of confirmation prompts.

Security Engineering

Credential Boundaries: What an Agent Should Never Be Able to See

The difference between an agent seeing a secret and using one, why wrapper scripts beat environment variables, and how to scope agent credentials so a steered session stays contained.

Codex vs Claude

Below the Prompt: What a Kernel Sandbox Actually Constrains

Seatbelt, bwrap and seccomp enforce policy that injected text cannot argue with. What a profile can express, why a workspace-write policy still permits everything inside your repository including .git/hooks, and a self-test that proves the policy is on.

Codex vs Claude

Default-Deny Egress: The Control You Turn Off in the First Hour

Outbound network access is the highest-value restriction on an agent and the one that breaks npm install. Pre-seeding dependencies, hostname filtering at a proxy, why a credential fits in a query string, and verifying the denial by IP.

← Interrupting an Agent: Almost Everything Is in the First Minute  ·  Local Terminal or Managed Workspace? →

All codex vs claude articles  ·  Every article