cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Supply-chain security

Learn how to verify JavaScript dependencies with audit reports, lockfiles, SRI, provenance, name checks, and smaller dependency choices.

By the end, you can
  • 01
    Map the chainName the packages, tools, registries, CDNs, and CI systems that can affect production code.
  • 02
    Verify installs and assetsUse audit reports, lockfiles, npm ci, lifecycle-script review, provenance, and SRI for the threats they actually cover.
  • 03
    Reduce the attack surfaceSpot suspicious package names, avoid dependency confusion, and choose fewer, better dependencies or platform APIs.

Trust, but verify

Supply-chain security is the practice of verifying code you did not write before it can run on your laptop, in CI, in a build tool, or in a user's browser. JavaScript makes reuse easy, so professional teams need habits that keep reuse from becoming blind trust.

Plain definition

A software supply chain is every dependency, tool, service, script, CDN, registry, token, and publishing step that can change the code your users eventually run. The lesson's rule is simple: trust, but verify, the code you install.

This lesson builds on npm & package.json for semver, lockfiles, and lifecycle scripts. It also links back to transpilers and polyfills because the 2024 Polyfill.io incident turned a compatibility CDN into a supply-chain warning. You will also see why bundlers, the concurrent Web Crypto lesson, text encoding, and XSS defenses matter here.

Real-life analogyFood supply chain and sealed packaging

A grocery store does not personally grow every ingredient. It chooses suppliers, checks packaging, tracks lots, responds to recalls, and removes products when ownership or safety changes. JavaScript teams do the same with package names, lockfiles, audits, SRI, and release workflows.

In real life: Farm, shipper, warehouse, and store
In JavaScript: Maintainers, registries, CDNs, CI, and package managers
In real life: Ingredient label
In JavaScript: package.json and release notes
In real life: Sealed package with a lot number
In JavaScript: Lockfile integrity values and SRI hashes
In real life: Recall notice
In JavaScript: Security advisories, Dependabot, Renovate, Snyk, or Socket alerts

Where the analogy stops: Food inspection can sample physical goods. Software can execute instantly and recursively: your dependency can run install scripts, fetch other packages, or ship code to every visitor.

What the supply chain includes

REAL COUNTS

The chain is bigger than the packages you import directly. It includes your direct dependencies, their dependencies, build tools, package registries, CI images, release tokens, lockfiles, CDNs, browser-loaded scripts, and the people or bots allowed to publish any of those pieces.

Supply-chain surfaces in a frontend project
SurfaceWhat it meansHow to verify it
Direct dependenciesPackages named in your manifests.Review names, owners, releases, licenses, scripts, and whether you need them.
Transitive dependenciesDependencies of dependencies.This repo's root lockfile currently has 611 package entries but only 35 direct dependency names across workspace manifests.
Build toolsBundlers, compilers, minifiers, test tools, and release scripts.They run with developer or CI permissions before users see your app.
CDNsScripts, styles, polyfills, fonts, and analytics loaded at runtime.Use SRI for immutable assets and avoid services that can change bytes per request.
CI and registriesTokens, publish workflows, package registries, artifact stores, and GitHub Actions.Prefer least-privilege tokens, trusted publishing, secret scanning, and reviewable release automation.

Transitive dependencies dominate because every direct package can bring its own tree. In this checkout, a script over the root package-lock.json counts 611 package entries excluding the root project, but only 35 unique direct dependency names across the workspace manifests recorded in that lockfile. That gap is why lockfiles, review tools, and fewer dependencies matter.

Count the real package-lock graphJavaScript
const fs = require("node:fs");const lock = JSON.parse(fs.readFileSync("package-lock.json", "utf8"));const entries = Object.entries(lock.packages ?? {});const lockfilePackageEntries = entries.filter(([path]) => path !== "").length;const workspaces = entries.filter(([path, entry]) =>  path !== "" && !path.startsWith("node_modules/") && entry.name);const direct = new Set();for (const [, entry] of workspaces) {  for (const bucket of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) {    for (const name of Object.keys(entry[bucket] ?? {})) direct.add(name);  }}console.log(lockfilePackageEntries + " lockfile package entries");console.log(direct.size + " direct dependency names across workspace manifests");
Why not just count direct dependencies?

