
When we launched Autonomous Worker Agents, governance inherited, not integrated, was the core promise: agents run inside the same pipelines, and inherit the same RBAC, policy, and audit trails already governing production, rather than getting security bolted on after the fact. The first post in this series covered one half of how we back that promise: isolation, the four walls (image hardening, process isolation, secret isolation, network isolation) that contain a compromised agent, even if it turns hostile. We proved that model by replaying a real CVSS-9.0 breach against our own hardened image and watching it fail at every layer.
This post covers the other half. Isolation answers what happens when an agent is compromised. This post answers what an agent is allowed to do when it isn't, when it's running exactly as designed. Even a perfectly sandboxed Autonomous Worker Agent is still a liability if it runs with more access than the task requires. What identity does it run as, and which permissions does it hold? This is authorization and least privilege, applied to a caller that picks its own actions at runtime.
THE INVARIANT EVERYTHING ELSE ENFORCES
An agent holds no standing privilege of its own. Its effective access is a bounded subset of the triggering principal's, the minimum the task needs, and nothing more.
Key takeaways
Delegated identity: An agent authenticates as the principal that triggered it: same user, same audit trail. It is never a standing superuser or a shared service account with broad role bindings.
Least privilege by construction: Its access is a scoped subset of the triggering user's, on a token minted per run and deleted when the run ends. The mint step rejects any permission it can't prove the parent already holds.
Policy and RBAC, inherited: The RBAC and OPA governance that already guard Harness pipelines extend to agents, covering who may build one and what policy allows, because an agent is just a new kind of pipeline step.
Enforced, not assumed: Every check runs on the server: RBAC, the scope filter, and the gateway's tool intersection. A hidden UI button proves nothing; the console, the API, and the agent all resolve to the same decision.
Isolation bounds a compromise. Authorization bounds intent.
We split agent security into three categories. The first post covered isolation: it assumes the process is already compromised and asks what the kernel, the filesystem, and the network will refuse to do on its behalf, controls that hold even when everything above them fails.
This post is category two. It answers a question isolation never touches: when the agent is functioning exactly as designed, what is it authorized to reach? An agent that's perfectly sandboxed but runs with the triggering user's full role bindings is still over-privileged. Isolation bounds the blast radius of a breach; authorization bounds the blast radius of correct, intended execution.
The full model, with this post's category marked:

Governance moved from authoring time to runtime
Before agents, a pipeline step was explicit. Someone wrote create_issue(project="INFRA"), it went through review, and you could read the pipeline and know exactly what it would do to the outside world. The decision was in the code, and the code was checked before it ran.
Agents don't work that way. You hand an agent a goal. At runtime, the model decides which tools to call, in what order, with what parameters. The action isn't in the code anymore. You can't read it before it happens, and every control in this post exists because of that shift.
Credentials work the way they always did; the harder problem is accountability. The decision-maker moved from a human at authoring time to a model at runtime, so the guardrails move to runtime too. If you can't review an action before it runs, you're limited to bounding which actions are possible and recording every one that happens. Harness already governs pipelines with RBAC, Policy-as-Code, and an audit trail, and since an agent is just a new kind of pipeline step, that governance extends to it directly.
The sharpest example of the shift is the credential the agent runs with, because getting that wrong makes every other control irrelevant.
The credential the agent runs with
Same agent, same job: run one deploy pipeline on the user's behalf. The only thing that differs is the token it carries.
Two controls before it runs, three while it runs
That scoped token is one of five controls. On its own, it bounds which Harness resources the agent can reach, not who was allowed to build the agent, which tools it may call, or what it did afterward. The full picture spans five controls in two phases. Two apply before the agent runs, at authoring and save time, deciding who may build it and what policy allows. Three apply while it runs, bounding the identity it carries, the tools it can call, and the record it leaves. In order:
Walking the five, in order
Each one below stands on its own: the gap it closes, how it works, and the diagram that makes it concrete. None of them trusts the model to behave; each is enforced by the platform.
Control 1: RBAC on the agent resource
Author, publish, and execute are separate permissions
The gap it closes
An agent can hold a privileged connector and act across production. If anyone with module access could create, publish, or delete one, there's no boundary at all: a low-trust user could publish an agent other teams run in production, or bind a privileged connector to a brand-new agent.
How it works
> Author, publish, and execute are three permissions, not one. A developer can build agents in their project without being able to publish one account-wide or run someone else's; each is a separate grant.
> Attaching a connector is gated on its own. Binding a privileged MCP connector to an agent is a distinct permission, so a low-trust user can't smuggle broad access in the back door.
> Enforced server-side, across account, org, and project. The UI hiding a button is never the source of truth.
In plain terms. A print shop. One badge lets you design a poster. A different, harder-to-get badge lets you put it on the wall where everyone sees it. And a third decides who's allowed to run the press. Nobody gets all three just for walking in.

