@codecot/pw-checker
Version:
A comprehensive CLI tool to audit passwords locally using HIBP and import from Chrome/Bitwarden
151 lines • 7.25 kB
JavaScript
import sqlite3 from "sqlite3";
import { open } from "sqlite";
import path from "path";
import { fileURLToPath } from "url";
import chalk from "chalk";
// ESM replacement for __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const dbPath = path.resolve(__dirname, "../db/pw_entries.sqlite");
async function analyzeBreaches() {
const db = await open({
filename: dbPath,
driver: sqlite3.Database,
});
// Get all entries with breach information
const query = `
SELECT username, breach_info
FROM pw_entries
WHERE breach_info IS NOT NULL
AND breach_info LIKE '%"breached":true%'
`;
const rows = await db.all(query);
await db.close();
if (rows.length === 0) {
console.log(chalk.yellow("⚠️ No breach information found."));
return;
}
console.log(chalk.blue.bold("🔍 Breach Analysis Report\n"));
console.log(chalk.gray(`Analyzing breach data from ${rows.length} affected accounts...\n`));
// Collect all breaches
const breachMap = new Map();
for (const row of rows) {
try {
const breachInfo = JSON.parse(row.breach_info);
if (breachInfo.breached && breachInfo.breaches) {
for (const breach of breachInfo.breaches) {
const breachKey = `${breach.name || breach.title}-${breach.date || breach.breachDate}`;
if (breachMap.has(breachKey)) {
const existing = breachMap.get(breachKey);
if (!existing.affectedEmails.includes(row.username.toLowerCase())) {
existing.affectedEmails.push(row.username.toLowerCase());
}
}
else {
breachMap.set(breachKey, {
name: breach.name || "Unknown",
title: breach.title || breach.name || "Unknown",
domain: breach.domain || "Unknown",
date: breach.date || breach.breachDate || "Unknown",
dataTypes: breach.dataTypes || [],
description: breach.description,
pwnCount: breach.pwnCount,
affectedEmails: [row.username.toLowerCase()],
});
}
}
}
}
catch (e) {
// Invalid JSON, skip
}
}
// Sort breaches by date (newest first) and by number of affected emails
const sortedBreaches = Array.from(breachMap.values()).sort((a, b) => {
const dateA = new Date(a.date === "Unknown" ? "1970-01-01" : a.date);
const dateB = new Date(b.date === "Unknown" ? "1970-01-01" : b.date);
// First sort by date (newest first)
const dateDiff = dateB.getTime() - dateA.getTime();
if (dateDiff !== 0)
return dateDiff;
// Then by number of affected emails (most affected first)
return b.affectedEmails.length - a.affectedEmails.length;
});
// Show recent breaches (last 2 years)
const twoYearsAgo = new Date();
twoYearsAgo.setFullYear(twoYearsAgo.getFullYear() - 2);
const recentBreaches = sortedBreaches.filter((breach) => {
const breachDate = new Date(breach.date === "Unknown" ? "1970-01-01" : breach.date);
return breachDate >= twoYearsAgo;
});
console.log(chalk.red.bold(`🚨 Recent Breaches (Last 2 Years): ${recentBreaches.length} found\n`));
recentBreaches.slice(0, 10).forEach((breach, index) => {
console.log(chalk.red(`${index + 1}. ${breach.title} (${breach.domain})`));
console.log(chalk.gray(` Date: ${breach.date}`));
console.log(chalk.yellow(` Your affected emails: ${breach.affectedEmails.length}`));
if (breach.pwnCount) {
console.log(chalk.blue(` Total accounts affected: ${breach.pwnCount.toLocaleString()}`));
}
if (breach.dataTypes.length > 0) {
console.log(chalk.cyan(` Data exposed: ${breach.dataTypes.join(", ")}`));
}
if (breach.description) {
const shortDesc = breach.description.length > 150
? breach.description.substring(0, 150) + "..."
: breach.description;
console.log(chalk.gray(` ${shortDesc}`));
}
console.log(chalk.magenta(` Your emails: ${breach.affectedEmails.slice(0, 3).join(", ")}${breach.affectedEmails.length > 3 ? "..." : ""}`));
console.log("");
});
// Show most impactful breaches (affecting multiple emails)
const impactfulBreaches = sortedBreaches.filter((breach) => breach.affectedEmails.length > 1);
if (impactfulBreaches.length > 0) {
console.log(chalk.red.bold(`⚠️ Breaches Affecting Multiple Accounts: ${impactfulBreaches.length} found\n`));
impactfulBreaches.slice(0, 5).forEach((breach, index) => {
console.log(chalk.red(`${index + 1}. ${breach.title} (${breach.domain}) - ${breach.date}`));
console.log(chalk.yellow(` Affects ${breach.affectedEmails.length} of your email accounts`));
console.log(chalk.magenta(` Emails: ${breach.affectedEmails.join(", ")}`));
console.log("");
});
}
// Summary statistics
console.log(chalk.blue.bold("📊 Summary Statistics\n"));
console.log(`Total unique breaches: ${chalk.yellow(sortedBreaches.length.toString())}`);
console.log(`Recent breaches (2+ years): ${chalk.red(recentBreaches.length.toString())}`);
console.log(`Breaches affecting multiple accounts: ${chalk.yellow(impactfulBreaches.length.toString())}`);
console.log(`Total affected email accounts: ${chalk.red(rows.length.toString())}`);
// Top domains
const domainCount = new Map();
sortedBreaches.forEach((breach) => {
if (breach.domain !== "Unknown") {
const count = domainCount.get(breach.domain) || 0;
domainCount.set(breach.domain, count + 1);
}
});
const topDomains = Array.from(domainCount.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 5);
if (topDomains.length > 0) {
console.log(`\nTop breached domains:`);
topDomains.forEach(([domain, count], index) => {
console.log(` ${index + 1}. ${chalk.cyan(domain)}: ${chalk.yellow(count.toString())} breaches`);
});
}
}
// Process command line arguments
const args = process.argv.slice(2);
if (args.includes("--help") || args.includes("-h")) {
console.log(chalk.blue.bold("🔍 pw-checker Breach Analysis Tool\n"));
console.log("Usage: npm run analyze:breaches\n");
console.log("This tool analyzes all breach data in your database and provides:");
console.log(" • Recent breaches (last 2 years)");
console.log(" • Breaches affecting multiple accounts");
console.log(" • Summary statistics");
console.log(" • Top breached domains");
process.exit(0);
}
analyzeBreaches().catch((err) => {
console.error(chalk.red("❌ Failed to analyze breaches:"), err.message);
});
//# sourceMappingURL=breachAnalysis.js.map