Kubernetes and agentic execution
Sergey read the Kubernetes demo page and said it is strongly CNAPP and CIEM territory, that Kubernetes-to-AWS access configuration is what Wiz does, and that he does not see how it links to agentic execution. His instruction was to treat Kubernetes as a reach endpoint and as a channel to execute other functions, and then to focus on execution rather than on configuration.
This document tests each of his claims, records what we have already built, names what is missing, and proposes what to build instead. Every claim about our own code carries a file and a line. Every claim about AWS carries a source.
The short version
He is right about the page we published, and the fix is smaller than it looks.
The AWS connector already collects the CloudTrail events that prove agentic execution. The Kubernetes-to-AWS handshake and the model invocations that follow it are both on its list of events of interest. It then throws the handshake away, because of a one-line identity filter written for a different workload type. Fixing that turns the demo from "here is what is configured" into "here is what this agent did", which is the sentence Sergey is asking for and the sentence a CIEM cannot say.
Claim one: the page is CIEM territory
True. The page shows authority read out of IAM trust policies. Wiz, Orca and every CIEM ship Kubernetes-to-AWS permission mapping. We added honesty about what a trust policy proves. Honesty is a credibility mechanic rather than a buyer problem, and we led with the mechanic.
The page's headline finding is Unbounded federated trust, which is a permissions finding. Read by a buyer, it says we are a permissions tool.
Claim two: agents do not live in Kubernetes today
Partly false, and getting more false. But the useful version of the point survives.
Against the claim: kagent is a CNCF sandbox project, built by the founders of Istio, whose entire premise is running agents as Kubernetes workloads. Its companion kmcp deploys Model Context Protocol servers as Kubernetes workloads with managed lifecycles. LangGraph, CrewAI and AG2 run on it unmodified.
For the claim: that ecosystem is early, and no prospect has told us they run it.
The framing that survives both: most enterprise agents in 2026 are ordinary containerised services that call a model API. An agent needs an identity and an outbound authority. In Kubernetes those are a ServiceAccount and a federated IAM role. Nothing about our story requires the workload to be a special kind of thing. If Airbnb runs anything agentic on its own infrastructure, it runs where their compute is, and their compute is Kubernetes.
So we should stop arguing that agents live in Kubernetes and start showing one that does.
Claim three: make it a reach endpoint and focus on execution
Correct, and buildable. There are two directions, and we currently serve neither.
Outbound — an agent inside Kubernetes reaching into AWS. We read the configuration for this and discard the execution.
Inbound — an agent anywhere reaching into Kubernetes to run something. We do not read this at all.
What is already built
More than expected. These are the pieces, with citations.
| Piece | Where | State |
|---|---|---|
| Kubernetes subject nodes from IAM trust policies | sv0-connectors integrations/aws/src/sv0_aws/core/transformer.py:4294 | Shipped. Node key is eks_service_account:<issuer-id>:<namespace>:<name> |
| CloudTrail evidence extraction | integrations/aws/src/sv0_aws/extractors/cloudtrail_extractor.py | Shipped and wired into the scan |
| The events that prove agentic execution | same file, lines 66–99 | Already on the list: AssumeRoleWithWebIdentity, InvokeModel, InvokeModelWithResponseStream, InvokeAgent, RetrieveAndGenerate |
| Bedrock agents, knowledge bases, flows and action groups as entities | integrations/aws/src/sv0_aws/extractors/bedrock_extractor.py | Shipped |
| An agent classification the whole product agrees on | sv0-platform src/domain/graph/identity-subtypes.ts:88 | Shipped. bedrock_agent, ai_agent, agent |
| Rooting a decision story at the agent that invoked the work | sv0-platform src/services/decision-surface-service.ts:439 | Shipped |
| Attributing observed execution to one destination, never guessed | sv0-platform src/ingestion/execution-attribution.ts | Shipped |
| Revocation rehearsal | sv0-platform src/rehearsal/ | Shipped |
The gap is not the platform. The platform can already tell an agent's story end to end. The gap is that no evidence about a Kubernetes identity ever reaches it.
The three gaps, each verified
One: the connector drops the event that names the ServiceAccount
Filed as sv0-connectors#310.
cloudtrail_extractor.py:423-425 rejects every record whose userIdentity.type is not
AssumedRole:
identity = record.get("userIdentity") or {}
if identity.get("type") != "AssumedRole":
return False
An AssumeRoleWithWebIdentity event has userIdentity.type of WebIdentityUser, per
the CloudTrail userIdentity reference.
That event is the only place the Kubernetes subject appears. Its principalId carries
system:serviceaccount:<namespace>:<name>, and its identityProvider names the OIDC provider.
OIDC is the token standard the cluster uses to prove a workload's identity to AWS, and the
provider name contains the cluster's issuer id.
So the event list says we meant to collect the handshake, and the identity filter means we never do. We collect what the role did afterwards and cannot say which ServiceAccount did it.
This matters most exactly where it hurts most. Many ServiceAccounts can assume a role trusted by a namespace-wide or wildcard subject, and those broad grants are the ones we raise as findings. Without the handshake event there is no way to say which ServiceAccount ran.
The join back is deterministic, which the determinism rule requires:
- the action event carries
userIdentity.accessKeyId - match it to the
AssumeRoleWithWebIdentityevent with the same access key - that event names the namespace and ServiceAccount
- our node key is already
eks_service_account:<issuer-id>:<namespace>:<name>
No inference, no scoring, one exact key. The technique is documented publicly by Mitiga.
The limit, which we must state and not exceed. This reaches the namespace and the ServiceAccount. It does not reach the pod. Only EKS Pod Identity carries the pod name. We say ServiceAccount and we never imply pod.
Two: nothing drives CloudTrail from a Kubernetes identity
Already filed as sv0-connectors#290. The loop that calls the extractor iterates Lambda functions, so a Kubernetes-federated role never enters it. That issue is accurate, and its proposed fix is the right one: a role-anchored entry point that iterates federated roles instead of compute resources.
Gap one is not a duplicate of it. Fixing the driver alone gets the role's downstream activity and still cannot name the subject.
Three: we hold permission to confirm existence and never use it
Filed as sv0-connectors#311.
Our read-only role already grants eks:ListPodIdentityAssociations and
eks:DescribePodIdentityAssociation (integrations/aws/cfn/securityv0-readonly-role.yaml:158-159).
Nothing calls either. The EKS extractor lists clusters and describes them for the OIDC issuer, and
stops (integrations/aws/src/sv0_aws/extractors/eks_extractor.py).
An EKS Pod Identity association is a real object in the EKS API carrying the cluster, the namespace and the ServiceAccount, with the role available from the describe call (API reference). It is a confirmed binding, not an inferred one, and it is readable without cluster credentials.
This is worth more than an extra data source. Every Kubernetes node we emit today carries
existenceConfirmed: false, and that is what produced the screen Ivan rejected — a page that
opens by telling the customer we are not sure the thing exists. A Pod Identity association, or an
observed handshake, proves it exists. The honest negative becomes an honest positive.
What we should demo instead
Root the story at the agent. The cluster is scenery.
An agent runs in the production cluster as ServiceAccount
agents/invoice-triage. It is federated into the data account. In the last thirty days it activated that authority 41 times, called the model 380 times, and read one bucket. It also holds standing reach into two buckets it has never touched. Cut the trust and here is what stops.
Every clause is a shipped capability except the binding from the agent to its observed execution. The demo we published shows only the first sentence.
The contrast with a CIEM then writes itself, and we do not have to claim it. A CIEM says the ServiceAccount can reach the bucket. We say the agent activated the authority 41 times, called a model, touched one bucket of three, and here is what breaks if you revoke it.
What we stop saying
- Stop leading with Unbounded federated trust. It is a permissions finding and it is the sentence that made the page read as CIEM. Keep it as a supporting finding.
- Stop presenting configured authority as the product. It is the floor, not the story.
- Stop implying pod-level attribution. We reach the ServiceAccount.
- Stop asserting that agents commonly run in Kubernetes. Show one running instead.
A useful accident: the model calls are free
Amazon logs InvokeModel, InvokeModelWithResponseStream, Converse and ConverseStream as
CloudTrail management events, which are recorded by default and carry no data-event charge
(Bedrock CloudTrail documentation).
AssumeRoleWithWebIdentity is likewise a management event.
So the whole outbound chain is visible in a default trail: the Kubernetes handshake, and the model
calls that follow it. InvokeAgent is a data event and needs advanced event selectors on
AWS::Bedrock::AgentAlias, which costs money and which the customer must have already enabled. I
have not verified how RetrieveAndGenerate is classified; treat it as unknown until someone does.
This is a real advantage in a customer conversation. The evidence for the agentic story needs no new logging, no agent installed in the cluster, and no cluster credentials.
The plan
Sequenced by evidence rather than by effort. Each gate states the test that decides whether it passed. Nothing after a gate starts until its test passes.
Gate zero — agree the spine with Sergey. Does the story above answer his objection? This is the cheapest gate and it invalidates everything below if the answer is no. Nothing else should start first.
Gate one — capture the handshake (sv0-connectors#310). Accept WebIdentityUser events in the CloudTrail extractor
and carry the access key through. Passes when a real scan of the enterprise demo tenant returns
at least one AssumeRoleWithWebIdentity record whose principal names a ServiceAccount the
connector already emits as a node. A fixture does not pass this gate.
Gate two — join the session deterministically. Match the access key from an action event to
its handshake. Passes when a downstream InvokeModel is attributed to the right
eks_service_account: node, and a mutation test proves the join fails closed: corrupt the access
key and attribution must drop to zero rather than fall back to a guess.
Gate three — drive extraction from federated roles. This is sv0-connectors#290. Passes when a Kubernetes-federated role produces observed activity with no Lambda anywhere in the tenant.
Gate four — confirm existence (sv0-connectors#311). Read Pod Identity associations. Passes when at least one
Kubernetes node in the demo tenant carries existenceConfirmed: true with the association as its
evidence, and the screen that used to open with a warning no longer does.
Gate five — run a real agent. Deploy an agent workload into the lab cluster that calls Bedrock under its own ServiceAccount. Passes when the events above appear in the customer's own trail from a workload nobody hand-authored.
Gate six — the inbound direction. Read EKS access entries, which map an AWS principal to Kubernetes permissions from the AWS control plane with no cluster credentials (access entries documentation). This is Sergey's "reach endpoint": an agent whose AWS role lets it act inside the cluster. It needs a permission we do not currently request, so it changes the customer ask.
Gate seven — rebuild the demo page around the agent, replacing the current one.
What this collides with
CloudTrail is already the dominant cost in an AWS scan — it was 98% of scan time in the measurement behind sv0-connectors#306. Gates one through three all add CloudTrail work, and Pod Identity needs a describe call per association. The scan-scaling issues stop being a background concern and become a dependency.
Two of them are correctness problems rather than speed problems, and they would corrupt the evidence this plan produces: #304, where a CloudTrail cell running past an hour reports success with zero evidence, and #305, where a budget-exhausted workload discards the evidence it already collected. An agentic execution story built on a source that can silently report "no activity" is worse than no story.
Open questions
For Sergey. Does an agent in Kubernetes calling a model, with its authority and its unused reach shown, answer the objection? If not, the disagreement is about the demo rather than about the connector, and that is a much cheaper thing to fix.
For Ivan. Gate six needs eks:ListAccessEntries, which is not in the read-only role today.
Adding a permission to the customer ask is a decision with a cost, and it should be made once
rather than per demo.
For whoever builds gate five. Which agent runtime. A plain container calling Bedrock is the most honest representation of what enterprises run now. kagent is the most representative of where the ecosystem is heading. They are not the same demo and the choice should be deliberate.