Open-source upload security for Node.js. Inspect first, store later.

Pompelmi
Article May 15, 2024

Supply Chain Security: Protecting Your Dependencies from Malicious Uploads

Learn how to secure your development pipeline against supply chain attacks through malicious file uploads in dependencies, packages, and third-party integrations.

supply-chain security dependencies devops

Supply Chain Security: Protecting Your Dependencies from Malicious Uploads

Software supply chain attacks have increased by 742% in the past three years, with malicious file uploads being a primary attack vector. When attackers compromise popular packages, developer tools, or CI/CD systems through malicious uploads, the impact cascades across thousands of downstream applications. This guide explores how to protect your entire development pipeline from these sophisticated threats.

Understanding Supply Chain Attack Vectors

The Anatomy of a Supply Chain Upload Attack

Supply chain attacks through file uploads typically follow this pattern:

  1. Initial Compromise: Attacker gains access to a trusted system
  2. Malicious Upload: Trojanized files uploaded to package repositories
  3. Distribution: Malicious code spreads through normal update mechanisms
  4. Activation: Payloads execute in target environments
  5. Persistence: Backdoors establish long-term access

Real-World Examples

The SolarWinds Compromise

  • Attackers uploaded malicious code to build systems
  • Trojanized updates distributed to 18,000+ customers
  • Undetected for months due to legitimate code signing

NPM Package Hijacking

  • Malicious packages uploaded with similar names to popular libraries
  • Typosquatting attacks targeting common package names
  • Immediate code execution upon installation

Securing Package Management

NPM/Yarn Security

