Back to Blog
critical SEVERITY7 min read

How supply chain code injection happens in Node.js build scripts and how to fix it

A critical supply chain vulnerability in the chatgpt-auto-continue extension's build utility allowed arbitrary code execution by fetching JavaScript from a CDN without integrity verification. The fix implements SHA-256 hash validation before executing the downloaded code, preventing potential supply chain attacks through compromised CDN content or man-in-the-middle attacks.

O
By Orbis AppSec
Published July 23, 2026Reviewed July 23, 2026

Answer Summary

Supply chain code injection in Node.js build scripts (CWE-494) occurs when external code is fetched from CDNs and executed without integrity verification. In `chatgpt-auto-continue/utils/bump/extension-manifests.js`, the script used `fs.writeFileSync()` to save fetched CDN content and immediately executed it via dynamic import. The fix adds SHA-256 hash validation using Node.js's `crypto.createHash()` to verify content integrity before execution, blocking malicious code injection from compromised CDNs or MITM attacks.

Vulnerability at a Glance

cweCWE-494 (Download of Code Without Integrity Check)
fixSHA-256 hash validation of CDN content before filesystem write and dynamic import
riskArbitrary code execution in developer environments through compromised CDN or MITM attacks
languageNode.js
root causeNo integrity verification before writing and executing externally fetched JavaScript code
vulnerabilitySupply chain code injection via unverified CDN content

Introduction

In the chatgpt-auto-continue extension repository, we discovered a critical supply chain vulnerability in chatgpt-auto-continue/utils/bump/extension-manifests.js at line 30. The build script was fetching JavaScript code from https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs, writing it directly to the filesystem using fs.writeFileSync(), and then immediately executing it via dynamic import—all without any integrity verification.

This pattern created a dangerous supply chain attack vector where compromised CDN content, DNS poisoning, or a man-in-the-middle attack could inject arbitrary malicious code directly into the developer environment. Since this is a Node.js library, the vulnerability affects every downstream consumer who uses this package and runs the build scripts.

The Vulnerability Explained

Let's examine the vulnerable code from extension-manifests.js:

// Import BUMP UTILS
fs.mkdirSync(path.dirname(cachePaths.bumpUtils), { recursive: true })
fs.writeFileSync(cachePaths.bumpUtils, (await (await fetch(
    'https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs')).text()))
const bump = await import(`file://${cachePaths.bumpUtils}`) ; fs.unlinkSync(cachePaths.bumpUtils)

The problem occurs in this sequence:

  1. Line 32-33: The script fetches JavaScript code from a jsdelivr CDN URL
  2. Line 32: It immediately writes the fetched content to disk using fs.writeFileSync()
  3. Line 34: It dynamically imports and executes the code with await import()
  4. Missing: There's absolutely no verification that the fetched content matches what's expected

How Could This Be Exploited?

Consider these attack scenarios specific to this code:

Scenario 1: Compromised CDN
An attacker who gains access to the jsdelivr CDN infrastructure could replace bump.min.mjs with malicious code. When developers run the build script, they would unknowingly download and execute the attacker's code. The malicious code could:
- Steal environment variables (API keys, tokens)
- Inject backdoors into the built extension
- Exfiltrate source code or credentials
- Modify the build output to include malware

Scenario 2: DNS Poisoning
An attacker performing DNS poisoning in a corporate network could redirect cdn.jsdelivr.net to their own server hosting malicious JavaScript. The build script would fetch and execute the attacker's code, believing it came from the legitimate CDN.

Scenario 3: Man-in-the-Middle Attack
Even with HTTPS, sophisticated attackers with MITM capabilities (compromised proxies, rogue certificates) could intercept the request and replace the content before it reaches the developer's machine.

Real-World Impact

This vulnerability is particularly dangerous because:

  • It executes in the developer environment with full filesystem access and environment variables
  • It runs during the build process, meaning it could inject malicious code into the production extension
  • It's in a library package, so every downstream consumer inherits this vulnerability
  • There's no audit trail of what code was actually executed—the file is deleted immediately after import

