🎉 Phorion ranked #1 in independent EDR telemetry evaluations. Learn more

Browser Cache Smuggling on macOS: Reproducing and Defending the Technique

TL;DR

Browser cache smuggling is also viable on macOS: a website can stage controlled bytes in the browser’s disk cache for a separate local command to recover without its own network request. Chrome and Firefox expose ordinary cache files to same-user processes; Safari adds an app-container access boundary.
Restricting cache access to trusted browser processes and explicit exceptions stops unauthorized recovery. Clipboard controls can interrupt the ClickFix handoff, while monitoring non-browser cache reads provides additional coverage.

In 2023, Aurélien Chalot published Browsers’ cache smuggling, demonstrating how a browser cache could be used to deliver a DLL on Windows. Accessing a website caused the browser to cache attacker-controlled content, which a socially-engineered PowerShell command subsequently located and recovered.

A 2025 follow-up returned to the technique and expanded on defensive guidance, including restricting scripting engines, clearing browser caches on exit, and monitoring non-browser access to cache files.

The technique itself is not a browser vulnerability. Browsers are supposed to persist cacheable content locally, reducing repeat downloads and improving the user experience. The interesting boundary appears later: can another process running as the user enumerate those files, identify controlled content, and turn it back into a standalone file?

On macOS, the answer depends on the browser and the recovering process’s access. ClickFix supplies the social-engineering step: convincing the user to run a command that retrieves the staged content.

From web response to local file

Browser cache smuggling solves the staging problem, not execution. The browser fetches and stores attacker-controlled bytes as ordinary cached content. Entries use hashed names and browser-specific layouts, and caching the bytes does not execute them.

A ClickFix command (or similar local action) is still needed to locate the cached object or known marker, parse or carve the response body, and pass the recovered content to whatever execution mechanism follows.

Browser staging
  1. 01 / host Attacker hosts website Serves a cacheable response containing a known marker
  2. 02 / visit Browser visits website Makes a normal HTTPS request to the page
  3. 03 / cache Response is cached Stored under a hashed name with browser metadata
Local recovery
  1. 04 / execute Execute ClickFix command The user runs pasted shell content
  2. 05 / locate Find the known marker The command enumerates cache objects
  3. 06 / recover Recover staged content Recovered bytes are written to disk or interpreted directly

While only one part of the overall attack chain, the technique offers some offensive advantages:

  • Browser-native delivery: The browser makes the network request, rather than curl or another obvious downloader.
  • No payload URL in the command: The pasted command does not need to contain a payload URL, which could be blocked by XProtect.
  • Payload obfuscation: Hashed cache filenames remove the original filename and extension. File signatures can remain visible, but an attacker can separately encode the payload for the recovery command to decode.
  • A less direct detection chain: The browser writes to its cache rather than Downloads, separating the network request from the process that later recovers and executes the content.

Of course, this doesn’t make the technique invisible. It just changes where defenders need to look.

Browser caches on macOS

The ordinary HTTP disk caches used for browsing content are separate from browser profile databases and credential stores:

BrowserHTTP disk cache
Google Chrome~/Library/Caches/Google/Chrome/<profile>/Cache/Cache_Data
Microsoft Edge~/Library/Caches/Microsoft Edge/<profile>/Cache/Cache_Data
Brave~/Library/Caches/BraveSoftware/Brave-Browser/<profile>/Cache/Cache_Data
Chromium~/Library/Caches/Chromium/<profile>/Cache/Cache_Data
Firefox~/Library/Caches/Firefox/Profiles/<profile>/cache2/entries
Safari~/Library/Containers/com.apple.Safari/Data/Library/Caches/com.apple.Safari/WebKitCache

Paths can change across browser versions and profiles. Chromium browsers also maintain separate code caches and Service Worker storage, but the ordinary HTTP cache is the relevant location for the technique tested here.

The on-disk representation differs between browser engines. Chromium browsers use the Simple Cache backend, storing hashed entry files that combine the cache key, response body and HTTP metadata. Firefox also uses hashed files, with each unencrypted cache2 entry containing response data followed by metadata, as described in Mozilla’s HTTP Cache source documentation. Mozilla also documents optional disk-cache encryption (neat!), which is disabled by default in the current source. Encrypted entries cannot be recovered by simply carving plaintext bytes.

Safari’s WebKit cache separates versioned records and larger response blobs, uses salted hashes and integrity validation, and stores the cache inside Safari’s app container.

Hashed names and cache metadata are not encryption and do not reliably hide file signatures. In unencrypted Firefox entries, response data begins at offset zero. WebKit stores small bodies inline after cache metadata, while larger bodies are written byte-for-byte as separate blobs, as shown in its cache storage implementation. Those blobs retain the response’s file signature.

