Advanced Guide

Detecting and Managing Viral Licenses in Your Codebase

How to identify viral licenses in dependencies, understand their impact, and implement compliance strategies.

8 min read
Last updated: Jan 12, 2024
Viral License License Detection Compliance

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)

LicenseContamination ScopeRisk Level
GPL-2.0Entire program🔴 Critical
GPL-3.0Entire program + patents🔴 Critical
AGPL-3.0Network use included🔴 Critical
OSL-3.0Entire program🔴 Critical
SSPLService + management🔴 Critical

Weak Copyleft (Moderate Risk)

LicenseContamination ScopeRisk Level
LGPL-2.1Library level (if static)🟡 Moderate
LGPL-3.0Library level (if static)🟡 Moderate
MPL-2.0File level🟡 Moderate
EPL-2.0Module level🟡 Moderate
CDDL-1.0File 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.json

3. 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.json

Remediation 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 functionality

2. 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: daily

Automated 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 update

Best 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

Conclusion

Viral license detection is critical for protecting proprietary code. Key takeaways:

  1. Automate detection at every stage
  2. Block viral licenses in CI/CD
  3. Monitor continuously for changes
  4. Have remediation plans ready
  5. 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.