How We Built This

Complete transparency about our client-side vulnerability scanner. For security-conscious developers who need to know exactly how their tools work.

Technical Architecture

A deep dive into how we built a completely client-side vulnerability scanner

🔍 Client-Side Parsing

All lockfile parsing happens in your browser using pure JavaScript. No server uploads required. We support npm, pnpm, and Yarn lockfiles with format detection.

// Parse npm lockfile locally
extractNpmDependencies(lockData, dependencies) {
    if (lockData.packages) {
        // Handle v2/v3 format
        Object.entries(lockData.packages).forEach(([path, pkg]) => {
            if (path && pkg.version) {
                const name = path.replace('node_modules/', '');
                dependencies.set(name, pkg.version);
            }
        });
    } else if (lockData.dependencies) {
        // Handle v1 format
        this.extractNpmV1Dependencies(lockData.dependencies, dependencies);
    }
}

// File reading with FileReader API
readFile(file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = (e) => resolve(e.target.result);
        reader.onerror = (e) => reject(e);
        reader.readAsText(file); // Never uploaded to server
    });
}

⚡ Real-Time Vulnerability Matching

Dependencies are matched against our pre-loaded vulnerability database using efficient algorithms. Critical incidents are checked first, followed by known vulnerabilities.

// Check vulnerabilities locally
async checkVulnerabilities(dependencies) {
    const results = [];
    
    for (const dep of dependencies) {
        const result = {
            name: dep.name,
            version: dep.version,
            status: 'ok',
            advisories: []
        };

        // Check incidents first (critical supply chain attacks)
        if (this.incidentData[dep.name]) {
            const incident = this.incidentData[dep.name];
            if (this.versionMatches(dep.version, incident.versions)) {
                result.status = 'critical';
                result.advisories.push({
                    id: incident.advisory,
                    severity: incident.severity,
                    summary: incident.summary
                });
            }
        }
        
        // Check vulnerability database
        if (this.vulnerabilityIndex[dep.name] && result.status !== 'critical') {
            const vulnRecord = this.vulnerabilityIndex[dep.name];
            const ranges = Array.isArray(vulnRecord.ranges) ? vulnRecord.ranges : [vulnRecord.ranges];
            const isAffected = ranges.some(range => this.versionMatches(dep.version, range));
            
            if (isAffected) {
                result.status = 'vulnerable';
                result.advisories.push(...vulnRecord.ids.map(id => ({
                    id: id,
                    severity: vulnRecord.severity || 'MODERATE',
                    summary: `Vulnerability in ${dep.name}`
                })));
            }
        }
        
        results.push(result);
    }
    
    return results.sort((a, b) => {
        const statusOrder = { critical: 0, vulnerable: 1, ok: 2 };
        return statusOrder[a.status] - statusOrder[b.status];
    });
}

🗄️ OSV Database Integration

We pre-process Google's Open Source Vulnerabilities database into an optimized format for fast client-side lookups. Fallback to live OSV API for packages not in our index.

// Load vulnerability data
async loadVulnerabilityData() {
    try {
        const [vulnData, incidentData] = await Promise.all([
            fetch('./data/npm-index.json').then(r => r.json()),
            this.loadIncidentOverlay()
        ]);
        
        this.vulnerabilityIndex = vulnData.vulnerabilities || {};
        this.incidentData = incidentData;
        
        console.log(`Loaded ${Object.keys(this.vulnerabilityIndex).length} packages with vulnerabilities`);
        console.log(`Loaded ${Object.keys(this.incidentData).length} security incidents`);
        
    } catch (error) {
        console.warn('Failed to load pre-built data, using OSV API fallback');
        this.vulnerabilityIndex = {};
        this.incidentData = {};
    }
}

// OSV API fallback for unknown packages
async queryOSVAPI(packageName, version) {
    const query = {
        package: { name: packageName, ecosystem: "npm" },
        version: version
    };

    const response = await fetch('https://api.osv.dev/v1/query', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(query)
    });

    const data = await response.json();
    return (data.vulns || []).map(vuln => ({
        id: vuln.id,
        severity: this.mapOSVSeverity(vuln.severity),
        summary: vuln.summary || `Vulnerability in ${packageName}`
    }));
}

🔒 Privacy-First Architecture

File processing uses the FileReader API to keep everything local to your browser. Drag & drop is handled entirely client-side with no network requests for file content.

// Drag & drop handling - completely local
setupEventListeners() {
    const dropZone = document.getElementById('dropZone');
    
    dropZone.addEventListener('drop', (e) => {
        e.preventDefault();
        dropZone.classList.remove('drag-over');
        
        const files = e.dataTransfer.files;
        if (files.length > 0) {
            this.handleFile(files[0]); // Process locally only
        }
    });
    
    // Also handle document-wide drops
    document.addEventListener('drop', (e) => {
        e.preventDefault();
        const files = e.dataTransfer.files;
        if (files.length > 0) {
            this.handleFile(files[0]);
        }
    });
}

