AI Workflow Service Account Permissions: A Least-Privilege Design
AI workflow service account permissions often start with one convenient credential: a shared account that can read the CRM, update tickets, send messages, and modify documents. That works in a demo. In production, it gives every workflow run the same authority, even when the employee who started the run could not perform the action themselves. It also makes an audit log answer "which service ran this?" rather than "who requested this, which workflow acted, and what was it allowed to do?"
Choose an identity model for each trigger. Then issue a short-lived credential for one tool and purpose, and check authorization again at the tool boundary. This guide gives you the decision rules, token shape, implementation sequence, denial tests, and rollout checks.
Why one shared service account fails
A model does not need credentials. The deterministic code that executes a model-selected tool does. When those layers share one broad credential, a bad tool choice, prompt injection, or ordinary programming mistake can reach every resource available to that account.
Reusing the identity for unrelated jobs compounds the permission problem. A scheduled invoice importer may need to create draft records. A support assistant may need to read customer history and add a private note. A manager-approved refund flow may need to change a payment record. Combining those jobs under one account creates a union of permissions. Each workflow can now reach systems that belong to the other two.
The audit trail becomes ambiguous. The target system sees the shared account as the caller. Your workflow database might record the requesting employee, but an investigator must join two logs and trust that the application did not alter that field. Carry both identities in the authorization context: the subject who requested the work and the workload that performed it.
Treat this as an authorization design problem, not a prompt-writing problem. The OWASP agentic threat guide recommends a threat-model-based approach for risks introduced by autonomous systems. For this workflow, draw the trust boundaries around the trigger, orchestrator, model, tool gateway, credential issuer, and target system. Then mark every point where untrusted text can influence an action.
Choose delegated or workload identity first
Use delegated identity when a signed-in person starts the workflow and the action should never exceed that person's permissions. Examples include searching customer records, drafting a reply in the user's queue, or updating a document the user can already edit.
Use workload identity when the job has its own approved authority independent of a current user. Scheduled reconciliation, inbound document processing, and queue cleanup usually fit this model. Give each job or tightly related workflow family a dedicated identity rather than sharing one default account across the application.
Use an explicit approval transition when a delegated workflow needs a stronger action. The approval should create a new authorization decision, not toggle a Boolean inside the original run. Record the approver, action, resource, expiry, and policy version. Issue a new credential only after that decision passes.
Do not impersonate a user merely to make downstream logs look familiar. OAuth 2.0 Token Exchange distinguishes impersonation, where the actor takes on the subject's identity, from delegation, where the actor acts on the subject's behalf. Its actor claim provides a standard way to represent that second identity. Delegation usually gives an internal AI workflow a clearer record because the target can retain both the human subject and the workflow actor.
A practical decision table looks like this:
| Trigger and action | Identity model | Permission ceiling |
|---|---|---|
| Employee asks to summarize a customer account | Delegated | Requesting employee's read access |
| Employee asks to update an assigned ticket | Delegated | Employee access plus an update-ticket scope |
| Nightly importer creates draft invoices | Workload | Dedicated importer identity, draft creation only |
| Workflow submits a refund after manager approval | Approval transition | Exact refund action and approved amount |
| Model proposes deleting records | Neither by default | Separate human-controlled administrative path |
If a team cannot choose a row for a proposed tool, the tool is not ready for production.
Define the authorization envelope
Do not pass the user's original web session token through every step. Exchange it at a trusted backend for a narrower credential. RFC 8693 defines request fields for the subject token, requested audience, resource, and scope. It also defines an optional actor token for delegation. Those elements map well to an internal workflow authorization envelope.
Keep the workflow's internal authorization record explicit even if a provider uses different token names:
{
"run_id": "wf_7f3c",
"subject": "employee:1842",
"actor": "workflow:support-assistant",
"tool": "ticketing.update_private_note",
"resource": "ticket:93815",
"scopes": ["ticket.note:create"],
"policy_version": "support-tools-v4",
"approval_id": null,
"expires_at": "2026-07-29T10:05:00Z"
}
This is an illustrative application record, not a claim that every identity provider emits these exact fields. What matters is the separate subject and actor, one intended tool, a resource constraint where possible, narrow scopes, policy provenance, and expiry.
Bind the credential to an audience accepted only by the relevant tool gateway. A token issued for ticket updates should fail at the document service. Bind it to one resource when your authorization system supports resource-level grants. Otherwise, enforce the resource check in the gateway before it calls the target API.
Keep raw provider credentials outside the model context and workflow state. The model should produce a typed tool request. Trusted code validates that request, obtains the credential, calls the target, and records the result. Never put a refresh token, service-account key, or bearer token into a prompt so the model can assemble its own HTTP request.
Implement AI workflow service account permissions
Start with the tool catalog, not the model. List every callable operation with its input schema, side effects, required identity model, permission, allowed resource pattern, and approval rule. Split broad operations. crm.execute is too vague to authorize safely, while crm.contact.read and crm.contact.add_note can have different policies.
Next, create dedicated workload identities. The Google Cloud service-account guidance recommends dedicated service accounts for applications, avoiding automatic grants on default accounts, and using temporary credentials where possible. The same principles apply outside Google Cloud: isolate jobs, avoid static keys, and grant only the roles needed for the intended operation.
Then put a credential broker between orchestration and tools. The broker accepts authenticated workflow context, evaluates policy, and returns either a short-lived credential or a denial. The orchestrator should not have permission to mint arbitrary scopes. It asks for a named tool action, and the broker maps that action to the maximum allowed audience and scopes.
A simplified flow can use these checks:
def authorize_tool_call(run, request):
tool = catalog.require(request.tool_name)
policy = policies.for_tool(tool.name)
require(run.actor == policy.allowed_actor)
require(request.arguments.matches(tool.input_schema))
require(policy.subject_can_act(run.subject, request.arguments))
require(policy.resource_allowed(run.subject, request.arguments))
if policy.requires_approval(request.arguments):
require(approvals.matches(run.approval_id, request))
return credential_broker.issue(
actor=run.actor,
subject=run.subject,
audience=tool.audience,
scopes=tool.scopes,
resource=tool.resource_from(request.arguments),
ttl=policy.credential_ttl,
)
The gateway must repeat the important checks. Do not rely on the orchestrator to say that authorization already passed. Verify the credential audience, expiry, actor, scope, and resource at the boundary that owns the tool. Validate arguments again after the model output has been parsed. This contains errors in orchestration code and prevents a tool request from being altered between policy evaluation and execution.
Finally, log the authorization decision separately from the API result. Record the subject, actor, requested tool, resource, granted scopes, policy version, approval reference, decision, and reason code. Exclude the token itself. A denied call belongs in the audit trail because it shows whether policy is catching unexpected behavior.
Work through a support-ticket example
Suppose an employee asks an assistant to summarize ticket 93815 and add a private note. The workflow has two tools: ticket.read and ticket.note.create. The employee can view the ticket because it belongs to their support queue.
For the read step, the broker exchanges the employee's session identity for a credential whose audience is the ticket gateway and whose scope permits ticket reads. The gateway checks that ticket 93815 is visible to that subject. The workflow can send the returned ticket text to the model, but not the access token.
The model drafts a note and proposes ticket.note.create. That is a separate authorization request. The gateway verifies the same subject still has access, validates the note schema and size, and obtains a credential for note creation only. A read credential cannot create the note, and a note credential cannot read another ticket.
Now assume the ticket body contains hostile text asking the assistant to export all customer records. The model might propose an unavailable tool or a malformed call. The catalog rejects unknown tools before credential issuance. If the model calls a known export tool, policy denies it because the support assistant actor is not allowed to request that action. The denial is useful evidence, but it never produces a token.
Prompt defenses are not an authorization boundary. Good prompts may reduce bad requests, but the authorization boundary decides whether a request can become an external action.
Handle failures without widening access
Credential expiry is a normal failure. Retry by returning to the broker with the original run identity and authorization context. Do not fall back to a long-lived key when temporary credential issuance fails. That turns an availability problem into a security bypass.
A target API may reject a token because the audience, scope, or resource is wrong. Classify that as an authorization error rather than a transient provider error. Blind retries will not fix it and may flood the audit log. Stop the action, preserve the denial reason, and route the run to an operator if the workflow cannot continue safely.
If the subject loses access during a long-running job, re-evaluate before each consequential call. Do not assume authorization from the beginning of the run remains valid. For workload jobs, re-evaluate the workload's role grants at credential issuance and keep credential lifetime short enough that revoked permissions take effect promptly.
If the credential broker is unavailable, fail closed for write operations. You may choose a documented read-only degradation path if the target and data classification allow it, but do not improvise that path during an incident. Define and test it before launch.
Verify both permissions and denials
A happy-path test proves the workflow can act. It does not prove the permission boundary works. Build a matrix that covers allowed and denied combinations for subject, actor, tool, resource, scope, approval, and time.
At minimum, test these cases:
- An authorized employee can read one permitted record.
- The same employee cannot read a record outside their assigned boundary.
- A read credential cannot perform a write.
- A credential for one tool is rejected by another tool's audience check.
- An expired credential is rejected without falling back to a static key.
- A scheduled workload cannot use delegated user-only tools.
- A missing, expired, or mismatched approval blocks the stronger action.
- A model-proposed unknown tool never reaches the credential broker.
- Revoking a subject or workload role blocks later credential issuance.
- Logs show the subject, actor, resource, decision, and policy version without exposing the token.
Run the denial suite in continuous integration against policy changes. In a staging environment, capture target-system audit events and match them to workflow decision records. The pair should identify the same actor, action, resource, and time window. Investigate mismatches before rollout.
Watch production denials by reason code. A rise in unknown-tool or out-of-scope requests can expose prompt manipulation, model regressions, catalog drift, or a new user workflow that needs deliberate design. Do not automatically grant the missing scope to make the alert disappear.
Put one workflow through the design this week
Choose the internal AI workflow with the broadest current credential. Inventory its tool calls, decide whether each call uses delegated or workload identity, and replace one shared credential with a short-lived, audience-bound credential for one operation. Add a test proving that credential fails against a second tool and an unauthorized resource.
Do not expand the rollout until the audit record can answer four questions for every call: who requested it, which workflow acted, what exact resource it touched, and why policy allowed or denied it. Once those answers are reliable, apply the same tool-catalog and credential-broker pattern to the next workflow.
References
- OAuth 2.0 Token Exchange, RFC 8693 defines token exchange, delegation and impersonation semantics, subject and actor claims, audience, resource, and scope.
- Google Cloud service-account best practices supports dedicated service accounts, temporary credentials, and least-privilege role design.
- OWASP Agentic AI threats and mitigations supports using a threat-model-based approach for autonomous-system risks.