Direct dependencies are the names your team chose. Transitive dependencies are code you still run because those choices pulled more packages into the graph. Attackers often aim at the deep graph because the package is widely used but rarely reviewed by application teams.

Incidents that shaped the threat model

TIMELINE

These incidents are not trivia. Each one teaches a different failure mode: availability, maintainer transfer, account hijack, protestware, long social engineering, CDN ownership, phishing, and install-time worm behavior.

Supply-chain incidents to remember
IncidentDateWhat it teaches
left-padMarch 22, 2016A maintainer unpublished tiny npm packages after a naming dispute; builds broke and npm tightened unpublish policy.
event-stream2018A new maintainer added malicious flatmap-stream code aimed at Copay wallet users; it was disclosed in November 2018.
ua-parser-jsOctober 22, 2021A hijacked maintainer account published malware that installed miners and password stealers in compromised versions.
colors/fakerJanuary 2022A maintainer intentionally sabotaged popular packages, including an infinite loop in colors and broken faker releases.
xz-utilsMarch 2024Not npm, but instructive: malicious xz 5.6.0/5.6.1 release tarballs hid an SSH-related backdoor, discovered by Andres Freund.
polyfill.ioFebruary-June 2024After ownership changed, scripts from cdn.polyfill.io were reported serving conditional malicious JavaScript to sites using the CDN.
npm phishing and Shai-HuludSeptember 2025Researchers reported phishing/token theft followed by a self-replicating npm worm that used install scripts to steal secrets and publish more infected packages, including popular packages such as @ctrl/tinycolor; package counts changed as advisories were updated.

The xz-utils backdoor was not an npm package, but it is essential for JavaScript developers because build systems rely on operating-system libraries, CI images, and maintainers you may never see. The Shai-Hulud reports matter because a package install can run code before your app starts; stolen npm, GitHub, or cloud credentials can then publish more compromised packages.

Use incidents as design tests

Ask how your workflow would respond: Would a lockfile pin the bad version or block drift? Would npm ci reproduce the graph? Would --ignore-scripts help investigate? Would secret scanning catch leaked tokens? Would a CDN hash fail closed? Different incidents need different controls.

npm audit, lockfiles, and lifecycle scripts

COMMANDS

npm audit compares your resolved dependency graph with published advisories. It reports severity, vulnerable paths, patched versions, and sometimes fix commands. It does not know about a brand-new hijack before an advisory exists, and it can be noisy when a vulnerable package is unreachable in your app or only used by development tooling.

Audit and install commands to knowbash
npm audit --audit-level=highnpm audit fixnpm audit fix --forcenpm cinpm install --ignore-scripts

Use npm audit fix for reviewed patch-level or compatible updates. Treat npm audit fix --force as a migration, because it may cross major versions or replace packages in ways that require tests and code review. Use npm ci in CI so the lockfile is the source of truth; it refuses drift between package.json and package-lock.json.

Lockfiles do not remove risk. They record exact versions and integrity hashes so the same graph installs again. Pinning every dependency exactly can reduce surprise but makes updates harder to receive. Semver ranges plus a committed lockfile are the common app trade-off: development can update intentionally, CI repeats exactly, and review tools open explicit changes.

Risk signal or safeguard?
  • A new dependency adds a postinstall script
  • CI uses npm ci from a committed lockfile
  • The package has many weekly downloads
  • A versioned CDN script has a checked sha384-... integrity value
  • The package name is loadsh when you meant lodash
  • npm audit reports zero vulnerabilities today
Try it yourself
0 of 6 correct

Sort each card by what it tells you during a dependency review. Some signals help but are not enough by themselves.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Review lifecycle scripts before running them

Packages can run preinstall, install, and postinstall scripts. npm install --ignore-scripts is useful when you want to inspect a new dependency without executing its install hooks. It is not a permanent workaround for a package whose legitimate build requires scripts; decide whether the package is worth that trust.

