Software supply chain: dependencies, install scripts, lockfiles
An install runs strangers' code as you: fix the versions, approve the scripts, and leave the install step nothing to take.
Predict first
Before you read: predict
ExamHall's CI job sets two job-level variables, the npm publish token EXH_PUBLISH_TOKEN and the cloud key EXH_CLOUD_KEY, and runs three steps: npm ci, publish and deploy. Job-level variables reach every step. A bot's pull request moves the lockfile to frame-codec 1.8.1, published that morning, which the proctoring SDK proctor-sdk depends on; its postinstall script looks for both variables.
Which is the smallest change after which that script finds neither, while publish and deploy still get theirs?
The 30-second version
Installing a package runs its code, and the code of every package below it, with the environment of the step that installs it. So choose which versions arrive (a lockfile installed with npm ci, a release-age delay), which install scripts may run (approved by name), and what an install step can reach (each credential only where it is used).
An install runs strangers' code as you: fix the versions, approve the scripts, and leave the install step nothing to take.
How it works
Step 1 of 12
Analogy: A zoo receiving an animal from another zoo (zoos)
The receiving zoo cannot examine an animal's every habit at the gate; it decides who arrives, how long they wait, and which keys travel with them.
| At the zoo | In an install |
|---|---|
| the sending zoo's keeper, handed the head keeper's ring that opens every house and the drugs safe | an install script running with the step's tokens (steps 2, 4) |
| a companion animal from a third zoo, in the same crate, never asked for | a nested package's script (step 3) |
| the transfer form listing each animal by microchip; the gate refuses a crate whose chips differ | the lockfile's versions and hashes, and npm ci (steps 6–7) |
| weeks of quarantine before a newcomer meets the collection | a release-age delay (step 9) |
| the studbook: where the animal was bred and by whom | provenance (step 11) |
| a visiting keeper given the quarantine-house key alone | a credential only on the step that needs it (step 5) |
In real terms: you choose what arrives, how long it waits and what it can reach; you cannot read every line of it.
Breaks down when: a sick animal usually shows symptoms in quarantine; a malicious version looks healthy until someone reads its code or an advisory names it (steps 9–10).
Examples, run for real
4.1 ExamHall EXH-3810: the token in reach of an install script
ExamHall's CI job held the npm publish token and the cloud key as job-level variables. A bot's pull request moved a dependency of the proctoring SDK to a version published that morning, with a postinstall script. MR !4110 sets each variable on the step that uses it and adds strict-allow-scripts=true to .npmrc:
4.1 before MR !4110 (both variables at job level), the bot's PR: npm ci exit 0 · frame-codec 1.8.1's postinstall ran · it saw: EXH_PUBLISH_TOKEN, EXH_CLOUD_KEY
4.1 after MR !4110, the bot's PR: npm ci stopped (ESTRICTALLOWSCRIPTS: frame-codec@1.8.1 has an install script not covered by allowScripts) · no install script ran
4.1 after MR !4110, main (frame-codec 1.8.0, no install script): npm ci exit 0 · publish step has its token: yes · deploy step has its key: yes
Before the MR, a package ExamHall never listed ran at install time with both secrets in reach (steps 3–4). After it, the unapproved script stops the install, and on main, where no new script arrived, each later step still gets its own variable (steps 5, 8).
4.2 Ticketing: other bytes under the same version
A box office's lockfile was resolved against qr-render 1.4.2. Later, a mirror it installs from serves other bytes under that same version:
4.2 the mirror's qr-render 1.4.2 has other bytes · npm ci with the committed lockfile: exit 1 (EINTEGRITY) · nothing installed
4.2 npm install with no lockfile: exit 0 · qr-render 1.4.2 installed with the mirror's other bytes
The lockfile kept the hash of the bytes it was resolved with, so npm ci refused the swap; with no lockfile, npm trusted whatever the mirror said that version was (step 6).
Try it
Answer first, then check. Nothing is saved.
Question 1 of 7
A box office's MR makes four changes; its package.json has no allowScripts entries.
Which change should the review block?
Question 2 of 7
A box office repository's package.json lists qr-render ^1.4.0, and its committed lockfile has 1.4.2. A developer changes it to ^2.0.0 (2.0.0 is on the registry) and pushes without running any install. CI runs npm ci.
What does that step do?
Question 3 of 7
A box office's .npmrc sets min-release-age=3. font-subset 2.3.0 was published 40 days ago; 2.3.1, published six hours ago, adds a postinstall script. With no lockfile entry for either, a developer runs npm install ticket-pdf; ticket-pdf 1.0.0, 90 days old, depends on font-subset@^2.3.0.
Which font-subset lands, and does an install script run?
Question 4 of 7
A box office team weighs ticket-pdf 1.4.0, whose registry page shows a provenance attestation linking it to a public repository and a GitHub Actions build.
What does that attestation give the team?
Question 5 of 7
ExamHall's lead says: "We run npm audit in CI, so supply-chain risk is covered." Your reply should say what audit catches, what it misses, and which controls cover what it misses.
Reply in at most 120 words.
Question 6 of 7
A box office repository's package.json lists seat-sdk ^2.0.0. On Tuesday morning a bot updates its lockfile to barcode-kit 3.1.1, which seat-sdk depends on, published six hours before; that afternoon the team adds min-release-age=3 to .npmrc. On Wednesday CI runs npm ci with that lockfile.
Which barcode-kit version does it install?
Question 7 of 7
On 17 September 2025 a box office team learns that one of its CI builds installed a package version named in the Shai-Hulud reports, in a job whose environment held an npm publish token and AWS keys.
What should the team do with those two credentials?
Go deeper
Pro tip
- In a dependency-update review, read the lockfile diff for packages that gained an install script and for versions under a few days old, not only the package.json lines. When: every bot or human PR that changes
package-lock.json. Status: SELF (this lesson's practice target; not run).
Source
Valid as of 2026-10-13.
- OWASP, Top 10:2025 A03 Software Supply Chain Failures (the row's first source), https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/ (
owasp-a03-2025.txt): "They are often caused by vulnerabilities or malicious changes in third-party code, tools, or other dependencies that the system relies on." (step 1); "Track not just your direct dependencies, but their (transitive) dependencies, and so on." (step 3); "Your build server & CI/CD (separation of duties, access control, signed builds, environment-scoped secrets, tamper-evident logs, more)" (step 5); "Deliberately choose which version of a dependency you use and upgrade only when there is need." (steps 6, 9); "The Shai-Hulud supply chain attack in 2025 was the first successful self-propagating npm worm." and "The malware would also detect npm tokens in the victim environment, and automatically use them to push malicious versions of any accessible package." (step 12). As of 2026-10-13. - CISA, Widespread Supply Chain Compromise Impacting npm Ecosystem, alert of September 23, 2025 (the row's second source), https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem (
cisa-npm-2025-09-23.txt): "A self-replicating worm—publicly known as “Shai-Hulud”—has compromised over 500 packages." (step 12); "The cyber actor then targeted GitHub Personal Access Tokens (PATs) and application programming interface (API) keys for cloud services, including Amazon Web Services (AWS)" (step 12); "Exfiltrated the harvested credentials to an endpoint controlled by the actor." (step 12); "Immediately rotate all developer credentials." (step 12); "Check for package-lock.json or yarn.lock files to identify affected packages, including those nested in dependency trees." (steps 3, 6). As of 2026-10-13. - npm Docs, Generating provenance statements (the row's third source), https://docs.npmjs.com/generating-provenance-statements (
npm-provenance.txt): "This allows you to publicly establish where a package was built and who published a package, which can increase supply-chain security for your packages." (step 11); "npm provenance provides a verifiable link to the package’s source code and build instructions, which developers can then audit and determine whether to trust it or not" (step 11); "it does not guarantee the package has no malicious code" (step 11); "This allows developers to verify where and how your package was built before they download it." (step 11); "You can verify the provenance attestations of downloaded packages with the following audit command:" (step 11). As of 2026-10-13.
Related lessons
Checked against the tools and docs as of 2026-10-13.