tractatus/scripts/check-dependency-licenses.js
TheFlow fec27fd54a feat(governance): wave 5 enforcement - 100% coverage achieved (79% → 100%)
Closes all remaining 8 enforcement gaps:
- inst_039: Document processing verification (scripts/verify-document-updates.js)
- inst_043: Runtime input validation middleware (full DOMPurify + NoSQL injection)
- inst_052: Scope adjustment tracking (scripts/log-scope-adjustment.js)
- inst_058: Schema sync validation (scripts/verify-schema-sync.js)
- inst_061: Hook approval pattern tracking (.claude/hooks/track-approval-patterns.js)
- inst_072: Defense-in-depth audit (scripts/audit-defense-in-depth.js)
- inst_080: Dependency license checker (scripts/check-dependency-licenses.js)
- inst_081: Pluralism code review checklist (docs/PLURALISM_CHECKLIST.md)

Enhanced:
- src/middleware/input-validation.middleware.js: Added DOMPurify, NoSQL injection detection
- scripts/audit-enforcement.js: Added Wave 5 mappings

Enforcement Status:
- Imperative instructions: 39/39 enforced (100%)
- Total improvement from baseline: 11 → 39 (+254%)
- Wave 5 contribution: +8 instructions enforced

Architecture:
- Runtime/Policy enforcement layer complete
- All MANDATORY instructions now architecturally enforced
- No voluntary compliance required

📊 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 14:10:23 +13:00

248 lines
6.9 KiB
JavaScript
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Dependency License Checker - Enforces inst_080
* Ensures all dependencies are Apache 2.0 compatible (open source)
*
* Prohibited without explicit human approval:
* - Closed-source dependencies for core functionality
* - Proprietary licenses
* - Restrictive licenses (e.g., AGPL for web apps)
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// Apache 2.0 compatible licenses
const COMPATIBLE_LICENSES = [
'MIT',
'Apache-2.0',
'BSD-2-Clause',
'BSD-3-Clause',
'ISC',
'CC0-1.0',
'Unlicense',
'Python-2.0' // Python Software Foundation
];
// Restrictive licenses (require review)
const RESTRICTIVE_LICENSES = [
'GPL-2.0',
'GPL-3.0',
'AGPL-3.0',
'LGPL',
'CC-BY-NC', // Non-commercial restriction
'CC-BY-SA' // Share-alike requirement
];
// Prohibited licenses (closed source)
const PROHIBITED_LICENSES = [
'UNLICENSED',
'PROPRIETARY',
'Commercial'
];
function getLicenseType(license) {
if (!license) return 'unknown';
const normalized = license.toUpperCase();
if (COMPATIBLE_LICENSES.some(l => normalized.includes(l.toUpperCase()))) {
return 'compatible';
}
if (RESTRICTIVE_LICENSES.some(l => normalized.includes(l.toUpperCase()))) {
return 'restrictive';
}
if (PROHIBITED_LICENSES.some(l => normalized.includes(l.toUpperCase()))) {
return 'prohibited';
}
return 'unknown';
}
function checkPackageJson() {
if (!fs.existsSync('package.json')) {
console.log('⚠️ No package.json found\n');
return { dependencies: [], issues: [] };
}
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const allDeps = {
...pkg.dependencies,
...pkg.devDependencies
};
const dependencies = [];
const issues = [];
// Try to get license info using npm
try {
const output = execSync('npm list --json --depth=0', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
const npmData = JSON.parse(output);
if (npmData.dependencies) {
for (const [name, info] of Object.entries(npmData.dependencies)) {
let license = 'unknown';
// Try to read license from node_modules
const packagePath = path.join('node_modules', name, 'package.json');
if (fs.existsSync(packagePath)) {
const depPkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
license = depPkg.license || 'unknown';
}
const type = getLicenseType(license);
dependencies.push({
name,
version: info.version,
license,
type
});
if (type === 'prohibited') {
issues.push({
severity: 'CRITICAL',
package: name,
license,
message: 'Prohibited closed-source license (inst_080)'
});
} else if (type === 'restrictive') {
issues.push({
severity: 'HIGH',
package: name,
license,
message: 'Restrictive license - requires human approval (inst_080)'
});
} else if (type === 'unknown') {
issues.push({
severity: 'MEDIUM',
package: name,
license: license || 'NONE',
message: 'Unknown license - verify Apache 2.0 compatibility'
});
}
}
}
} catch (e) {
console.log('⚠️ Unable to read npm dependencies (run npm install)\n');
return { dependencies: [], issues: [] };
}
return { dependencies, issues };
}
function checkCoreFunctionality(issues) {
// Identify packages that provide core functionality
const coreDependencies = [
'express',
'mongoose',
'mongodb',
'jsonwebtoken',
'bcrypt',
'validator'
];
const coreIssues = issues.filter(issue =>
coreDependencies.some(core => issue.package.includes(core))
);
if (coreIssues.length > 0) {
console.log('\n🚨 CRITICAL: Core functionality dependencies with license issues:\n');
coreIssues.forEach(issue => {
console.log(`${issue.package} (${issue.license})`);
console.log(` ${issue.message}\n`);
});
return false;
}
return true;
}
function main() {
console.log('\n📜 Dependency License Check (inst_080)\n');
console.log('Ensuring Apache 2.0 compatible licenses only\n');
console.log('━'.repeat(70) + '\n');
const { dependencies, issues } = checkPackageJson();
if (dependencies.length === 0) {
console.log('⚠️ No dependencies found or unable to analyze\n');
process.exit(0);
}
console.log(`Scanned ${dependencies.length} dependencies\n`);
const compatible = dependencies.filter(d => d.type === 'compatible');
const restrictive = dependencies.filter(d => d.type === 'restrictive');
const prohibited = dependencies.filter(d => d.type === 'prohibited');
const unknown = dependencies.filter(d => d.type === 'unknown');
console.log(`✅ Compatible: ${compatible.length}`);
if (restrictive.length > 0) {
console.log(`⚠️ Restrictive: ${restrictive.length}`);
}
if (prohibited.length > 0) {
console.log(`❌ Prohibited: ${prohibited.length}`);
}
if (unknown.length > 0) {
console.log(`❓ Unknown: ${unknown.length}`);
}
console.log('\n' + '━'.repeat(70) + '\n');
if (issues.length === 0) {
console.log('✅ All dependencies are Apache 2.0 compatible\n');
console.log('Open source commitment (inst_080) maintained.\n');
process.exit(0);
}
// Group issues by severity
const critical = issues.filter(i => i.severity === 'CRITICAL');
const high = issues.filter(i => i.severity === 'HIGH');
const medium = issues.filter(i => i.severity === 'MEDIUM');
if (critical.length > 0) {
console.log(`❌ CRITICAL: ${critical.length} prohibited license(s)\n`);
critical.forEach(issue => {
console.log(`${issue.package}: ${issue.license}`);
console.log(` ${issue.message}\n`);
});
}
if (high.length > 0) {
console.log(`⚠️ HIGH: ${high.length} restrictive license(s)\n`);
high.forEach(issue => {
console.log(`${issue.package}: ${issue.license}`);
console.log(` ${issue.message}\n`);
});
}
if (medium.length > 0) {
console.log(` MEDIUM: ${medium.length} unknown license(s)\n`);
medium.forEach(issue => {
console.log(`${issue.package}: ${issue.license}`);
});
console.log('');
}
// Check if core functionality affected
const coreOk = checkCoreFunctionality(issues);
console.log('━'.repeat(70) + '\n');
console.log('Actions required:\n');
console.log(' 1. Review all flagged dependencies');
console.log(' 2. Replace prohibited/restrictive licenses with compatible alternatives');
console.log(' 3. Obtain explicit human approval for any exceptions (inst_080)');
console.log(' 4. Document justification in docs/DEPENDENCIES.md\n');
if (critical.length > 0 || !coreOk) {
process.exit(1);
} else {
process.exit(0);
}
}
main();