Subresource Integrity

HASH IT

Subresource Integrity, or SRI, lets HTML say “download this external file only if its bytes hash to this exact value.” It is most useful for versioned CDN assets that should be immutable. The browser fetches the file, computes the declared hash, compares it with the integrity attribute, and refuses to execute or apply the resource on mismatch.

A versioned script with SRIHTML
<script  src="https://cdn.example.com/app.v1.js"  integrity="sha384-Tf2FyVXw3trfCQ/AHspPELVdH6/5BICrCwHkiQtu+pcIUQZpzFtsc+Fajj2ob/4e"  crossorigin="anonymous"></script>
Step through an SRI hash
Step 0 of 6Ready
Your turn: follow the blue line

Step through how a real SRI hash is produced: bytes, SHA-384, base64, and the sha384- prefix.

Running in
  1. script
Next: line 8
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
async function computeSriHash(source) {  const bytes = new TextEncoder().encode(source);  const digest = await crypto.subtle.digest("SHA-384", bytes);  const base64 = btoa(String.fromCharCode(...new Uint8Array(digest)));  return `sha384-${base64}`;} computeSriHash(file).then(console.log);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Change bytes, change the SRI hash
Web Crypto SRI helperPop out in the code editor (opens in a new tab)JavaScript
async function computeSriHash(source) {  const bytes = new TextEncoder().encode(source);  const digest = await crypto.subtle.digest("SHA-384", bytes);  const base64 = btoa(String.fromCharCode(...new Uint8Array(digest)));  return `sha384-${base64}`;} const file = 'console.log("CompleteFrontend");\n';computeSriHash(file).then(console.log);
Integrity resultoriginal bytes
Original SRIsha384-Tf2FyVXw3trfCQ/AHspPELVdH6/5BICrCwHkiQtu+pcIUQZpzFtsc+Fajj2ob/4e
Current SRIComputing...
Browser decisionallow

Waiting for Web Crypto.

Try it yourself

Waiting for Web Crypto.

The hash is computed in the browser with crypto.subtle.digest('SHA-384', bytes). The comparison simulates what the browser does after fetching a script with an SRI attribute.

The crossorigin="anonymous" attribute lets the browser perform the CORS-enabled fetch needed for many cross-origin integrity checks without sending credentials. If the CDN changes a byte, the hash changes and the browser fails closed. That is why SRI does not fit resources that intentionally change behind the same URL, such as a live latest.js endpoint or the old Polyfill.io style of generating different code per request.

SRI is integrity, not safety by itself

If you copy the hash after a file is already malicious, SRI faithfully pins the malicious file. Pair it with trusted sources, reviewed versions, self-hosting when sensible, and the Web Crypto and text encoding ideas behind the hash.

Typosquatting and dependency confusion

NAME CHECK

Typosquatting registers a name that looks like the package you meant: loadsh instead of lodash, a swapped letter, a missing scope, or a lookalike word. Dependency confusion is different: internal package names accidentally resolve from a public registry, often because private scopes or registry rules are not configured clearly.

Check a package name before install
Suspicious name checkerPop out in the code editor (opens in a new tab)JavaScript
const popularPackages = ["lodash", "react", "express", "next", "axios"];function normalizePackageName(name) {  return name.toLowerCase().replace(/^@[^/]+[/]/, "").replace(/[^a-z0-9]/g, "");}function levenshteinDistance(left, right) {  const previous = Array.from({ length: right.length + 1 }, (_, index) => index);  for (let row = 1; row <= left.length; row += 1) {    const current = [row];    for (let column = 1; column <= right.length; column += 1) {      const cost = left[row - 1] === right[column - 1] ? 0 : 1;      current[column] = Math.min(current[column - 1] + 1, previous[column] + 1, previous[column - 1] + cost);    }    previous.splice(0, previous.length, ...current);  }  return previous[right.length];}function checkSuspiciousPackageName(candidate) {  const normalized = normalizePackageName(candidate);  const closest = popularPackages    .map((packageName) => ({ packageName, distance: levenshteinDistance(normalized, normalizePackageName(packageName)) }))    .sort((left, right) => left.distance - right.distance)[0];  const suspicious = closest.distance > 0 && closest.distance <= 2;  return { candidate, closest: closest.packageName, distance: closest.distance, suspicious };}console.log(JSON.stringify(checkSuspiciousPackageName("loadsh")));
Check resultnear miss
Normalizedloadsh
Closest popular namelodash
Edit distance2
Decisionsuspicious

