deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

kagent 0.10 ships audience-bound agent tokens via RFC 8707, off by default

kagent 0.10 adds RFC 8707 resource indicators so agents can exchange tokens scoped to a single backend, but both new variables default to empty and misconfiguration fails silently.

kagent 0.10 ships audience-bound agent tokens via RFC 8707, off by default

What shipped

kagent v0.10.0 went generally available on September 4, and listed among many other changes are two environment variables that decide whether an agent's credential works at one backend or at all of them. According to a dev.to post by webofmike that walked the tagged source, KAGENT_STS_RESOURCE and KAGENT_STS_AUDIENCE bring RFC 8707 resource indicators into the project's token exchange flow. Both default to empty, so the new scoping behaviour is inactive unless an operator enables it.

The problem it addresses

A kagent agent authenticates as a Kubernetes service account and exchanges that identity for a token it presents to backends such as MCP servers, using the RFC 8693 OAuth 2.0 Token Exchange client kagent has shipped for some time. What that exchange previously left open was the purpose of the resulting token. When a request names no target, the security token service has nothing to scope the result to, and the response is honoured by every backend that trusts the STS.

The post describes this as a confused-deputy scenario with the agent playing the deputy, and argues it is sharper than the classic version because agents are explicitly built to act on instructions found in text at runtime. An agent wired to one MCP server for reading GitHub issues and another for moving money can, through a prompt injection or a tampered tool description, end up presenting its token to the second backend.

How the scoping works

The fix follows RFC 8707: name the target resource in the exchange, receive a token whose aud claim is that resource, and have the backend refuse everything else. In the 0.10 code, the variables are registered in go/core/pkg/env/kagent.go with descriptions citing the RFCs, read at runtime startup in go/adk/pkg/runner/adapter.go, and split on commas because both parameters accept multiple values. They reach the wire in go/adk/pkg/sts/client.go, with each entry becoming its own form field.

To demonstrate the behaviour, the author built a three-container harness requiring no Kubernetes cluster, cloud account or provider key, with two mock backends: one accepting only tokens audience-bound to a GitHub URL, the other to a payments URL. The harness runs a stand-in for the kagent runtime that mirrors the relevant code paths rather than kagent itself. With the variable unset, the exchange carried no scoping parameters and both validating backends rejected the resulting unscoped token. Scoped to one backend, the token was accepted there and turned away with HTTP 401 at the other. Scoped to both, it worked at both — broad access remains possible, but it now has to be requested explicitly in a config file that someone can review.

The silent failure mode

The notable catch is what an empty or mistyped variable produces. When the value is empty, whitespace-only, or misspelled, the code appends no field at all, so the request on the wire looks identical to an unscoped exchange. Whether that becomes a hard failure or a silent over-grant depends entirely on the backend. In the demo the mock servers validate aud, so a misconfiguration surfaces immediately as a 401. Against a backend that skips audience checks, the same mistake yields a token accepted everywhere, with nothing anywhere reporting a problem. The control is therefore two-sided: setting KAGENT_STS_RESOURCE buys little unless backends actually reject the audiences they are not, and the post advises verifying both sides.

What it does not solve

This is a runtime shipping a standard OAuth mechanism for scoping a token to a service, not agent identity. The token's subject remains the pod's Kubernetes service account and the act claim names the runtime; nothing in the exchange distinguishes one agent session from another or records what the agent was asked to do. The author notes that MCP spec work here is unfinished: of the four identity workstreams named in the August roadmap for the MCP auth extensions repository, only one has produced a stable spec, with DPoP and Workload Identity Federation still open. He suggests SPIFFE workload identity as a natural pairing for the layer underneath.

Why it matters

The release turns a long-running agent-security argument into a config field. Where operators set the variables and backends enforce audience checks, an injected agent that reaches for the wrong backend gets rejected at the point of use rather than succeeding with a credential it should not hold. But the default-off, silently-failing combination means the improvement is opt-in and easy to misconfigure, and the gaps above it — per-session agent identity, workload federation — remain open spec work. For teams running agent runtimes against multiple MCP backends, the practical takeaway is direct: enable the variables, and confirm the backends actually check aud.

  • #kagent
  • #oauth
  • #security
  • #kubernetes
  • #ai-agents
  • #mcp

Related posts