home intel cve-2026-42575-apko-apk-checksum-bypass
CVE Analysis 2026-05-09 · 8 min read

CVE-2026-42575: apko Skips Per-Package Checksum Validation

apko verifies the APKINDEX signature but never compares downloaded .apk checksums against the signed index. An attacker controlling any mirror can silently substitute arbitrary packages into built OCI images.

#supply-chain-attack#checksum-validation-bypass#package-tampering#signature-verification-incomplete#oci-container-security
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-42575 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2026-42575HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-42575 is a checksum-bypass vulnerability in apko, Chainguard's tool for building OCI container images from Alpine APK packages. The vulnerability exists in the package fetch pipeline: apko correctly verifies the GPG signature on APKINDEX.tar.gz — the signed package index — but never actually compares the checksum of each individually downloaded .apk file against the checksum recorded in that signed index. The result is a classic TOCTOU-style trust gap: the signed manifest is validated, but the artifacts it describes are not.

Any attacker positioned between apko and the package mirror — a compromised mirror, an HTTP (non-TLS) repository, a poisoned CDN cache — can serve a maliciously substituted .apk while the signed index remains untouched. The build completes without error. The resulting OCI image contains the attacker's payload.

Root cause: getPackageImpl() computes the downloaded package's checksum and retrieves the expected value via ChecksumString() but never invokes a comparison between the two, silently accepting any substituted package.

Affected Component

The vulnerability lives in apko's APK fetch layer, specifically in pkg/apk/impl/fetch.go (pre-patch path). The affected function is getPackageImpl(), called for every package resolved during image construction. All apko versions prior to 1.2.7 are affected. The vulnerability impacts any build pipeline using HTTP repositories or mirrors not exclusively controlled by the build operator — including CI/CD environments pulling from public Alpine mirrors.

Root Cause Analysis

The APK index verification flow is correct in isolation. apko fetches APKINDEX.tar.gz, verifies its GPG signature, and populates package metadata including the per-package SHA-256 checksum. The checksum is exposed via ChecksumString() on the parsed Package struct. The problem is that getPackageImpl() — the function responsible for fetching and accepting individual packages — computes the actual checksum of the downloaded bytes but never performs the comparison.

// pkg/apk/impl/fetch.go — VULNERABLE (pre-1.2.7)
// Reconstructed Go pseudocode as C for clarity

int getPackageImpl(Repository *repo, Package *pkg, io.Writer *w) {
    // Step 1: fetch the .apk tarball from the mirror
    resp, err = http_get(repo->url + "/" + pkg->Filename);
    if (err != nil) return err;

    // Step 2: wrap writer with hash computation
    hasher   = sha256.New();
    teeReader = io.TeeReader(resp.Body, hasher);

    // Step 3: stream package data to output
    io_copy(w, teeReader);

    // Step 4: retrieve what we computed
    actualChecksum = hasher.Sum(nil);

    // Step 5: retrieve what the *signed index* says it should be
    expectedChecksum = pkg->ChecksumString();   // e.g. "Q1abc123..."

    // BUG: actualChecksum and expectedChecksum are NEVER COMPARED.
    // Both values are computed/retrieved and then discarded.
    // Any .apk body served by the mirror is silently accepted.

    return nil;
}

