SaibaRadar

CVE-2026-118763 — 脆弱性インテリジェンス

sigstore — @sigstore/oci

公表日:

CVSS
7.4
CISA KEV
未掲載(悪用がないことを意味しません)

情報源の原文(翻訳ではありません)

@sigstore/oci sends the registry Authorization header to a cross-host blob-upload Location ### Summary @sigstore/oci sends the registry Authorization header to a cross-host blob-upload Location ## Affected versions and vulnerable location - Confirmed on sigstore-js at `3a83271` (current HEAD). - File: `packages/oci/src/registry.ts`. - `signIn()` installs the credential as a DEFAULT header on the shared client: `this.#fetch = this.#fetch.defaults({ headers: { [HEADER_AUTHORIZATION]: 'Basic ' + basicAuth } })` (`:126-128`) or `... 'Bearer ' + token` (`:145-147`), plus `creds.headers` from the Docker config (`:104`). - `uploadBlob()` reads the `Location` response header (`:184`), builds `uploadLocation = new URL(location.startsWith('/') ? this.#baseURL + location : location)` (`:190-192`), and PUTs the blob to it through the same client: `await this.#fetch(uploadLocation.href, { method: 'PUT', body: blob, headers: { [HEADER_CONTENT_TYPE]: ... } })` (`:198`). - There is no check that `uploadLocation`'s host (or scheme) equals the registry host before the PUT is sent. ## Root cause Because `signIn()` sets `Authorization` via `.defaults()`, it is attached to every request the client issues, including requests to a host the client read out of a server response. When a POST to `.../blobs/uploads/` returns an absolute `Location` pointing to a different host, `uploadBlob()` PUTs the blob there with the registry `Authorization` header still attached. If the challenge scheme was `basic`, that header carries the raw registry `username:password`; if bearer, it carries the access token; `creds.headers` custom auth headers ride along too. The library's HTTP layer (`make-fetch-happen` / `minipass-fetch`) strips `Authorization` on cross-host redirects it follows internally, which protects the `HEAD ... { redirect: 'follow' }` at `:167`. It does not help here, because the PUT to the upload `Location` is a fresh, explicit `this.#fetch()` call, not a followed redirect, so the default header is applied unconditionally. This is a residual of GHSA-pf56-329r-95rw / CVE-2026-59891 (credential confusion in `@sigstore/oci`). The fix for that advisory (commit `85c5838`) changed only `credentials.ts`, replacing a `key.includes(registry)` substring match with an exact host match when SELECTING which Docker credential to load. It never touched `registry.ts`, which controls WHERE the loaded credential is sent. The cross-host send remains. ## Attacker model A user pushing a signed artifact/attestation to an OCI registry via `attachArtifactToImage()`. The credential leaks to any host the registry names in the blob-upload `Location`. This is reachable when the registry is malicious or compromised, or when a registry that legitimately redirects blob uploads to third-party storage is impersonated. The OCI distribution spec permits absolute upload `Location` values (registries commonly redirect blob storage to a separate host), so a compliant-but-hostile registry can steer the PUT to an attacker host and capture the credential. This is the same "attacker-controlled registry" threat model accepted for GHSA-pf56. ## Reachability trace `index.ts:43 attachArtifactToImage()` -> `new OCIImage(...).addArtifact()` -> `image.ts:67 this.#client.signIn(credentials)` (installs the default Authorization header) -> `image.ts:76/79 this.#client.uploadBlob(...)` -> `registry.ts:198` PUT to the response-supplied `Location` with the default Authorization header attached. Same `RegistryClient` instance throughout, so the default persists. ## Reproduction Source-verified. No live push against a hostile registry was executed in this pass. Confirmed: ```bash rg -n "defaults\(|HEADER_AUTHORIZATION|Location|new URL\(|this.#fetch\(uploadLocation" packages/oci/src/registry.ts git log --oneline -3 -- packages/oci/src/credentials.ts packages/oci/src/registry.ts ``` `signIn()` sets `Authorization` via `.defaults()`; `uploadBlob()` PUTs to `uploadLocation.href` (which may be an absolute cross-host URL) with no host check; the GHSA-pf56 fix commit is the latest change to `credentials.ts`, while `registry.ts` has only dependency bumps since. ## Suggested fix Do not send the registry `Authorization` (or `creds.headers`) to an upload `Location` on a different host. Compare `uploadLocation.host` (and require `https`) against the registry host before the PUT; if it differs, issue the PUT without the credential (matching how registries that redirect to pre-signed storage URLs expect the upload to be unauthenticated), or explicitly re-authenticate for that host. More generally, avoid attaching the credential via a client-wide `.defaults()` and instead attach it per-request only to the registry host. ## How it was found and a note on tooling Every place `@sigstore/oci` was traced to attach a credential to an outbound request and checked whether the request host can be steered by a server response. The blob-upload `Location` path attaches the client-default `Authorization` to a PUT whose host comes from the registry response, with no host-equality che

情報源