A third-party script can drain your users: SRI and CSP in Laravel and Nginx
You add a small script to measure how fast your pages load. One line in the layout, a link to the vendor's CDN, done. Months later, someone replaces that file on the vendor's server. Your code hasn't changed, but your si
You add a small script to measure how fast your pages load. One line in the layout, a link to the vendor's CDN, done. Months later, someone replaces that file on the vendor's server. Your code hasn't changed, but your site is now running someone else's.
That is exactly what happened to Polymarket in late October. The script of a performance monitoring tool, DebugBear, was replaced on October 29. The result: 726 wallets drained and about 2.7 million dollars stolen (The Hacker News).
A third-party script runs with all your rights
When a page loads an external script, the browser makes no difference between your code and its code. It can read forms, change buttons, send requests on the user's behalf.
Yet we treat it as a detail: an analytics tool, a chat widget, a font, a library on a CDN. Each one is a door into the most sensitive pages, like payment, login or signing.
Every third-party script is code you run without having read it.
SRI checks that the file hasn't changed
The integrity attribute (Subresource Integrity) gives the browser the expected fingerprint of the file. If the content changes, even by one character, the script is blocked.
<script
src="https://cdn.example.com/lib/1.4.2/lib.min.js"
integrity="sha384-FILE_FINGERPRINT"
crossorigin="anonymous"></script>
To compute the fingerprint of a downloaded file:
openssl dgst -sha384 -binary lib.min.js | openssl base64 -A
SRI requires a pinned version: a "latest" style URL changes without warning and would break loading. That is precisely the point, no silent updates.
When possible, the simplest option is still to install the library with npm and serve it yourself through Vite. It then goes through your build, with a version locked in package-lock.json.
CSP limits what the page is allowed to load
The Content Security Policy tells the browser the only allowed sources. An injected script or one coming from an unknown domain is refused. In Nginx:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always;
In Laravel, you can add it through a middleware, which allows a stricter policy on sensitive pages:
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'"
);
return $response;
}
Before enforcing it, you can test it with the Content-Security-Policy-Report-Only header: the browser reports what it would have blocked, without breaking anything.
The best third-party script is the one you don't load
SRI and CSP reduce the risk, they don't remove it. The first question remains: is this script really needed, and on this particular page?
- List the external scripts loaded by each layout.
- Remove every third-party script from payment, login and signing pages.
- Serve yourself whatever you can, with a locked version.
- Add
integrityto everything left on a CDN. - Deploy a CSP, first in report mode, then in blocking mode.
And you, how many scripts you didn't write run on your login page?
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.