Skip to content

Holding credentials is a posture

This module offers two ambient rungs that do the same work and differ only in what they keep:

awsclient.Ambient(...)   // resolve once, share the result
awsclient.PerCall(...)   // resolve afresh every time, retain nothing

PerCall is not a degraded Ambient, and it is not there for symmetry. It exists because how long a component holds a credential is a security decision that belongs to the component, not to this module.

The case that forced it

go/signing-aws-kms resolves AWS configuration inside every NewSigner call, and says why in its own comment: there is exactly one Sign per mint, so resolving lazily costs nothing measurable, and doing so avoids holding AWS credentials longer than the operation requires.

Had this module offered only a memoising rung, that component would have faced a choice between keeping its own duplicate resolution logic — defeating the point of a shared module — or changing its security posture to adopt ours. Neither is acceptable, so the non-memoising rung is part of the contract.

Both return the same Source interface, so swapping posture is one word at the construction site and nothing else changes.

Concurrent PerCall calls are deliberately not collapsed

Ambient collapses a concurrent wave into a single attempt. PerCall does not — it resolves separately for each caller.

That looks wasteful until you state what collapsing would mean: sharing one resolution between callers who explicitly asked not to share one. The saving would come at the cost of the property the rung exists to provide.

Why isolation is the default

Two components that each take their ambient default resolve the chain twice. That is intended.

A hidden process-wide cache would save the duplication and cost more than it saves: it would silently share credentials between components that may deliberately differ — a different profile, an assumed role, a distinct endpoint — and it would make one component's transient failure everybody's, invisibly.

So sharing is something you do on purpose, by building one source and injecting it. That is what this module is for.

The footprint boundary

Resolving an ambient AWS credential chain is not free in dependency terms. On the config adapters it measures at +7 to +10 modulescredentials, sso, ssooidc, sts, imds and their transitive graph.

That cost is real, and it is why importing this module is an explicit act rather than something an adapter does on your behalf. The AWS-backed config adapters put their ambient rung behind an ambient subpackage precisely so that a consumer who never wants ambient credentials never pays for them, and each subpackage carries a test stating its own cost.

The rule the estate follows: an adapter's root package must not grow the credential graph. If you never import the ambient subpackage, your dependency footprint is exactly what it was before the ladder existed.

What refreshes, and what does not

An aws.Config from the default chain wraps its provider in aws.NewCredentialsCache, so credentials renew underneath you. A long-lived process holding one from Ambient does not go stale.

That is a property of AWS, not of this module, and it is not universal — a Vault or Consul token does not renew itself. Where a value cannot renew, clientlifecycle offers an invalidatable strategy; AWS does not need it, and adopting it here would be strictly worse.

Where this is specified

org spec 0003 — isolation as the default (P-5), the non-memoised rung (P-12), and the import boundary where an ambient rung would enlarge a graph (P-7).