From ccc604aef720201c45e1a643a37cb6cbcba92ab1 Mon Sep 17 00:00:00 2001 From: Timothy Date: Sat, 5 Sep 2026 00:47:16 +0200 Subject: [PATCH] fix(885): the never-asked message reports what the RUN did, not what the registry sent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "WITHOUT issuing a Bearer challenge" is a claim about the registry's response that this script never checks. `probe` enters the token leg on a `401` only, so a `403` carrying a perfectly good `Www-Authenticate` would be refused with that sentence having never looked at the header — the same defect one branch over, in the message written to fix it. It now says NO TOKEN WAS EVER REQUESTED, which is a fact about the run: the token leg was not entered, and this answer was never followed as a challenge. The assertion and the outcome-table row move with it, and the comment says why the weaker claim is the honest one. Refs #885 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV --- docs/ci-cd.md | 2 +- scripts/ci-toolchain-image-resolves.sh | 13 ++++++++----- scripts/tests/test_ci_toolchain_image_resolves.py | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/ci-cd.md b/docs/ci-cd.md index e73683619..ad710297a 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -2454,7 +2454,7 @@ because they send you to different places: | HTTP 200, body is not a manifest | **red** | something is answering for the registry (proxy, login page) | | 401 / 403 that survives a bearer the run actually obtained | **red** | the registry `refused an ANONYMOUS read ... even after a Bearer token was obtained` — about the PACKAGE: check that `timothy/ersatztv` and its `ersatztv-ci` package are still PUBLIC | | 401 whose token leg yielded no bearer — no `Www-Authenticate` at all, a challenge naming no realm, or a token endpoint answering with no token | **red** | `could NOT OBTAIN an anonymous pull token` — about the registry's TOKEN ENDPOINT, not the package's visibility. Not retried | -| 403 on the FIRST read, before any token leg — `probe` enters it on a `401` only, so nothing was ever asked for | **red** | `refused an ANONYMOUS read ... WITHOUT issuing a Bearer challenge` — about ACCESS to the registry: visibility, or something answering for it | +| 403 on the FIRST read, before any token leg — `probe` enters it on a `401` only | **red** | `refused an ANONYMOUS read ... and NO TOKEN WAS EVER REQUESTED` — it reports what the RUN did (never followed this answer as a challenge), not what the registry sent. About ACCESS to the registry: visibility, or something answering for it | | anything else (5xx, unreachable, no `curl`) | **red** after `ETV_CI_ATTEMPTS` tries | `could NOT VERIFY` — check the registry's health, NOT the pin | **The three refusal rows say only what actually ran**, and that is the point of wording them apart diff --git a/scripts/ci-toolchain-image-resolves.sh b/scripts/ci-toolchain-image-resolves.sh index edf8265de..41ea0dab1 100755 --- a/scripts/ci-toolchain-image-resolves.sh +++ b/scripts/ci-toolchain-image-resolves.sh @@ -203,10 +203,13 @@ for pin in $pins; do # * a challenge that yielded no usable token is an infrastructure answer about the TOKEN # ENDPOINT; # * a FIRST-READ 403 never reached the token leg at all — `probe` enters it on a 401 only — - # so nothing was ever asked for. This is an answer about ACCESS to the registry. Note the - # boundary: a 401 carrying no `Www-Authenticate` DOES enter the token leg and abandon it, - # so it lands in the row above, not this one. The branch order below is `token` first - # precisely so this case cannot borrow either of the other two mechanisms. + # so nothing was ever asked for. This is an answer about ACCESS to the registry. Its + # message says NO TOKEN WAS EVER REQUESTED rather than "the registry issued no challenge": + # a 403 MAY carry a `Www-Authenticate` and this script would still not follow it, so only + # the first is something the run observed. Note the boundary too: a 401 carrying no + # challenge DOES enter the token leg and abandon it, so it lands in the arm above, not + # this one. The branch order below is `token` first precisely so this case cannot borrow + # either of the other two mechanisms. # # A failed token leg is NOT retried, deliberately: `token_leg_done` is set before the attempt # so a registry genuinely refusing anonymous reads is asked once rather than once per pin. The @@ -218,7 +221,7 @@ for pin in $pins; do if [ "$token_leg_done" -eq 1 ]; then fail "could NOT OBTAIN an anonymous pull token for $registry/$image_repo:$pin — the registry answered HTTP $code and the token leg produced none: either there was no Www-Authenticate challenge, or it named no realm, or the token endpoint did not answer with a token. The pin was NOT checked. Look at the registry's token endpoint, not at the pin." fi - fail "the registry refused an ANONYMOUS read (HTTP $code) of $registry/$image_repo:$pin WITHOUT issuing a Bearer challenge, so no token was ever requested and the pin could not be checked. A challenge is what tells a client where a token can be had; an outright refusal is an answer about ACCESS to the registry, not about the pin. Every container: job pulls this image without a credential too, so they will fail at image pull. Check that timothy/ersatztv and its ersatztv-ci package are still PUBLIC, and that nothing (a proxy, an ACL) is answering for the registry — do not read this as a pass." + fail "the registry refused an ANONYMOUS read (HTTP $code) of $registry/$image_repo:$pin and NO TOKEN WAS EVER REQUESTED: the token leg is entered on a 401 only, so this answer was never followed as a Bearer challenge and the pin could not be checked. An outright refusal is an answer about ACCESS to the registry, not about the pin. Every container: job pulls this image without a credential too, so they will fail at image pull. Check that timothy/ersatztv and its ersatztv-ci package are still PUBLIC, and that nothing (a proxy, an ACL) is answering for the registry — do not read this as a pass." ;; *) # NOT gone, and NOT a pass either. Deliberately worded apart from the 404 message: this sends diff --git a/scripts/tests/test_ci_toolchain_image_resolves.py b/scripts/tests/test_ci_toolchain_image_resolves.py index 9a117eaa6..b974186c0 100644 --- a/scripts/tests/test_ci_toolchain_image_resolves.py +++ b/scripts/tests/test_ci_toolchain_image_resolves.py @@ -318,7 +318,7 @@ def test_a_refusal_with_NO_CHALLENGE_never_claims_a_token_was_obtained(preflight assert preflight.authenticated_manifest_calls() == [], "no read can have carried a token here" if refusal == "403": assert preflight.token_calls() == [], "a 403 first read must not even ask for a token" - assert "WITHOUT issuing a Bearer challenge" in result.stderr, result.stderr + assert "NO TOKEN WAS EVER REQUESTED" in result.stderr, result.stderr assert "PUBLIC" in result.stderr, "the message must still name a cause an operator can act on" else: # A challenge-less 401 DOES enter the token leg (and abandons it for want of a realm), so it