Control 2: Policy-as-Code governance (OPA)
Pipeline OPA policies, extended to agent definitions at save and trigger time
The gap it closes
RBAC says who may build and run an agent. But it can't express a rule about the agent's content: which model it uses, which connectors it may attach, how many turns it may take. A security team wants to write that rule once and have every agent obey it, no matter who authors it: "only approved models," "this connector is off-limits," "cap agents at ten turns," "names must follow our standard." Without it, every guardrail is per-agent and easy to miss.
How it works
Harness has governed pipelines with Open Policy Agent for years: Policy-as-Code that evaluates a pipeline against your rules at save and run time and blocks or warns on a violation. Agents are a pipeline construct, so they inherit the same engine. That policy surface now reads agent definitions too:
> Model restrictions: which model connectors an agent may use.
> Connector restrictions: which MCP connectors may be attached.
> Guardrail limits: caps like maximum turns, so it can't loop unbounded.
> Naming and sensitive variables: standards enforced, sensitive inputs kept out.
> Permission boundaries: flagging high-blast-radius verbs for explicit sign-off.
In plain terms. OPA is the building code that the plans must pass before construction. The gateway is the guard at the door once it's open. You want both: an illegal plan and a person walking out with something they shouldn't have are two different failures.

Keep one distinction straight, because a later control looks similar. OPA checks the definition up front, when the agent is saved or the pipeline is triggered. The MCP gateway (control 4) checks each call inline, as it happens. One stops a bad agent from shipping; the other stops a shipped agent from making a call it shouldn't.
Control 3: Scoped, ephemeral tokens
A subset of the parent's grants, on a key that expires with the run
The gap it closes
The easy thing to hand an automated job is a normal token, a personal or service-account key. But those carry every permission their owner has. Give one to an agent that only needed to run one pipeline and you've handed it the whole keyring, and a leak spills the entire account.
How it works
> You can't grant what you don't have. At mint time, every requested permission is checked against the parent's own access: ask for something the parent can't do and creation fails. A scoped token can only ever narrow, never widen.
> Scope is enforced server-side, on every check. After access control says "yes," a scope filter flips anything outside the token's slice back to "no." Effective access is the overlap of the two.
> Ephemeral, and still you. The token is minted at step start and deleted when the run ends, minutes, not months, yet it resolves to the human who triggered it, so the audit trail names a person, not a faceless bot.
In plain terms. Your building pass opens forty doors. Before an errand, the desk cuts a paper key that opens door twelve, expires tonight, and is logged under your name. The runner can open door twelve, nothing else. Tomorrow it's scrap.

Effective access = what the parent has AND what the token was cut for. Whichever is smaller wins.
The token comes in two shapes, with the same scoping rules but different lifespans. Ephemeral is the default for pipeline agents: born at step start, bulk-deleted when the run ends, with no cleanup to forget. Persistent is user-created for standing needs (a CI job that only pulls one registry): longer-lived, but still just its declared slice, and revocable like any key.
Control 4: Tool authorization at the MCP gateway
Connector and agent allow-lists, enforced as an intersection
The gap it closes
Say two agents share one Jira connector: CI AutoFix needs to create issues, Vulnerability Remediation only needs to read them. Left alone, both can call every tool the Jira MCP server exposes, including delete_issue and transition_issue, because a connector has no tool restrictions by default. The scoped token bounds that Harness resources the agent reaches; it says nothing about which tools it may call on a third-party server. That's a separate leash.
How it works
> The connector declares what may flow through it at all. An admin sets the connector's tool allow-list once (get_issue, add_comment, search_issues) plus the list of agents approved to use it.
> Each agent declares the narrower set it needs. On the same connector, the read-only agent lists only the read tools; the AutoFix agent lists the write ones, so the same bot has different reach depending on which agent is calling.
> The gateway allows only the intersection. The effective set for any call is connector.allowedTools ∩ agent.allowedTools. A tool in the overlap proceeds; anything outside is blocked and logged before the call ever leaves Harness.
In plain terms. The connector is the toolbox the shop owns; the agent's list is what this worker signed out today. The gate only lets out what's on both lists. Grab for anything else, and it stops you at the door.

allowed = connector.allowedTools ∩ agent.allowedTools. Only tools on both lists reach the server.
The check runs outside the agent, so a prompt injection that hijacks the agent can't switch it off. It's centrally governed: change a connector's allow-list once, and every agent picks it up. The agent never holds the connector's real credentials either; the gateway attaches it on the way out.
Control 5: Per-call attribution
Every tool call is bound to a principal, a run, and a result
The gap it closes
A pipeline runs at 2 am; the next morning, a few Jira tickets have changed. The run log says the pipeline ran and the agent step completed, but it does not say which tools it called, with what parameters, or which tickets it touched. Worse, the third-party system attributes the change to whoever created the connector months ago, not to this run. Authorization can be perfect, and you're still blind to what happened.
How it works
Authorization and attribution are different jobs: one decides whether an action is allowed, the other records who is answerable for it. A system can nail the first and fail the second, so every outbound tool call emits a structured record, not a buried log line, but fields you can query:
> Which agent made the call, and which run did it belong to?
> Which principal it acted as: the real human who triggered it, preserved through the scoped token.
> Which tool was called, with what parameters, and what result came back?

Because the scoped token carries the real identity all the way through, each record names the person behind the run, not just the agent. When something looks off, you read the record instead of cross-referencing timestamps by hand.
Grants are resource-type plus verb
Both the RBAC layer and the scoped token use the same grammar, and it isn't a coarse role like "deployer." A grant is a resource type bound to an explicit set of verbs, optionally narrowed to named resource IDs and a scope. It maps one-to-one onto Harness's existing permission identifiers, so an agent grant reads exactly like a human role binding.
Each grant is scoped to an account, org, or project, and can even name specific resource IDs. Amber verbs are the high-blast-radius ones (create, edit, delete, push); they're default-deny for agents and have to be asked for on purpose. The catalog spans every Harness module: deployments, GitOps, infrastructure-as-code, security, cost. A real agent grant is a handful of these lines, never the whole list.
Least privilege, made literal
Put the five together, and the model collapses into one sentence: an agent's real power is the overlap of everything that had to say yes, and everyone defaults to no. Miss any one, RBAC, OPA, the scoped token, the gateway intersection, attribution, and the action doesn't happen. That overlap is deliberately tiny: far smaller than the owner's full access, on a key that expires with the job.
This is the same instinct as the isolation post, applied to a different failure mode. Isolation shrinks what a compromised agent can reach; permissions shrink what a trusted one can reach. Both assume the reach will be abused and make it as small as the job allows, enforced by the platform, never by the model's judgment.
Five controls, two phases. An agent's effective reach is the intersection of all five, and every one defaults to deny.
The whole thing in one request
One tool call, from the agent's goal to the result. Two gates clear before it runs, two check the call itself, and one records what happened afterward, all five in sequence.
Two gates before the run, two on every call, one record after. None of them ask the model to be trustworthy.
The grant is declared on the stage
You don't mint tokens, configure scope filters, or wire the gateway by hand. You declare the agent's permission set and tool allow-list on the stage spec; the platform mints the ephemeral scoped token, enforces the intersection at the gateway, and revokes the token when the run ends. A least-privilege "deploy and reconcile" agent looks like this:

No create, no delete, no secret access, and only two Jira tools. The agent can ship, reconcile, and comment, but it can't rewrite a pipeline, read a credential, or delete an issue, even though the person who triggered it might. The grant is checked at save time against that person's access and your OPA policy, so an over-privileged agent never ships.
FAQs
Whose identity does the agent act as? The person who triggered the run. A scoped token still resolves to that user, so every action the agent takes is attributed to them in the audit log. The agent borrows the identity for reach; it doesn't get one of its own.
Can an agent ever end up with more access than the person running it? No. Its effective access is the overlap of what its grant asked for and what its triggering user actually has, whichever is smaller. The mint step verifies every requested permission against the parent's access first, so a grant can only ever subtract from what the human could already do, never add to it.
What happens to the token when the run ends? For a pipeline agent it's deleted. The token is ephemeral and tied to the execution: when the run finishes, every token it spawned is bulk-deleted. Lifetime is minutes, capped at a day, so a leaked one is expired and worthless almost immediately.
Is this enforced, or just hidden in the UI? Enforced on the server. Every permission check goes through one place, and the scope filter runs there, after normal access control, flipping anything outside the token's slice back to denied. Hiding a button changes nothing; the API, the console, and the agent all get the same answer.
How does a new team member avoid shipping an over-privileged agent? The grant is declarative YAML, checked before the agent runs. Ask for permission the triggering principal doesn't have, and it fails fast, naming the missing grant, instead of silently succeeding with too much or silently failing at runtime with too little.
Where does this sit next to isolation and the LLM gateway? Isolation (the first post) contains a compromised agent; permissions (this post) bound a well-behaved one. The behavioral layer, reading prompts and responses for injection and sensitive data, is the third part and its own post. Three parts, one goal: the agent's reach is decided by the system, not the model's judgment.

