magus v0.4.3 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 "spell-publish" 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 a target that publishes with the credential it is not: its sources rarely change between runs, so it becomes a cache hit that never contacts the provider, never pushes, and reports success.

Nothing fails there, or later. The release goes out without what that target should have published, on a pipeline whose publish step is green.

The fix

Declare the target uncacheable, with the reason:

import "magus";

magus\project({
    "targets": {
        "spell-publish": {"skip_cache": "pushes to a registry per invocation; a replay would publish nothing"},
    },
})

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 (895d94e4)
Earlier changes on this page (4)

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. Project in full.

Magusfile

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

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. Target in full.

Op

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

Spell

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

Buzz

The language magusfiles are written in (the .buzz engine). Buzz in full.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. Cache in full.

Service

A long-running or shared process magus manages across runs, distinct from a one-shot target. Service in full.

CI

An ordinary target you compose yourself with ctx.needs; magus does not hardcode its stages. It is the anchor magus affected ci keys off, and ci strips the rw charm. CI in full.

Invocation

One magus process's recorded facts (the targets it finished, their outcomes, the lease it acted as, and the session it ran in when a host delivered one), kept in a repo-scoped store every worktree shares. magus session lists them; the store prunes itself by last-fact age. Invocation in full.

Run

One target executing under one magus invocation, such as magus run test web or magus affected ci. A run keeps its captured output behind an output reference. Every magus run is a run whether or not any job asked for it; see Job for how the two relate. Run in full.

Lease

The grant a holder takes on a job: the write and read paths that job declared, enforced in the checkout that took it with magus job exec. A job is the piece of work; a lease is permission over it. Lease in full.

Conventions

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