The bump utility that's being imported handles version bumping for the extension manifests. A compromised version could manipulate version numbers, inject malicious code into manifests, or steal sensitive build-time data.

The Fix

The fix implements Subresource Integrity (SRI) verification using SHA-256 hashing. Here's the corrected code:

// Import BUMP UTILS
fs.mkdirSync(path.dirname(cachePaths.bumpUtils), { recursive: true })
const bumpUtilsContent = await (await fetch(
    'https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs')).text()
if (createHash('sha256').update(bumpUtilsContent).digest('hex') !==
        'fea2efd9d2bf02cae89e7fa7c2385a4a0910db93236543394cb087b6884b89c7')
    throw new Error('Integrity check failed: unexpected content in bump.min.mjs')
fs.writeFileSync(cachePaths.bumpUtils, bumpUtilsContent)
const bump = await import(`file://${cachePaths.bumpUtils}`) ; fs.unlinkSync(cachePaths.bumpUtils)

Before and After Comparison

Before (Vulnerable):

fs.writeFileSync(cachePaths.bumpUtils, (await (await fetch(
    'https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs')).text()))
const bump = await import(`file://${cachePaths.bumpUtils}`)

After (Secure):

const bumpUtilsContent = await (await fetch(
    'https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs')).text()
if (createHash('sha256').update(bumpUtilsContent).digest('hex') !==
        'fea2efd9d2bf02cae89e7fa7c2385a4a0910db93236543394cb087b6884b89c7')
    throw new Error('Integrity check failed: unexpected content in bump.min.mjs')
fs.writeFileSync(cachePaths.bumpUtils, bumpUtilsContent)
const bump = await import(`file://${cachePaths.bumpUtils}`)

How This Fix Solves the Problem

  1. Line 33: The fetched content is stored in bumpUtilsContent instead of being immediately written
  2. Lines 34-36: A SHA-256 hash is computed using Node.js's built-in crypto.createHash() method
  3. Line 35: The computed hash is compared against the known-good hash fea2efd9d2bf02cae89e7fa7c2385a4a0910db93236543394cb087b6884b89c7
  4. Line 36: If the hashes don't match, an error is thrown and execution stops—preventing malicious code from being written or executed
  5. Line 37: Only if the integrity check passes is the content written to disk

The fix also adds { createHash } = require('crypto') to the imports at line 24, using Node.js's built-in cryptography module without adding external dependencies.

Security Improvement

This change provides cryptographic assurance that the fetched code is exactly what the developer expects. Even if an attacker compromises the CDN, performs DNS poisoning, or executes a MITM attack, the integrity check will fail because the malicious code will produce a different SHA-256 hash. The script will throw an error and halt execution before any malicious code can run.

The hash fea2efd9d2bf02cae89e7fa7c2385a4a0910db93236543394cb087b6884b89c7 acts as a cryptographic fingerprint of the legitimate bump.min.mjs file. Any modification—even a single byte—will produce a completely different hash, making tampering immediately detectable.

Prevention & Best Practices

1. Always Verify External Code Integrity

When fetching code from CDNs or external sources, implement integrity checks:

const crypto = require('crypto');

function verifyIntegrity(content, expectedHash, algorithm = 'sha256') {
    const actualHash = crypto.createHash(algorithm)
        .update(content)
        .digest('hex');

    if (actualHash !== expectedHash) {
        throw new Error(`Integrity check failed: expected ${expectedHash}, got ${actualHash}`);
    }
}

2. Use Subresource Integrity (SRI) in HTML

For browser-based applications, use SRI attributes:

<script src="https://cdn.example.com/library.js"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux..."
        crossorigin="anonymous"></script>

3. Pin Specific Versions

The URL in this fix already pins a specific commit hash (f63b650), which is good practice:

https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs

Avoid using @latest or version ranges when fetching executable code.

4. Consider Vendoring Critical Dependencies

