magus v0.4.2 is out. See what's new
¶ View markdown source · ✎ Suggest an edit
3 min read

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");
MGS1026magusfilesecretscacheskip_cachecredentialsreplay
Last updated (6b3c7db4)
Earlier changes on this page (1)

Full history ↗ · Blame source ↗

Glossary

Project

A directory magus recognizes as a unit of work (it has a magusfile); the unit of caching, scheduling, and dependency tracking. See workspace.

Magusfile

The magusfile.buzz that declares a project's targets (as export funs) and binds its spells. See targets.

Target

A named operation (build, test, ...) you invoke with magus run <target>; it may compose a spell's tool-native operations and depend on other targets. See targets.

Op

A single tool-native command a target composes (long form: operation); the middle of the work hierarchy (Spell to Op to Target). See operations.

Spell

A language/runtime adapter (e.g. go, md) that maps generic targets onto a toolchain's real commands. See spells.

Buzz

The language magusfiles are written in (the .buzz engine). See engines.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. See cache.

Service

A long-running or shared process magus manages across runs, distinct from a one-shot target. See services.

CI

An ordinary magusfile-defined target you compose yourself with magus\needs - magus does not hardcode its stages. Magus.RunCI treats it specially only in that it strips the rw charm, it is the anchor magus affected ci keys off, and a selected scope with no project declaring it is a load error rather than a silent no-op. See targets.

Conventions

This page uses none of the site's convention markers. The full set is on the conventions page.