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.