For critical build scripts, consider vendoring (copying) dependencies into your repository:

# Download and commit the dependency
curl -o utils/bump.min.mjs https://cdn.jsdelivr.net/gh/...
git add utils/bump.min.mjs
git commit -m "Vendor bump utility"

This eliminates runtime CDN dependencies but requires manual updates.

5. Use Lock Files and Dependency Scanning

For npm packages, use package-lock.json or yarn.lock to pin transitive dependencies. Use tools like:
- npm audit for vulnerability scanning
- Dependabot for automated security updates
- Snyk or Socket.dev for supply chain security

6. Implement Content Security Policy (CSP)

For Node.js applications that might execute in browser contexts, implement strict CSP:

app.use((req, res, next) => {
    res.setHeader("Content-Security-Policy", 
        "script-src 'self' https://trusted-cdn.com");
    next();
});

7. Use Static Analysis Tools

Configure tools to detect this pattern:

Semgrep rule example:

rules:
  - id: unverified-remote-code-execution
    pattern: |
      fs.writeFileSync($PATH, await (await fetch($URL)).text())
    message: Remote code is fetched and written without integrity verification
    severity: ERROR

Key Takeaways

  • Never execute CDN-fetched code without integrity verification in extension-manifests.js or any build script—always use SHA-256 or stronger hashes
  • The fs.writeFileSync() and dynamic import() combination creates immediate code execution risk when fed unverified external content
  • jsdelivr CDN URLs with commit hashes (like @f63b650) provide version pinning but not integrity—hash verification is still essential
  • Node.js's built-in crypto.createHash('sha256') provides zero-dependency integrity checking for supply chain security
  • Supply chain attacks target build scripts specifically because they run with developer privileges and can inject malicious code into production artifacts

How Orbis AppSec Detected This

  • Source: External CDN content fetched from https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@f63b650/utils/bump/lib/bump.min.mjs
  • Sink: fs.writeFileSync() followed by dynamic import() at chatgpt-auto-continue/utils/bump/extension-manifests.js:32-34
  • Missing control: No cryptographic integrity verification (hash check, signature validation, or SRI) before writing and executing fetched code
  • CWE: CWE-494 (Download of Code Without Integrity Check)
  • Fix: Added SHA-256 hash validation using crypto.createHash() to verify content integrity before filesystem write and execution

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This supply chain vulnerability in the chatgpt-auto-continue extension's build script demonstrates how seemingly innocuous utility code can create critical security risks. By fetching and executing JavaScript from a CDN without integrity verification, the script opened a direct path for arbitrary code execution in developer environments.

The fix—implementing SHA-256 hash validation before execution—is a simple yet powerful defense that provides cryptographic assurance of code integrity. This pattern should be applied wherever your code fetches and executes external content, whether in build scripts, runtime dependency loading, or plugin systems.

Supply chain security is increasingly critical as modern development relies heavily on external code. Every fetch-and-execute pattern is a potential attack vector. By adopting integrity verification as a standard practice, you can protect your development pipeline and production systems from compromised dependencies.

Remember: trust but verify. Even code from trusted CDNs should be validated with cryptographic hashes before execution.

References

Frequently Asked Questions

What is supply chain code injection in Node.js build scripts?

It's a vulnerability where build scripts fetch and execute external code (typically from CDNs) without verifying its integrity through cryptographic hashes or signatures. If the CDN is compromised or a man-in-the-middle attack occurs, malicious code can be injected into the build process, affecting all developers and potentially production deployments.

How do you prevent supply chain code injection in Node.js?

Implement Subresource Integrity (SRI) checks using SHA-256, SHA-384, or SHA-512 hashes. In Node.js, use the `crypto` module to compute hashes of fetched content and compare against known-good values before execution. Additionally, pin specific versions in URLs, use HTTPS exclusively, and consider vendoring critical dependencies locally.

What CWE is supply chain code injection?

