release: 0.6.0 — fix dash-prefixed credential leak, *.* allowed_hosts bypass, and publish-path digest verification - #49
Merged
Conversation
…tation
`docker buildx imagetools create` wraps the scanned manifest in a new OCI
index rather than reusing its digest, so comparing the tag's own digest
against steps.build.outputs.digest compared an index to one of its children
and failed on every push to main. Because the check sits before the
attestation step, images published since 2026-07-26 carry no provenance.
- Verify the set of image manifests a tag serves is exactly {scanned digest},
ignoring attestation manifests, instead of hashing raw manifest bytes.
- Confirmed against the live registry: :latest was index sha256:8204263e
whose sole child was the scanned sha256:12db37ce.
- Regression test fails on the previous workflow and passes on this one.
The Trivy gate was never bypassed: the scan runs against the digest before
any tag is promoted.
…osts, cut 0.6.0 Two credential/network security defects found while preparing the release, both pre-existing on main: - _redact_secrets left a space-separated credential value unredacted when it began with "-" (mysql --password -Xy9$kL2mQp leaked verbatim). The guard treating a following dash token as "this flag was boolean" is narrowed from "-" to "--", which keeps the earlier skip-leak fix intact. A value beginning with "--" remains a documented residual. - SSH_MCP_HTTP_ALLOWED_HOSTS=*.* was accepted despite being in the refusal set: the suffix-wildcard allowance was evaluated first. Validation now strips only the two permitted wildcard forms and demands a concrete remainder, which also refuses infix wildcards and whitespace-only values. Also: reorder except asyncio.TimeoutError before except OSError, where it was unreachable dead code on Python >=3.11, restoring the specific timeout message; make test_fuzzed_long_flag_junk_prefix_is_unbounded assert exact output so a secret that is a substring of the flag name no longer reports a false leak; bump to 0.6.0 and close the CHANGELOG entry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prepares the 0.6.0 release. Three defects, all confirmed by execution and all pre-existing on
main.1. Credential leak: dash-prefixed values (security)
_redact_secretsleft a space-separated credential value unredacted when it began with-, so it reached logs and OTel spans in plaintext. Generated passwords routinely start with-, so this is not a corner case.The guard that treats a following dash token as "this flag was boolean" existed to fix an earlier real leak (a credential flag following a boolean flag being silently skipped). It is narrowed from
-to--rather than removed, so that fix stays intact —--password --token=xstill redacts--token=x.Documented residual: a value beginning with
--is still treated as a flag and not redacted. Widening further would reopen the skip-leak. Redaction is a tripwire; the documented mitigation remains env files or stdin over argv.2.
SSH_MCP_HTTP_ALLOWED_HOSTS=*.*bypassed DNS-rebinding protection (security)*.*was listed in the refusal set but the suffix-wildcard allowance (*.internal.example.com) was evaluated first, so it slipped through.*.*matches effectively any dotted hostname — on the HTTP transport that is remote command execution behind a rebinding attack.Validation now strips only the two deliberately-permitted wildcard forms and demands a concrete remainder. Full truth table verified empirically:
*,*:*,*.*,*.*:*,*.*.*,*.:*,a*b.example.com,.., whitespace-only*.internal.example.com,*.internal.example.com:*,ok.example.com:*,ok.example.com,*.comSide effects, both fail-closed: infix wildcards like
a*b.example.comwere never caught before, and a whitespace-only value previously fell back silently to loopback-only.*.comstays permitted, consistent with the documented suffix-wildcard policy.3. Publish-path digest verification (CI + supply chain)
Docker Build & Pushfailed on every push tomain.docker buildx imagetools createdoes not reuse the source digest — it wraps the scanned manifest in a new OCI index — so the check compared an index against one of its children.The Trivy gate was never bypassed: the scan runs against the digest before any tag is promoted, so published content was always scanned content. But the broken check sits before
Attest build provenance, so images published to GHCR between 2026-07-26 and this release carry no provenance attestation. Verification now asserts the invariant that matters — the set of image manifests a tag serves is exactly the scanned digest — inspecting the index's children and ignoring attestation manifests.Also
except asyncio.TimeoutErrorsat afterexcept OSError; sinceasyncio.TimeoutError is TimeoutErrorandTimeoutErrorsubclassesOSErroron Python ≥3.11, it was unreachable dead code and every timeout logged the less specific message. Reordered.test_fuzzed_long_flag_junk_prefix_is_unboundednow asserts exact output. It was a latent test defect: a generated secret that is a substring of the flag name (credentiinsidecredential) reported a false "leak". It passes on a fresh checkout but fails deterministically once Hypothesis caches the counterexample — a landmine for release CI. Confirmed pre-existing by reverting both source files.[Unreleased]closed as[0.6.0] - 2026-07-26, and the now-inaccurate claim that tag verification "fails the job if any tag resolves elsewhere" corrected.Verification
pytest— 717 passedruff format --check/ruff check src/ tests//mypy src//bandit -r src/ -q/pip-audit— all cleangitleaksscoped toorigin/main..HEAD— no leaksOperator action still required before tagging
release.ymlplus the reviewer-gatedpypienvironment, and delete any legacy API token. Cannot be done from repo files.v0.6.0tag publishes twice:release.ymlto PyPI (reviewer-gated) andci.yml'sdockerjob to GHCR (ungated).