@profullstack/threatcrush 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ import Database from 'better-sqlite3';
2
+ import type { ThreatEvent } from '../types/events.js';
3
+
4
+ let db: Database.Database | null = null;
5
+
6
+ export function initStateDB(dbPath: string = '/var/lib/threatcrush/state.db'): Database.Database {
7
+ if (db) return db;
8
+
9
+ try {
10
+ db = new Database(dbPath);
11
+ } catch {
12
+ // Fall back to in-memory if we can't write to the path
13
+ db = new Database(':memory:');
14
+ }
15
+
16
+ db.pragma('journal_mode = WAL');
17
+
18
+ db.exec(`
19
+ CREATE TABLE IF NOT EXISTS events (
20
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21
+ timestamp TEXT NOT NULL,
22
+ module TEXT NOT NULL,
23
+ category TEXT NOT NULL,
24
+ severity TEXT NOT NULL,
25
+ message TEXT NOT NULL,
26
+ source_ip TEXT,
27
+ details TEXT
28
+ );
29
+
30
+ CREATE TABLE IF NOT EXISTS module_state (
31
+ module TEXT NOT NULL,
32
+ key TEXT NOT NULL,
33
+ value TEXT,
34
+ PRIMARY KEY (module, key)
35
+ );
36
+
37
+ CREATE TABLE IF NOT EXISTS stats (
38
+ key TEXT PRIMARY KEY,
39
+ value TEXT NOT NULL,
40
+ updated_at TEXT NOT NULL
41
+ );
42
+
43
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
44
+ CREATE INDEX IF NOT EXISTS idx_events_module ON events(module);
45
+ CREATE INDEX IF NOT EXISTS idx_events_severity ON events(severity);
46
+ CREATE INDEX IF NOT EXISTS idx_events_source_ip ON events(source_ip);
47
+ `);
48
+
49
+ return db;
50
+ }
51
+
52
+ export function insertEvent(event: ThreatEvent): number {
53
+ const database = db || initStateDB();
54
+ const stmt = database.prepare(`
55
+ INSERT INTO events (timestamp, module, category, severity, message, source_ip, details)
56
+ VALUES (?, ?, ?, ?, ?, ?, ?)
57
+ `);
58
+ const result = stmt.run(
59
+ event.timestamp.toISOString(),
60
+ event.module,
61
+ event.category,
62
+ event.severity,
63
+ event.message,
64
+ event.source_ip || null,
65
+ event.details ? JSON.stringify(event.details) : null,
66
+ );
67
+ return result.lastInsertRowid as number;
68
+ }
69
+
70
+ export function getRecentEvents(limit: number = 50): ThreatEvent[] {
71
+ const database = db || initStateDB();
72
+ const rows = database.prepare(`
73
+ SELECT * FROM events ORDER BY timestamp DESC LIMIT ?
74
+ `).all(limit) as any[];
75
+ return rows.map(rowToEvent);
76
+ }
77
+
78
+ export function getEventCount(since?: Date): number {
79
+ const database = db || initStateDB();
80
+ if (since) {
81
+ return (database.prepare(`SELECT COUNT(*) as count FROM events WHERE timestamp >= ?`)
82
+ .get(since.toISOString()) as any).count;
83
+ }
84
+ return (database.prepare(`SELECT COUNT(*) as count FROM events`).get() as any).count;
85
+ }
86
+
87
+ export function getThreatCount(since?: Date): number {
88
+ const database = db || initStateDB();
89
+ const severities = "('medium','high','critical')";
90
+ if (since) {
91
+ return (database.prepare(
92
+ `SELECT COUNT(*) as count FROM events WHERE severity IN ${severities} AND timestamp >= ?`
93
+ ).get(since.toISOString()) as any).count;
94
+ }
95
+ return (database.prepare(
96
+ `SELECT COUNT(*) as count FROM events WHERE severity IN ${severities}`
97
+ ).get() as any).count;
98
+ }
99
+
100
+ export function getTopSources(limit: number = 10): Array<{ ip: string; count: number }> {
101
+ const database = db || initStateDB();
102
+ return database.prepare(`
103
+ SELECT source_ip as ip, COUNT(*) as count FROM events
104
+ WHERE source_ip IS NOT NULL
105
+ GROUP BY source_ip ORDER BY count DESC LIMIT ?
106
+ `).all(limit) as any[];
107
+ }
108
+
109
+ export function getModuleState(module: string, key: string): unknown {
110
+ const database = db || initStateDB();
111
+ const row = database.prepare(`SELECT value FROM module_state WHERE module = ? AND key = ?`)
112
+ .get(module, key) as any;
113
+ if (!row) return undefined;
114
+ try {
115
+ return JSON.parse(row.value);
116
+ } catch {
117
+ return row.value;
118
+ }
119
+ }
120
+
121
+ export function setModuleState(module: string, key: string, value: unknown): void {
122
+ const database = db || initStateDB();
123
+ database.prepare(`
124
+ INSERT OR REPLACE INTO module_state (module, key, value) VALUES (?, ?, ?)
125
+ `).run(module, key, JSON.stringify(value));
126
+ }
127
+
128
+ function rowToEvent(row: any): ThreatEvent {
129
+ return {
130
+ id: row.id,
131
+ timestamp: new Date(row.timestamp),
132
+ module: row.module,
133
+ category: row.category,
134
+ severity: row.severity,
135
+ message: row.message,
136
+ source_ip: row.source_ip,
137
+ details: row.details ? JSON.parse(row.details) : undefined,
138
+ };
139
+ }
140
+
141
+ export function closeDB(): void {
142
+ if (db) {
143
+ db.close();
144
+ db = null;
145
+ }
146
+ }
package/src/index.ts ADDED
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import chalk from "chalk";
5
+ import readline from "readline";
6
+
7
+ const LOGO = `
8
+ ${chalk.green(" ████████╗██╗ ██╗██████╗ ███████╗ █████╗ ████████╗")}
9
+ ${chalk.green(" ╚══██╔══╝██║ ██║██╔══██╗██╔════╝██╔══██╗╚══██╔══╝")}
10
+ ${chalk.green(" ██║ ███████║██████╔╝█████╗ ███████║ ██║ ")}
11
+ ${chalk.green(" ██║ ██╔══██║██╔══██╗██╔══╝ ██╔══██║ ██║ ")}
12
+ ${chalk.green(" ██║ ██║ ██║██║ ██║███████╗██║ ██║ ██║ ")}
13
+ ${chalk.green(" ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝")}
14
+ ${chalk.dim(" C R U S H")}
15
+ `;
16
+
17
+ const API_URL = process.env.THREATCRUSH_API_URL || "https://threatcrush.com";
18
+
19
+ async function promptEmail(): Promise<string | null> {
20
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
21
+ return new Promise((resolve) => {
22
+ rl.question(chalk.green("\n Enter your email to continue: "), (answer) => {
23
+ rl.close();
24
+ resolve(answer.trim() || null);
25
+ });
26
+ });
27
+ }
28
+
29
+ async function joinWaitlist(email: string): Promise<{ referral_code?: string } | null> {
30
+ try {
31
+ const res = await fetch(`${API_URL}/api/waitlist`, {
32
+ method: "POST",
33
+ headers: { "Content-Type": "application/json" },
34
+ body: JSON.stringify({ email }),
35
+ });
36
+ return await res.json();
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ async function emailGate(): Promise<boolean> {
43
+ console.log(LOGO);
44
+ console.log(chalk.yellow(" ⚡ Coming soon — ThreatCrush is in private beta.\n"));
45
+
46
+ const email = await promptEmail();
47
+ if (!email || !email.includes("@")) {
48
+ console.log(chalk.red("\n Invalid email. Try again.\n"));
49
+ return false;
50
+ }
51
+
52
+ const result = await joinWaitlist(email);
53
+ if (result?.referral_code) {
54
+ console.log(chalk.green(`\n ✓ You're on the list!`));
55
+ console.log(chalk.dim(` Referral code: ${chalk.white(result.referral_code)}`));
56
+ console.log(chalk.dim(` Share: ${API_URL}?ref=${result.referral_code}`));
57
+ console.log(chalk.green(`\n 🎁 Refer a friend → both get lifetime access for $249 (instead of $499)\n`));
58
+ } else {
59
+ console.log(chalk.green(`\n ✓ Thanks! We'll notify you when ThreatCrush launches.\n`));
60
+ }
61
+
62
+ return true;
63
+ }
64
+
65
+ const program = new Command();
66
+
67
+ program
68
+ .name("threatcrush")
69
+ .description("All-in-one security agent — monitor, detect, scan, protect")
70
+ .version("0.1.0");
71
+
72
+ // Every command goes through email gate for now
73
+ const gatedCommand = (name: string, desc: string) => {
74
+ program.command(name).description(desc).action(async () => {
75
+ await emailGate();
76
+ });
77
+ };
78
+
79
+ gatedCommand("monitor", "Real-time security monitoring (all ports, all protocols)");
80
+ gatedCommand("tui", "Interactive security dashboard (htop for security)");
81
+ gatedCommand("init", "Auto-detect services and configure ThreatCrush");
82
+ gatedCommand("scan", "Scan codebase for vulnerabilities and secrets");
83
+ gatedCommand("pentest", "Penetration test URLs and APIs");
84
+ gatedCommand("status", "Show daemon status and loaded modules");
85
+ gatedCommand("start", "Start the ThreatCrush daemon");
86
+ gatedCommand("stop", "Stop the ThreatCrush daemon");
87
+ gatedCommand("logs", "Tail daemon logs");
88
+ gatedCommand("update", "Update CLI and all installed modules");
89
+ gatedCommand("activate", "Activate your license key");
90
+
91
+ program
92
+ .command("modules")
93
+ .description("Manage security modules")
94
+ .argument("[action]", "list | install | remove | available")
95
+ .argument("[name]", "module name")
96
+ .action(async () => {
97
+ await emailGate();
98
+ });
99
+
100
+ program
101
+ .command("store")
102
+ .description("Browse the module marketplace")
103
+ .argument("[action]", "search | info | publish")
104
+ .argument("[query]", "search query or module name")
105
+ .action(async () => {
106
+ await emailGate();
107
+ });
108
+
109
+ // Default action (no command)
110
+ program.action(async () => {
111
+ await emailGate();
112
+ });
113
+
114
+ program.parse();
@@ -0,0 +1,63 @@
1
+ export interface DaemonConfig {
2
+ pid_file: string;
3
+ log_level: 'debug' | 'info' | 'warn' | 'error';
4
+ log_file: string;
5
+ state_db: string;
6
+ }
7
+
8
+ export interface ApiConfig {
9
+ enabled: boolean;
10
+ bind: string;
11
+ tls: boolean;
12
+ }
13
+
14
+ export interface AlertChannelConfig {
15
+ enabled: boolean;
16
+ [key: string]: unknown;
17
+ }
18
+
19
+ export interface ModulesConfig {
20
+ auto_update: boolean;
21
+ update_interval: string;
22
+ module_dir: string;
23
+ config_dir: string;
24
+ }
25
+
26
+ export interface ThreatCrushConfig {
27
+ daemon: DaemonConfig;
28
+ api: ApiConfig;
29
+ alerts: Record<string, AlertChannelConfig>;
30
+ modules: ModulesConfig;
31
+ license?: {
32
+ key_file?: string;
33
+ key?: string;
34
+ };
35
+ }
36
+
37
+ export interface ModuleManifest {
38
+ module: {
39
+ name: string;
40
+ version: string;
41
+ description: string;
42
+ author: string;
43
+ license: string;
44
+ homepage?: string;
45
+ pricing?: {
46
+ type: 'free' | 'paid' | 'freemium';
47
+ price_usd?: number;
48
+ };
49
+ requirements?: {
50
+ threatcrush?: string;
51
+ os?: string[];
52
+ capabilities?: string[];
53
+ };
54
+ config?: {
55
+ defaults?: Record<string, unknown>;
56
+ };
57
+ };
58
+ }
59
+
60
+ export interface ModuleConfig {
61
+ enabled: boolean;
62
+ [key: string]: unknown;
63
+ }
@@ -0,0 +1,54 @@
1
+ export type EventSeverity = 'info' | 'low' | 'medium' | 'high' | 'critical';
2
+ export type EventCategory = 'auth' | 'web' | 'network' | 'system' | 'scan' | 'pentest';
3
+
4
+ export interface ThreatEvent {
5
+ id?: number;
6
+ timestamp: Date;
7
+ module: string;
8
+ category: EventCategory;
9
+ severity: EventSeverity;
10
+ message: string;
11
+ source_ip?: string;
12
+ details?: Record<string, unknown>;
13
+ }
14
+
15
+ export interface ParsedLogLine {
16
+ timestamp: Date;
17
+ raw: string;
18
+ source: string;
19
+ fields: Record<string, string>;
20
+ }
21
+
22
+ export interface NginxLogEntry extends ParsedLogLine {
23
+ source: 'nginx';
24
+ fields: {
25
+ ip: string;
26
+ method: string;
27
+ path: string;
28
+ status: string;
29
+ size: string;
30
+ user_agent: string;
31
+ [key: string]: string;
32
+ };
33
+ }
34
+
35
+ export interface AuthLogEntry extends ParsedLogLine {
36
+ source: 'auth';
37
+ fields: {
38
+ process: string;
39
+ message: string;
40
+ ip?: string;
41
+ user?: string;
42
+ [key: string]: string;
43
+ };
44
+ }
45
+
46
+ export interface SyslogEntry extends ParsedLogLine {
47
+ source: 'syslog';
48
+ fields: {
49
+ facility: string;
50
+ process: string;
51
+ message: string;
52
+ [key: string]: string;
53
+ };
54
+ }
@@ -0,0 +1,42 @@
1
+ import type { ThreatEvent } from './events.js';
2
+ import type { ModuleConfig } from './config.js';
3
+
4
+ export interface ModuleContext {
5
+ config: ModuleConfig;
6
+ logger: ModuleLogger;
7
+ emit: (event: ThreatEvent) => void;
8
+ getState: (key: string) => unknown;
9
+ setState: (key: string, value: unknown) => void;
10
+ }
11
+
12
+ export interface ModuleLogger {
13
+ debug: (msg: string, ...args: unknown[]) => void;
14
+ info: (msg: string, ...args: unknown[]) => void;
15
+ warn: (msg: string, ...args: unknown[]) => void;
16
+ error: (msg: string, ...args: unknown[]) => void;
17
+ }
18
+
19
+ export interface ThreatCrushModule {
20
+ name: string;
21
+ version: string;
22
+ description?: string;
23
+
24
+ init(ctx: ModuleContext): Promise<void>;
25
+ start(): Promise<void>;
26
+ stop(): Promise<void>;
27
+ onEvent?(event: ThreatEvent): Promise<void>;
28
+ }
29
+
30
+ export interface LoadedModule {
31
+ manifest: {
32
+ name: string;
33
+ version: string;
34
+ description: string;
35
+ author: string;
36
+ };
37
+ config: ModuleConfig;
38
+ instance?: ThreatCrushModule;
39
+ status: 'loaded' | 'running' | 'stopped' | 'error';
40
+ eventCount: number;
41
+ path: string;
42
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src",
12
+ "declaration": true,
13
+ "resolveJsonModule": true,
14
+ "sourceMap": true,
15
+ "types": ["node"]
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs'],
6
+ target: 'node20',
7
+ platform: 'node',
8
+ outDir: 'dist',
9
+ clean: true,
10
+ splitting: false,
11
+ sourcemap: true,
12
+ dts: false,
13
+
14
+ external: ['better-sqlite3', 'blessed', 'blessed-contrib'],
15
+ noExternal: ['chalk', 'ora', '@iarna/toml', 'commander'],
16
+ });