Supply chain code injection falls under CWE-494 (Download of Code Without Integrity Check), which is part of the broader category of supply chain vulnerabilities. Related CWEs include CWE-829 (Inclusion of Functionality from Untrusted Control Sphere) and CWE-345 (Insufficient Verification of Data Authenticity).

Is HTTPS enough to prevent supply chain code injection?

No. While HTTPS prevents man-in-the-middle attacks during transit, it doesn't protect against compromised CDN servers, DNS poisoning, or malicious CDN operators. An attacker who gains control of the CDN can serve malicious content over valid HTTPS. Cryptographic hash verification (SRI) is essential to ensure content integrity regardless of transport security.

Can static analysis detect supply chain code injection?

Yes. Static analysis tools can detect patterns like fetching remote code followed by `eval()`, `Function()`, or dynamic imports without integrity checks. Tools like Semgrep, CodeQL, and specialized supply chain security scanners can flag these patterns. However, determining the correct hash value requires runtime analysis or manual inspection of the legitimate content.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #502

Related Articles

high

How XML Node Injection happens in JavaScript XML parsing and how to fix it

CVE-2026-41672 is a high-severity XML node injection vulnerability in the `@xmldom/xmldom` package, caused by insufficient validation during comment serialization that allows attackers to inject arbitrary XML nodes into a document. The fix upgrades `@xmldom/xmldom` from version 0.8.12 to 0.8.13 (and 0.9.x to 0.9.10), closing the injection path by tightening how untrusted comment content is handled before it reaches the serializer.

high

How Remote Code Execution via serialize-javascript happens in Node.js and how to fix it

The `serialize-javascript` package version 6.0.2 contained a high-severity Remote Code Execution (RCE) vulnerability (GHSA-5c6j-r48x-rmvq) exploitable through crafted `RegExp.flags` and `Date.prototype.toISOString()` payloads. Upgrading to version 7.0.3 eliminates the vulnerable serialization logic and removes the `randombytes` dependency that was part of the attack surface. This fix was applied via a `package.json` override and `package-lock.json` update.

medium

How supply chain trust policy bypass happens in Node.js pnpm workspaces and how to fix it

A missing `trustPolicy` configuration in `pnpm-workspace.yaml` left a Node.js library vulnerable to supply chain attacks where malicious package updates could downgrade security settings. Combined with insufficient input validation in the todo application component, this created a medium-severity attack surface. The fix adds `trustPolicy: no-downgrade`, `blockExoticSubdeps: true`, and `minimumReleaseAge: 10080` to harden the package manager configuration.

high

How unbounded brace-expansion DoS happens in Node.js and how to fix it

CVE-2026-14257 is a denial-of-service vulnerability in the brace-expansion library that allows attackers to crash applications through unbounded expansion of brace patterns. By upgrading from version 2.1.2 to 2.1.3 and 5.0.6 to 5.0.8, we eliminated the risk of memory exhaustion attacks targeting glob pattern expansion in build tools and scripts.

high

How DNS rebinding happens in Node.js MCP TypeScript SDK and how to fix it

The `@modelcontextprotocol/sdk` package (version 0.5.0) shipped without DNS rebinding protection enabled by default, leaving any Node.js application that hosts an MCP server exposed to cross-origin attacks from malicious websites. Upgrading to version 1.24.0 enables host-header validation and brings a suite of new security dependencies—including `cors`, `express-rate-limit`, and `jose`—that collectively close the attack surface. This fix was identified and patched automatically by Orbis AppSec b

high

How broken access control via supply chain dependency poisoning happens in TYPO3 with Dependabot and how to fix it

A missing cooldown configuration in Dependabot allowed the automatic proposal of newly published (and potentially malicious) package versions, creating a supply chain attack vector that could have facilitated the exploitation of CVE-2026-47343 — a broken access control vulnerability in TYPO3's File Abstraction Layer. The fix adds a 7-day cooldown period to all package ecosystem entries in `.github/dependabot.yml`, ensuring newly published packages are vetted by the community before being propose