OWASP's "Software and Data Integrity Failures" (A08) describes a family of problems with one root: code or data is trusted without verifying where it came from or whether it changed on the way. A CDN script that was modified, a CI pipeline that runs an unpinned plugin, a serialized object that turns into code when it is loaded, a webhook whose sender was never checked. Each is an integrity failure, and each has become a real-world breach.
In this lesson you will learn to protect browser assets with Subresource Integrity, avoid insecure deserialization, harden the build and deploy pipeline, and verify signed messages from third parties.
Loading a script from a CDN means trusting the CDN forever. Subresource Integrity (SRI) pins the exact bytes: the browser hashes the downloaded file and refuses to execute it if the hash differs.
<script src="https://cdn.example.com/lib/4.2.0/lib.min.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>Generate the value at build time and update it whenever you upgrade the library:
openssl dgst -sha384 -binary lib.min.js | openssl base64 -ASRI only works for files with fixed content, so pin versions rather than loading latest. For everything else, self-host the asset so it lives under your own release process.
Deserialization turns bytes back into objects. Formats that can describe behaviour as well as data (Java serialized objects, Python pickle, PHP unserialize, and Node.js packages that "serialize functions") execute attacker-controlled logic when they load untrusted input. JSON is safer because JSON.parse produces plain data, but it still needs care:
const data = JSON.parse(req.body); // data only, never code
// Guard against prototype pollution when merging parsed objects
function safeMerge(target, source) {
for (const key of Object.keys(source)) {
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
target[key] = source[key];
}
return target;
}Rules: never deserialize untrusted input with a format that can carry code, validate the parsed structure with a schema before use, and treat any library that "revives" functions or classes from user input as a remote code execution feature.
The build pipeline has write access to production, which makes it a prime target. Harden it like an application:
# .github/workflows/deploy.yml (excerpt)
permissions:
id-token: write # OIDC: short-lived cloud credentials, no stored secrets
contents: read
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # pinned by commit SHA, not tag
- run: npm ci --ignore-scripts
- run: npm test
- run: npm publish --provenance --access public # signed build attestationAny message that arrives from outside must prove its origin before it changes state. The sample code verifies a payment webhook: the HMAC is computed over the raw body (re-serialising JSON would change the bytes), compared in constant time, and only then parsed. The same principle covers software updates (verify the vendor's signature before installing), configuration fetched at runtime (sign it or fetch it over a mutually authenticated channel) and any "callback" URL parameter your application follows.
What does the `integrity` attribute on a `<script>` tag do?
__proto__ keys.Next lesson: Security Logging and Monitoring — seeing an attack while it is happening instead of months later.