Chapters
Try It For Free
July 16, 2026

Identity and Permissions for AI Worker Agents in Harness
| Harness Blog

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:

three-category-model.png

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.

Token Comparison Card

The easy way out

Full-access token

reuse the user's or a service account's key

  • Carries every permission the owner has — dozens of resources, all verbs
  • Long-lived: the same key works for months, long after the run ends
  • A leak exposes the whole account, not just this one job
  • Shared service account blurs the audit trail — who really did this?

What Harness mints

Scoped, ephemeral token

cut for this run, expires with it

  • Carries only pipeline:execute on the one pipeline — nothing else
  • Tied to the execution: auto-deleted the moment the run finishes
  • A leak is near-worthless — wrong permissions, and dead within the hour
  • Still resolves to the real user, so the audit trail stays clean

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:

Controls Table
# Control What it asks
1

Before it runs · RBAC

Who may build and run it

Can this person author, publish, or run an agent — and attach a privileged connector to one?
2

Before it runs · OPA

What policy allows at all

Does the agent's definition pass our rules — approved models, allowed connectors, turn limits?
3

While it runs · Scoped token

The identity and access it carries

Whose identity does it act as, and how little of that identity's access does it hold?
4

While it runs · Tool scope

Which tools it may call

Of the tools a connector exposes, which handful is this agent actually cleared to invoke?
5

While it runs · Attribution

The record it leaves behind

When it acts, who is accountable — and can you see exactly what it did?

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.

separation-of-duties.png

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.

opa-vs-mcp-gateway.png

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.

scoped-token-mint-and-filter.png

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.

mcp-gateway-tool-intersection.png

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?

attribution-record.png

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.

Resource Verbs Table
RESOURCE TYPE VERBS  (amber = high blast-radius, default-deny)
pipeline view execute abort create edit delete
environment view access rollback create edit delete
service view access create edit delete
connector view access create edit delete
secret view create edit delete
gitops_application view sync create edit delete
artifact_registry view pull push delete

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.

Controls Mechanism Table v2
CONTROL MECHANISM WHAT IT BOUNDS

RBAC

BEFORE IT RUNS

Agent as a first-class RBAC resource; author, publish, execute, and use_in_agent as separate grants. Who may build, publish, and run an agent — and who may attach a privileged connector

OPA policy

BEFORE IT RUNS

Policy-as-Code is evaluated on the agent definition at save and trigger time. Which models and connectors are allowed, turn limits, naming, and sensitive variables

Scoped token

WHILE IT RUNS

Ephemeral token = parent's RBAC ∩ declared scope, checked server-side per request. Which Harness resources does the agent reach, as the triggering user, for the run's lifetime only

Tool scope

WHILE IT RUNS

MCP gateway allows connector.allowedToolsagent.allowedTools per call. Which third-party tools the agent may actually invoke on a connector

Attribution

WHILE IT RUNS

Structured record per outbound call: agent, run, principal, tool, params, result. Who is accountable after the fact, tied to a real person through the scoped token

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.

Sequence List
01
The pipeline was saved and triggered — OPA policy already cleared the agent's definition
gate · save time
02
The agent calls a tool carrying its scoped, ephemeral token, resolving to the real user
acts as you
03
RBAC + scope filter: does the user have this, and is it inside the token's slice?
both, or no
04
MCP gateway: Is this tool in connector ∩ agent? If not, blocked before it leaves
gate · runtime
05
The call goes out, and an attribution record ties it to the agent, run, and person
logged · who

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:

agent-stage-yaml.png

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.

Shivansh Srivastava

Shivansh Srivastava is a Software Engineer on the AI Platform team at Harness, working on the security architecture behind Autonomous Worker Agents.

Shubham Jindal

Shubham Jindal is a Director of AI Software Engineering at Harness, where he leads the development of intelligent systems that enhance developer productivity and software delivery. With a strong foundation in distributed systems and web performance.

Similar Blogs

Harness AI
Harness Platform