The ‘Syntax Error’ that bricks your checkout: Fixing SRI corruption in Magento 2
You’ve just deployed a critical update or launched a marketing campaign. Suddenly, customers report they can’t finish their purchase. Your logs are screaming: Unable to unserialize value. Error: Syntax error coming from the CSP module.
You find that deleting sri-hashes.json fixes it temporarily, but like a ghost in the machine, the error eventually returns. This isn’t a “bad build” – it’s a high-traffic race condition. In this post, we’ll look at why Magento’s default handling of Subresource Integrity (SRI) fails under load and how to implement Atomic Storage to ensure your checkout never breaks this way again.
What’s happing to the checkout?
Magento 2’s Content Security Policy (CSP) module can enforce Subresource Integrity (SRI) for JavaScript (and related assets). Browsers use integrity metadata to ensure a script’s bytes match what the store expects, which helps limit tampering and unexpected third‑party script changes.
To do that at scale, Magento computes hashes for static view assets and stores them in a JSON map on disk. You will see that file as something like:
pub/static/frontend/<vendor>/<theme>/<locale>/sri-hashes.jsonpub/static/adminhtml/.../sri-hashes.json
At runtime, that map is read so the application can emit correct integrity values (or related handling, depending on configuration) for the assets it serves. If the file is missing, unreadable, or not valid JSON, code that depends on a coherent map can fail. In practice, teams often first notice this on high‑value pages (checkout, admin flows) when CSP / SRI features are enabled.
What goes wrong: corruption, not “a bad build”
The failure mode is not always a bad static deploy. A common production pattern is:
- A request needs to update the SRI map (new asset, merge new entries, etc.).
- Another request does the same at the same time.
Magento’s default storage approach writes the map by replacing the contents of sri-hashes.json on the live path. When several PHP workers do that concurrently, several bad outcomes appear:
- Interleaved or concatenated JSON
Two writers can produce invalid structures such as back‑to‑back objects ({...}{...}) or otherwise malformed text. That is a classic race on a shared file without an atomic publish step or appropriate locking. - Half‑written (truncated) files
If a write is interrupted (worker killed, timeout, I/O error) after the file was opened for writing, the path that storefront code reads can be left in a partial state: the beginning of JSON, then nothing that parses. To readers, that is “corrupt file on disk,” not a clean empty state. - A brief “file missing” window (delete-then-rename patterns)
Some mitigations (or hand-rolled fixes) delete the target and then rename a new file into place. Between those two steps, the canonical filename may not exist; concurrent readers can see an empty or missing file. A single-step replace avoids that class of glitch on typical Linux filesystems.
The community has documented this class of problems in the context of specific Magento patch levels and CSP behavior; for example, discussions and patches around concurrent writes to sri-hashes.json and related CSP/SRI issues have been shared publicly (e.g. community gists and patch sets that add locking and other fixes). The underlying theme is the same: shared mutable state in a file under web traffic needs safe publication semantics.
How you see it in the wild
Symptoms depend on what exactly breaks first, but teams often report one or more of:
- 500 errors or hard failures on checkout or other CSP‑heavy frontends.
- Log lines or exceptions around JSON decode / integrity map handling.
- A broken
sri-hashes.jsonon disk: invalid JSON, obvious truncation, or two JSON values stuck together. - Intermittency that tracks traffic (worse under load, fine after deleting the file or redeploying until the race returns).
What corruption looks like on disk:
JSON
{"scripts/file.js":"hash-123"}{"scripts/file.js":"hash-123"}
// Error: Two JSON objects concatenated
{"scripts/file.js":"hash-123"
// Error: File truncated mid-write
Because the issue is concurrency- and timing‑dependent, it can look like “random” breakage after deploys or during campaigns.
What “fixed” needs to mean
A solid fix, at the storage layer, should do at least the following:
- Never publish a partial file at the final path
Readers should not observe a grow‑in‑place stream of bytes for the canonical filename. - Publish updates atomically (as far as the OS allows)
On common Linux hosting, the standard pattern is: write a complete new file to a temporary name in the same directory, thenrename()it over the real name. On the same filesystem,renamereplacing an existing file is a single atomic update to the directory entry, so other processes see either the previous file or the new one – not a half‑replaced file. - Avoid unnecessary delete‑before‑rename on the live name**
Deleting the target first creates a window where the file is missing. Replacing in onerenameremoves that window. - Self-heal if the file is already bad
If a bad file is already on disk (from an older bug, manual edit, or interrupted deploy), failing the whole request forever is avoidable. The map can be treated as cache-like: if JSON is invalid, log, remove the bad file, and let a later request rebuild a valid map.
These properties address both new races and legacy bad state in production.
Creating a module to fix the issue
The SriHashesAtomicStorage-style module (implemented as a Magento 2 module that prefers the core Magento\Csp\Model\SubresourceIntegrity\StorageInterface) replaces the default file storage behavior with a small, focused implementation that:
- Writes the full JSON payload to a unique temp file next to
sri-hashes.jsoninpub/static(same volume → saferename). - Publishes with a single
rename()to the final filename so the live path is not subject to in‑place half writes or a delete/missing window. - Loads the file, validates JSON, and if validation fails, logs, deletes the invalid file, and returns an empty state so the application can reconstruct a valid file instead of being stuck in a permanent error state.
It does not change how hashes are computed; it only changes how the JSON is persisted and recovered. That is intentionally narrow: it targets the class of filesystem and concurrency bugs that manifest as a broken sri-hashes.json file on disk.
It also complements other fixes you might apply separately (for example, vendor patches that add flock when writing through Magento’s writeFile API, or future Adobe fixes). Those approaches serialize or harden the core path; this module’s approach hardens the SRI map file lifecycle in a way that is difficult to get wrong in production: write complete → rename once.
Important scope note: public write-ups and patches sometimes also cover separate issues such as minified bundle filenames (e.g. static.min.js vs static.js) in integrity maps when bundling/minification and CSP are combined. A storage swap alone does not replace fixes that belong in integrity processing or asset resolution. If symptoms persist with a known-good JSON file, investigate those areas too.
Operational requirements
pub/staticmust be writable in environments where Magento is allowed to update the SRI map at runtime. If you treatpub/staticas read-only on application nodes, behavior falls back to whatever your deploy pipeline does – this module cannot write through a read-only mount.- After install, use normal Magento DI compile in production mode so the
StorageInterfacepreference is applied consistently.
Takeaway for searchers
If you run Magento 2 with CSP / SRI enabled and see corrupt, partial, or glued JSON in sri-hashes.json – especially with concurrent traffic – you are likely hitting a unsafe publication pattern for a shared file. Replacing the storage implementation with write-temp-then-atomic-rename, plus invalid-JSON recovery on load, addresses the failure mode in a way that is easy to reason about and survives real production crashes and load patterns better than a raw overwrite of the final path.
Leave a Reply