
Agent identity: who an AI agent acts for, and with what rights
Whose rights an agent uses, how to hand them over safely, and how to prove afterward who did what.
The whole loopPicture a support agent that handles refunds.
A customer writes in, the agent looks up the order, and it calls a refund tool.
One afternoon it refunds the wrong customer, perhaps because a message it read contained an instruction, perhaps because it mixed up two order numbers.
The money is gone, and the questions start.
Who asked for this refund?
Which program made the call?
Which of the many conversations running that day was it?
What allowed a support agent to refund any customer at all?
If the agent used one shared API key, the service’s log says only that the key was used, and nobody can answer any of the four.
The cause is usually that three different identities were treated as one:
- The user the agent acts for: the customer or employee whose request started the work.
- The agent: the software itself, with its own name, version, and place in your system.
- The run: this one session or task, with a start and an end.
An action is correct only when all three are known and each is limited on its own terms.
The user’s rights set the ceiling.
The agent’s role may lower it.
The run’s task lowers it further, to the few things this task needs.
This page walks through how to do that with Go from the companion repository’s identity example, which runs without a model or an API key: go run ./identity.
The confused deputy
A confused deputy is a program that holds authority and is tricked into using it for someone who should not have it. The program is the deputy: it has the power, and it acts on requests. It is confused when it cannot tell whose request it is serving, so it applies its own power to a stranger’s wishes.
A prompt injection is mostly this attack. Someone other than the user plants text in a page, a ticket, or a crash report, and the model, which holds the user’s authority for the session, acts on that text as if the user had written it. The attacker has no rights of their own. They borrow the agent’s. The defense is the same as for any deputy: make the authority small and specific, so that what a confused deputy can do is small too. The rest of this page is about doing that.
Acting on someone’s behalf
An agent that acts for a user needs a delegated credential: a token that says “this program may do these things for this user, until this time.” Four properties matter:
- Narrow. It lists scopes, named permissions such as
refund:order-1042, and only the ones this task needs. - Short-lived. It expires in minutes, so a leaked copy soon becomes useless.
- Tied to the user. The service can see which user the action is for and apply that user’s limits.
- Never the user’s password, and never one long-lived key shared across all users. A password gives the agent everything the user can do, forever, and a shared key makes every action look the same.
The general pattern for getting such a credential is token exchange. The agent’s platform holds a token that proves who the user is, and it asks an authorization server (the service that issues tokens) to swap it for a new token with a narrower audience, narrower scopes, and a short life. OAuth 2.0 Token Exchange, RFC 8693, defines this request. It separates two cases. In impersonation, the new token lets A be treated as B, indistinguishable from B. In delegation, A keeps its own identity, and it is understood that A acts on B’s behalf. For agents, delegation is the right case, because the service can then log both parties.
An RFC 8693 request is an HTTP POST to the authorization server’s token endpoint with grant_type set to urn:ietf:params:oauth:grant-type:token-exchange.
The subject_token parameter carries a token for the party the request is made on behalf of.
The optional actor_token identifies the acting party, which here would be the agent.
The audience parameter names the service the new token is for, and scope asks for the permissions wanted.
The issued token can carry an act claim naming the actor, which is how a receiving service sees “the agent, acting for this user”.
A separate may_act claim lets a token say which parties are allowed to become the actor.
The authorization server decides whether to grant the request, so the narrowing happens where the user’s rights are known.
Most teams do not need the full protocol on day one.
The shape is what counts: a signed statement of user, agent, run, scopes, and expiry, issued by one place and checked by the service.
The identity example uses that shape, signed with crypto/ed25519 from the standard library.
A credential’s claims name the user, the agent, the run, the scopes, and the expiry:
// Claims is what a credential says: who the agent acts for, which
// agent and which run hold it, what it may do, and until when.
type Claims struct {
ID string `json:"jti"` // credential ID, for the audit log
User string `json:"sub"` // who the agent acts for
Agent string `json:"agent"` // which software
Run string `json:"run"` // which session or task
Scopes []string `json:"scopes"` // what it may do
Expires time.Time `json:"exp"`
}
Only the harness holds the signing key, in an Issuer:
// Issuer mints credentials. Only the harness holds one: the model,
// the tools, and sub-agents never see its key.
type Issuer struct {
Key ed25519.PrivateKey
}
// Mint signs c. The token is the claims as JSON and their signature,
// each base64url-encoded, joined by a dot.
func (is Issuer) Mint(c Claims) (string, error) {
body, err := json.Marshal(c)
if err != nil {
return "", fmt.Errorf("encode claims: %w", err)
}
sig := ed25519.Sign(is.Key, body)
enc := base64.RawURLEncoding
return enc.EncodeToString(body) + "." + enc.EncodeToString(sig), nil
}
The service holds the public key, in a Verifier, and checks the signature and the expiry on every call.
Each failure is a sentinel error the caller can test with errors.Is:
// Verifier checks credentials. A service holds one, with the issuer's
// public key.
type Verifier struct {
Key ed25519.PublicKey
Now func() time.Time
}
// Verify checks token's signature and expiry and returns its claims.
// It checks no scope: the caller does, for the action it is taking.
func (v Verifier) Verify(token string) (Claims, error) {
var c Claims
b, s, ok := strings.Cut(token, ".")
if !ok {
return c, ErrMalformed
}
enc := base64.RawURLEncoding
body, err1 := enc.DecodeString(b)
sig, err2 := enc.DecodeString(s)
if err := errors.Join(err1, err2); err != nil {
return c, fmt.Errorf("%w: %w", ErrMalformed, err)
}
if !ed25519.Verify(v.Key, body, sig) {
return c, ErrBadSignature
}
if err := json.Unmarshal(body, &c); err != nil {
return c, fmt.Errorf("%w: %w", ErrMalformed, err)
}
if !v.Now().Before(c.Expires) {
return c, ErrExpired
}
return c, nil
}
A real system would use a standard token format such as a JWT, issued by an authorization server, with an audience claim; the example keeps only what the argument needs.
When a run starts, the harness mints a credential for exactly what the user asked for, and builds the tool with it:
// The run starts: user u-42 asked the support agent for a refund on
// their own order, so the harness mints a ten-minute credential
// with exactly that scope.
parent := Claims{
ID: "cred-1", User: "u-42", Agent: "support", Run: "run-7",
Scopes: []string{"refund:42", "read:orders"},
Expires: now().Add(10 * time.Minute),
}
token, err := issuer.Mint(parent)
if err != nil {
return err
}
tool := refunder{base: base, client: &http.Client{
Transport: attach{token: token, next: http.DefaultTransport},
}}
The model never holds the secret
The credential is attached by the harness, at the point where a tool call leaves for the service, after the model has asked for the action.
The model sees a tool named refund and its arguments.
It never sees the token.
This matters because anything in the context window can come back out.
The model can repeat it in a reply to a user, a log can record it, and an injected instruction can ask the model to pass it as an argument to some other tool.
A secret the model never received cannot be leaked by the model.
The prompt injection page lists the other ways text leaves an agent and the checks that sit on them.
The service page shows a related leak: a key held in an environment variable can still be read from /proc/1/environ by any process of the same user, even after the program removes the variable.
In Go, an http.RoundTripper is a natural place to attach a credential, because every request a tool makes passes through it:
// attach is an http.RoundTripper that adds a run's credential to every
// request a tool makes. The harness builds the tool's client with it,
// so the token never passes through the model, the tool's arguments,
// or its results.
type attach struct {
token string
next http.RoundTripper
}
func (a attach) RoundTrip(r *http.Request) (*http.Response, error) {
r = r.Clone(r.Context()) // a RoundTripper must not change its input
r.Header.Set("Authorization", "Bearer "+a.token)
resp, err := a.next.RoundTrip(r)
if err != nil {
return nil, fmt.Errorf("attach: %w", err)
}
return resp, nil
}
The tool itself names the customer and nothing else; its client was built by the harness with that run’s transport. The model’s arguments and the tool’s result text never contain the token. Make sure error messages and debug logs do not print request headers.
Permission lives in the tools
A system prompt that says “only touch this customer’s data” is a request to the model. The model may follow it, and an injected instruction may talk it out of it. A check in code decides what happens, whatever the model was persuaded to want.
So the service, or the tool in front of it, checks the credential on every call. An action outside the credential’s scopes fails. If the action is one a person should be able to approve, it goes to a person instead, as described in human approval.
The example’s refund service does both checks, and records the attempt whether or not it was allowed:
// refund handles POST /refund?customer=ID. It verifies the credential
// and checks its scope before acting, and records the attempt either
// way, so a refused call leaves a trace too.
func (s *service) refund(w http.ResponseWriter, r *http.Request) {
customer := r.URL.Query().Get("customer")
tok, _ := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ")
c, err := s.verify.Verify(tok)
status := http.StatusUnauthorized
if err == nil && !c.Has("refund:"+customer) {
err = fmt.Errorf("%w: refund:%s", ErrScope, customer)
status = http.StatusForbidden
}
rec := Record{
Time: s.verify.Now(), User: c.User, Agent: c.Agent, Run: c.Run,
Credential: c.ID, Action: "refund:" + customer, Allowed: err == nil,
}
if err != nil {
rec.Reason = err.Error()
}
s.log.add(rec)
if err != nil {
http.Error(w, err.Error(), status)
return
}
fmt.Fprintf(w, "refunded customer %s\n", customer)
}
If the agent is talked into refunding customer 43 while holding only refund:42, the call fails with an ordinary error, which goes back to the model as a tool result like any other failure.
The prompt can still say what the agent should do.
The credential decides what it can do.
Sub-agents get less, never more
When an agent starts a sub-agent for part of the work, the sub-agent should receive a credential narrower than its parent’s.
The idea is called attenuation: each handoff can remove permissions and none can add them.
A sub-agent that only reads orders gets read: scopes and no refund: scope, and it cannot get the refund permission back, because it has no way to ask for more than it was given.
// Narrow derives a sub-agent's claims from its parent's: the same user
// and run, its own ID and agent name, only scopes the parent holds,
// and an expiry no later than the parent's. Only the harness, which
// holds the Issuer, can turn the result into a token.
func Narrow(parent Claims, g Grant, now time.Time) (Claims, error) {
for _, s := range g.Scopes {
if !parent.Has(s) {
return Claims{}, fmt.Errorf("%w: parent lacks %s", ErrScope, s)
}
}
child := parent
child.ID, child.Agent, child.Scopes = g.ID, g.Agent, g.Scopes
child.Expires = now.Add(g.TTL)
if child.Expires.After(parent.Expires) {
child.Expires = parent.Expires
}
return child, nil
}
The child keeps the same user and run, gets its own credential ID and agent name, holds only scopes the parent held, and never outlives the parent.
The harness calls Narrow and then Mint:
// A sub-agent that only reads orders gets a narrower credential.
// Asking for a scope the parent lacks fails before any token exists.
_, err = Narrow(parent, Grant{
ID: "cred-2", Agent: "reader", Scopes: []string{"refund:43"},
TTL: time.Hour,
}, now())
fmt.Fprintf(w, "narrow to refund:43: %s\n", result("", err))
child, err := Narrow(parent, Grant{
ID: "cred-2", Agent: "reader", Scopes: []string{"read:orders"},
TTL: time.Hour,
}, now())
if err != nil {
return err
}
childToken, err := issuer.Mint(child)
if err != nil {
return err
}
Only the harness holds the signing key, so a sub-agent cannot build a wider token for itself. The sub-agent’s tools, in turn, check that narrower token exactly as in the previous section.
The agent’s own identity
The software also needs an identity, separate from any user’s. A workload identity is an identity issued by the platform to a running piece of software, so the software proves what it is without a stored password. Cloud IAM roles work this way: the platform gives the process short-lived credentials tied to a role. So does SPIFFE, an open standard whose identity documents (SVIDs) are short-lived X.509 certificates or JWTs that a workload obtains from a local Workload API, with automatic rotation. The SPIFFE overview describes the standard.
The alternative is one shared API key in a config file. Everything that holds the key looks like the same caller, and a leak means replacing it everywhere. With a workload identity, each agent is a distinct caller whose credentials expire and rotate on their own. Keep any remaining long-lived keys where tools and test code cannot read them: files with strict permissions or a secret manager, and out of the environment. Rotate them on a schedule, and keep rotation cheap enough that you actually do it.
Telling the world you are an agent
When your agent calls someone else’s website or API, the other side may want to know that an automated agent is calling, and whose. They can then give agents different limits, apply different rules, or block abuse without blocking people.
A User-Agent header does not prove this, because anyone can copy any string.
A signed request does.
HTTP Message Signatures, RFC 9421, let a sender sign chosen parts of an HTTP message, such as the method, the target URI, and selected headers.
The signature travels in a Signature header, and a Signature-Input header says which components it covers and gives details such as a key identifier and creation time.
The receiver verifies the signature with the sender’s public key.
The standard is careful about its limits: a signature shows that someone holding a key made the request, and who that key belongs to depends on how the receiver finds and trusts it, which the standard leaves to applications. The IETF has a working group on this subject, Web Bot Auth. Its charter covers standardizing ways for automated clients, including AI agents retrieving content for users, to authenticate to websites. The charter excludes authenticating the end user and excludes APIs and agent-to-agent interfaces, so check its current documents before relying on it.
How agent protocols handle it
The Model Context Protocol (MCP) makes authorization optional.
For HTTP-based transports, its authorization specification builds on OAuth 2.1 (a draft consolidation of OAuth 2.0 practice): the MCP server acts as a resource server that accepts access tokens, the MCP client acts as an OAuth client making requests on behalf of the user, and a separate authorization server issues the tokens.
The specification requires clients to send a resource parameter naming the server the token is for (RFC 8707), and requires servers to accept only tokens issued for them.
Clients must not send an MCP server any token other than one issued by that server’s authorization server, and servers must not accept or pass along tokens meant for other services.
Those rules block one common confused-deputy path, where a server forwards a token it received to a different service.
For a local server started over stdio, the specification says implementations should not follow that flow and should instead take credentials from the environment. That puts the secret in the server process’s environment, which is why the previous sections about where keys live apply to it as well. These details come from the current MCP authorization specification, which was dated 2026-07-28 when this page was written; read the version you target, since it is still changing.
The audit trail
When something goes wrong, you need one record per action that answers the four questions from the opening story. Write it from the harness, at the moment of the call:
// Record is one entry in the audit log: for whom, which agent, which
// run, which credential, who approved it, what was tried, and whether
// it was allowed. It names the credential by ID, never by its token.
type Record struct {
Time time.Time `json:"time"`
User string `json:"user"`
Agent string `json:"agent"`
Run string `json:"run"`
Credential string `json:"credential"`
Approver string `json:"approver,omitzero"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
Reason string `json:"reason,omitzero"` // why it was refused
}
User, Agent, Run, and Credential come from the verified claims, so the record names the credential used as well as the person.
Approver is filled when a person approved the call, and Reason says why a call was refused.
Record denials too, since a pattern of refused calls is often the first sign of an injection.
Log the credential’s ID and never the token itself.
To make the log itself trustworthy, use the signed, chained log described in human approval, so edits and deletions show up.
Run it
From the code directory of the companion repository, with no API key:
$ go run ./identity
run run-7: agent support acting for u-42, scopes refund:42 read:orders
refund 42: refunded customer 42
refund 43: refund: refused: identity: scope not held: refund:43
narrow to refund:43: identity: scope not held: parent lacks refund:43
sub-agent reader, scopes read:orders, expires with its parent: true
refund 42: refund: refused: identity: scope not held: refund:42
audit log:
u-42 support run-7 cred-1 refund:42 allowed=true
u-42 support run-7 cred-1 refund:43 allowed=false
u-42 reader run-7 cred-2 refund:42 allowed=false
The first refund is the user’s own and goes through.
The second names another customer and is refused by the service, whatever the model was told.
The sub-agent can’t be granted a refund scope the parent lacks, and the one it holds, read:orders, can’t refund anything.
Every attempt is in the audit log, refused ones included.
go test ./identity checks the rest: an expired token, a token from another key, a tampered signature, and a request the transport must leave unchanged.
Where each identity and credential lives
One request, followed from the user to the service:
| Step | Who or what acts | Identity in play | Credential held here | What is recorded |
|---|---|---|---|---|
| 1. Request | The user, in a signed-in session | The user | The user’s own session | The user starts a run |
| 2. Harness | The agent’s loop, outside the model | The agent (workload identity) and the run | A narrow, short-lived delegated token for this user and run, minted here | Run ID, user, agent, credential ID |
| 3. Model | The model, inside the loop | None; it only proposes calls | None; the model never sees a secret | The call it proposed |
| 4. Tool | The tool wrapper | The agent acting for the user | Attaches the token to the outgoing request | Whether a person approved |
| 5. Service | The API being called | Reads user, agent, run from the token | Checks signature, expiry, and scope on every call | An audit record: user, agent, run, credential, approver, allowed or denied |
Read it top to bottom: the user’s authority narrows at step 2, the secret is attached at step 4, and the check that matters happens at step 5.
Questions to ask before an agent acts for real users
- Can you name the user, the agent, and the run for every action? If a log line has only an API key, the answer is no.
- Is each credential narrow? It lists the scopes this task needs and no more.
- Does each credential expire in minutes or hours, and is it tied to one user? Check that no password or shared long-lived key is in use.
- Does the model never see a secret? Look in the system prompt, tool results, logs, and error messages.
- Does the service check the credential on every call? A rule that exists only in the prompt does not count.
- Do sub-agents get a narrower credential than their parent, and can they widen it? They should not be able to.
- Does the software have its own workload identity, and are leftover keys out of tools’ reach and rotated?
- Is there an audit record with user, agent, run, approver, and credential ID, and can edits to it be detected?
Where next
- Prompt injection and sandboxing: the attack that most often confuses an agent, and the defenses outside the model.
- Asking a person before a risky tool call: the gate for actions that need a decision, and the tamper-evident audit log.
- Sub-agents: how a fresh agent is started from inside a tool.
- The Model Context Protocol: how tool servers are reached, and what runs in each process.
- An agent as a service: keeping keys out of the environment.
- Launch checklist: the wider list to check before leaving an agent running.
type Claims struct {
ID string `json:"jti"` // credential ID, for the audit log
User string `json:"sub"` // who the agent acts for
Agent string `json:"agent"` // which software
Run string `json:"run"` // which session or task
Scopes []string `json:"scopes"` // what it may do
Expires time.Time `json:"exp"`
}
Claims is what a credential says: who the agent acts for, which agent and which run hold it, what it may do, and until when.
Claims is what a credential says: who the agent acts for, which agent and which run hold it, what it may do, and until when. type Claims struct { ID string `json:"jti"` // credential ID, for the audit log User string `json:"sub"` // who the agent acts for Agent string `json:"agent"` // which software Run string `json:"run"` // which session or task Scopes []string `json:"scopes"` // what it may do Expires time.Time `json:"exp"` }. In identity/token.go, line 26.
field ID string `json:"jti"`
credential ID, for the audit log
credential ID, for the audit log field ID string `json:"jti"`. In identity/token.go, line 27.
type string string
string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text.
string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. type string string. Documented at pkg.go.dev, builtin.string.
field User string `json:"sub"`
who the agent acts for
who the agent acts for field User string `json:"sub"`. In identity/token.go, line 28.
field Agent string `json:"agent"`
which software
which software field Agent string `json:"agent"`. In identity/token.go, line 29.
field Run string `json:"run"`
which session or task
which session or task field Run string `json:"run"`. In identity/token.go, line 30.
field Scopes []string `json:"scopes"`
what it may do
what it may do field Scopes []string `json:"scopes"`. In identity/token.go, line 31.
field Expires time.Time `json:"exp"`
field Expires time.Time `json:"exp"`. In identity/token.go, line 32.
package time ("time")
Package time provides functionality for measuring and displaying time.
Package time provides functionality for measuring and displaying time. package time ("time"). Documented at pkg.go.dev, time.
type Time struct {
// wall and ext encode the wall time seconds, wall time nanoseconds,
// and optional monotonic clock reading in nanoseconds.
//
// From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
// a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
// The nanoseconds field is in the range [0, 999999999].
// If the hasMonotonic bit is 0, then the 33-bit field must be zero
// and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
// If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
// ...
}
A Time represents an instant in time with nanosecond precision.
A Time represents an instant in time with nanosecond precision. type Time struct { // wall and ext encode the wall time seconds, wall time nanoseconds, // and optional monotonic clock reading in nanoseconds. // // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic), // a 33-bit seconds field, and a 30-bit wall time nanoseconds field. // The nanoseconds field is in the range [0, 999999999]. // If the hasMonotonic bit is 0, then the 33-bit field must be zero // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext. // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit // ... }. Documented at pkg.go.dev, time.Time.
type Issuer struct {
Key ed25519.PrivateKey
}
Issuer mints credentials.
Issuer mints credentials. type Issuer struct { Key ed25519.PrivateKey }. In identity/token.go, line 46.
field Key ed25519.PrivateKey
field Key ed25519.PrivateKey. In identity/token.go, line 47.
package ed25519 ("crypto/ed25519")
Package ed25519 implements the Ed25519 signature algorithm.
Package ed25519 implements the Ed25519 signature algorithm. package ed25519 ("crypto/ed25519"). Documented at pkg.go.dev, crypto/ed25519.
type PrivateKey []byte
PrivateKey is the type of Ed25519 private keys.
PrivateKey is the type of Ed25519 private keys. type PrivateKey []byte. Documented at pkg.go.dev, crypto/ed25519.PrivateKey.
func (is Issuer) Mint(c Claims) (string, error)
Mint signs c.
Mint signs c. func (is Issuer) Mint(c Claims) (string, error). In identity/token.go, line 52.
type error interface {
Error() string
}
The error built-in interface type is the conventional interface for representing an error condition, with the nil value representing no error.
The error built-in interface type is the conventional interface for representing an error condition, with the nil value representing no error. type error interface { Error() string }. Documented at pkg.go.dev, builtin.error.
package json ("encoding/json/v2")
Package json implements semantic processing of JSON as specified in RFC 8259.
Package json implements semantic processing of JSON as specified in RFC 8259. package json ("encoding/json/v2"). Documented at pkg.go.dev, encoding/json/v2.
func Marshal(in any, opts ...Options) (out []byte, err error)
Marshal serializes a Go value as a []byte according to the provided marshal and encode options (while ignoring unmarshal or decode options).
Marshal serializes a Go value as a []byte according to the provided marshal and encode options (while ignoring unmarshal or decode options). func Marshal(in any, opts ...Options) (out []byte, err error). Documented at pkg.go.dev, encoding/json/v2.Marshal.
var nil Type
nil is a predeclared identifier representing the zero value for a pointer, channel, func, interface, map, or slice type.
nil is a predeclared identifier representing the zero value for a pointer, channel, func, interface, map, or slice type. var nil Type. Documented at pkg.go.dev, builtin.nil.
package fmt ("fmt")
Package fmt implements formatted I/O with functions analogous to C's printf and scanf.
Package fmt implements formatted I/O with functions analogous to C's printf and scanf. package fmt ("fmt"). Documented at pkg.go.dev, fmt.
func Errorf(format string, a ...any) (err error)
Errorf formats according to a format specifier and returns the string as a value that satisfies error.
Errorf formats according to a format specifier and returns the string as a value that satisfies error. func Errorf(format string, a ...any) (err error). Documented at pkg.go.dev, fmt.Errorf.
func Sign(privateKey PrivateKey, message []byte) []byte
Sign signs the message with privateKey and returns a signature.
Sign signs the message with privateKey and returns a signature. func Sign(privateKey PrivateKey, message []byte) []byte. Documented at pkg.go.dev, crypto/ed25519.Sign.
var enc *base64.Encoding
var enc *base64.Encoding. In identity/token.go, line 58.
package base64 ("encoding/base64")
Package base64 implements base64 encoding as specified by RFC 4648.
Package base64 implements base64 encoding as specified by RFC 4648. package base64 ("encoding/base64"). Documented at pkg.go.dev, encoding/base64.
var RawURLEncoding = URLEncoding.WithPadding(NoPadding)
RawURLEncoding is the unpadded alternate base64 encoding defined in RFC 4648.
encoding/base64.RawURLEncoding
RawURLEncoding is the unpadded alternate base64 encoding defined in RFC 4648. var RawURLEncoding = URLEncoding.WithPadding(NoPadding). Documented at pkg.go.dev, encoding/base64.RawURLEncoding.
func (enc *Encoding) EncodeToString(src []byte) string
EncodeToString returns the base64 encoding of src.
encoding/base64.Encoding.EncodeToString
EncodeToString returns the base64 encoding of src. func (enc *Encoding) EncodeToString(src []byte) string. Documented at pkg.go.dev, encoding/base64.Encoding.EncodeToString.
type Verifier struct {
Key ed25519.PublicKey
Now func() time.Time
}
Verifier checks credentials.
Verifier checks credentials. type Verifier struct { Key ed25519.PublicKey Now func() time.Time }. In identity/token.go, line 68.
field Key ed25519.PublicKey
field Key ed25519.PublicKey. In identity/token.go, line 69.
type PublicKey []byte
PublicKey is the type of Ed25519 public keys.
PublicKey is the type of Ed25519 public keys. type PublicKey []byte. Documented at pkg.go.dev, crypto/ed25519.PublicKey.
field Now func() time.Time
field Now func() time.Time. In identity/token.go, line 70.
func (v Verifier) Verify(token string) (Claims, error)
Verify checks token's signature and expiry and returns its claims.
Verify checks token's signature and expiry and returns its claims. func (v Verifier) Verify(token string) (Claims, error). In identity/token.go, line 75.
package strings ("strings")
Package strings implements simple functions to manipulate UTF-8 encoded strings.
Package strings implements simple functions to manipulate UTF-8 encoded strings. package strings ("strings"). Documented at pkg.go.dev, strings.
func Cut(s, sep string) (before, after string, found bool)
Cut slices s around the first instance of sep, returning the text before and after sep.
Cut slices s around the first instance of sep, returning the text before and after sep. func Cut(s, sep string) (before, after string, found bool). Documented at pkg.go.dev, strings.Cut.
var ErrMalformed = errors.New("identity: malformed token")
var ErrMalformed = errors.New("identity: malformed token"). In identity/token.go, line 16.
var enc *base64.Encoding
var enc *base64.Encoding. In identity/token.go, line 81.
func (enc *Encoding) DecodeString(s string) ([]byte, error)
DecodeString returns the bytes represented by the base64 string s.
encoding/base64.Encoding.DecodeString
DecodeString returns the bytes represented by the base64 string s. func (enc *Encoding) DecodeString(s string) ([]byte, error). Documented at pkg.go.dev, encoding/base64.Encoding.DecodeString.
package errors ("errors")
Package errors implements functions to manipulate errors.
Package errors implements functions to manipulate errors. package errors ("errors"). Documented at pkg.go.dev, errors.
func Join(errs ...error) error
Join returns an error that wraps the given errors.
Join returns an error that wraps the given errors. func Join(errs ...error) error. Documented at pkg.go.dev, errors.Join.
func Verify(publicKey PublicKey, message, sig []byte) bool
Verify reports whether sig is a valid signature of message by publicKey.
Verify reports whether sig is a valid signature of message by publicKey. func Verify(publicKey PublicKey, message, sig []byte) bool. Documented at pkg.go.dev, crypto/ed25519.Verify.
var ErrBadSignature = errors.New("identity: bad signature")
var ErrBadSignature = errors.New("identity: bad signature"). In identity/token.go, line 17.
func Unmarshal(in []byte, out any, opts ...Options) (err error)
Unmarshal decodes a []byte input into a Go value according to the provided unmarshal and decode options (while ignoring marshal or encode options).
Unmarshal decodes a []byte input into a Go value according to the provided unmarshal and decode options (while ignoring marshal or encode options). func Unmarshal(in []byte, out any, opts ...Options) (err error). Documented at pkg.go.dev, encoding/json/v2.Unmarshal.
func (t Time) Before(u Time) bool
Before reports whether the time instant t is before u.
Before reports whether the time instant t is before u. func (t Time) Before(u Time) bool. Documented at pkg.go.dev, time.Time.Before.
var ErrExpired = errors.New("identity: token expired")
var ErrExpired = errors.New("identity: token expired"). In identity/token.go, line 18.
func (t Time) Add(d Duration) Time
Add returns the time t+d.
Add returns the time t+d. func (t Time) Add(d Duration) Time. Documented at pkg.go.dev, time.Time.Add.
package time ("time")
Package time provides functionality for measuring and displaying time.
Package time provides functionality for measuring and displaying time. package time ("time"). Documented at pkg.go.dev, time.
const Minute = 60 * Second
const Minute = 60 * Second. Documented at pkg.go.dev, time.Minute.
type refunder struct {
client *http.Client
base string // the service's URL
}
refunder is the agent's refund tool.
refunder is the agent's refund tool. type refunder struct { client *http.Client base string // the service's URL }. In identity/service.go, line 59.
field base string
the service's URL
the service's URL field base string. In identity/service.go, line 61.
field client *http.Client
field client *http.Client. In identity/service.go, line 60.
package http ("net/http")
Package http provides HTTP client and server implementations.
Package http provides HTTP client and server implementations. package http ("net/http"). Documented at pkg.go.dev, net/http.
type Client struct {
// Transport specifies the mechanism by which individual
// HTTP requests are made.
// If nil, DefaultTransport is used.
Transport RoundTripper
// CheckRedirect specifies the policy for handling redirects.
// If CheckRedirect is not nil, the client calls it before
// following an HTTP redirect. The arguments req and via are
// the upcoming request and the requests made already, oldest
// ...
}
A Client is an HTTP client.
A Client is an HTTP client. type Client struct { // Transport specifies the mechanism by which individual // HTTP requests are made. // If nil, DefaultTransport is used. Transport RoundTripper // CheckRedirect specifies the policy for handling redirects. // If CheckRedirect is not nil, the client calls it before // following an HTTP redirect. The arguments req and via are // the upcoming request and the requests made already, oldest // ... }. Documented at pkg.go.dev, net/http.Client.
field Transport RoundTripper
Transport specifies the mechanism by which individual HTTP requests are made.
Transport specifies the mechanism by which individual HTTP requests are made. field Transport RoundTripper. Documented at pkg.go.dev, net/http.Client.Transport.
type attach struct {
token string
next http.RoundTripper
}
attach is an http.RoundTripper that adds a run's credential to every request a tool makes.
attach is an http.RoundTripper that adds a run's credential to every request a tool makes. type attach struct { token string next http.RoundTripper }. In identity/transport.go, line 14.
field next http.RoundTripper
field next http.RoundTripper. In identity/transport.go, line 16.
var DefaultTransport RoundTripper = &Transport{ ...
DefaultTransport is the default implementation of [Transport] and is used by [DefaultClient].
DefaultTransport is the default implementation of [Transport] and is used by [DefaultClient]. var DefaultTransport RoundTripper = &Transport{ .... Documented at pkg.go.dev, net/http.DefaultTransport.
package http ("net/http")
Package http provides HTTP client and server implementations.
Package http provides HTTP client and server implementations. package http ("net/http"). Documented at pkg.go.dev, net/http.
type RoundTripper interface {
// RoundTrip executes a single HTTP transaction, returning
// a Response for the provided Request.
//
// RoundTrip should not attempt to interpret the response. In
// particular, RoundTrip must return err == nil if it obtained
// a response, regardless of the response's HTTP status code.
// A non-nil err should be reserved for failure to obtain a
// response. Similarly, RoundTrip should not attempt to
// handle higher-level protocol details such as redirects,
// ...
}
RoundTripper is an interface representing the ability to execute a single HTTP transaction, obtaining the [Response] for a given [Request].
RoundTripper is an interface representing the ability to execute a single HTTP transaction, obtaining the [Response] for a given [Request]. type RoundTripper interface { // RoundTrip executes a single HTTP transaction, returning // a Response for the provided Request. // // RoundTrip should not attempt to interpret the response. In // particular, RoundTrip must return err == nil if it obtained // a response, regardless of the response's HTTP status code. // A non-nil err should be reserved for failure to obtain a // response. Similarly, RoundTrip should not attempt to // handle higher-level protocol details such as redirects, // ... }. Documented at pkg.go.dev, net/http.RoundTripper.
func (a attach) RoundTrip(r *http.Request) (*http.Response, error)
func (a attach) RoundTrip(r *http.Request) (*http.Response, error). In identity/transport.go, line 19.
type Request struct {
// Method specifies the HTTP method (GET, POST, PUT, etc.).
// For client requests, an empty string means GET.
Method string
// URL specifies either the URI being requested (for server
// requests) or the URL to access (for client requests).
//
// For server requests, the URL is parsed from the URI
// supplied on the Request-Line as stored in RequestURI. For
// ...
}
A Request represents an HTTP request received by a server or to be sent by a client.
A Request represents an HTTP request received by a server or to be sent by a client. type Request struct { // Method specifies the HTTP method (GET, POST, PUT, etc.). // For client requests, an empty string means GET. Method string // URL specifies either the URI being requested (for server // requests) or the URL to access (for client requests). // // For server requests, the URL is parsed from the URI // supplied on the Request-Line as stored in RequestURI. For // ... }. Documented at pkg.go.dev, net/http.Request.
type Response struct {
Status string // e.g. "200 OK"
StatusCode int // e.g. 200
Proto string // e.g. "HTTP/1.0"
ProtoMajor int // e.g. 1
ProtoMinor int // e.g. 0
// Header maps header keys to values. If the response had multiple
// headers with the same key, they may be concatenated, with comma
// delimiters. (RFC 7230, section 3.2.2 requires that multiple headers
// ...
}
Response represents the response from an HTTP request.
Response represents the response from an HTTP request. type Response struct { Status string // e.g. "200 OK" StatusCode int // e.g. 200 Proto string // e.g. "HTTP/1.0" ProtoMajor int // e.g. 1 ProtoMinor int // e.g. 0 // Header maps header keys to values. If the response had multiple // headers with the same key, they may be concatenated, with comma // delimiters. (RFC 7230, section 3.2.2 requires that multiple headers // ... }. Documented at pkg.go.dev, net/http.Response.
func (r *Request) Clone(ctx context.Context) *Request
Clone returns a deep copy of r with its context changed to ctx.
Clone returns a deep copy of r with its context changed to ctx. func (r *Request) Clone(ctx context.Context) *Request. Documented at pkg.go.dev, net/http.Request.Clone.
func (r *Request) Context() context.Context
Context returns the request's context.
Context returns the request's context. func (r *Request) Context() context.Context. Documented at pkg.go.dev, net/http.Request.Context.
field Header Header
Header contains the request header fields either received by the server or to be sent by the client.
Header contains the request header fields either received by the server or to be sent by the client. field Header Header. Documented at pkg.go.dev, net/http.Request.Header.
func (h Header) Set(key, value string)
Set sets the header entries associated with key to the single element value.
Set sets the header entries associated with key to the single element value. func (h Header) Set(key, value string). Documented at pkg.go.dev, net/http.Header.Set.
var resp *http.Response
var resp *http.Response. In identity/transport.go, line 22.
func (RoundTripper) RoundTrip(*Request) (*Response, error)
RoundTrip executes a single HTTP transaction, returning a Response for the provided Request.
net/http.RoundTripper.RoundTrip
RoundTrip executes a single HTTP transaction, returning a Response for the provided Request. func (RoundTripper) RoundTrip(*Request) (*Response, error). Documented at pkg.go.dev, net/http.RoundTripper.RoundTrip.
package fmt ("fmt")
Package fmt implements formatted I/O with functions analogous to C's printf and scanf.
Package fmt implements formatted I/O with functions analogous to C's printf and scanf. package fmt ("fmt"). Documented at pkg.go.dev, fmt.
type service struct {
verify Verifier
log *auditLog
}
service is a stand-in for the system an agent acts on: it refunds orders, and it trusts no caller, only credentials it can verify.
service is a stand-in for the system an agent acts on: it refunds orders, and it trusts no caller, only credentials it can verify. type service struct { verify Verifier log *auditLog }. In identity/service.go, line 15.
func (s *service) refund(w http.ResponseWriter, r *http.Request)
refund handles POST /refund?customer=ID.
refund handles POST /refund?customer=ID. func (s *service) refund(w http.ResponseWriter, r *http.Request). In identity/service.go, line 25.
var w http.ResponseWriter
var w http.ResponseWriter. In identity/service.go, line 25.
package http ("net/http")
Package http provides HTTP client and server implementations.
Package http provides HTTP client and server implementations. package http ("net/http"). Documented at pkg.go.dev, net/http.
type ResponseWriter interface {
// Header returns the header map that will be sent by
// [ResponseWriter.WriteHeader]. The [Header] map also is the mechanism with which
// [Handler] implementations can set HTTP trailers.
//
// Changing the header map after a call to [ResponseWriter.WriteHeader] (or
// [ResponseWriter.Write]) has no effect unless the HTTP status code was of the
// 1xx class or the modified headers are trailers.
//
// There are two ways to set Trailers. The preferred way is to
// ...
}
A ResponseWriter interface is used by an HTTP handler to construct an HTTP response.
A ResponseWriter interface is used by an HTTP handler to construct an HTTP response. type ResponseWriter interface { // Header returns the header map that will be sent by // [ResponseWriter.WriteHeader]. The [Header] map also is the mechanism with which // [Handler] implementations can set HTTP trailers. // // Changing the header map after a call to [ResponseWriter.WriteHeader] (or // [ResponseWriter.Write]) has no effect unless the HTTP status code was of the // 1xx class or the modified headers are trailers. // // There are two ways to set Trailers. The preferred way is to // ... }. Documented at pkg.go.dev, net/http.ResponseWriter.
field URL *url.URL
URL specifies either the URI being requested (for server requests) or the URL to access (for client requests).
URL specifies either the URI being requested (for server requests) or the URL to access (for client requests). field URL *url.URL. Documented at pkg.go.dev, net/http.Request.URL.
func (u *URL) Query() Values
Query parses RawQuery and returns the corresponding values.
Query parses RawQuery and returns the corresponding values. func (u *URL) Query() Values. Documented at pkg.go.dev, net/url.URL.Query.
func (v Values) Get(key string) string
Get gets the first value associated with the given key.
Get gets the first value associated with the given key. func (v Values) Get(key string) string. Documented at pkg.go.dev, net/url.Values.Get.
package strings ("strings")
Package strings implements simple functions to manipulate UTF-8 encoded strings.
Package strings implements simple functions to manipulate UTF-8 encoded strings. package strings ("strings"). Documented at pkg.go.dev, strings.
func CutPrefix(s, prefix string) (after string, found bool)
CutPrefix returns s without the provided leading prefix string and reports whether it found the prefix.
CutPrefix returns s without the provided leading prefix string and reports whether it found the prefix. func CutPrefix(s, prefix string) (after string, found bool). Documented at pkg.go.dev, strings.CutPrefix.
func (h Header) Get(key string) string
Get gets the first value associated with the given key.
Get gets the first value associated with the given key. func (h Header) Get(key string) string. Documented at pkg.go.dev, net/http.Header.Get.
const StatusUnauthorized = 401
RFC 9110, 15.5.2
RFC 9110, 15.5.2 const StatusUnauthorized = 401. Documented at pkg.go.dev, net/http.StatusUnauthorized.
func (c Claims) Has(scope string) bool
Has reports whether c grants scope.
Has reports whether c grants scope. func (c Claims) Has(scope string) bool. In identity/token.go, line 38.
package fmt ("fmt")
Package fmt implements formatted I/O with functions analogous to C's printf and scanf.
Package fmt implements formatted I/O with functions analogous to C's printf and scanf. package fmt ("fmt"). Documented at pkg.go.dev, fmt.
var ErrScope = errors.New("identity: scope not held")
var ErrScope = errors.New("identity: scope not held"). In identity/token.go, line 19.
const StatusForbidden = 403
RFC 9110, 15.5.4
RFC 9110, 15.5.4 const StatusForbidden = 403. Documented at pkg.go.dev, net/http.StatusForbidden.
type Record struct {
Time time.Time `json:"time"`
User string `json:"user"`
Agent string `json:"agent"`
Run string `json:"run"`
Credential string `json:"credential"`
Approver string `json:"approver,omitzero"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
Reason string `json:"reason,omitzero"` // why it was refused
}
Record is one entry in the audit log: for whom, which agent, which run, which credential, who approved it, what was tried, and whether it was allowed.
Record is one entry in the audit log: for whom, which agent, which run, which credential, who approved it, what was tried, and whether it was allowed. type Record struct { Time time.Time `json:"time"` User string `json:"user"` Agent string `json:"agent"` Run string `json:"run"` Credential string `json:"credential"` Approver string `json:"approver,omitzero"` Action string `json:"action"` Allowed bool `json:"allowed"` Reason string `json:"reason,omitzero"` // why it was refused }. In identity/audit.go, line 13.
field Time time.Time `json:"time"`
field Time time.Time `json:"time"`. In identity/audit.go, line 14.
field User string `json:"user"`
field User string `json:"user"`. In identity/audit.go, line 15.
field Agent string `json:"agent"`
field Agent string `json:"agent"`. In identity/audit.go, line 16.
field Run string `json:"run"`
field Run string `json:"run"`. In identity/audit.go, line 17.
field Credential string `json:"credential"`
field Credential string `json:"credential"`. In identity/audit.go, line 18.
field Action string `json:"action"`
field Action string `json:"action"`. In identity/audit.go, line 20.
field Allowed bool `json:"allowed"`
field Allowed bool `json:"allowed"`. In identity/audit.go, line 21.
field Reason string `json:"reason,omitzero"`
why it was refused
why it was refused field Reason string `json:"reason,omitzero"`. In identity/audit.go, line 22.
func (error).Error() string
func (error).Error() string.
func (l *auditLog) add(r Record)
func (l *auditLog) add(r Record). In identity/audit.go, line 35.
func Error(w ResponseWriter, error string, code int)
Error replies to the request with the specified error message and HTTP code.
Error replies to the request with the specified error message and HTTP code. func Error(w ResponseWriter, error string, code int). Documented at pkg.go.dev, net/http.Error.
func Fprintf(w io.Writer, format string, a ...any) (n int, err error)
Fprintf formats according to a format specifier and writes to w.
Fprintf formats according to a format specifier and writes to w. func Fprintf(w io.Writer, format string, a ...any) (n int, err error). Documented at pkg.go.dev, fmt.Fprintf.
func Narrow(parent Claims, g Grant, now time.Time) (Claims, error)
Narrow derives a sub-agent's claims from its parent's: the same user and run, its own ID and agent name, only scopes the parent holds, and an expiry no later than the parent's.
Narrow derives a sub-agent's claims from its parent's: the same user and run, its own ID and agent name, only scopes the parent holds, and an expiry no later than the parent's. func Narrow(parent Claims, g Grant, now time.Time) (Claims, error). In identity/narrow.go, line 22.
type Grant struct {
ID string // the child credential's ID
Agent string // the sub-agent's name
Scopes []string // what the sub-agent may do
TTL time.Duration // how long it may do it
}
Grant is what a parent asks for when it starts a sub-agent.
Grant is what a parent asks for when it starts a sub-agent. type Grant struct { ID string // the child credential's ID Agent string // the sub-agent's name Scopes []string // what the sub-agent may do TTL time.Duration // how long it may do it }. In identity/narrow.go, line 9.
package time ("time")
Package time provides functionality for measuring and displaying time.
Package time provides functionality for measuring and displaying time. package time ("time"). Documented at pkg.go.dev, time.
field Scopes []string
what the sub-agent may do
what the sub-agent may do field Scopes []string. In identity/narrow.go, line 12.
package fmt ("fmt")
Package fmt implements formatted I/O with functions analogous to C's printf and scanf.
Package fmt implements formatted I/O with functions analogous to C's printf and scanf. package fmt ("fmt"). Documented at pkg.go.dev, fmt.
field ID string
the child credential's ID
the child credential's ID field ID string. In identity/narrow.go, line 10.
field Agent string
the sub-agent's name
the sub-agent's name field Agent string. In identity/narrow.go, line 11.
field TTL time.Duration
how long it may do it
how long it may do it field TTL time.Duration. In identity/narrow.go, line 13.
func (t Time) After(u Time) bool
After reports whether the time instant t is after u.
After reports whether the time instant t is after u. func (t Time) After(u Time) bool. Documented at pkg.go.dev, time.Time.After.
package fmt ("fmt")
Package fmt implements formatted I/O with functions analogous to C's printf and scanf.
Package fmt implements formatted I/O with functions analogous to C's printf and scanf. package fmt ("fmt"). Documented at pkg.go.dev, fmt.
func result(out string, err error) string
result formats a tool's outcome for the demo's output.
result formats a tool's outcome for the demo's output. func result(out string, err error) string. In identity/main.go, line 125.
package time ("time")
Package time provides functionality for measuring and displaying time.
Package time provides functionality for measuring and displaying time. package time ("time"). Documented at pkg.go.dev, time.
field Approver string `json:"approver,omitzero"`
field Approver string `json:"approver,omitzero"`. In identity/audit.go, line 19.
type bool bool
bool is the set of boolean values, true and false.
bool is the set of boolean values, true and false. type bool bool. Documented at pkg.go.dev, builtin.bool.
func Is(err, target error) bool
Is reports whether any error in err's tree matches target.
Is reports whether any error in err's tree matches target. func Is(err, target error) bool. Documented at pkg.go.dev, errors.Is.