UNPKG

npm-api-analyzer

Version:

CLI tool to analyze npm packages for network API usage, prototype pollution, and security vulnerabilities

354 lines (299 loc) 12.7 kB
import { APIDetector } from '../src/apiDetector'; import { APIUsage } from '../src/types'; describe('API Detection', () => { // Helper function to get all API results const getAPIResults = (code: string): APIUsage[] => { return APIDetector.detectAPIUsages(code, 'test.js'); }; // Helper function to filter by category const getByCategory = (results: APIUsage[], category: string): APIUsage[] => { return results.filter(r => r.category === category); }; describe('Network API Detection', () => { test('should detect fetch API calls', () => { const code = ` fetch('/api/data'); window.fetch('/api/user'); globalThis.fetch('/api/config'); `; const results = getByCategory(getAPIResults(code), 'network'); expect(results.length).toBeGreaterThanOrEqual(3); expect(results.filter(r => r.type === 'fetch').length).toBeGreaterThanOrEqual(3); expect(results.filter(r => r.type === 'fetch').every(r => r.riskLevel === 'medium')).toBe(true); }); test('should detect XMLHttpRequest usage', () => { const code = ` new XMLHttpRequest(); const xhr = XMLHttpRequest(); window.XMLHttpRequest(); `; const results = getByCategory(getAPIResults(code), 'network'); expect(results.length).toBeGreaterThanOrEqual(3); expect(results.filter(r => r.type === 'xhr').length).toBeGreaterThanOrEqual(3); expect(results.filter(r => r.type === 'xhr').every(r => r.riskLevel === 'medium')).toBe(true); }); test('should detect WebSocket usage', () => { const code = ` new WebSocket('ws://localhost'); const ws = WebSocket('ws://example.com'); window.WebSocket('wss://secure.com'); `; const results = getByCategory(getAPIResults(code), 'network'); expect(results.length).toBeGreaterThanOrEqual(3); expect(results.filter(r => r.type === 'websocket').length).toBeGreaterThanOrEqual(3); expect(results.filter(r => r.type === 'websocket').every(r => r.riskLevel === 'high')).toBe(true); }); test('should detect Node.js network modules', () => { const code = ` require('http'); require('https'); require('node-fetch'); require('axios'); import fetch from 'node-fetch'; import axios from 'axios'; `; const results = getByCategory(getAPIResults(code), 'network'); expect(results.length).toBeGreaterThan(0); expect(results.every(r => r.type === 'node')).toBe(true); expect(results.every(r => r.riskLevel === 'medium')).toBe(true); }); test('should detect other network APIs', () => { const code = ` navigator.sendBeacon('/analytics', data); new EventSource('/stream'); new URL('https://example.com'); new Image(); `; const results = getByCategory(getAPIResults(code), 'network'); expect(results.length).toBeGreaterThan(0); expect(results.every(r => r.type === 'other')).toBe(true); }); }); describe('DOM Manipulation Detection', () => { test('should detect element creation', () => { const code = ` document.createElement('div'); document.createTextNode('text'); document.createDocumentFragment(); `; const results = getByCategory(getAPIResults(code), 'dom'); expect(results).toHaveLength(3); expect(results.every(r => r.type === 'dom-manipulation')).toBe(true); }); test('should detect element query methods', () => { const code = ` document.getElementById('test'); document.querySelector('.class'); document.querySelectorAll('div'); document.getElementsByTagName('p'); document.getElementsByClassName('item'); `; const results = getByCategory(getAPIResults(code), 'dom'); expect(results).toHaveLength(5); expect(results.every(r => r.type === 'dom-manipulation')).toBe(true); expect(results.every(r => r.riskLevel === 'low')).toBe(true); }); test('should detect high-risk DOM manipulation', () => { const code = ` element.innerHTML = userInput; element.outerHTML = dangerousContent; element.insertAdjacentHTML('beforeend', htmlString); document.write('<div>content</div>'); document.writeln('text'); `; const results = getByCategory(getAPIResults(code), 'dom'); expect(results).toHaveLength(5); expect(results.every(r => r.type === 'dom-manipulation')).toBe(true); expect(results.every(r => r.riskLevel === 'high')).toBe(true); }); test('should detect node manipulation methods', () => { const code = ` parent.appendChild(child); parent.insertBefore(newNode, referenceNode); parent.removeChild(child); parent.replaceChild(newChild, oldChild); `; const results = getByCategory(getAPIResults(code), 'dom'); expect(results).toHaveLength(4); expect(results.every(r => r.type === 'dom-manipulation')).toBe(true); }); }); describe('Dynamic JavaScript Execution Detection', () => { test('should detect eval usage', () => { const code = ` eval('alert("xss")'); window.eval('malicious code'); globalThis.eval('dangerous'); `; const results = getByCategory(getAPIResults(code), 'dynamic-js'); expect(results.length).toBeGreaterThanOrEqual(3); expect(results.filter(r => r.type === 'eval').length).toBeGreaterThanOrEqual(3); expect(results.filter(r => r.type === 'eval').every(r => r.riskLevel === 'high')).toBe(true); }); test('should detect Function constructor usage', () => { const code = ` new Function('return malicious'); Function('alert', 'alert("xss")'); window.Function('code'); `; const results = getByCategory(getAPIResults(code), 'dynamic-js'); expect(results.length).toBeGreaterThanOrEqual(3); expect(results.filter(r => r.type === 'eval').length).toBeGreaterThanOrEqual(3); expect(results.filter(r => r.type === 'eval').every(r => r.riskLevel === 'high')).toBe(true); }); test('should detect dangerous timer usage', () => { const code = ` setTimeout('alert("xss")', 1000); setInterval('malicious()', 5000); `; const results = getByCategory(getAPIResults(code), 'dynamic-js'); expect(results).toHaveLength(2); expect(results.every(r => r.type === 'eval')).toBe(true); expect(results.every(r => r.riskLevel === 'high')).toBe(true); }); test('should detect dynamic script creation', () => { const code = ` document.createElement('script'); document.createElement('link'); document.createElement('iframe'); document.head.appendChild(scriptTag); document.body.appendChild(element); `; const results = getByCategory(getAPIResults(code), 'dynamic-js'); expect(results.length).toBeGreaterThan(0); expect(results.every(r => r.type === 'dynamic-script')).toBe(true); }); test('should detect dynamic imports', () => { const code = ` import('./module.js'); require(moduleName); `; const results = getByCategory(getAPIResults(code), 'dynamic-js'); expect(results.length).toBeGreaterThanOrEqual(1); expect(results.filter(r => r.type === 'dynamic-script').length).toBeGreaterThanOrEqual(1); expect(results.filter(r => r.type === 'dynamic-script').every(r => r.riskLevel === 'medium')).toBe(true); }); test('should detect Web Workers', () => { const code = ` new Worker('worker.js'); new SharedWorker('shared-worker.js'); `; const results = getByCategory(getAPIResults(code), 'dynamic-js'); expect(results).toHaveLength(2); expect(results.every(r => r.type === 'dynamic-script')).toBe(true); expect(results.every(r => r.riskLevel === 'medium')).toBe(true); }); test('should detect Service Worker registration', () => { const code = 'navigator.serviceWorker.register("sw.js");'; const results = getByCategory(getAPIResults(code), 'dynamic-js'); expect(results).toHaveLength(1); expect(results[0].type).toBe('dynamic-script'); expect(results[0].riskLevel).toBe('medium'); }); test('should detect Blob URL creation', () => { const code = ` URL.createObjectURL(blob); window.URL.createObjectURL(file); `; const results = getByCategory(getAPIResults(code), 'dynamic-js'); expect(results.length).toBeGreaterThanOrEqual(2); expect(results.filter(r => r.type === 'dynamic-script').length).toBeGreaterThanOrEqual(2); expect(results.filter(r => r.type === 'dynamic-script').every(r => r.riskLevel === 'medium')).toBe(true); }); }); describe('Context Extraction', () => { test('should extract context around API calls', () => { const code = ` // Before line 1 // Before line 2 // Before line 3 const result = fetch('/api/data'); // This is the target line // After line 1 // After line 2 // After line 3 `; const results = getAPIResults(code); expect(results).toHaveLength(1); const context = results[0].context; expect(context.beforeLines).toHaveLength(3); expect(context.afterLines).toHaveLength(3); expect(context.currentLine).toContain('fetch'); expect(context.fullContext).toContain('Before line'); expect(context.fullContext).toContain('After line'); }); test('should provide accurate location information', () => { const code = `line1 line2 const api = fetch('/test'); line4`; const results = getAPIResults(code); expect(results).toHaveLength(1); expect(results[0].location.line).toBe(3); expect(results[0].location.column).toBeGreaterThan(0); expect(results[0].location.file).toBe('test.js'); }); }); describe('Multiple API Categories', () => { test('should detect mixed API usage correctly', () => { const code = ` // Network fetch('/api/data'); // DOM document.getElementById('test'); element.innerHTML = content; // Dynamic JS eval('code'); // Prototype Pollution Object.prototype.polluted = true; `; const results = getAPIResults(code); const byCategory = { network: getByCategory(results, 'network'), dom: getByCategory(results, 'dom'), dynamicJs: getByCategory(results, 'dynamic-js'), prototypePollution: getByCategory(results, 'prototype-pollution') }; expect(byCategory.network).toHaveLength(1); expect(byCategory.dom).toHaveLength(2); expect(byCategory.dynamicJs).toHaveLength(1); expect(byCategory.prototypePollution).toHaveLength(1); // Check risk levels expect(byCategory.network[0].riskLevel).toBe('medium'); expect(byCategory.dom.some(r => r.riskLevel === 'high')).toBe(true); expect(byCategory.dynamicJs[0].riskLevel).toBe('high'); expect(byCategory.prototypePollution[0].riskLevel).toBe('high'); }); }); describe('Pattern Matching Edge Cases', () => { test('should handle code in strings and comments', () => { const code = ` // This is eval('not detected'); const str = "fetch('not detected')"; /* Object.prototype.test = 'not detected'; */ const regex = /innerHTML.*=/; `; const results = getAPIResults(code); // Should detect some patterns in comments/strings due to regex nature // This is expected behavior for static analysis expect(results.length).toBeGreaterThan(0); }); test('should handle minified code', () => { const code = 'fetch("/api");eval("code");Object.prototype.x=1;'; const results = getAPIResults(code); expect(results.length).toBeGreaterThan(0); const categories = ['network', 'dynamic-js', 'prototype-pollution']; categories.forEach(category => { expect(getByCategory(results, category).length).toBeGreaterThan(0); }); }); test('should handle whitespace variations', () => { const code = ` fetch ( '/api' ) ; eval ( 'code' ) ; Object . prototype . test = true ; `; const results = getAPIResults(code); expect(results.length).toBeGreaterThan(0); }); }); });