We validated Safari’s location with a controlled request on Safari 27.2. A cacheable PNG containing a unique marker was written byte-for-byte to:

~/Library/Containers/com.apple.Safari/Data/Library/Caches/com.apple.Safari/
  WebKitCache/Version 17/Records/<partition>/Resource/<hash>-blob
  WebKitCache/Version 17/Blobs/<hash>

Safari’s protected app container blocks Terminal from enumerating or reading this cache by default. Granting Terminal Full Disk Access (FDA) permits recovery. This is a boundary around Safari’s cache, not a platform-wide mitigation: ordinary Chrome and Firefox cache paths do not require FDA for same-user access.

Reproducing the technique

Recovering a Chromium response body

Chromium’s Simple Cache format is documented in simple_entry_format.h. A normal _0 entry is arranged as:

SimpleFileHeader
cache key
stream 1 response body
stream 1 footer
stream 0 HTTP metadata
optional SHA-256 of the cache key
final footer

This makes the response body recoverable without a browser API. The extraction command reads the key length from the 24-byte header, then works backward from the final 24-byte footer. That footer supplies the stream 0 metadata size and indicates whether a 32-byte key hash is present. Stepping over those fields and the preceding stream 1 footer reveals the exact start and length of the original response body.

The entry filename itself is derived from the cache key (you’ll see filenames like 82cafd9ad3c026a7_0). On macOS, these files are restricted to the owning user, but those permissions do not distinguish Chrome from another unsandboxed process running as that same user.

Try it against this post

The image below is a benign demonstration of browser cache smuggling. It’s a valid PNG containing the benign string below between two AABBCCDDEEFF markers in a PNG metadata chunk:

Phorion browser cache smuggling demo recovered successfully.
Safe browser cache smuggling demonstration image containing a benign marker-delimited payload in PNG metadata.

After loading this page in a normal Chrome window with the default profile and disk caching enabled, the following one-liner finds the image’s cache entry, parses Chromium’s Simple Cache boundaries, and writes the complete PNG response body to a file under /tmp. It checks the cache key, rather than accepting a filename mentioned in another response.

Extract the complete cached image
d="$HOME/Library/Caches/Google/Chrome/Default/Cache/Cache_Data"; n='cache-smuggling-demo.png'; u32(){ od -An -tu4 -N4 -j"$2" "$1" | tr -d ' '; }; f=$(find "$d" -maxdepth 1 -type f -name '*_0' -exec grep -aFl "$n" {} + | while IFS= read -r x; do kl=$(u32 "$x" 12); [ "$kl" -gt 0 ] && [ "$kl" -le 65536 ] || continue; dd if="$x" bs=1 skip=24 count="$kl" status=none | grep -Fq "$n" && { printf '%s\n' "$x"; break; }; done); [ -n "$f" ] || { echo 'cache entry not found' >&2; exit 1; }; sz=$(stat -f%z "$f"); kl=$(u32 "$f" 12); fo=$((sz-24)); flags=$(u32 "$f" $((fo+8))); s0=$(u32 "$f" $((fo+16))); sha=$((flags&2?32:0)); end=$((fo-sha-s0-24)); start=$((24+kl)); count=$((end-start)); [ "$count" -gt 0 ] && [ "$count" -le $((10*1024*1024)) ] || { echo 'invalid body range' >&2; exit 1; }; out=$(mktemp /tmp/cache-smuggling-image.XXXXXX) || exit 1; dd if="$f" of="$out" bs=1 skip="$start" count="$count" status=none && printf 'output=%s size=%d\n' "$out" "$count" && file "$out"

A known marker allows recovery of just the embedded content without parsing the cache format.

The one-liner below enumerates cache objects, locates two marker instances, and prints only the enclosed content (the marker is split in the command so caching this blog doesn’t match itself!):