Compared with 15 names in the lesson watchlist.

Try it yourself

Pause: loadsh is only 2 edits from lodash. Verify the package page before installing.

This is a teaching guardrail. Production scanners combine spelling, maintainer history, publish timing, scripts, and registry metadata.
Replay the loadsh check
Step 0 of 5Ready
Your turn: follow the blue line

Step through a real suspicious-name check. It is a guardrail, not a verdict; humans still verify intent and ownership.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function normalizePackageName(name) {  return name.toLowerCase().replace(/^@[^/]+[/]/, "").replace(/[^a-z0-9]/g, "");}function levenshteinDistance(left, right) {  const previous = Array.from({ length: right.length + 1 }, (_, index) => index);  for (let row = 1; row <= left.length; row += 1) {    const current = [row];    for (let column = 1; column <= right.length; column += 1) {      const cost = left[row - 1] === right[column - 1] ? 0 : 1;      current[column] = Math.min(current[column - 1] + 1, previous[column] + 1, previous[column - 1] + cost);    }    previous.splice(0, previous.length, ...current);  }  return previous[right.length];}function checkSuspiciousPackageName(candidate) {  const normalized = normalizePackageName(candidate);  const closest = popularPackages    .map((packageName) => ({ packageName, distance: levenshteinDistance(normalized, normalizePackageName(packageName)) }))    .sort((left, right) => left.distance - right.distance)[0];  const suspicious = closest.distance > 0 && closest.distance <= 2;  return { candidate, closest: closest.packageName, distance: closest.distance, suspicious };}console.log(JSON.stringify(checkSuspiciousPackageName("loadsh")));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Scoped packages reduce ambiguity when the organization controls the scope and every tool uses the same registry mapping. Put private packages behind a private scope and configure npm so @acme/* goes to the private registry instead of the public default.

Private scope mapping in .npmrcbash
@acme:registry=https://npm.acme.internal///npm.acme.internal/:_authToken=${NPM_TOKEN}always-auth=true

Slopsquatting is the AI-era cousin of typosquatting: attackers register package names that coding assistants are likely to hallucinate. Treat generated install commands as suggestions, not facts. Search the registry, read the package page, verify maintainers and source, and ask whether the package should exist at all.

Fewer, better dependencies

REVIEW

The most reliable dependency is the one you did not need. Modern JavaScript and browsers already provide many APIs that older projects used packages for: fetch, URL, URLSearchParams, Intl, structuredClone, Array.isArray, crypto.subtle, and TextEncoder cover many small helpers.

Prefer platform APIs when they fitPop out in the code editor (opens in a new tab)JavaScript
const original = { user: { name: "Ada" }, tags: ["security"] };const copy = structuredClone(original);copy.tags.push("reviewed"); console.log(original.tags.join(", "));console.log(new URL("/javascript/npm", "https://completefrontend.com").href);console.log(new Intl.ListFormat("en").format(["fetch", "URL", "structuredClone"]));
Dependency review checklist
QuestionCheckProfessional habit
NeedCan the platform do it?Prefer fetch, URL, Intl, structuredClone, crypto.subtle, and TextEncoder when they solve the job.
MaintenanceRecent releases, issue responses, tests, and bus factor.A tiny abandoned package can be riskier than writing five clear lines yourself.
Install behaviorpreinstall, install, postinstall, native builds, and binary downloads.Inspect scripts before first install; use --ignore-scripts for investigation when appropriate.
Package shapeBundle size, ESM/CJS exports, transitive count, and tree-shaking.A dependency that pulls a large graph for one helper is expensive to defend.
GovernanceLicense, maintainers, provenance, 2FA/trusted publishing, and release notes.A healthy project makes releases explainable and reversible.
A tiny dependency review objectPop out in the code editor (opens in a new tab)JavaScript
const packageChecklist = {  name: "left-pad-alternative",  needed: false,  maintenance: "last release 3 years ago",  installScripts: ["postinstall"],  license: "MIT",}; console.log(packageChecklist.needed ? "review deeply" : "prefer platform code");console.log(packageChecklist.installScripts.includes("postinstall") ? "inspect scripts" : "no install script");

Vendoring means copying a small, reviewed piece of code into your repository. It can be a good choice for stable, tiny helpers when you want no install-time behavior and no transitive graph. It also makes you responsible for fixes, license compliance, and updates. Depending is better when the library is complex, security-sensitive, actively maintained, and hard to get right yourself.

Tooling and workflow

Tools help when they are used as evidence rather than as autopilot. Dependabot and Renovate open dependency update pull requests. Socket and Snyk add package-risk, malware, reachability, and vulnerability signals. npm provenance and signatures add publish-time evidence when a package and registry support them.

Provenance and signature commandsbash
npm publish --provenancenpm audit signatures
Controls that are easy to confuse
ControlGood atNot good at
npm auditKnown published advisories in the resolved dependency graph.New malware, ownership risk, typosquats, bad maintainer practices, and advisories that do not exist yet.
Lockfile + npm ciReproduces the same package graph and integrity hashes in CI.Whether the locked graph is trustworthy, maintained, or free of malicious lifecycle scripts.
Subresource IntegrityBrowser refuses a fetched CDN file whose bytes do not match the hash.Resources that change dynamically, packages installed by npm, or compromised code whose hash you already trusted.
Provenance/signaturesLinks a published package to a signed build or verifies registry signatures when available.The code's quality, whether the maintainer meant to publish, or packages without supported attestations.
  1. Start with need: remove the dependency if a platform API or a few maintained lines solve it.
  2. Verify the name, scope, owner, repository, license, release history, and install scripts before first install.
  3. Install with a committed lockfile, review the diff, and use npm ci in CI.
  4. Run audit and risk tooling, then apply updates through pull requests with tests.
  5. Use SRI or self-hosting for browser-loaded third-party assets, and prefer provenance-aware publishing for your own packages.

Common misconceptions

  • “npm audit clean means safe.” It means no known advisory matched the resolved graph. It does not prove maintainers, names, scripts, or new releases are safe.
  • “A lockfile prevents attacks.” A lockfile prevents accidental drift. It can also preserve a bad version until you update it intentionally.
  • “SRI solves all CDN risk.” SRI protects exact bytes for immutable resources. It does not help dynamic resources or a hash copied from already-compromised code.
  • “Popular packages are automatically trustworthy.” Popular packages are attractive targets. Popularity is one signal, not a security model.
  • “Install scripts are always malicious.” Many are legitimate, especially for native modules. The point is to know when you are running third-party code.
  • “AI-generated install commands are facts.” Slopsquatting exists because plausible package names can be hallucinated and then registered by attackers.
Misconception checks
If you hearAskBetter habit
Just run audit fixWill it cross semver boundaries or hide a migration?Review the diff and run tests, especially with --force.
Use a CDN scriptIs the URL immutable, owned by someone trustworthy, and protected by SRI?Self-host or pin a versioned URL with a verified hash.
Install this package from an AI answerDoes the package exist, who owns it, and why do we need it?Verify names manually and prefer platform APIs when possible.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upRead the dependency count

Type the exact output of the repository count snippet.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const lockfilePackageEntries = 611;
const directDependencyNames = 35;
console.log(lockfilePackageEntries + " vs " + directDependencyNames);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upCopy the SRI hash

    Type the SRI string for the sample file.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    console.log("sha384-Tf2FyVXw3trfCQ/AHspPELVdH6/5BICrCwHkiQtu+pcIUQZpzFtsc+Fajj2ob/4e");

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeInterpret a near-miss package name

      Type the exact output of the name-check snippet.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const result = { candidate: "loadsh", closest: "lodash", distance: 2, suspicious: true };
      console.log(result.closest + " / " + result.distance + " / " + result.suspicious);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeName the audit limitation

        Predict the phrase printed by the audit-limitation snippet.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const auditKnows = ["published advisories", "severity", "patched versions"];
        console.log(auditKnows.includes("unreported malware") ? "complete" : "advisory database only");

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeReplace a tiny package with the platform

          Which platform API should replace the tiny package?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const tinyPackage = { name: "is-array", native: "Array.isArray" };
          console.log(tinyPackage.native);

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            8 QUESTIONS
            Supply-chain security quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What is the software supply chain for a frontend app?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does the real repository count print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const lockfilePackageEntries = 611;
              const directDependencyNames = 35;
              console.log(lockfilePackageEntries + " vs " + directDependencyNames);

              Choose an answer to see the explanation.

            3. Question 3 of 8Which statement about npm audit is accurate?

              Choose an answer to see the explanation.

            4. Question 4 of 8What does the SRI helper print for the sample file?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log("sha384-Tf2FyVXw3trfCQ/AHspPELVdH6/5BICrCwHkiQtu+pcIUQZpzFtsc+Fajj2ob/4e");

              Choose an answer to see the explanation.

            5. Question 5 of 8Why does the checker flag loadsh?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const popularPackages = ["lodash", "react", "express", "next", "axios"];
              function normalizePackageName(name) {
                return name.toLowerCase().replace(/^@[^/]+[/]/, "").replace(/[^a-z0-9]/g, "");
              }
              function levenshteinDistance(left, right) {
                const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
                for (let row = 1; row <= left.length; row += 1) {
                  const current = [row];
                  for (let column = 1; column <= right.length; column += 1) {
                    const cost = left[row - 1] === right[column - 1] ? 0 : 1;
                    current[column] = Math.min(current[column - 1] + 1, previous[column] + 1, previous[column - 1] + cost);
                  }
                  previous.splice(0, previous.length, ...current);
                }
                return previous[right.length];
              }
              function checkSuspiciousPackageName(candidate) {
                const normalized = normalizePackageName(candidate);
                const closest = popularPackages
                  .map((packageName) => ({ packageName, distance: levenshteinDistance(normalized, normalizePackageName(packageName)) }))
                  .sort((left, right) => left.distance - right.distance)[0];
                const suspicious = closest.distance > 0 && closest.distance <= 2;
                return { candidate, closest: closest.packageName, distance: closest.distance, suspicious };
              }
              console.log(JSON.stringify(checkSuspiciousPackageName("loadsh")));

              Choose an answer to see the explanation.

            6. Question 6 of 8Which control is best for a CDN script that must never change silently?

              Choose an answer to see the explanation.

            7. Question 7 of 8What does npm install --ignore-scripts help with?

              Choose an answer to see the explanation.

            8. Question 8 of 8Which incident best illustrates a long social-engineering path outside npm?

              Choose an answer to see the explanation.

            Key takeaways

            • The supply chain includes dependencies, transitive dependencies, build tools, registries, CDNs, CI, tokens, and publish workflows.
            • npm audit reports known advisories; lockfiles and npm ci make installs reproducible, not automatically safe.
            • SRI hashes exact CDN bytes with Web Crypto-style algorithms and fails closed on mismatches, but only for immutable resources.
            • Typosquatting, dependency confusion, and slopsquatting are name-resolution problems. Verify names, scopes, and registry configuration.
            • Fewer, better dependencies are easier to audit. Prefer platform APIs, review install scripts, and use update tooling as evidence.

            One-line summary: treat dependency installation and third-party scripts as code review events, then use hashes, lockfiles, provenance, and smaller graphs to make trust verifiable.

            Up next: the concurrent isolation and sandboxing lesson shows how to run untrusted code or content with stronger boundaries when verification is not enough.

            CompleteFrontend Clear concepts. Working examples.