Browser extensions are high-value targets for supply chain attacks. A compromised extension gains access to browsing data, stored credentials, and user activity across all websites. This guide covers defensive measures to protect your extension and users from supply chain threats.
Understanding Supply Chain Risks
Extension supply chain attacks can occur through:
- Compromised dependencies: Malicious code in npm packages
- Hijacked developer accounts: Attacker pushes malicious update
- Build system compromise: Malware injected during CI/CD
- Extension acquisition: Legitimate extension sold to malicious actor
- The Great Suspender: Acquired, turned malicious
- Nano Defender: Developer sold extension to malware operator
- event-stream: npm package backdoor affected many extensions
- ua-parser-js: Supply chain attack via npm
Dependency Security
Auditing Dependencies
# Run security audit
npm audit
# Check for known vulnerabilities
npx audit-ci --moderate
# Generate SBOM (Software Bill of Materials)
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
// package.json - Lock dependencies to exact versions
{
"dependencies": {
"lodash": "4.17.21", // Exact version, not ^4.17.21
"axios": "1.6.2"
},
"overrides": {
// Force specific versions of transitive dependencies
"minimist": "1.2.8"
}
}
Dependency Lockfiles
Always commit lockfiles and verify integrity:
# Verify lockfile integrity
npm ci # Fails if lockfile doesn't match package.json
# Check for lockfile tampering in CI
npm ci --ignore-scripts # Don't run postinstall scripts
Minimizing Dependencies
// Before: Heavy dependency for simple task
import _ from 'lodash';
const result = _.get(obj, 'a.b.c');
// After: Native implementation
const result = obj?.a?.b?.c;
// Before: Moment.js (500KB+)
import moment from 'moment';
const formatted = moment().format('YYYY-MM-DD');
// After: Native Intl API
const formatted = new Date().toISOString().split('T')[0];
Dependency reduction checklist:
- Can this be done with native browser APIs?
- Do I need the entire library or just one function?
- Is this dependency actively maintained?
- Does this dependency have its own dependencies?
Vendoring Critical Dependencies
For security-critical code, consider vendoring:
// vendor/simple-hash.js - Vendored and audited
export function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0;
}
return hash.toString(16);
}
// main.js
import { simpleHash } from './vendor/simple-hash.js';
Build Pipeline Security
Secure CI/CD Configuration
# .github/workflows/build.yml
name: Secure Build
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read # Minimal permissions
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false # Don't persist git credentials
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci --ignore-scripts # Skip postinstall scripts
- name: Run security audit
run: npm audit --audit-level=high
- name: Run linting
run: npm run lint
- name: Run tests
run: npm test
- name: Build extension
run: npm run build
- name: Verify build integrity
run: |
# Check no unexpected files in build
find dist -type f | sort > actual-files.txt
diff expected-files.txt actual-files.txt
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: extension-build
path: dist/
retention-days: 30
Build Reproducibility
// build.config.js
module.exports = {
// Pin tool versions
nodeVersion: '20.10.0',
npmVersion: '10.2.3',
// Deterministic builds
sourceMap: false, // Don't include in production
minify: true,
deterministic: true,
// Verify build output
expectedFiles: [
'manifest.json',
'background.js',
'popup.html',
'popup.js',
'content.js',
'icons/icon16.png',
'icons/icon48.png',
'icons/icon128.png'
],
// No eval or dynamic imports
noEval: true,
noDynamicImport: true
};
Signing and Verification
// sign-build.js
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
function hashDirectory(dir) {
const hashes = [];
function walk(directory) {
const files = fs.readdirSync(directory).sort();
for (const file of files) {
const fullPath = path.join(directory, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
walk(fullPath);
} else {
const content = fs.readFileSync(fullPath);
const hash = crypto.createHash('sha256').update(content).digest('hex');
const relativePath = path.relative(dir, fullPath);
hashes.push(`${hash} ${relativePath}`);
}
}
}
walk(dir);
return hashes.join('\n');
}
// Generate manifest of build
const manifest = hashDirectory('./dist');
fs.writeFileSync('./dist/CHECKSUMS.sha256', manifest);
console.log('Build checksums:');
console.log(manifest);
Account Security
Protecting Developer Accounts
Chrome Web Store:
- Enable 2-factor authentication
- Use hardware security keys
- Limit team member access
- Review audit logs regularly
Code Repository:
- Require signed commits
- Protect main branch
- Require code reviews
- Use branch protection rules
# Configure signed commits
git config --global commit.gpgsign true
git config --global user.signingkey YOUR_GPG_KEY_ID
Access Control
# .github/CODEOWNERS
# Require approval for sensitive files
/manifest.json @security-team
/build/ @security-team
/.github/workflows/ @security-team
/src/background.js @security-team
- Enable 2FA on all accounts (store, git, npm)
- Use unique passwords per service
- Rotate credentials periodically
- Audit access permissions quarterly
- Set up alerts for suspicious activity
Runtime Security
Content Security Policy
// manifest.json
{
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; style-src 'self' 'unsafe-inline'"
}
}
Input Validation
// Validate all external data
function validateApiResponse(response) {
// Schema validation
const schema = {
type: 'object',
required: ['data', 'version'],
properties: {
data: { type: 'array' },
version: { type: 'string', pattern: '^\\d+\\.\\d+\\.\\d+$' }
}
};
if (!validateSchema(response, schema)) {
throw new Error('Invalid API response format');
}
// Sanitize string content
if (response.data) {
response.data = response.data.map(item => ({
...item,
text: sanitizeHtml(item.text),
url: validateUrl(item.url) ? item.url : null
}));
}
return response;
}
function sanitizeHtml(html) {
const div = document.createElement('div');
div.textContent = html; // Escapes HTML
return div.innerHTML;
}
function validateUrl(url) {
try {
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
}
Subresource Integrity
For any external resources (if absolutely necessary):
<!-- If loading external scripts (avoid if possible) -->
<script
src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"
></script>
Monitoring and Detection
Extension Integrity Monitoring
// background.js - Self-integrity check
async function checkExtensionIntegrity() {
// Get expected hashes from secure storage
const { expectedHashes } = await chrome.storage.local.get('expectedHashes');
if (!expectedHashes) return; // First run
// Check critical files
const filesToCheck = [
'background.js',
'content.js',
'popup.js'
];
for (const file of filesToCheck) {
const response = await fetch(chrome.runtime.getURL(file));
const content = await response.text();
const hash = await hashString(content);
if (hash !== expectedHashes[file]) {
// Integrity violation detected
console.error(`Integrity check failed for ${file}`);
await reportIntegrityViolation(file, hash, expectedHashes[file]);
// Optionally disable extension
// chrome.management.setEnabled(chrome.runtime.id, false);
}
}
}
async function hashString(str) {
const encoder = new TextEncoder();
const data = encoder.encode(str);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// Check on startup and periodically
chrome.runtime.onStartup.addListener(checkExtensionIntegrity);
chrome.alarms.create('integrity-check', { periodInMinutes: 60 });
Anomaly Detection
// Monitor for suspicious behavior patterns
class BehaviorMonitor {
constructor() {
this.metrics = {
apiCalls: 0,
storageWrites: 0,
tabAccess: 0,
networkRequests: 0
};
this.baseline = null;
this.windowStart = Date.now();
}
track(metric) {
this.metrics[metric]++;
}
checkAnomaly() {
const windowMinutes = (Date.now() - this.windowStart) / 60000;
if (!this.baseline) {
// Establish baseline after 24 hours
if (windowMinutes >= 1440) {
this.baseline = { ...this.metrics };
this.resetWindow();
}
return;
}
// Check for significant deviations
for (const [metric, value] of Object.entries(this.metrics)) {
const expected = this.baseline[metric] * (windowMinutes / 1440);
const deviation = value / (expected || 1);
if (deviation > 10) { // 10x normal activity
this.reportAnomaly(metric, value, expected);
}
}
}
async reportAnomaly(metric, actual, expected) {
console.warn(`Anomaly detected: ${metric} is ${actual} (expected ~${expected.toFixed(0)})`);
// Send to monitoring service
await fetch('https://api.yourextension.com/security/anomaly', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
extensionVersion: chrome.runtime.getManifest().version,
metric,
actual,
expected,
timestamp: Date.now()
})
});
}
resetWindow() {
this.metrics = { apiCalls: 0, storageWrites: 0, tabAccess: 0, networkRequests: 0 };
this.windowStart = Date.now();
}
}
Incident Response
Preparation
## Security Incident Response Plan
### Detection
- [ ] Monitor store reviews for security complaints
- [ ] Set up alerts for anomalous API usage
- [ ] Watch for reports on social media / forums
### Containment
1. Identify affected versions
2. Disable auto-update if possible
3. Publish clean update immediately
4. Notify affected users
### Recovery
1. Identify attack vector
2. Remove compromised code/dependencies
3. Reset all credentials
4. Rebuild from known-good source
### Post-Incident
1. Document incident timeline
2. Update security practices
3. Notify users of resolution
4. Consider bug bounty program
Summary
Supply chain security requires defense in depth: minimize dependencies, verify builds, protect accounts, and monitor runtime behavior. The effort invested in security protects both your users and your reputation.
Key security measures:
- Audit and minimize dependencies
- Lock exact dependency versions
- Secure CI/CD pipeline
- Enable 2FA on all accounts
- Implement runtime integrity checks
- Monitor for anomalous behavior
- Have an incident response plan