Read the marker-delimited content
d="$HOME/Library/Caches/Google/Chrome/Default/Cache/Cache_Data"; m='AABB''CCDDEEFF'; f=$(find "$d" -maxdepth 1 -type f -exec grep -aFl "$m" {} + | while IFS= read -r x; do [ "$(LC_ALL=C grep -aobF "$m" "$x" | sed -n '2p')" ] && { printf '%s\n' "$x"; break; }; done); [ -n "$f" ] || { echo 'marker pair not found' >&2; exit 1; }; set -- $(LC_ALL=C grep -aobF "$m" "$f" | sed -n '1,2{s/:.*//p;}'); start=$(($1+${#m})); count=$(($2-start)); [ "$count" -ge 0 ] && [ "$count" -le 4096 ] || { echo 'invalid marker range' >&2; exit 1; }; dd if="$f" bs=1 skip="$start" count="$count" status=none && printf '\n'

What macOS prevents - and what it doesn’t

Native controls act at different stages: permissions and Transparency, Consent and Control (TCC) govern access, while Gatekeeper and code-signing enforcement concern what happens after recovery.

ControlWhat it helps withWhat it does not prevent
POSIX
permissions
Chrome’s 0700 cache directory and 0600 files prevent access from other user accounts.Another unsandboxed process running under the same user ID can still read the files.
TCC + Full
Disk Access
Limits access to protected privacy categories; removing unnecessary Terminal FDA reduces broader exposure.It does not add a TCC gate to ordinary Chrome and Firefox caches. A non-sandboxed process running as the same user can generally read them without FDA.
Safari app-containerAdds an app identity and authorization boundary around Safari’s container, blocking Terminal cache access by default.Full Disk Access authorizes Terminal to read the cached responses. The boundary controls access; it does not encrypt the content.
Quarantine + GatekeeperQuarantine identifies downloaded content for Gatekeeper’s trust checks when opened through an assessed launch path.Writing cached bytes to a new file with dd does not copy com.apple.quarantine. Provenance alone is not an execution block, and Gatekeeper is not a universal script-control mechanism.
Code
signing
Enforces platform requirements for native executables, including code-signature validation where required.Trusted interpreters can consume recovered scripts, and a valid signature does not mean code is benign.
Cache
integrity
checks
Chromium CRCs, Firefox hashes and WebKit salted validation detect corruption when the browser reads an entry.They do not prevent another process reading a valid entry and copying its response body elsewhere.

In practice, Chrome and Safari cache objects in our reproductions carried com.apple.quarantine, but a new file carved with dd did not inherit it. A benign, ad-hoc-signed Mach-O recovered from Chrome’s cache ran through direct Terminal execution after its execute permission was set, despite carrying com.apple.provenance. Of course, this doesn’t guarantee that every recovered payload will run or evade other controls.

Preventing cache recovery

A file-access protection can provide the process-level boundary that general filesystem permissions lack. It protects browser cache paths and checks the requesting process before allowing a file open. Trusted browser and helper processes retain access; shells, interpreters and unrelated applications are denied the reads needed to recover response content.

The allow rules should validate the complete process identity: a trusted code signature, the expected signing ID and, for third-party browsers, the expected Team ID. Legitimate security, management or support tooling can be added as explicit exclusions after observing normal access in audit mode.

Phorion Protections provides this cache protection today for Chrome, Firefox, Safari, Edge, Brave, Arc and Opera.

Stop the ClickFix

Clipboard and process-execution controls complement cache restrictions by stopping the local recovery command.

In macOS Tahoe 26.4, Apple added a warning for browser-to-Terminal pastes under specific user-activity conditions, alongside checks for known-bad domains in pasted content. A cache-only recovery command need not contain a domain, so domain checks alone do not cover this technique.

Phorion Clipboard Protection supports auditing, user prompts and policy-driven blocking of browser-to-terminal pastes, without relying solely on domains in the command.

For appropriate user groups, Phorion Protections also allows for unsigned executables and script interpreters to be blocked completely.

Detection opportunities

Prevention should be the first choice, but cache access also provides useful telemetry.

Look for non-browser processes opening many files under Cache_Data, cache2/entries or Safari’s WebKitCache, particularly shells and interpreters. Correlate those reads with file creation, execute-permission changes and subsequent execution. Broad enumeration is useful evidence, not a prerequisite: a command that knows the entry’s location can read it directly. Baseline legitimate backup and security tooling before adding exclusions.

Our demo commands used find, grep and dd, but detections should not depend exclusively on those commands. The same operation could be performed with native file APIs or other native command-line tools.

Conclusion

Browser cache smuggling brings browser-native payload staging to macOS. By default, Chrome and Firefox cache content is recoverable by other unsandboxed processes running as the same user, without browser APIs or another network request. Safari’s protected app container adds an access boundary absent from those cache paths; granting Full Disk Access to the recovery application removes that barrier.

Staging is not execution: a ClickFix command or another local action must still recover and run the content. But writing cached bytes into a new file with dd does not copy the source’s quarantine attribute, and provenance alone does not prevent execution. Browser delivery does not guarantee that the recovered payload receives the same treatment as a conventional download.

The most direct defense is to deny unrelated processes access to browser caches, stopping recovery before execution becomes a question. Limit unnecessary Full Disk Access, interrupt the ClickFix handoff with clipboard controls, and monitor non-browser cache reads followed by file creation or execution.

Let's Talk

See how Phorion protects your macOS fleet

Purpose-built by macOS security researchers. One lightweight agent delivering detection, prevention, and visibility.

Ready to see it in action? Book a demo and we'll show you how Phorion can protect your fleet.

Book a Demo

Error

Expect a personal email from our team.

Pricing About Us Blog