flawopen.com/Teardowns/cve-2026-66384-jfrog-artifactory-docker-cache-path-traversal
CVE-2026-66384: JFrog Artifactory Docker Cache Path Traversal
CVE-2026-66384 lets an authenticated user write outside the intended JFrog Artifactory Docker cache under specific remote-repository conditions. Upgrade affected self-hosted instances.
Imagine two warehouse desks read the same parcel label differently: the receiving desk fetches a parcel from an untrusted supplier, while the storage desk files it under a trusted product name. A later picker can then receive the substitute when asking for the trusted parcel. In this Artifactory case, the documented cache substitution required authentication and specific remote-repository conditions; it did not give every authenticated user arbitrary write access.
Core Concepts & Subsystem Terms
CWE-22- Improper limitation of a pathname to a restricted directory: a path is not reliably kept inside its intended storage boundary.
Docker remote cache- Repository cache that stores content fetched from an upstream container registry.
Authenticated user- A user who passed authentication. JFrog also requires specific remote-repository conditions; anonymous access is not described.
Framework mappings
- CWE reference for the weakness described in this guide: CWE-22
- CISA KEV — added 2026-08-27 — CVE-2026-66384
Root Cause Analysis
JFrog classifies CVE-2026-66384 as CWE-22 and says an authenticated user may write outside the intended Docker cache path under specific remote-repository conditions. OpenAI’s technical report supplies the incident-specific mechanism it linked to this CVE: Artifactory interpreted a crafted request differently when selecting the local cache location and resolving the upstream registry request. It fetched a manifest and layers from an attacker-controlled repository, then cached them under a trusted CyberGym image name. That is a cache-identity substitution across two request interpretations, not evidence that every affected deployment can write arbitrary files. OpenAI confirmed the cache substitution but found no evidence another workload pulled or ran the substituted image. JFrog does not publish vulnerable source lines or a product patch diff, so the code below is an illustrative path-containment control, not JFrog source or a reproduction. Affected versions are before 7.146.35 and 7.161.0 through 7.161.16; fixed versions are 7.146.35 and 7.161.16. JFrog says affected Cloud environments were fortified. Severity is Medium per JFrog; no numerical CVSS score is asserted.
Step-by-Step Attack Flow
Authenticated request reaches remote cache
The issue requires an authenticated user and JFrog’s specific remote-repository conditions.
Cache path boundary fails
The report says Artifactory interpreted the crafted request differently for local cache selection and upstream registry resolution: it fetched a manifest and layers from an attacker-controlled repository but stored them under a trusted CyberGym image name. The report does not publish the request itself.
Impact remains bounded by evidence
A later request for the trusted image could have received the substituted content. OpenAI confirmed that the cache was poisoned, but found no evidence another workload pulled or executed that image. In that evaluation environment, execution would have depended on a target container later resolving the trusted reference.
Source Code: Flaw vs. Secure Implementation
Representative Python interfaces, not JFrog source. The vulnerable pattern lets separate parsers derive the cache key and upstream source from one raw request. The hardened pattern resolves one immutable, server-authorized RemoteImage and uses that same value for both fetch and cache identity. Build that object from validated configuration, include the remote namespace in the cache key, and bind content to a trusted immutable digest where the system provides one. This states the defensive invariant described by the incident report; it is not a complete parser, a product patch, or a reproduction. Upgrade to a JFrog-fixed release.
from typing import Callable
Fetch = Callable[[str], bytes]
Select = Callable[[str], str]
def cache_request(
request: str,
fetch: Fetch,
select_local_key: Select,
select_upstream: Select,
cache: dict[str, bytes],
) -> None:
local_key = select_local_key(request)
upstream = select_upstream(request)
cache[local_key] = fetch(upstream)
from dataclasses import dataclass
from typing import Protocol
@dataclass(frozen=True)
class RemoteImage:
namespace: str
registry: str
repository: str
reference: str
class Registry(Protocol):
def fetch(self, registry: str, repository: str, reference: str) -> bytes: ...
class Cache(Protocol):
def put(self, namespace: str, repository: str, reference: str, content: bytes) -> None: ...
def cache_image(target: RemoteImage, registry: Registry, cache: Cache) -> None:
content = registry.fetch(target.registry, target.repository, target.reference)
cache.put(target.namespace, target.repository, target.reference, content)
Engineering & System Hardening Checklist
- ✓Inventory self-hosted Artifactory and upgrade affected branches to 7.146.35 or 7.161.16 as applicable; confirm supported targets with JFrog.
- ✓Review remote Docker repositories and limit cache-write permission to required identities.
- ✓After upgrading, review audit and repository logs for unexpected cache writes or image-reference changes; investigate because an upgrade does not reverse earlier changes.