How to audit AI agent actions in Kubernetes

To audit what an AI agent does in Kubernetes, give the agent its own service account, then turn on API server auditing with an audit policy that names that account. Each event records the authenticated user, the verb, the target object where the request has one, and the response status. Without a policy file, the API server logs nothing.
This post covers the Kubernetes API server audit log. It does not cover application logs inside the agent’s container. For a coding agent’s own session on a developer’s machine, read how to audit what your AI coding agent did. For the response after an agent has misbehaved, read how to investigate an AI agent incident.
Checked against the Kubernetes auditing, API reference and service account pages as fetched on 2026-11-08. The pages print no version number, so this post does not name one.
What an audit event records
An audit event is a JSON object defined in the audit.k8s.io API group. Only user, verb, requestURI, stage and auditID are required. The other fields appear when the request has them. The table lists the ones that matter most for an agent audit.
| Field | What it holds | What to know |
|---|---|---|
user |
The authenticated identity | Required. For an agent, this is the service account username. |
impersonatedUser |
The identity being impersonated | Present for impersonated requests. |
verb |
The Kubernetes verb | For non-resource requests, the lower-cased HTTP method. |
objectRef |
The target object | Not set for List requests or non-resource requests. |
responseStatus |
The response status | Filled in even when the response is not a Status object. |
sourceIPs |
Where the request came from | Partly set by the client, so it is not proof of origin. |
userAgent |
The client’s user agent string | The docs say it must not be trusted. |
Give each agent its own identity
Each service account has a username of the form system:serviceaccount:<namespace>:<name>. The account also belongs to the groups system:serviceaccounts and system:serviceaccounts:<namespace>. A policy rule can match on either, so one rule can cover every agent in a namespace.
That is only useful if each agent has its own account. If two agents share one service account, their events look the same, and the log cannot say which agent acted. Give each agent a service account of its own and bind it to a narrow Role, as the RBAC for AI agents post describes. Agents that act for a person need more than a service account. The agent identity post covers how to tell the agent’s identity apart from the person it works for.
Write the audit policy for the agent’s namespace
The policy is a file the kube-apiserver reads at start-up. These steps build one for agents in the agents namespace.
- Create a file with
apiVersion: audit.k8s.io/v1andkind: Policy. Theruleslist is required, and the docs treat a policy with no rules as illegal. - Put the secret rule first, so that agent access to Secrets is recorded without request bodies.
- Add a rule for agent writes that records the request body.
- Add a rule for all other agent requests at the Metadata level.
- End with a catch-all Metadata rule for every other user.
Source: https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/ (fetched 2026-11-08); field names from https://kubernetes.io/docs/reference/config-api/apiserver-audit.v1/ (fetched 2026-11-08).
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
# Agent access to Secrets: metadata only, so secret values stay out of the log.
- level: Metadata
userGroups: ["system:serviceaccounts:agents"]
resources:
- group: ""
resources: ["secrets"]
# Agent writes: record the request body.
- level: Request
userGroups: ["system:serviceaccounts:agents"]
verbs: ["create", "update", "patch", "delete"]
# Everything else the agent does: metadata only.
- level: Metadata
userGroups: ["system:serviceaccounts:agents"]
# Catch-all: every other user, metadata only.
- level: Metadata
Rules are checked from top to bottom, and the first rule that matches sets the level. Within one rule, every field must match. The Secret rule sits first, so the Request rule never sees agent Secret writes. The omitStages list drops the RequestReceived stage, as the docs’ own example does.
Turn on the log file
Pass the policy and the log path as kube-apiserver flags. The log backend is off unless you set --audit-log-path, and kube-apiserver logs no events unless you set --audit-policy-file. The rotation values below are examples for sizing your own retention, not defaults.
Source: https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/ (fetched 2026-11-08).
kube-apiserver \
--audit-policy-file=/etc/kubernetes/audit-policy.yaml \
--audit-log-path=/var/log/kubernetes/audit/audit.log \
--audit-log-maxage=30 \
--audit-log-maxbackup=10 \
--audit-log-maxsize=100
If the control plane runs kube-apiserver as a static Pod, mount the policy file and the log directory into it with hostPath volumes, so that the audit records are persisted, as the docs advise. On a managed control plane, check the provider’s own audit settings. This post does not cover them.
Read the agent’s records
The log backend writes JSON Lines, one event per line. Read it with the filter below.
- Open the audit log file. Each line is one event.
- Keep the events whose
user.usernameissystem:serviceaccount:agents:<name>, using the agent’s account name. - Keep the
ResponseCompletestage for the outcome. The docs define that stage as the point when the response body has been completed and no more bytes will be sent. - Read
verbandobjectRefto see what the agent tried to do and to which object. - Read
responseStatus.codeto see whether the request succeeded. - Compare the verbs with the agent’s Role. Any verb the agent used that its Role does not grant needs a look.
The docs do not say whether a request that RBAC denies gets an event. Test a denied request on your own cluster before you rely on the log to show refusals.
Know where the record is weak
The level you choose sets how much each event carries. The docs define the four levels below.
| Level | What the event records | Request and response bodies |
|---|---|---|
| None | Nothing for matching requests | Not recorded |
| Metadata | User, timestamp, resource and verb | Not recorded |
| Request | Metadata plus the request body | Request body only, and not for non-resource requests |
| RequestResponse | Metadata plus request and response bodies | Both, and not for non-resource requests |
Several gaps are worth checking before you trust the log:
- The API reference says the default audit level is None, so a request that no rule matches is not recorded unless a catch-all rule at the end of the list covers it.
- Request bodies can hold sensitive values. The docs’ own example logs Secrets at Metadata, and this policy follows that.
- A JSON Patch request body is an array of operations. Merge patch and strategic merge patch bodies are objects. Write your rules for the content type the agent actually sends. Source: API concepts, patch and apply (fetched 2026-11-08).
- The log backend has batching disabled by default. The webhook backend has batching enabled by default.
- The API docs say audit logging increases the memory use of the API server.
Sources
- Kubernetes: Auditing: policy format, levels, stages, the log and webhook backends, and the kube-apiserver flags.
- Kubernetes API reference: audit.k8s.io/v1: Policy and PolicyRule fields, Event fields, and the default level.
- Kubernetes: Service accounts: the default account and how Pods receive a service account.
- Kubernetes: Managing service accounts: the username and group format of a service account.
Enforcing this with AIControls
AIControls has confirmed per-user tokens, and confirmed per-user and per-session cost limits for Claude Code through the gateway. This post makes no AIControls claim about recording Kubernetes API calls. The audit policy above is the record for those calls. To see the platform, start at the AIControls platform page, or the governing AI agents hub for the wider context.
Frequently asked questions
Does the Kubernetes API server log an AI agent's requests by default?
No. The API server writes no audit events unless you pass --audit-policy-file. The API reference says the default audit level is None, and a catch-all rule at the end of the policy's rule list can override it. Add a catch-all Metadata rule if you want every request recorded with its user, verb and object.
What username does an AI agent's service account show in audit logs?
The username has the form system:serviceaccount:<namespace>:<name>, for example system:serviceaccount:agents:claude-ops. The account also belongs to the groups system:serviceaccounts and system:serviceaccounts:<namespace>. A userGroups rule can match every agent in one namespace, but give each agent its own account so the log can tell them apart.
Should the audit policy record request bodies for every agent action?
No. Request bodies add detail, but they make the log much larger and can hold sensitive data. Use the Request level only for the writes you need to reconstruct. Keep Secret bodies out of the log by putting a Metadata rule for secrets ahead of any broader rule, and check the log size on a staging cluster before you roll the policy out.
Why does my audit event include managedFields?
Managed fields are omitted only when omitManagedFields is set to true, either in the policy or on a single rule. Otherwise they stay in the request and response bodies that the log records. Most agent audits do not need them, so setting omitManagedFields to true keeps the log shorter.