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 Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How dependabot-missing-cooldown happens in GitHub Actions/Node.js and how to fix it

The repository's `.github/dependabot.yml` had no cooldown period configured, meaning Dependabot could immediately propose updates to newly published package versions with zero time for the community to flag malware or instability. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, forcing a 7-day waiting period before new releases are surfaced as update PRs.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.