@profullstack/threatcrush 0.2.0 → 0.2.1

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.
package/dist/daemon.js CHANGED
@@ -2134,12 +2134,21 @@ bus.setMaxListeners(50);
2134
2134
  // src/core/state.ts
2135
2135
  var import_better_sqlite3 = __toESM(require("better-sqlite3"));
2136
2136
  var db = null;
2137
+ var dbUnavailable = false;
2137
2138
  function initStateDB(dbPath = "/var/lib/threatcrush/state.db") {
2138
2139
  if (db) return db;
2140
+ if (dbUnavailable) {
2141
+ throw new Error("state db unavailable (previous init failed)");
2142
+ }
2139
2143
  try {
2140
- db = new import_better_sqlite3.default(dbPath);
2141
- } catch {
2142
- db = new import_better_sqlite3.default(":memory:");
2144
+ try {
2145
+ db = new import_better_sqlite3.default(dbPath);
2146
+ } catch {
2147
+ db = new import_better_sqlite3.default(":memory:");
2148
+ }
2149
+ } catch (err) {
2150
+ dbUnavailable = true;
2151
+ throw err;
2143
2152
  }
2144
2153
  db.pragma("journal_mode = WAL");
2145
2154
  db.exec(`
@@ -2175,7 +2184,8 @@ function initStateDB(dbPath = "/var/lib/threatcrush/state.db") {
2175
2184
  return db;
2176
2185
  }
2177
2186
  function insertEvent(event) {
2178
- const database = db || initStateDB();
2187
+ const database = tryDb();
2188
+ if (!database) return -1;
2179
2189
  const stmt = database.prepare(`
2180
2190
  INSERT INTO events (timestamp, module, category, severity, message, source_ip, details)
2181
2191
  VALUES (?, ?, ?, ?, ?, ?, ?)
@@ -2191,22 +2201,34 @@ function insertEvent(event) {
2191
2201
  );
2192
2202
  return result.lastInsertRowid;
2193
2203
  }
2204
+ function tryDb() {
2205
+ if (db) return db;
2206
+ if (dbUnavailable) return null;
2207
+ try {
2208
+ return initStateDB();
2209
+ } catch {
2210
+ return null;
2211
+ }
2212
+ }
2194
2213
  function getRecentEvents(limit = 50) {
2195
- const database = db || initStateDB();
2214
+ const database = tryDb();
2215
+ if (!database) return [];
2196
2216
  const rows = database.prepare(`
2197
2217
  SELECT * FROM events ORDER BY timestamp DESC LIMIT ?
2198
2218
  `).all(limit);
2199
2219
  return rows.map(rowToEvent);
2200
2220
  }
2201
2221
  function getEventCount(since) {
2202
- const database = db || initStateDB();
2222
+ const database = tryDb();
2223
+ if (!database) return 0;
2203
2224
  if (since) {
2204
2225
  return database.prepare(`SELECT COUNT(*) as count FROM events WHERE timestamp >= ?`).get(since.toISOString()).count;
2205
2226
  }
2206
2227
  return database.prepare(`SELECT COUNT(*) as count FROM events`).get().count;
2207
2228
  }
2208
2229
  function getThreatCount(since) {
2209
- const database = db || initStateDB();
2230
+ const database = tryDb();
2231
+ if (!database) return 0;
2210
2232
  const severities = "('medium','high','critical')";
2211
2233
  if (since) {
2212
2234
  return database.prepare(
@@ -2218,13 +2240,30 @@ function getThreatCount(since) {
2218
2240
  ).get().count;
2219
2241
  }
2220
2242
  function getTopSources(limit = 10) {
2221
- const database = db || initStateDB();
2243
+ const database = tryDb();
2244
+ if (!database) return [];
2222
2245
  return database.prepare(`
2223
2246
  SELECT source_ip as ip, COUNT(*) as count FROM events
2224
2247
  WHERE source_ip IS NOT NULL
2225
2248
  GROUP BY source_ip ORDER BY count DESC LIMIT ?
2226
2249
  `).all(limit);
2227
2250
  }
2251
+ function getModuleState(module2, key) {
2252
+ const database = db || initStateDB();
2253
+ const row = database.prepare(`SELECT value FROM module_state WHERE module = ? AND key = ?`).get(module2, key);
2254
+ if (!row) return void 0;
2255
+ try {
2256
+ return JSON.parse(row.value);
2257
+ } catch {
2258
+ return row.value;
2259
+ }
2260
+ }
2261
+ function setModuleState(module2, key, value) {
2262
+ const database = db || initStateDB();
2263
+ database.prepare(`
2264
+ INSERT OR REPLACE INTO module_state (module, key, value) VALUES (?, ?, ?)
2265
+ `).run(module2, key, JSON.stringify(value));
2266
+ }
2228
2267
  function rowToEvent(row) {
2229
2268
  return {
2230
2269
  id: row.id,
@@ -2413,9 +2452,10 @@ var IpcServer = class {
2413
2452
  };
2414
2453
 
2415
2454
  // src/daemon/module-host.ts
2416
- var import_node_fs5 = require("fs");
2417
- var import_node_path2 = require("path");
2418
- var import_toml = __toESM(require_toml());
2455
+ var import_node_fs6 = require("fs");
2456
+ var import_node_path3 = require("path");
2457
+ var import_node_url = require("url");
2458
+ var import_toml2 = __toESM(require_toml());
2419
2459
 
2420
2460
  // src/daemon/watchers/log-watcher.ts
2421
2461
  var import_node_fs4 = require("fs");
@@ -2674,6 +2714,182 @@ var LogWatcher = class {
2674
2714
  }
2675
2715
  };
2676
2716
 
2717
+ // src/daemon/watchers/journal-watcher.ts
2718
+ var import_node_child_process = require("child_process");
2719
+ var JournalWatcher = class _JournalWatcher {
2720
+ constructor(bus2) {
2721
+ this.bus = bus2;
2722
+ }
2723
+ bus;
2724
+ proc = null;
2725
+ buffer = "";
2726
+ moduleName = "user-journal";
2727
+ active = false;
2728
+ static isAvailable() {
2729
+ const probe = (0, import_node_child_process.spawnSync)("journalctl", ["--user", "-n", "0", "--no-pager"], {
2730
+ stdio: ["ignore", "ignore", "ignore"]
2731
+ });
2732
+ return probe.status === 0;
2733
+ }
2734
+ start() {
2735
+ if (!_JournalWatcher.isAvailable()) return false;
2736
+ const child = (0, import_node_child_process.spawn)(
2737
+ "journalctl",
2738
+ ["--user", "-o", "json", "-f", "--since", "now"],
2739
+ { stdio: ["ignore", "pipe", "pipe"] }
2740
+ );
2741
+ if (!child.stdout) return false;
2742
+ child.stdout.setEncoding("utf-8");
2743
+ child.stdout.on("data", (chunk) => this.onData(chunk));
2744
+ child.on("exit", () => {
2745
+ this.proc = null;
2746
+ this.active = false;
2747
+ });
2748
+ this.proc = child;
2749
+ this.active = true;
2750
+ return true;
2751
+ }
2752
+ stop() {
2753
+ if (this.proc) {
2754
+ try {
2755
+ this.proc.kill("SIGTERM");
2756
+ } catch {
2757
+ }
2758
+ this.proc = null;
2759
+ }
2760
+ this.active = false;
2761
+ }
2762
+ isActive() {
2763
+ return this.active;
2764
+ }
2765
+ moduleNameValue() {
2766
+ return this.moduleName;
2767
+ }
2768
+ onData(chunk) {
2769
+ this.buffer += chunk;
2770
+ let idx;
2771
+ while ((idx = this.buffer.indexOf("\n")) >= 0) {
2772
+ const line = this.buffer.slice(0, idx);
2773
+ this.buffer = this.buffer.slice(idx + 1);
2774
+ if (!line.trim()) continue;
2775
+ this.handleLine(line);
2776
+ }
2777
+ }
2778
+ handleLine(line) {
2779
+ let entry;
2780
+ try {
2781
+ entry = JSON.parse(line);
2782
+ } catch {
2783
+ return;
2784
+ }
2785
+ const message = entry.MESSAGE;
2786
+ if (!message) return;
2787
+ const priority = parseInt(entry.PRIORITY ?? "6", 10);
2788
+ const severity = priorityToSeverity(priority);
2789
+ const ident = entry.SYSLOG_IDENTIFIER || entry._COMM || "journal";
2790
+ const bumpedSeverity = bumpForIdent(ident, message, severity);
2791
+ const event = {
2792
+ timestamp: realtimeToDate(entry.__REALTIME_TIMESTAMP) || /* @__PURE__ */ new Date(),
2793
+ module: this.moduleName,
2794
+ category: "system",
2795
+ severity: bumpedSeverity,
2796
+ message: `[${ident}] ${message}`.slice(0, 500)
2797
+ };
2798
+ try {
2799
+ insertEvent(event);
2800
+ } catch {
2801
+ }
2802
+ this.bus.publish(event);
2803
+ }
2804
+ };
2805
+ function priorityToSeverity(priority) {
2806
+ if (priority <= 2) return "critical";
2807
+ if (priority === 3) return "high";
2808
+ if (priority === 4) return "medium";
2809
+ if (priority === 5) return "low";
2810
+ return "info";
2811
+ }
2812
+ function bumpForIdent(ident, message, base) {
2813
+ if (/sudo/i.test(ident) && /authentication failure|incorrect password|FAILED/i.test(message)) {
2814
+ return "high";
2815
+ }
2816
+ if (/sshd/i.test(ident) && /failed|invalid user|break-in/i.test(message)) {
2817
+ return "high";
2818
+ }
2819
+ return base;
2820
+ }
2821
+ function realtimeToDate(rt) {
2822
+ if (!rt) return null;
2823
+ const us = parseInt(rt, 10);
2824
+ if (!Number.isFinite(us)) return null;
2825
+ return new Date(Math.floor(us / 1e3));
2826
+ }
2827
+
2828
+ // src/core/config.ts
2829
+ var import_node_fs5 = require("fs");
2830
+ var import_node_path2 = require("path");
2831
+ var import_toml = __toESM(require_toml());
2832
+ var DEFAULT_CONFIG_PATH = "/etc/threatcrush/threatcrushd.conf";
2833
+ var DEFAULT_CONFDIR = "/etc/threatcrush/threatcrushd.conf.d";
2834
+ var DEFAULT_CONFIG = {
2835
+ daemon: {
2836
+ pid_file: "/var/run/threatcrush/threatcrushd.pid",
2837
+ log_level: "info",
2838
+ log_file: "/var/log/threatcrush/threatcrushd.log",
2839
+ state_db: "/var/lib/threatcrush/state.db"
2840
+ },
2841
+ api: {
2842
+ enabled: true,
2843
+ bind: "127.0.0.1:9393",
2844
+ tls: false
2845
+ },
2846
+ alerts: {},
2847
+ modules: {
2848
+ auto_update: true,
2849
+ update_interval: "24h",
2850
+ module_dir: "/etc/threatcrush/modules",
2851
+ config_dir: DEFAULT_CONFDIR
2852
+ }
2853
+ };
2854
+ function loadConfig(configPath) {
2855
+ const path = configPath || DEFAULT_CONFIG_PATH;
2856
+ if (!(0, import_node_fs5.existsSync)(path)) {
2857
+ return { ...DEFAULT_CONFIG };
2858
+ }
2859
+ try {
2860
+ const raw = (0, import_node_fs5.readFileSync)(path, "utf-8");
2861
+ const parsed = import_toml.default.parse(raw);
2862
+ return {
2863
+ daemon: { ...DEFAULT_CONFIG.daemon, ...parsed.daemon },
2864
+ api: { ...DEFAULT_CONFIG.api, ...parsed.api },
2865
+ alerts: parsed.alerts || {},
2866
+ modules: { ...DEFAULT_CONFIG.modules, ...parsed.modules },
2867
+ license: parsed.license
2868
+ };
2869
+ } catch {
2870
+ return { ...DEFAULT_CONFIG };
2871
+ }
2872
+ }
2873
+ function loadModuleConfigs(confDir) {
2874
+ const dir = confDir || DEFAULT_CONFDIR;
2875
+ const configs = /* @__PURE__ */ new Map();
2876
+ if (!(0, import_node_fs5.existsSync)(dir)) {
2877
+ return configs;
2878
+ }
2879
+ const files = (0, import_node_fs5.readdirSync)(dir).filter((f) => f.endsWith(".conf"));
2880
+ for (const file of files) {
2881
+ try {
2882
+ const raw = (0, import_node_fs5.readFileSync)((0, import_node_path2.join)(dir, file), "utf-8");
2883
+ const parsed = import_toml.default.parse(raw);
2884
+ for (const [name, config] of Object.entries(parsed)) {
2885
+ configs.set(name, config);
2886
+ }
2887
+ } catch {
2888
+ }
2889
+ }
2890
+ return configs;
2891
+ }
2892
+
2677
2893
  // src/daemon/module-host.ts
2678
2894
  var ModuleHost = class {
2679
2895
  constructor(bus2) {
@@ -2681,14 +2897,23 @@ var ModuleHost = class {
2681
2897
  bus2.on("event", (event) => {
2682
2898
  const mod = this.modules.get(event.module);
2683
2899
  if (mod) mod.events++;
2900
+ for (const hosted of this.modules.values()) {
2901
+ if (hosted.status !== "running" || !hosted.instance?.onEvent) continue;
2902
+ void hosted.instance.onEvent(event).catch((err) => {
2903
+ hosted.status = "error";
2904
+ hosted.detail = `onEvent failed: ${String(err.message || err)}`;
2905
+ this.bus.announceModule(hosted.name, "error", hosted.detail);
2906
+ });
2907
+ }
2684
2908
  });
2685
2909
  }
2686
2910
  bus;
2687
2911
  modules = /* @__PURE__ */ new Map();
2688
2912
  logWatcher = null;
2913
+ journalWatcher = null;
2689
2914
  async start() {
2690
2915
  this.registerBuiltins();
2691
- this.discoverInstalled();
2916
+ await this.discoverAndStartInstalled();
2692
2917
  this.logWatcher = new LogWatcher(this.bus);
2693
2918
  const watched = this.logWatcher.start();
2694
2919
  for (const modName of this.logWatcher.activeModules()) {
@@ -2699,10 +2924,30 @@ var ModuleHost = class {
2699
2924
  this.bus.announceModule(modName, "running", mod.detail);
2700
2925
  }
2701
2926
  }
2927
+ this.journalWatcher = new JournalWatcher(this.bus);
2928
+ if (this.journalWatcher.start()) {
2929
+ const mod = this.modules.get("user-journal");
2930
+ if (mod) {
2931
+ mod.status = "running";
2932
+ mod.detail = "tailing journalctl --user";
2933
+ this.bus.announceModule("user-journal", "running", mod.detail);
2934
+ }
2935
+ }
2702
2936
  }
2703
2937
  async stop() {
2704
2938
  this.logWatcher?.stop();
2939
+ this.journalWatcher?.stop();
2705
2940
  for (const mod of this.modules.values()) {
2941
+ try {
2942
+ if (mod.instance && mod.status === "running") {
2943
+ await mod.instance.stop();
2944
+ }
2945
+ } catch (err) {
2946
+ mod.status = "error";
2947
+ mod.detail = `stop failed: ${String(err.message || err)}`;
2948
+ this.bus.announceModule(mod.name, "error", mod.detail);
2949
+ continue;
2950
+ }
2706
2951
  mod.status = "loaded";
2707
2952
  this.bus.announceModule(mod.name, "stopped");
2708
2953
  }
@@ -2718,30 +2963,129 @@ var ModuleHost = class {
2718
2963
  registerBuiltins() {
2719
2964
  const builtins = [
2720
2965
  { name: "log-watcher", version: "0.1.0", source: "builtin", status: "loaded", events: 0 },
2721
- { name: "ssh-guard", version: "0.1.0", source: "builtin", status: "loaded", events: 0 }
2966
+ { name: "ssh-guard", version: "0.1.0", source: "builtin", status: "loaded", events: 0 },
2967
+ { name: "user-journal", version: "0.1.0", source: "builtin", status: "loaded", events: 0 }
2722
2968
  ];
2723
2969
  for (const m of builtins) this.modules.set(m.name, m);
2724
2970
  }
2725
- discoverInstalled() {
2726
- if (!(0, import_node_fs5.existsSync)(PATHS.moduleDir)) return;
2727
- const entries = (0, import_node_fs5.readdirSync)(PATHS.moduleDir, { withFileTypes: true });
2971
+ async discoverAndStartInstalled() {
2972
+ if (!(0, import_node_fs6.existsSync)(PATHS.moduleDir)) return;
2973
+ const configs = loadModuleConfigs(PATHS.confD);
2974
+ const entries = (0, import_node_fs6.readdirSync)(PATHS.moduleDir, { withFileTypes: true });
2728
2975
  for (const entry of entries) {
2729
2976
  if (!entry.isDirectory()) continue;
2730
- const manifestPath = (0, import_node_path2.join)(PATHS.moduleDir, entry.name, "mod.toml");
2731
- if (!(0, import_node_fs5.existsSync)(manifestPath)) continue;
2977
+ const manifestPath = (0, import_node_path3.join)(PATHS.moduleDir, entry.name, "mod.toml");
2978
+ if (!(0, import_node_fs6.existsSync)(manifestPath)) continue;
2732
2979
  try {
2733
- const manifest = import_toml.default.parse((0, import_node_fs5.readFileSync)(manifestPath, "utf-8"));
2980
+ const manifest = import_toml2.default.parse((0, import_node_fs6.readFileSync)(manifestPath, "utf-8"));
2734
2981
  const name = manifest.module?.name || entry.name;
2735
- this.modules.set(name, {
2982
+ const defaults = manifest.module?.config?.defaults || {};
2983
+ const config = {
2984
+ enabled: true,
2985
+ ...defaults,
2986
+ ...configs.get(name) || {}
2987
+ };
2988
+ const hosted = {
2736
2989
  name,
2737
2990
  version: manifest.module?.version || "0.0.0",
2738
2991
  source: "installed",
2739
- status: "loaded",
2740
- events: 0
2992
+ status: config.enabled === false ? "disabled" : "loaded",
2993
+ events: 0,
2994
+ path: (0, import_node_path3.join)(PATHS.moduleDir, entry.name),
2995
+ config
2996
+ };
2997
+ this.modules.set(name, hosted);
2998
+ if (config.enabled === false) continue;
2999
+ await this.startInstalled(hosted);
3000
+ } catch (err) {
3001
+ const name = entry.name;
3002
+ this.modules.set(name, {
3003
+ name,
3004
+ version: "0.0.0",
3005
+ source: "installed",
3006
+ status: "error",
3007
+ events: 0,
3008
+ detail: `manifest load failed: ${String(err.message || err)}`,
3009
+ path: (0, import_node_path3.join)(PATHS.moduleDir, entry.name)
2741
3010
  });
3011
+ }
3012
+ }
3013
+ }
3014
+ async startInstalled(hosted) {
3015
+ const entrypoint = this.installedEntrypoint(hosted.path);
3016
+ if (!entrypoint) {
3017
+ hosted.status = "loaded";
3018
+ hosted.detail = "no built entrypoint found; run npm install && npm run build in the module directory";
3019
+ return;
3020
+ }
3021
+ try {
3022
+ const imported = await import((0, import_node_url.pathToFileURL)(entrypoint).href);
3023
+ const exported = imported.default || imported.module || imported;
3024
+ const instance = typeof exported === "function" ? new exported() : exported;
3025
+ if (!this.isThreatCrushModule(instance)) {
3026
+ throw new Error("entrypoint does not export a ThreatCrush module");
3027
+ }
3028
+ hosted.instance = instance;
3029
+ await instance.init(this.contextFor(hosted));
3030
+ await instance.start();
3031
+ hosted.status = "running";
3032
+ hosted.detail = `started from ${entrypoint}`;
3033
+ this.bus.announceModule(hosted.name, "running", hosted.detail);
3034
+ } catch (err) {
3035
+ hosted.status = "error";
3036
+ hosted.detail = String(err.message || err);
3037
+ this.bus.announceModule(hosted.name, "error", hosted.detail);
3038
+ }
3039
+ }
3040
+ installedEntrypoint(modulePath) {
3041
+ const packageJson = (0, import_node_path3.join)(modulePath, "package.json");
3042
+ const candidates = [];
3043
+ if ((0, import_node_fs6.existsSync)(packageJson)) {
3044
+ try {
3045
+ const pkg = JSON.parse((0, import_node_fs6.readFileSync)(packageJson, "utf-8"));
3046
+ if (pkg.main) candidates.push((0, import_node_path3.join)(modulePath, pkg.main));
2742
3047
  } catch {
2743
3048
  }
2744
3049
  }
3050
+ candidates.push((0, import_node_path3.join)(modulePath, "dist", "index.js"), (0, import_node_path3.join)(modulePath, "index.js"));
3051
+ return candidates.find((candidate) => (0, import_node_fs6.existsSync)(candidate)) || null;
3052
+ }
3053
+ isThreatCrushModule(value) {
3054
+ return Boolean(
3055
+ value && typeof value === "object" && typeof value.init === "function" && typeof value.start === "function" && typeof value.stop === "function"
3056
+ );
3057
+ }
3058
+ contextFor(hosted) {
3059
+ return {
3060
+ config: hosted.config || { enabled: true },
3061
+ logger: this.loggerFor(hosted.name),
3062
+ emit: (event) => this.bus.publish(event),
3063
+ subscribe: (eventType, handler) => {
3064
+ this.bus.on("event", (event) => {
3065
+ if (event.category === eventType || event.module === eventType) handler(event);
3066
+ });
3067
+ },
3068
+ alert: (alert) => {
3069
+ this.bus.emit("alert", alert.event || {
3070
+ timestamp: /* @__PURE__ */ new Date(),
3071
+ module: hosted.name,
3072
+ category: "system",
3073
+ severity: alert.severity,
3074
+ message: alert.title,
3075
+ details: alert.body ? { body: alert.body } : void 0
3076
+ });
3077
+ },
3078
+ getState: (key) => getModuleState(hosted.name, key),
3079
+ setState: (key, value) => setModuleState(hosted.name, key, value)
3080
+ };
3081
+ }
3082
+ loggerFor(moduleName) {
3083
+ return {
3084
+ debug: (msg, ...args) => console.debug(`[${moduleName}] ${msg}`, ...args),
3085
+ info: (msg, ...args) => console.info(`[${moduleName}] ${msg}`, ...args),
3086
+ warn: (msg, ...args) => console.warn(`[${moduleName}] ${msg}`, ...args),
3087
+ error: (msg, ...args) => console.error(`[${moduleName}] ${msg}`, ...args)
3088
+ };
2745
3089
  }
2746
3090
  };
2747
3091
 
@@ -2863,14 +3207,14 @@ function slackChannel(webhookUrl) {
2863
3207
  }
2864
3208
 
2865
3209
  // src/core/cli-config.ts
2866
- var import_node_fs6 = require("fs");
2867
- var import_node_path3 = require("path");
3210
+ var import_node_fs7 = require("fs");
3211
+ var import_node_path4 = require("path");
2868
3212
  var import_node_os2 = require("os");
2869
- var CLI_CONFIG_DIR = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".threatcrush");
2870
- var CLI_CONFIG_PATH = (0, import_node_path3.join)(CLI_CONFIG_DIR, "config.json");
3213
+ var CLI_CONFIG_DIR = (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".threatcrush");
3214
+ var CLI_CONFIG_PATH = (0, import_node_path4.join)(CLI_CONFIG_DIR, "config.json");
2871
3215
  function readCliConfig() {
2872
3216
  try {
2873
- return JSON.parse((0, import_node_fs6.readFileSync)(CLI_CONFIG_PATH, "utf-8"));
3217
+ return JSON.parse((0, import_node_fs7.readFileSync)(CLI_CONFIG_PATH, "utf-8"));
2874
3218
  } catch {
2875
3219
  return {};
2876
3220
  }
@@ -2889,8 +3233,8 @@ function authHeaders() {
2889
3233
  }
2890
3234
 
2891
3235
  // src/commands/scan.ts
2892
- var import_node_fs7 = require("fs");
2893
- var import_node_path4 = require("path");
3236
+ var import_node_fs8 = require("fs");
3237
+ var import_node_path5 = require("path");
2894
3238
 
2895
3239
  // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
2896
3240
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -3518,12 +3862,12 @@ async function runScan(targetPath) {
3518
3862
  function scanDirectory(basePath, currentPath, findings, onFile) {
3519
3863
  let entries;
3520
3864
  try {
3521
- entries = (0, import_node_fs7.readdirSync)(currentPath, { withFileTypes: true });
3865
+ entries = (0, import_node_fs8.readdirSync)(currentPath, { withFileTypes: true });
3522
3866
  } catch {
3523
3867
  return;
3524
3868
  }
3525
3869
  for (const entry of entries) {
3526
- const fullPath = (0, import_node_path4.join)(currentPath, entry.name);
3870
+ const fullPath = (0, import_node_path5.join)(currentPath, entry.name);
3527
3871
  if (entry.isDirectory()) {
3528
3872
  if (SKIP_DIRS.has(entry.name)) continue;
3529
3873
  scanDirectory(basePath, fullPath, findings, onFile);
@@ -3533,7 +3877,7 @@ function scanDirectory(basePath, currentPath, findings, onFile) {
3533
3877
  for (const mc of MISCONFIG_FILES) {
3534
3878
  if (entry.name === mc.pattern || entry.name.endsWith(mc.pattern)) {
3535
3879
  findings.push({
3536
- file: (0, import_node_path4.relative)(basePath, fullPath),
3880
+ file: (0, import_node_path5.relative)(basePath, fullPath),
3537
3881
  line: 0,
3538
3882
  type: "Sensitive File",
3539
3883
  severity: "high",
@@ -3542,10 +3886,10 @@ function scanDirectory(basePath, currentPath, findings, onFile) {
3542
3886
  });
3543
3887
  }
3544
3888
  }
3545
- const ext = (0, import_node_path4.extname)(entry.name).toLowerCase();
3889
+ const ext = (0, import_node_path5.extname)(entry.name).toLowerCase();
3546
3890
  if (!SCAN_EXTENSIONS.has(ext) && !entry.name.startsWith(".env")) continue;
3547
3891
  try {
3548
- const stat = (0, import_node_fs7.statSync)(fullPath);
3892
+ const stat = (0, import_node_fs8.statSync)(fullPath);
3549
3893
  if (stat.size > 1024 * 1024) continue;
3550
3894
  } catch {
3551
3895
  continue;
@@ -3553,7 +3897,7 @@ function scanDirectory(basePath, currentPath, findings, onFile) {
3553
3897
  onFile();
3554
3898
  let content;
3555
3899
  try {
3556
- content = (0, import_node_fs7.readFileSync)(fullPath, "utf-8");
3900
+ content = (0, import_node_fs8.readFileSync)(fullPath, "utf-8");
3557
3901
  } catch {
3558
3902
  continue;
3559
3903
  }
@@ -3565,7 +3909,7 @@ function scanDirectory(basePath, currentPath, findings, onFile) {
3565
3909
  pattern.pattern.lastIndex = 0;
3566
3910
  if (pattern.pattern.test(line)) {
3567
3911
  findings.push({
3568
- file: (0, import_node_path4.relative)(basePath, fullPath),
3912
+ file: (0, import_node_path5.relative)(basePath, fullPath),
3569
3913
  line: i + 1,
3570
3914
  type: pattern.name,
3571
3915
  severity: pattern.severity,
@@ -3843,52 +4187,6 @@ var RunsWorker = class {
3843
4187
  }
3844
4188
  };
3845
4189
 
3846
- // src/core/config.ts
3847
- var import_node_fs8 = require("fs");
3848
- var import_node_path5 = require("path");
3849
- var import_toml2 = __toESM(require_toml());
3850
- var DEFAULT_CONFIG_PATH = "/etc/threatcrush/threatcrushd.conf";
3851
- var DEFAULT_CONFDIR = "/etc/threatcrush/threatcrushd.conf.d";
3852
- var DEFAULT_CONFIG = {
3853
- daemon: {
3854
- pid_file: "/var/run/threatcrush/threatcrushd.pid",
3855
- log_level: "info",
3856
- log_file: "/var/log/threatcrush/threatcrushd.log",
3857
- state_db: "/var/lib/threatcrush/state.db"
3858
- },
3859
- api: {
3860
- enabled: true,
3861
- bind: "127.0.0.1:9393",
3862
- tls: false
3863
- },
3864
- alerts: {},
3865
- modules: {
3866
- auto_update: true,
3867
- update_interval: "24h",
3868
- module_dir: "/etc/threatcrush/modules",
3869
- config_dir: DEFAULT_CONFDIR
3870
- }
3871
- };
3872
- function loadConfig(configPath) {
3873
- const path = configPath || DEFAULT_CONFIG_PATH;
3874
- if (!(0, import_node_fs8.existsSync)(path)) {
3875
- return { ...DEFAULT_CONFIG };
3876
- }
3877
- try {
3878
- const raw = (0, import_node_fs8.readFileSync)(path, "utf-8");
3879
- const parsed = import_toml2.default.parse(raw);
3880
- return {
3881
- daemon: { ...DEFAULT_CONFIG.daemon, ...parsed.daemon },
3882
- api: { ...DEFAULT_CONFIG.api, ...parsed.api },
3883
- alerts: parsed.alerts || {},
3884
- modules: { ...DEFAULT_CONFIG.modules, ...parsed.modules },
3885
- license: parsed.license
3886
- };
3887
- } catch {
3888
- return { ...DEFAULT_CONFIG };
3889
- }
3890
- }
3891
-
3892
4190
  // src/core/telemetry.ts
3893
4191
  var ready = false;
3894
4192
  var sentry = null;