Juicy Shop
Software & Data Integrity Failures
Integrity failures happen when code or data is trusted without verifying it wasn't tampered with — from unsigned auto-updates to insecure deserialization and poisoned CI/CD pipelines. Learn to verify before you trust.
Trust requires verification
Integrity failures arise whenever your application accepts code, updates, or serialized data without checking that it came from who you think and wasn't modified in transit or at rest. Confidentiality (encryption) is not integrity: TLS protects data on the wire, but it does not prove an artifact you downloaded is the one the author published. You need signatures and checksums, verified against a trusted key, to establish integrity.
Insecure deserialization
Turning untrusted bytes back into objects is dangerous when the format can encode arbitrary types or behaviour. Attackers craft payloads that, when deserialized, instantiate unexpected objects and trigger 'gadget chains' leading to remote code execution. Defend by avoiding native/binary deserialization of untrusted input entirely; prefer simple data formats (JSON) decoded into known schemas. In this codebase, decode external input with an Effect `Schema` so only the expected shape is ever produced.
CI/CD and update pipelines
Your build pipeline is a high-value target: it has credentials, ships to production, and is implicitly trusted. A malicious dependency, a compromised build plugin, or an unsigned auto-update can inject code that every user then runs. Sign artifacts and verify signatures before deploy or update, lock down who can change pipeline definitions, use ephemeral build runners, and generate provenance (e.g. SLSA attestations) so you can prove how an artifact was built.
Verify dependencies and configuration
Pull dependencies only from trusted registries with integrity hashes (lockfiles), and beware dependency confusion — an attacker publishing a public package with your internal name. Don't pull configuration or plugins from untrusted CDNs without Subresource Integrity (SRI). Treat any auto-loaded code or data as untrusted until a signature or hash proves otherwise.
Insecure vs secure
import { deserialize } from "node-serialize";
app.post("/profile", (req, res) => {
// Untrusted bytes -> arbitrary objects (RCE risk)
const profile = deserialize(req.body.data);
res.json(profile);
});Deserializing attacker-controlled data with a format that can encode behaviour enables gadget-chain remote code execution.
import { Schema } from "effect";
const Profile = Schema.Struct({
name: Schema.String,
bio: Schema.String,
});
app.post("/profile", (req, res) => {
// Decode JSON into a known schema; nothing else can be produced
const profile = Schema.decodeUnknownSync(Profile)(req.body);
res.json(profile);
});JSON decoded against an explicit schema can only ever produce the declared shape — no arbitrary types, no gadget chains.
Key takeaways
- Integrity ≠ confidentiality: TLS doesn't prove an artifact is untampered.
- Verify signatures/checksums against a trusted key before trusting code or updates.
- Avoid native deserialization of untrusted input; decode JSON into known schemas.
- Protect CI/CD: sign artifacts, restrict pipeline changes, generate provenance.
- Use lockfile integrity hashes and SRI; beware dependency confusion.