// package.json - Secure dependency management
{
"name": "secure-app",
"dependencies": {
"pompelmi": "^1.2.0"
},
"scripts": {
"preinstall": "node scripts/audit-dependencies.js",
"postinstall": "node scripts/verify-packages.js"
},
"overrides": {
// Pin vulnerable dependencies
"lodash": "4.17.21"
}
}
scripts/audit-dependencies.js
import { FileScanner } from 'pompelmi';
import { execSync } from 'child_process';
import path from 'path';
class DependencyScanner {
constructor() {
this.scanner = new FileScanner({
// Specialized rules for package scanning
enableSupplyChainDetection: true,
packageAnalysis: {
scanInstallScripts: true,
checkCodeSigning: true,
validatePublishers: true,
detectSuspiciousPatterns: true
}
});
this.trustedPublishers = new Set([
'npm-official',
'verified-org',
// Add your trusted publishers
]);
}
async auditPackages() {
console.log('🔍 Scanning dependencies for security threats...');
// Get package list
const packages = this.getInstalledPackages();
const threats = [];
for (const pkg of packages) {
try {
const result = await this.scanPackage(pkg);
if (result.threats.length > 0) {
threats.push(...result.threats);
}
} catch (error) {
console.error(`Failed to scan package ${pkg.name}:`, error.message);
}
}
if (threats.length > 0) {
this.reportThreats(threats);
process.exit(1); // Fail the build
}
console.log('✅ All dependencies passed security scan');
}
async scanPackage(pkg) {
const packagePath = path.join('node_modules', pkg.name);
const threats = [];
// Scan package files
const scanResult = await this.scanner.scanDirectory(packagePath);
if (scanResult.verdict !== 'clean') {
threats.push({
package: pkg.name,
version: pkg.version,
threats: scanResult.findings,
severity: 'high'
});
}
// Check install scripts
const installScriptThreats = await this.scanInstallScripts(pkg);
threats.push(...installScriptThreats);
// Verify publisher
const publisherThreat = await this.verifyPublisher(pkg);
if (publisherThreat) threats.push(publisherThreat);
return { threats };
}
async scanInstallScripts(pkg) {
const packageJson = require(`node_modules/${pkg.name}/package.json`);
const threats = [];
const dangerousScripts = ['preinstall', 'install', 'postinstall'];
for (const scriptType of dangerousScripts) {
if (packageJson.scripts?.[scriptType]) {
const script = packageJson.scripts[scriptType];
// Scan for suspicious patterns
const suspiciousPatterns = [
/curl\s+.*\|\s*(bash|sh)/i, // Download and execute
/wget\s+.*\|\s*(bash|sh)/i, // Download and execute
/rm\s+-rf\s+\//i, // Dangerous file operations
/chmod\s+\+x/i, // Making files executable
/eval\s*\(/i, // Dynamic code execution
/process\.env\[['"][A-Z_]+['"]\]/i, // Environment access
];
for (const pattern of suspiciousPatterns) {
if (pattern.test(script)) {
threats.push({
package: pkg.name,
type: 'suspicious_install_script',
script: scriptType,
content: script,
severity: 'medium'
});
}
}
}
}
return threats;
}
async verifyPublisher(pkg) {
try {
// Check package registry info
const registryInfo = await this.getRegistryInfo(pkg.name);
// Verify publisher is trusted
if (!this.trustedPublishers.has(registryInfo.publisher)) {
// Check if it's a new/suspicious publisher
if (registryInfo.publisherAge < 30) { // Less than 30 days
return {
package: pkg.name,
type: 'untrusted_publisher',
publisher: registryInfo.publisher,
publisherAge: registryInfo.publisherAge,
severity: 'medium'
};
}
}
// Check for typosquatting
const legitimatePackages = ['react', 'express', 'lodash', 'axios'];
for (const legitimate of legitimatePackages) {
if (this.isTyposquat(pkg.name, legitimate)) {
return {
package: pkg.name,
type: 'potential_typosquat',
targetPackage: legitimate,
severity: 'high'
};
}
}
} catch (error) {
console.warn(`Could not verify publisher for ${pkg.name}:`, error.message);
}
return null;
}
isTyposquat(packageName, targetName) {
// Simple Levenshtein distance check
if (packageName === targetName) return false;
const distance = this.levenshteinDistance(packageName, targetName);
const similarity = 1 - (distance / Math.max(packageName.length, targetName.length));
// If packages are very similar but not identical, flag as potential typosquat
return similarity > 0.8 && similarity < 1.0;
}
levenshteinDistance(str1, str2) {
const matrix = Array(str2.length + 1).fill().map(() => Array(str1.length + 1).fill(0));
for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
for (let j = 1; j <= str2.length; j++) {
for (let i = 1; i <= str1.length; i++) {
const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
matrix[j][i] = Math.min(
matrix[j][i - 1] + 1, // deletion
matrix[j - 1][i] + 1, // insertion
matrix[j - 1][i - 1] + cost // substitution
);
}
}
return matrix[str2.length][str1.length];
}
}
// Run the audit
const scanner = new DependencyScanner();
scanner.auditPackages().catch(console.error);

Python/Pip Security

requirements-security.txt
# Pinned versions with integrity checks
requests==2.28.2 --hash=sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa
flask==2.2.3 --hash=sha256:7eb373984bf1c770023716b98c4b2a9b5b1b5d6b1c4e3d1e4b5a1a2b3c4d5e6f
# Security scanning integration
pommelmi-python>=1.0.0
def audit_python_packages():
"""Audit Python packages for security threats"""
import subprocess
import json
from pompelmi import PythonPackageScanner
# Get installed packages
result = subprocess.run([
'pip', 'list', '--format=json'
], capture_output=True, text=True)
packages = json.loads(result.stdout)
scanner = PythonPackageScanner()
for package in packages:
# Scan package for threats
scan_result = scanner.scan_package(
package['name'],
package['version']
)
if scan_result.verdict != 'clean':
print(f"⚠️ Threat detected in {package['name']}:")
for finding in scan_result.findings:
print(f" - {finding.description}")
# Quarantine malicious packages
if scan_result.verdict == 'malicious':
subprocess.run(['pip', 'uninstall', '-y', package['name']])
print(f"🚨 Removed malicious package: {package['name']}")
if __name__ == '__main__':
audit_python_packages()

CI/CD Pipeline Security

GitHub Actions Security

.github/workflows/supply-chain-security.yml
name: Supply Chain Security
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Daily security audit
- cron: '0 2 * * *'
jobs:
dependency-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Pompelmi CLI
run: npm install -g @pompelmi/cli
- name: Audit Dependencies
run: |
# Scan package.json for suspicious dependencies
pompelmi scan-deps --strict --report=sarif
# Scan installed packages
npm audit --audit-level=moderate
# Custom security checks
node scripts/supply-chain-audit.js
- name: Upload Security Report
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: security-report.sarif
container-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker Image
run: docker build -t app:test .
- name: Scan Container for Malicious Files
run: |
# Extract container filesystem
docker create --name temp app:test
docker export temp | tar -x -C /tmp/container
# Scan extracted files
pompelmi scan-directory /tmp/container \
--exclude=/tmp/container/proc \
--exclude=/tmp/container/sys \
--report=json > container-scan.json
# Check for threats
if jq -e '.threats[] | select(.severity == "high")' container-scan.json; then
echo "High severity threats found in container"
exit 1
fi
supply-chain-monitor:
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- name: Monitor Supply Chain Changes
run: |
# Check for new dependencies
git diff HEAD~1 HEAD package.json | grep '^+' | grep -E '(dependencies|devDependencies)'
# Alert on suspicious changes
node scripts/monitor-changes.js

Docker Security

# Multi-stage build for security scanning
FROM node:18-alpine AS deps-scanner
# Install Pompelmi for dependency scanning
RUN npm install -g @pompelmi/cli
# Copy package files
COPY package*.json ./
# Scan dependencies before installation
RUN pompelmi scan-deps --strict
# Install only after security approval
RUN npm ci --only=production
FROM node:18-alpine AS runtime
# Create non-privileged user
RUN addgroup -g 1001 -S pompelmi && \
adduser -S pompelmi -u 1001 -G pompelmi
# Copy dependencies from scanner stage
COPY --from=deps-scanner --chown=pompelmi:pompelmi node_modules ./node_modules
# Copy application code
COPY --chown=pompelmi:pompelmi . .
# Final security scan of the complete image
RUN apk add --no-cache curl && \
curl -O https://github.com/pompelmi/scanner/releases/latest/download/pompelmi-linux && \
chmod +x pompelmi-linux && \
./pompelmi-linux scan-filesystem / \
--exclude=/proc --exclude=/sys --exclude=/dev \
--fail-on=malicious && \
rm pompelmi-linux
USER pompelmi
EXPOSE 3000
CMD ["node", "server.js"]

Build System Security

Webpack/Build Tool Integration

webpack.config.js
const PompelmiWebpackPlugin = require('@pompelmi/webpack-plugin');
module.exports = {
// ... other config
plugins: [
new PompelmiWebpackPlugin({
// Scan all imported modules
scanImports: true,
// Scan dynamically loaded code
scanDynamicImports: true,
// Fail build on threats
failOnThreats: true,
// Custom rules for build-time scanning
rules: [
'build-time-threats.yar',
'suspicious-imports.yar'
],
// Report generation
reports: {
console: true,
json: './build-security-report.json',
sarif: './build-security.sarif'
}
})
]
};
// Custom build security plugin
class BuildSecurityPlugin {
apply(compiler) {
compiler.hooks.compilation.tap('BuildSecurityPlugin', (compilation) => {
compilation.hooks.seal.tapAsync('BuildSecurityPlugin', async (callback) => {
// Scan all modules in the compilation
for (const module of compilation.modules) {
if (module.resource) {
await this.scanModule(module);
}
}
callback();
});
});
}
async scanModule(module) {
const scanner = new FileScanner({
enableSupplyChainDetection: true,
buildTimeAnalysis: true
});
try {
const result = await scanner.scanFile(module.resource);
if (result.verdict === 'malicious') {
throw new Error(
`Malicious code detected in module: ${module.resource}\n` +
`Threats: ${result.findings.map(f => f.title).join(', ')}`
);
}
if (result.verdict === 'suspicious') {
console.warn(
`⚠️ Suspicious code in module: ${module.resource}\n` +
`Warnings: ${result.findings.map(f => f.title).join(', ')}`
);
}
} catch (error) {
console.error(`Security scan failed for ${module.resource}:`, error);
throw error;
}
}
}

Git Hook Security

.git/hooks/pre-commit
#!/bin/sh
# Scan staged files before commit
echo "🔍 Running pre-commit security scan..."
# Get staged files
STAGED_FILES=$(git diff --cached --name-only)
# Scan staged files for threats
for file in $STAGED_FILES; do
if [[ -f "$file" ]]; then
# Scan file with Pompelmi
if ! pompelmi scan "$file" --quiet; then
echo "🚨 Security threat detected in: $file"
echo "Commit blocked for security reasons"
exit 1
fi
fi
done
# Additional checks for package files
if [[ " $STAGED_FILES " =~ " package.json " ]] || [[ " $STAGED_FILES " =~ " package-lock.json " ]]; then
echo "📦 Package files modified, running dependency audit..."
if ! node scripts/audit-new-dependencies.js; then
echo "🚨 Dependency audit failed"
exit 1
fi
fi
# Check for secrets in code
if command -v truffleHog > /dev/null; then
echo "🔐 Scanning for secrets..."
truffleHog git file://. --since_commit HEAD~1 --only-verified
fi
echo "✅ Pre-commit security checks passed"

Infrastructure as Code Security

Terraform/CloudFormation Integration

terraform/security-scanning.tf
resource "aws_lambda_function" "supply_chain_scanner" {
filename = "supply-chain-scanner.zip"
function_name = "supply-chain-security"
role = aws_iam_role.scanner_role.arn
handler = "index.handler"
runtime = "nodejs18.x"
environment {
variables = {
POMPELMI_API_KEY = var.pompelmi_api_key
THREAT_WEBHOOK = var.security_webhook_url
}
}
}
# Scan all uploaded artifacts
resource "aws_s3_bucket_notification" "artifact_scan" {
bucket = aws_s3_bucket.artifacts.id
lambda_function {
lambda_function_arn = aws_lambda_function.supply_chain_scanner.arn
events = ["s3:ObjectCreated:*"]
filter_prefix = "artifacts/"
}
}
# CloudWatch monitoring
resource "aws_cloudwatch_metric_alarm" "high_threat_rate" {
alarm_name = "supply-chain-high-threat-rate"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "ThreatDetections"
namespace = "SupplyChain/Security"
period = "300"
statistic = "Sum"
threshold = "5"
alarm_actions = [aws_sns_topic.security_alerts.arn]
}

Runtime Protection

Application-Level Monitoring

// Runtime supply chain monitoring
class RuntimeSupplyChainMonitor {
constructor() {
this.scanner = new FileScanner({
realtimeMonitoring: true,
enableBehavioralAnalysis: true
});
this.suspiciousPatterns = [
/require\(['"]fs['"]\)/, // Dynamic file system access
/eval\(.*\)/, // Dynamic code execution
/Function\(.*\)/, // Function constructor
/setTimeout\(.*,\s*0\)/, // Immediate execution
];
this.setupMonitoring();
}
setupMonitoring() {
// Monitor require() calls
this.interceptRequire();
// Monitor file system operations
this.monitorFileSystem();
// Monitor network requests
this.monitorNetworkActivity();
}
interceptRequire() {
const originalRequire = Module.prototype.require;
Module.prototype.require = function(id) {
// Log and analyze require calls
monitor.logRequireCall(id, this.filename);
// Check for suspicious module loading
if (monitor.isSuspiciousModule(id)) {
monitor.alertSuspiciousRequire(id, this.filename);
}
return originalRequire.apply(this, arguments);
};
}
monitorFileSystem() {
const fs = require('fs');
const originalWriteFile = fs.writeFileSync;
fs.writeFileSync = (path, data, options) => {
// Scan data being written
if (this.containsSuspiciousCode(data)) {
this.alertFileSystemThreat('write', path, data);
}
return originalWriteFile(path, data, options);
};
}
containsSuspiciousCode(code) {
if (typeof code !== 'string') {
code = code.toString();
}
return this.suspiciousPatterns.some(pattern => pattern.test(code));
}
alertFileSystemThreat(operation, path, data) {
const alert = {
type: 'RUNTIME_SUPPLY_CHAIN_THREAT',
operation,
path,
suspicious_content: data.substring(0, 1000), // First 1KB
timestamp: new Date().toISOString(),
stack_trace: new Error().stack
};
this.sendSecurityAlert(alert);
}
}
// Initialize runtime monitoring
const monitor = new RuntimeSupplyChainMonitor();

Container Runtime Security

# Kubernetes pod security policies
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: supply-chain-secure
spec:
privileged: false
allowPrivilegeEscalation: false
# Prevent container image vulnerabilities
requiredDropCapabilities:
- ALL
runAsUser:
rule: MustRunAsNonRoot
# File system restrictions
fsGroup:
rule: RunAsAny
readOnlyRootFilesystem: true
# Network restrictions
hostNetwork: false
hostIPC: false
hostPID: false
# Volume restrictions
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'

Incident Response for Supply Chain Attacks

Detection and Response Pipeline

class SupplyChainIncidentResponse {
constructor() {
this.responseTeam = new SecurityResponseTeam();
this.forensics = new DigitalForensics();
this.communication = new CrisisCommunication();
}
async handleSupplyChainIncident(threat) {
// Immediate containment
await this.containThreat(threat);
// Evidence collection
const evidence = await this.collectEvidence(threat);
// Impact assessment
const impact = await this.assessImpact(threat);
// Stakeholder notification
await this.notifyStakeholders(threat, impact);
// Recovery planning
const recoveryPlan = await this.createRecoveryPlan(threat, impact);
// Execute recovery
await this.executeRecovery(recoveryPlan);
// Post-incident analysis
await this.conductPostIncidentReview(threat, evidence);
}
async containThreat(threat) {
// Immediate actions to stop threat spread
switch (threat.type) {
case 'malicious_dependency':
await this.quarantineDependency(threat.package);
await this.rollbackDeployments(threat.affected_versions);
break;
case 'compromised_artifact':
await this.quarantineArtifact(threat.artifact_id);
await this.blockArtifactDistribution(threat.artifact_id);
break;
case 'build_system_compromise':
await this.shutdownBuildSystem();
await this.isolateAffectedSystems(threat.affected_systems);
break;
}
}
async collectEvidence(threat) {
return {
// System state at time of detection
system_snapshot: await this.captureSystemState(),
// Network activity logs
network_logs: await this.collectNetworkLogs(threat.detection_time),
// File system evidence
file_evidence: await this.collectFileEvidence(threat.affected_files),
// Process information
process_info: await this.collectProcessInfo(),
// Timeline reconstruction
timeline: await this.reconstructTimeline(threat)
};
}
async createRecoveryPlan(threat, impact) {
return {
// Immediate recovery steps
immediate: [
'Remove malicious dependencies',
'Restore from clean backup',
'Rebuild affected artifacts'
],
// Medium-term recovery
medium_term: [
'Update security policies',
'Enhance monitoring',
'Train development team'
],
// Long-term improvements
long_term: [
'Implement zero-trust architecture',
'Enhanced supply chain visibility',
'Automated threat response'
],
// Validation steps
validation: [
'Security scan all recovered systems',
'Verify artifact integrity',
'Test application functionality'
]
};
}
}

Monitoring and Alerting

Supply Chain Threat Intelligence

class SupplyChainThreatIntelligence {
constructor() {
this.threatFeeds = [
'https://api.npmjs.org/security-advisories',
'https://pypi.org/security-advisories',
'https://github.com/advisories',
'https://cve.mitre.org/cgi-bin/cvename.cgi'
];
this.riskDatabase = new SupplyChainRiskDatabase();
this.alertManager = new AlertManager();
}
async monitorSupplyChain() {
// Continuous monitoring loop
setInterval(async () => {
await this.checkThreatFeeds();
await this.scanDependencyChanges();
await this.analyzeRiskTrends();
}, 5 * 60 * 1000); // Every 5 minutes
}
async checkThreatFeeds() {
for (const feed of this.threatFeeds) {
try {
const newThreats = await this.fetchNewThreats(feed);
for (const threat of newThreats) {
// Check if threat affects our dependencies
const affectedDeps = await this.checkDependencyExposure(threat);
if (affectedDeps.length > 0) {
await this.alertManager.sendAlert({
type: 'SUPPLY_CHAIN_THREAT',
threat: threat,
affectedDependencies: affectedDeps,
severity: this.calculateThreatSeverity(threat, affectedDeps)
});
}
}
} catch (error) {
console.error(`Failed to check threat feed ${feed}:`, error);
}
}
}
async scanDependencyChanges() {
// Monitor for unauthorized dependency changes
const currentDeps = await this.getCurrentDependencies();
const lastKnownDeps = await this.riskDatabase.getLastKnownDependencies();
const changes = this.compareDependencies(currentDeps, lastKnownDeps);
if (changes.length > 0) {
// Analyze each change
for (const change of changes) {
const riskScore = await this.assessChangeRisk(change);
if (riskScore > 7) { // High risk threshold
await this.alertManager.sendAlert({
type: 'HIGH_RISK_DEPENDENCY_CHANGE',
change: change,
riskScore: riskScore,
recommendations: await this.getRecommendations(change)
});
}
}
// Update baseline
await this.riskDatabase.updateDependencies(currentDeps);
}
}
}

Best Practices Summary

Development Team Guidelines

  1. Dependency Hygiene

    • Pin exact versions in production
    • Regularly audit dependencies
    • Use integrity hashes when available
    • Minimize dependency count
  2. Build Security

    • Scan dependencies before installation
    • Use multi-stage Docker builds
    • Implement build-time security checks
    • Sign and verify artifacts
  3. Runtime Protection

    • Monitor application behavior
    • Implement runtime sandboxing
    • Use least-privilege principles
    • Monitor file system changes
  4. Incident Preparedness

    • Have incident response plan
    • Practice supply chain breach scenarios
    • Maintain clean backup systems
    • Document recovery procedures

Organizational Policies

  1. Vendor Management

    • Vet all software vendors
    • Require security attestations
    • Monitor vendor security posture
    • Have termination procedures
  2. Change Management

    • All dependency changes require approval
    • Security review for new vendors
    • Automated scanning in CI/CD
    • Rollback procedures documented
  3. Monitoring Requirements

    • Real-time threat intelligence
    • Dependency vulnerability scanning
    • Behavioral anomaly detection
    • Compliance monitoring

Conclusion

Supply chain security is a shared responsibility requiring vigilance at every stage of the development lifecycle. From dependency selection to runtime monitoring, each step must be secured against sophisticated attackers who exploit the trust relationships in modern software development.

By implementing comprehensive scanning, monitoring, and response capabilities with tools like Pompelmi, organizations can significantly reduce their exposure to supply chain attacks while maintaining development velocity and innovation.

Remember: The security of your application is only as strong as the weakest link in your supply chain. Make every link strong.


Need help implementing supply chain security? Contact our security consulting team for customized threat modeling and implementation guidance.

Keep reading

Related articles