// File validation and processing
async handleFile(file) {
    const fileName = file.name.toLowerCase();
    
    // Validate file type locally
    if (!this.isValidLockfile(fileName)) {
        this.showError('Please upload a valid lockfile');
        return;
    }

    this.showLoading(true);

    try {
        const content = await this.readFile(file); // Local only
        const dependencies = await this.parseLockfile(content, fileName);
        const results = await this.checkVulnerabilities(dependencies);
        
        this.displayResults(results);
    } catch (error) {
        this.showError('Failed to process lockfile: ' + error.message);
    } finally {
        this.showLoading(false);
    }
}

📊 Smart Caching & Performance

We use localStorage to cache OSV API responses and implement intelligent batching to avoid overwhelming external services while maintaining fast response times.

// Smart caching system
getOSVCache() {
    try {
        const cache = localStorage.getItem('osv-cache');
        return cache ? JSON.parse(cache) : {};
    } catch (error) {
        console.warn('Failed to load OSV cache:', error);
        return {};
    }
}

saveOSVCache(cache) {
    try {
        // Keep cache size reasonable - remove entries older than 7 days
        const oneWeekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
        const cleanedCache = {};
        
        Object.entries(cache).forEach(([key, value]) => {
            if (value.timestamp && value.timestamp > oneWeekAgo) {
                cleanedCache[key] = value;
            }
        });
        
        localStorage.setItem('osv-cache', JSON.stringify(cleanedCache));
    } catch (error) {
        console.warn('Failed to save OSV cache:', error);
    }
}

// Batched API requests
async checkPackagesViaOSV(packagesNeedingCheck, osvCache) {
    const batchSize = 10; // Process in batches
    
    for (let i = 0; i < packagesNeedingCheck.length; i += batchSize) {
        const batch = packagesNeedingCheck.slice(i, i + batchSize);
        
        await Promise.all(batch.map(async ({ dep, result }) => {
            try {
                const vulnerabilities = await this.queryOSVAPI(dep.name, dep.version);
                const cacheKey = `${dep.name}@${dep.version}`;
                
                // Cache the result
                osvCache[cacheKey] = {
                    vulnerabilities,
                    timestamp: Date.now()
                };
                
                if (vulnerabilities.length > 0) {
                    result.status = 'vulnerable';
                    result.advisories.push(...vulnerabilities);
                }
            } catch (error) {
                console.warn(`Failed to check ${dep.name}@${dep.version}:`, error);
            }
        }));
        
        // Small delay between batches to be respectful
        if (i + batchSize < packagesNeedingCheck.length) {
            await new Promise(resolve => setTimeout(resolve, 100));
        }
    }
    
    this.saveOSVCache(osvCache);
}

🎯 Version Matching Algorithm

Sophisticated version range matching that handles semantic versioning, wildcards, and complex range expressions commonly found in vulnerability databases.

// Version matching with semver support
versionMatches(version, range) {
    if (range === '*') return true;
    
    // Handle common range patterns
    if (range.startsWith('<')) {
        const targetVersion = range.substring(1);
        return this.compareVersions(version, targetVersion) < 0;
    }
    
    if (range.startsWith('>=')) {
        const targetVersion = range.substring(2);
        return this.compareVersions(version, targetVersion) >= 0;
    }
    
    if (range.startsWith('>')) {
        const targetVersion = range.substring(1);
        return this.compareVersions(version, targetVersion) > 0;
    }
    
    if (range.startsWith('<=')) {
        const targetVersion = range.substring(2);
        return this.compareVersions(version, targetVersion) <= 0;
    }
    
    // Exact match
    return version === range;
}

// Semantic version comparison
compareVersions(a, b) {
    const aParts = a.split('.').map(Number);
    const bParts = b.split('.').map(Number);
    
    for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
        const aPart = aParts[i] || 0;
        const bPart = bParts[i] || 0;
        
        if (aPart < bPart) return -1;
        if (aPart > bPart) return 1;
    }
    
    return 0;
}
50,000+ Vulnerabilities Tracked
100% Client-Side Processing
3 Lockfile Formats
0 Files Uploaded

Trust & Transparency

🔍 Open Source Approach

While we don't publish our full source code, we provide complete transparency about our methods. Every algorithm and approach is documented here with real code examples.

🛡️ Security First

Your files never leave your browser. We use standard web APIs (FileReader, fetch) with no custom network protocols or hidden data transmission.

📊 Data Sources

All vulnerability data comes from Google's OSV database and official security advisories. We don't create or modify vulnerability information.

🔒 No Tracking

We don't track what packages you scan, what vulnerabilities you find, or any other usage data. Analytics are limited to basic page views.