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:
- Line 32-33: The script fetches JavaScript code from a jsdelivr CDN URL
- Line 32: It immediately writes the fetched content to disk using
fs.writeFileSync() - Line 34: It dynamically imports and executes the code with
await import() - 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
- Line 33: The fetched content is stored in
bumpUtilsContentinstead of being immediately written - Lines 34-36: A SHA-256 hash is computed using Node.js's built-in
crypto.createHash()method - Line 35: The computed hash is compared against the known-good hash
fea2efd9d2bf02cae89e7fa7c2385a4a0910db93236543394cb087b6884b89c7 - Line 36: If the hashes don't match, an error is thrown and execution stops—preventing malicious code from being written or executed
- 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.jsor any build script—always use SHA-256 or stronger hashes - The
fs.writeFileSync()and dynamicimport()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 dynamicimport()atchatgpt-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
- CWE-494: Download of Code Without Integrity Check
- CWE-829: Inclusion of Functionality from Untrusted Control Sphere
- OWASP: Vulnerable and Outdated Components
- Node.js Crypto Documentation
- Subresource Integrity (SRI) Specification
- Semgrep Rules: Supply Chain Security
- GitHub PR: fix: build utility scripts fetch javascript code fro... in...