MGS1026: a cacheable target reaches for a credential
A target reaches for a credential - magus\secret.read or magus\secret.endpoint - and
is still cacheable:
[fail] cacheable secret reads: 1 target(s) read a credential but may replay from cache,
so a rotated or revoked credential invalidates nothing and the target reports success
without authenticating; declare skip_cache with a reason
.: target "image-login" reads a secret and is cacheable (magusfile.buzz)
Why this is a failure you would not otherwise see
A resolved credential contributes nothing to the cache key. That is deliberate: hashing one would write the credential into cache metadata and partition your cache per rotation. The consequence is that rotating or revoking a credential invalidates nothing.
For most targets that is harmless. For an authentication target it is not, and the
-login convention magus recommends makes it more likely
rather than less. Giving authentication its own small target is good practice for every other
reason - it keeps the privileged, interactive step one you asked for by name - but it also
means the target's sources almost never change. So it becomes a permanent cache hit that never
contacts the provider, never authenticates, and reports success.
The build does not fail there. It fails later, in the push or deploy that assumed the login worked, with the registry's own 401 and nothing connecting it to the green step above it.
The fix
Declare the target uncacheable, with the reason:
magus\project({
"targets": {
"image-login": {"skip_cache": "authenticates to a registry per invocation; a replay would reuse stale credentials"},
},
})
skip_cache takes a reason string rather than a boolean on purpose, and this is the case it
was written for: six months from now the reason is what tells a reader the target is
uncacheable by decision rather than by accident.
The same applies to any target whose output is a function of a credential - a signed artifact, a fetch from a private registry, a deploy. The test is simple: if revoking the credential should change what the target produces, the target cannot be replayable.
When to ignore it
This is a warning, not a load error, because a target can legitimately read a credential and
still produce a cacheable artifact - fetching a dependency whose content is addressed by hash,
for instance, where the credential authorizes the fetch but does not determine the result. Only
the author knows. Declaring skip_cache with a reason is still the cheaper answer unless the
target is expensive and provably content-addressed.
What this check cannot see
It is a static read of the target body, so it finds a direct magus\secret.read call. A
read behind a helper the static walk cannot reach is invisible to it, exactly like an unreached
ctx.readsFiles (MGS1004). It under-reports rather than over-reports, which is
the right direction for a check whose remedy is to opt a target out of the cache.
It also does not see Command.secrets on a spell op, which resolves at spawn rather than in a
target body.
See also
- Secrets - why a credential is not in the cache key
- Cache - what the key does hash
- MGS1004 - the sibling check for footprint declarations the walk cannot reach
An endpoint carries this in a sharper form
magus\secret.endpoint counts here too, and the hazard is worse than for a read rather
than milder.
With a read, the credential at least passes through the magusfile, so an author has some chance of noticing the target never ran. With an endpoint the value never appears at all: the file names a reference and a host, and magus attaches the credential on the way upstream. So changing that reference - staging to production, a rotated vault path, a different service account - alters nothing the cache can see. The sources are identical, the target replays, and the output it replays was produced with the other credential.
That is a wrong build rather than a stale login, which is why the fix is the same and the reason to apply it is stronger:
ctx.skip_cache("reaches for a credential, which contributes nothing to the cache key");