The ChecksumString() method returns the checksum field parsed directly from the signed APKINDEX. It is a Q1-prefixed base64-encoded SHA-1 hash (Alpine's legacy format) or a raw hex SHA-256, depending on index version. Either way, the value is available and correct — it just goes unused.

// Package struct — relevant fields
struct Package {
    /* ... */
    char    *Name;           // package name
    char    *Version;        // package version string
    char    *Filename;       // relative path, e.g. "x86_64/busybox-1.36.1-r0.apk"
    uint8_t  Checksum[32];   // SHA-256 from signed APKINDEX — NEVER CHECKED
    /* ... */
};

// ChecksumString() returns hex encoding of Package.Checksum.
// Computed at parse time from the verified, signed index.
// Correct. Trustworthy. Ignored.

Exploitation Mechanics

The attack requires the adversary to control or influence responses from any HTTP-accessible APK mirror. HTTPS does not fully mitigate this if the mirror itself is compromised; the vulnerability is at the content integrity layer, not the transport layer.

EXPLOIT CHAIN:
1. Identify a target apko build pipeline using an HTTP mirror or a CDN-fronted
   HTTPS mirror (e.g., dl-cdn.alpinelinux.org via a poisoned CDN PoP).

2. Obtain (do not tamper with) the legitimate APKINDEX.tar.gz and its GPG
   signature. apko will verify this successfully — leave it untouched.

3. Identify a target package in the index, e.g.:
       alpine-baselayout-3.4.3-r2.apk
       Checksum: 9f3a1b2c... (recorded in signed index)

4. Craft a malicious replacement .apk:
       - Valid APK tar structure (gzip'd tar with .PKGINFO, scripts, data)
       - Arbitrary payload in /usr/bin/ or init scripts
       - SHA-256 of this file != 9f3a1b2c... (mismatch is irrelevant)

5. Serve the malicious .apk in place of the legitimate one when apko's
   HTTP client requests:
       GET /alpine/v3.19/main/x86_64/alpine-baselayout-3.4.3-r2.apk

6. apko receives the malicious body, computes its SHA-256, retrieves the
   expected checksum from the signed index — and discards both values.
   No error is raised.

7. The malicious package is unpacked into the OCI image layer.
   apko signs the resulting OCI image (e.g., with cosign), attesting
   to an image that contains attacker-controlled content.

8. The poisoned image is pushed to a registry and consumed by downstream
   workloads, Kubernetes pods, or CI runners.

The CVSS 7.5 rating reflects network-exploitable, no-authentication-required access with high impact on integrity of built artifacts. The attack is particularly severe in supply-chain contexts: apko is heavily used by Chainguard Images, meaning a successful attack poisons "hardened" base images that downstream users trust implicitly — and that carry valid cosign signatures from the build system.

Memory Layout

This is a logic vulnerability rather than a memory corruption bug, so the relevant "layout" is the data flow through the fetch pipeline — specifically how the two checksum values co-exist in the same stack frame without ever being compared.

STACK FRAME: getPackageImpl() — pre-patch

  [ resp         ] → HTTP response body (attacker-controlled)
  [ hasher       ] → sha256.Hash instance
  [ teeReader    ] → io.TeeReader(resp.Body, hasher)
  [ actualSum    ] → hasher.Sum(nil)  ← computed from attacker data
                          |
                          |   NO COMPARISON PERFORMED
                          |
  [ expectedSum  ] → pkg.ChecksumString()  ← from signed APKINDEX
  [ return nil   ] ← function exits cleanly regardless of mismatch

TRUST BOUNDARY VIOLATION:
  ┌─────────────────────────────────┐
  │  APKINDEX.tar.gz (GPG verified) │  ← integrity guaranteed
  │   └─ checksum: 9f3a1b2c...      │
  └─────────────────────────────────┘
              │
              │  ← gap: checksum never linked to downloaded artifact
              ▼
  ┌─────────────────────────────────┐
  │  .apk download (unverified)     │  ← attacker substitutes here
  │   └─ actual:   deadbeef...      │  ← silently accepted
  └─────────────────────────────────┘

Patch Analysis

The fix in apko 1.2.7 is exactly what it should be: after computing the actual checksum of the downloaded body, compare it against the expected value from the signed index. If they differ, return an error and abort the build.

// BEFORE (vulnerable, pre-1.2.7):
func getPackageImpl(repo *Repository, pkg *Package, w io.Writer) error {
    resp, err := http.Get(repo.URL + "/" + pkg.Filename)
    if err != nil { return err }
    defer resp.Body.Close()

    hasher := sha256.New()
    teeReader := io.TeeReader(resp.Body, hasher)

    if _, err := io.Copy(w, teeReader); err != nil {
        return err
    }

    actualChecksum  := hasher.Sum(nil)
    expectedChecksum := pkg.ChecksumString()

    // BUG: actualChecksum computed, expectedChecksum retrieved — neither used.
    _ = actualChecksum
    _ = expectedChecksum

    return nil  // always succeeds
}

// AFTER (patched, 1.2.7):
func getPackageImpl(repo *Repository, pkg *Package, w io.Writer) error {
    resp, err := http.Get(repo.URL + "/" + pkg.Filename)
    if err != nil { return err }
    defer resp.Body.Close()

    hasher := sha256.New()
    teeReader := io.TeeReader(resp.Body, hasher)

    if _, err := io.Copy(w, teeReader); err != nil {
        return err
    }

    actualChecksum   := hex.EncodeToString(hasher.Sum(nil))
    expectedChecksum := pkg.ChecksumString()

    // FIX: actually compare the two values
    if actualChecksum != expectedChecksum {
        return fmt.Errorf(
            "checksum mismatch for package %s: got %s, expected %s",
            pkg.Filename,
            actualChecksum,
            expectedChecksum,
        )
    }

    return nil
}

The fix is minimal and correct. Note that the checksum comparison must occur after io.Copy completes — the hash finalizes only once all bytes have been consumed. The patch correctly positions the comparison at this point. The error message surfaces both actual and expected checksums, which aids forensic detection of active exploitation attempts in build logs.

Detection and Indicators

Prior to patching, there are no runtime indicators — the vulnerability produces no errors, no warnings, and no log output. Detection requires out-of-band verification.

For historical builds: Re-fetch each .apk from a trusted source and compare SHA-256 against the checksum in the APKINDEX your build used. If your build logs retain the resolved index snapshot, extract checksums with:

import tarfile, hashlib, base64

def verify_apk(apk_path: str, expected_checksum_b64: str) -> bool:
    # Alpine Q1-prefix checksum: base64(sha1(control section))
    # Full-package SHA256 stored as raw hex in newer indexes
    with open(apk_path, "rb") as f:
        data = f.read()
    actual = hashlib.sha256(data).hexdigest()
    # Compare against index-recorded value (hex or Q1-stripped b64)
    expected = expected_checksum_b64.lstrip("Q1")
    try:
        expected_hex = base64.b64decode(expected + "==").hex()
    except Exception:
        expected_hex = expected_checksum_b64  # already hex
    return actual == expected_hex

# Flag any mismatch as potential supply-chain substitution

Post-patch indicators of attempted exploitation: apko 1.2.7+ will emit a fatal error containing "checksum mismatch for package" with both the actual and expected hashes. Monitor build logs for this string. A mismatch in production should be treated as an active supply-chain attack and the mirror should be immediately quarantined and reported.

Remediation

Immediate: Upgrade apko to version 1.2.7 or later. This is the only complete fix.

Defense in depth:

  • Pin all APK repositories to HTTPS endpoints with certificate pinning where your infrastructure supports it. This raises the bar for CDN cache poisoning but does not substitute for content verification.
  • Use --repository-append only with fully trusted, internally mirrored repositories in high-assurance builds.
  • Rebuild any OCI images produced by affected apko versions from a known-good build environment and re-verify all image digests.
  • If using Chainguard Images (which are built with apko), check the Chainguard advisory for confirmation that production builds were not affected; Chainguard has indicated their production infrastructure was not compromised.
  • Integrate apk verify or an equivalent checksum audit step into post-build image scanning pipelines as a secondary control.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →