Skip to content

fix(docker): office image no longer gets :latest on a release — latest=false - #184

Open
MrChengLen wants to merge 1 commit into
mainfrom
pr-docker-latest-flavor
Open

MrChengLen wants to merge 1 commit into
mainfrom
pr-docker-latest-flavor

Conversation

@MrChengLen

Copy link
Copy Markdown
Owner

What

docker.yml's "Extract metadata" step (docker/metadata-action v6.2.0, pinned) gets flavor: latest=false. :latest now comes only from the existing type=raw entry, which names it for the slim (base) leg; the office image keeps :office. Every other tag is unchanged.

Why

metadata-action's default flavor, latest=auto, adds a latest tag of its own whenever a type=semver entry matches a non-prerelease tag. A tag entry's suffix=-office does not apply to it: src/meta.ts runs procSemver → setVersion(latest=true), and generateTags builds latest from the global flavor prefix/suffix only. So on every v* release both matrix legs pushed :latest, and whichever finished last kept it, usually the office image (the slower build). Anyone pulling :latest then got LibreOffice without choosing the office image, and with the default FILEMORPH_OFFICE_ENGINE=auto, complex DOCX files went through it.

The v1.1.0 run's logs have expired (HTTP 410), but its job timestamps show the office build-and-push step ending 12 s after the slim one's. So on 2026-06-01, :latest was most likely the office image for about an hour, until the next main build. Main builds were never affected, because the action adds latest only for tag refs. Found by the security review of #180.

How it was checked

  • Source at the pinned SHA dc80280… (= v6.2.0). src/flavor.ts, src/meta.ts and src/tag.ts are byte-identical in v6.1.0, which built v1.1.0.

  • The action's own code: I ran dist/index.cjs (blob 9edb5c8, hash-checked) locally on the workflow's inputs, against a localhost API stub and without credentials:

    ref leg before after
    v1.2.0 base 1.2.0, 1.2, latest, sha-…, latest 1.2.0, 1.2, latest, sha-…
    v1.2.0 office 1.2.0-office, 1.2-office, office, sha-…-office, latest 1.2.0-office, 1.2-office, office, sha-…-office
    v1.3.0-rc.1 base / office latest on base only unchanged
    main base / office latest, sha-… / office, sha-…-office unchanged
  • Guard: test_only_the_slim_image_is_tagged_latest in tests/test_supply_chain_hygiene.py does three things:

    • It pins the flavor whole, as exactly latest=false. A looser parser passed #old, latest=false and 'latest=false,"latest=true"', because the action skips # lines and unquotes CSV fields.
    • It fails if another tag entry of the step names latest. As in the action, only a line starting with # is a comment, so an indented # type=raw,value=latest counts as an entry.
    • It fails if the matrix loses the unsuffixed base leg that the raw entry is meant for.

    I ran 26 mutations through both the guard and the real action:

    Mutations Guard
    15 that tag the wrong image all fail
    1 the action rejects, 1 ${{ vars.X }} line, 1 swapped-out action all fail
    4 controls pass
    4 equivalent spellings fail on purpose (exact pin)

    There were 0 misses.

  • Reviews: code-reviewer and security-auditor both said ready. Their suggestions are folded in: the exact pin, the base-leg check, the comment rule for tag lines, precise CHANGELOG wording and a shorter entry.

  • The full suite passes (1489 on main 7c4a46c, 72 skipped on Windows). ruff, gitleaks and the scope guard are clean.

  • The docs (docs/self-hosting.md, docs/formats.md, docs/patch-policy.md, docs/release-signing.md) already call :latest the slim image. That is now true right after a release too, so no doc change was needed.

Not verifiable before merge

docker.yml pushes :latest/:office, so it was not dispatched on this branch.

  • After the merge: the main run's "Extract metadata" log shows latest=false under "Processing flavor input". It pushes the same tags as before.
  • After the next release: docker buildx imagetools inspect ghcr.io/mrchenglen/filemorph:latest must show the digest of :X.Y.Z, not :X.Y.Z-office.

Out of scope (follow-up)

The raw entry has no enable= condition. So any docker.yml run on a ref other than main moves :latest/:office: a pre-release tag, a backport tag, or a manual run on a branch. There is also no concurrency: group. This is filed as a separate task.

🤖 Generated with Claude Code

…t=false

docker/metadata-action's default flavor, latest=auto, adds a `latest` tag
of its own whenever a type=semver entry matches a non-prerelease tag, and
a tag entry's suffix= does not reach that tag. On every v* release both
matrix legs therefore pushed :latest, and whichever finished last kept
it — usually the office image, the slower build. Anyone pulling :latest
then got LibreOffice without choosing the office image, and with the
default FILEMORPH_OFFICE_ENGINE=auto complex DOCX files went through it.
The v1.1.0 run's logs have expired; its timestamps show the office
build-and-push step ending 12 s after the slim one's, so on 2026-06-01
:latest was most likely the office image for about an hour, until the
next main build. Main builds were never affected: the action adds
`latest` only for tag refs.

flavor: latest=false leaves the type=raw entry as the only source of
:latest, set for the base leg; the office image keeps :office, and every
other tag is unchanged. Rejected: keeping latest=auto for the base leg
only (the raw entry already tags it on every build) and a per-leg
`suffix=-office,onlatest=true` flavor (it would add a new
:latest-office tag).

Confirmed from the action's source at the pinned SHA (src/flavor.ts;
src/meta.ts procSemver, setVersion, generateTags; identical in v6.1.0,
which built v1.1.0) and by running its dist/index.cjs (blob 9edb5c8,
hash-checked) locally on the workflow's inputs, against a localhost API
stub and without credentials: before, tag v1.2.0 gave the office leg
`latest`; after, only the slim leg gets it, on a release tag, a
pre-release tag and main.

Guard: test_only_the_slim_image_is_tagged_latest pins the flavor whole
(the action skips only lines starting with `#` and unquotes CSV fields,
so a looser match passed flavors it reads as latest=auto or
latest=true), fails if another tag entry of the step names `latest`
(an indented `# ...` line is an entry to the action, not a comment),
and fails if the matrix loses the unsuffixed base leg the raw entry is
meant for. Of 26 mutations run through both the guard and the real
action, the guard fails all 15 that tag the wrong image, plus 3 more
(an entry the action rejects, a `${{ vars.X }}` line, a swapped-out
action); 4 controls pass; 4 equivalent spellings fail on purpose. Both
reviews' suggestions are folded in.

docker.yml pushes images, so it was not dispatched on this branch; the
first main run shows the new flavor in its log, and the first real check
of the release tags is the next release (main runs create no semver
tags). Found by the security review of PR #180.

Full suite 1489 green (72 skipped on Windows); ruff clean; gitleaks and
the scope guard clean. No dependency, template or i18n change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant