#!/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();