Advanced Guide
Detecting and Managing Viral Licenses in Your Codebase
How to identify viral licenses in dependencies, understand their impact, and implement compliance strategies.
Table of Contents
Understanding Viral License Detection
Viral licenses (copyleft licenses) can "infect" your codebase, requiring you to open-source your proprietary code. This guide shows you how to detect, manage, and remediate viral license issues before they become legal problems. For safer alternatives, explore our commercial-friendly licenses guide.Why Viral License Detection Matters
The Risk
// One GPL dependency can affect your entire project
import { usefulFunction } from 'gpl-licensed-library';
// Your entire application may now need to be GPL!
class ProprietaryApp {
criticalBusinessLogic() {
return usefulFunction(); // GPL contamination
}
}The Cost
- Legal liability: License violations can lead to lawsuits
- Forced disclosure: May need to open-source proprietary code
- Business impact: Loss of competitive advantage
- Remediation cost: Expensive to replace dependencies
Viral License Types
Strong Copyleft (High Risk)
| License | Contamination Scope | Risk Level |
|---|---|---|
| GPL-2.0 | Entire program | 🔴 Critical |
| GPL-3.0 | Entire program + patents | 🔴 Critical |
| AGPL-3.0 | Network use included | 🔴 Critical |
| OSL-3.0 | Entire program | 🔴 Critical |
| SSPL | Service + management | 🔴 Critical |
Weak Copyleft (Moderate Risk)
| License | Contamination Scope | Risk Level |
|---|---|---|
| LGPL-2.1 | Library level (if static) | 🟡 Moderate |
| LGPL-3.0 | Library level (if static) | 🟡 Moderate |
| MPL-2.0 | File level | 🟡 Moderate |
| EPL-2.0 | Module level | 🟡 Moderate |
| CDDL-1.0 | File level | 🟡 Moderate |
Detection Strategies
1. Automated Scanning Tools
JavaScript/Node.js
# Install license checker
npm install -g license-checker
# Scan for viral licenses
license-checker --onlyAllow 'MIT;ISC;BSD;Apache-2.0' --excludePackages 'mypackage'
# Generate detailed report
license-checker --json > licenses.json// analyze-licenses.js
const fs = require('fs');
const licenses = JSON.parse(fs.readFileSync('licenses.json'));
const VIRAL_LICENSES = [
'GPL', 'AGPL', 'LGPL', 'OSL', 'SSPL',
'CC-BY-SA', 'EUPL', 'CPAL'
];
for (const [pkg, info] of Object.entries(licenses)) {
const license = info.licenses;
if (VIRAL_LICENSES.some(viral => license.includes(viral))) {
console.error(`⚠️ VIRAL LICENSE DETECTED: ${pkg} (${license})`);
}
}Python
#!/usr/bin/env python3
# detect_viral_licenses.py
import subprocess
import json
import sys
VIRAL_PATTERNS = [
'GPL', 'AGPL', 'LGPL', 'OSL', 'SSPL',
'Sleepycat', 'Copyleft', 'Share-alike'
]
def scan_python_deps():
# Get all installed packages
result = subprocess.run(
['pip-licenses', '--format=json'],
capture_output=True,
text=True
)
licenses = json.loads(result.stdout)
viral_found = []
for package in licenses:
license_name = package.get('License', '')
for pattern in VIRAL_PATTERNS:
if pattern.lower() in license_name.lower():
viral_found.append({
'package': package['Name'],
'version': package['Version'],
'license': license_name
})
break
return viral_found
if __name__ == '__main__':
viral = scan_python_deps()
if viral:
print("🔴 VIRAL LICENSES DETECTED:")
for v in viral:
print(f" - {v['package']} v{v['version']}: {v['license']}")
sys.exit(1)
else:
print("✅ No viral licenses detected")Java/Maven
<!-- pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>2.0.0</version>
<configuration>
<excludedLicenses>
GPL|AGPL|LGPL|OSL|SSPL
</excludedLicenses>
<failOnBlacklist>true</failOnBlacklist>
</configuration>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>check-third-party</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>2. CI/CD Integration
GitHub Actions
name: Viral License Detection
on: [push, pull_request]
jobs:
detect-viral:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Check for viral licenses
run: |
npx license-checker --json > licenses.json
# Check for GPL/AGPL/LGPL
if grep -E '"(GPL|AGPL|LGPL|OSL|SSPL)"' licenses.json; then
echo "::error::Viral license detected!"
exit 1
fi
- name: Generate [SBOM](/learn/what-is-sbom)
uses: anchore/sbom-action@v0
with:
format: [CycloneDX](/tools/cyclonedx-cli)-json
output-file: sbom.json
- name: Scan SBOM for viral licenses
run: |
python3 scripts/scan_viral_licenses.py sbom.json3. Pre-Commit Hooks
#!/bin/bash
# .git/hooks/pre-commit
echo "Checking for viral licenses..."
# For Node.js projects
if [ -f "package.json" ]; then
npx license-checker --onlyAllow 'MIT;BSD;Apache-2.0;ISC' || {
echo "❌ Viral license detected! Commit blocked."
exit 1
}
fi
# For Python projects
if [ -f "requirements.txt" ]; then
pip-licenses | grep -E "(GPL|AGPL|LGPL)" && {
echo "❌ Viral license detected in Python dependencies!"
exit 1
}
fi
echo "✅ No viral licenses detected"Deep Dependency Scanning
Transitive Dependencies
// scan-transitive.js
const { exec } = require('child_process');
function scanTransitiveDependencies() {
exec('npm ls --json', (error, stdout) => {
const tree = JSON.parse(stdout);
function checkNode(node, path = []) {
for (const [name, details] of Object.entries(node.dependencies || {})) {
const currentPath = [...path, name];
// Check license
if (isViralLicense(details.license)) {
console.warn(`
Viral License Chain Detected:
${currentPath.join(' → ')}
License: ${details.license}
`);
}
// Recurse
if (details.dependencies) {
checkNode(details, currentPath);
}
}
}
checkNode(tree);
});
}
function isViralLicense(license) {
const viral = ['GPL', 'AGPL', 'LGPL', 'OSL', 'SSPL'];
return viral.some(v => license?.includes(v));
}Container Scanning
# [Docker](/guides/docker)file.scan
FROM alpine:latest
RUN apk add --no-cache \
python3 \
py3-pip \
nodejs \
npm
# Install scanning tools
RUN pip install scancode-toolkit
RUN npm install -g license-checker
# Copy project
COPY . /scan
# Run comprehensive scan
WORKDIR /scan
RUN scancode --license --copyright --package --json /tmp/scan.json .
# Analyze results
COPY scripts/analyze_viral.py /
RUN python3 /analyze_viral.py /tmp/scan.jsonRemediation Strategies
1. Immediate Actions
## Viral License Found - Action Plan
### Step 1: Quarantine
- [ ] Stop deployment
- [ ] Tag current version
- [ ] Create hotfix branch
### Step 2: Assess Impact
- [ ] Identify contamination scope
- [ ] Check distribution status
- [ ] Review legal obligations
### Step 3: Remediate
- [ ] Find alternative library
- [ ] Isolate via microservice
- [ ] Negotiate dual license
- [ ] Remove functionality2. Alternative Library Finder
// find-alternatives.js
const alternatives = {
'readline': ['inquirer', 'prompt', 'readline-sync'], // GPL → MIT
'node-gpl-library': ['mit-alternative'],
'gpl-image-processor': ['jimp', 'sharp'], // GPL → Apache/MIT
};
function findAlternatives(viralPackage) {
if (alternatives[viralPackage]) {
console.log(`Suggested alternatives for ${viralPackage}:`);
alternatives[viralPackage].forEach(alt => {
console.log(` - ${alt} (Check license)`);
});
} else {
console.log(`Search npm for alternatives: npm search ${viralPackage}`);
}
}3. Isolation Techniques
# docker-compose.yml - Isolate GPL services
version: '3'
services:
main-app:
build: ./proprietary-app
environment:
- GPL_SERVICE_URL=http://gpl-service:8080
gpl-service:
build: ./gpl-service
# GPL code isolated in separate container
# Communication via HTTP API only# api_wrapper.py - Process isolation
import subprocess
import json
class GPLServiceWrapper:
"""Isolate GPL code in separate process"""
def call_gpl_function(self, data):
# GPL code runs in subprocess
result = subprocess.run(
['python', 'gpl_service.py'],
input=json.dumps(data),
capture_output=True,
text=True
)
return json.loads(result.stdout)Monitoring and Alerting
Continuous Monitoring
# monitor_licenses.py
import schedule
import time
import smtplib
from email.mime.text import MIMEText
def scan_for_viral():
"""Daily scan for viral licenses"""
viral_found = run_license_scan()
if viral_found:
send_alert(viral_found)
create_jira_ticket(viral_found)
block_deployment()
def send_alert(violations):
msg = MIMEText(f"Viral licenses detected: {violations}")
msg['Subject'] = 'URGENT: Viral License Alert'
msg['From'] = 'compliance@company.com'
msg['To'] = 'legal@company.com'
smtp = smtplib.SMTP('localhost')
smtp.send_message(msg)
smtp.quit()
# Schedule daily scan
schedule.every().day.at("09:00").do(scan_for_viral)
while True:
schedule.run_pending()
time.sleep(60)Dashboard Integration
// license-dashboard.js
const express = require('express');
const app = express();
app.get('/api/license-status', async (req, res) => {
const status = await scanLicenses();
res.json({
total_dependencies: status.total,
viral_licenses: status.viral,
risk_level: status.viral.length > 0 ? 'HIGH' : 'LOW',
last_scan: new Date().toISOString()
});
});
app.get('/api/viral-licenses', async (req, res) => {
const viral = await detectViralLicenses();
res.json(viral.map(v => ({
package: v.name,
version: v.version,
license: v.license,
risk: 'HIGH',
alternatives: findAlternatives(v.name)
})));
});Policy Implementation
Organization License Policy
# license-policy.yaml
version: 1.0
banned_licenses:
- GPL-2.0
- GPL-3.0
- AGPL-3.0
- OSL-3.0
- SSPL-1.0
restricted_licenses:
- LGPL-2.1 # Requires architecture review
- LGPL-3.0 # Requires architecture review
- MPL-2.0 # Requires legal review
- EPL-2.0 # Requires legal review
allowed_licenses:
- MIT
- Apache-2.0
- BSD-2-Clause
- BSD-3-Clause
- ISC
- Unlicense
enforcement:
block_on_banned: true
warn_on_restricted: true
require_approval: true
scan_frequency: dailyAutomated Enforcement
// enforce-policy.js
const policy = require('./license-policy.json');
function enforcePolicy(dependencies) {
const violations = [];
const warnings = [];
for (const dep of dependencies) {
if (policy.banned_licenses.includes(dep.license)) {
violations.push({
severity: 'CRITICAL',
package: dep.name,
license: dep.license,
action: 'MUST_REMOVE'
});
} else if (policy.restricted_licenses.includes(dep.license)) {
warnings.push({
severity: 'WARNING',
package: dep.name,
license: dep.license,
action: 'NEEDS_REVIEW'
});
}
}
if (violations.length > 0) {
throw new Error(`Banned licenses detected: ${JSON.stringify(violations)}`);
}
return { violations, warnings };
}Recovery Procedures
If Viral License is Detected
## Emergency Response Procedure
### Hour 1: Assessment
1. Stop all deployments
2. Identify affected systems
3. Determine distribution scope
4. Notify legal team
### Hour 2-4: Containment
1. Roll back if possible
2. Isolate affected code
3. Document all distributions
4. Prepare compliance package
### Day 1: Remediation
1. Remove or replace viral dependency
2. Test alternative solution
3. Update all affected systems
4. Document changes
### Week 1: Follow-up
1. Legal review
2. Customer notifications if required
3. Process improvement
4. Training updateBest Practices
1. Proactive Prevention
- Scan before adding dependencies
- Maintain approved license list
- Regular dependency audits
- Developer training
2. Defense in Depth
- Multiple scanning tools
- CI/CD integration
- Pre-commit hooks
- Runtime monitoring
3. Clear Documentation
- License inventory
- Approval records
- Architecture decisions
- Compliance history
Related Articles
- CI/CD Integration Guide
- Docker SBOM Generation
- Kubernetes SBOM Management
- SBOM Formats Comparison
- Best SBOM Tools
Conclusion
Viral license detection is critical for protecting proprietary code. Key takeaways:
- Automate detection at every stage
- Block viral licenses in CI/CD
- Monitor continuously for changes
- Have remediation plans ready
- Train developers on license risks
Remember: It's much easier to prevent viral license contamination than to remediate it after the fact. Invest in detection tools and processes before you need them.