@profullstack/threatcrush 0.11.1 → 0.11.2
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 +536 -113
- package/dist/daemon.js.map +1 -1
- package/dist/index.js +819 -248
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3891,14 +3891,52 @@ var init_paths = __esm({
|
|
|
3891
3891
|
}
|
|
3892
3892
|
});
|
|
3893
3893
|
|
|
3894
|
+
// src/daemon/control-token.ts
|
|
3895
|
+
function issueControlToken() {
|
|
3896
|
+
const token = (0, import_node_crypto.randomBytes)(32).toString("hex");
|
|
3897
|
+
(0, import_node_fs2.writeFileSync)(CONTROL_TOKEN_FILE, token, { mode: 384 });
|
|
3898
|
+
try {
|
|
3899
|
+
(0, import_node_fs2.chmodSync)(CONTROL_TOKEN_FILE, 384);
|
|
3900
|
+
} catch {
|
|
3901
|
+
}
|
|
3902
|
+
return token;
|
|
3903
|
+
}
|
|
3904
|
+
function readControlToken() {
|
|
3905
|
+
if (!(0, import_node_fs2.existsSync)(CONTROL_TOKEN_FILE)) return null;
|
|
3906
|
+
try {
|
|
3907
|
+
const token = (0, import_node_fs2.readFileSync)(CONTROL_TOKEN_FILE, "utf-8").trim();
|
|
3908
|
+
return token || null;
|
|
3909
|
+
} catch {
|
|
3910
|
+
return null;
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
function tokensMatch(expected, provided) {
|
|
3914
|
+
if (typeof provided !== "string" || !provided) return false;
|
|
3915
|
+
const a = Buffer.from(expected);
|
|
3916
|
+
const b = Buffer.from(provided);
|
|
3917
|
+
return a.length === b.length && (0, import_node_crypto.timingSafeEqual)(a, b);
|
|
3918
|
+
}
|
|
3919
|
+
var import_node_crypto, import_node_fs2, import_node_path2, CONTROL_TOKEN_FILE;
|
|
3920
|
+
var init_control_token = __esm({
|
|
3921
|
+
"src/daemon/control-token.ts"() {
|
|
3922
|
+
"use strict";
|
|
3923
|
+
import_node_crypto = require("crypto");
|
|
3924
|
+
import_node_fs2 = require("fs");
|
|
3925
|
+
import_node_path2 = require("path");
|
|
3926
|
+
init_paths();
|
|
3927
|
+
CONTROL_TOKEN_FILE = (0, import_node_path2.join)(PATHS.runDir, "control.token");
|
|
3928
|
+
}
|
|
3929
|
+
});
|
|
3930
|
+
|
|
3894
3931
|
// src/core/ipc-client.ts
|
|
3895
|
-
var import_node_net,
|
|
3932
|
+
var import_node_net, import_node_fs3, IpcClient;
|
|
3896
3933
|
var init_ipc_client = __esm({
|
|
3897
3934
|
"src/core/ipc-client.ts"() {
|
|
3898
3935
|
"use strict";
|
|
3899
3936
|
import_node_net = require("net");
|
|
3900
|
-
|
|
3937
|
+
import_node_fs3 = require("fs");
|
|
3901
3938
|
init_paths();
|
|
3939
|
+
init_control_token();
|
|
3902
3940
|
IpcClient = class {
|
|
3903
3941
|
constructor(opts = {}) {
|
|
3904
3942
|
this.opts = opts;
|
|
@@ -3911,10 +3949,10 @@ var init_ipc_client = __esm({
|
|
|
3911
3949
|
pending = /* @__PURE__ */ new Map();
|
|
3912
3950
|
socketPath;
|
|
3913
3951
|
static isDaemonRunning(socketPath = resolveClientSocket()) {
|
|
3914
|
-
return (0,
|
|
3952
|
+
return (0, import_node_fs3.existsSync)(socketPath);
|
|
3915
3953
|
}
|
|
3916
3954
|
async connect(timeoutMs = 2e3) {
|
|
3917
|
-
if (!(0,
|
|
3955
|
+
if (!(0, import_node_fs3.existsSync)(this.socketPath)) {
|
|
3918
3956
|
throw new Error(`threatcrushd socket not found at ${this.socketPath}`);
|
|
3919
3957
|
}
|
|
3920
3958
|
return new Promise((resolve5, reject) => {
|
|
@@ -3975,7 +4013,7 @@ var init_ipc_client = __esm({
|
|
|
3975
4013
|
await this.request("subscribe", { channels });
|
|
3976
4014
|
}
|
|
3977
4015
|
async shutdown() {
|
|
3978
|
-
await this.request("shutdown");
|
|
4016
|
+
await this.request("shutdown", { token: readControlToken() });
|
|
3979
4017
|
}
|
|
3980
4018
|
onData(chunk) {
|
|
3981
4019
|
this.buffer += chunk;
|
|
@@ -4018,17 +4056,17 @@ var init_ipc_client = __esm({
|
|
|
4018
4056
|
// src/daemon/pidfile.ts
|
|
4019
4057
|
function writePidFile() {
|
|
4020
4058
|
ensureRuntimeDirs();
|
|
4021
|
-
(0,
|
|
4059
|
+
(0, import_node_fs4.writeFileSync)(PATHS.pidFile, String(process.pid), "utf-8");
|
|
4022
4060
|
}
|
|
4023
4061
|
function readPidFile() {
|
|
4024
|
-
if (!(0,
|
|
4025
|
-
const raw = (0,
|
|
4062
|
+
if (!(0, import_node_fs4.existsSync)(PATHS.pidFile)) return null;
|
|
4063
|
+
const raw = (0, import_node_fs4.readFileSync)(PATHS.pidFile, "utf-8").trim();
|
|
4026
4064
|
const pid = parseInt(raw, 10);
|
|
4027
4065
|
return Number.isFinite(pid) ? pid : null;
|
|
4028
4066
|
}
|
|
4029
4067
|
function removePidFile() {
|
|
4030
4068
|
try {
|
|
4031
|
-
if ((0,
|
|
4069
|
+
if ((0, import_node_fs4.existsSync)(PATHS.pidFile)) (0, import_node_fs4.unlinkSync)(PATHS.pidFile);
|
|
4032
4070
|
} catch {
|
|
4033
4071
|
}
|
|
4034
4072
|
}
|
|
@@ -4047,11 +4085,11 @@ function findRunningDaemon() {
|
|
|
4047
4085
|
if (pid) removePidFile();
|
|
4048
4086
|
return null;
|
|
4049
4087
|
}
|
|
4050
|
-
var
|
|
4088
|
+
var import_node_fs4;
|
|
4051
4089
|
var init_pidfile = __esm({
|
|
4052
4090
|
"src/daemon/pidfile.ts"() {
|
|
4053
4091
|
"use strict";
|
|
4054
|
-
|
|
4092
|
+
import_node_fs4 = require("fs");
|
|
4055
4093
|
init_paths();
|
|
4056
4094
|
}
|
|
4057
4095
|
});
|
|
@@ -8260,12 +8298,12 @@ var source_default = chalk;
|
|
|
8260
8298
|
// src/index.ts
|
|
8261
8299
|
var import_readline = __toESM(require("readline"));
|
|
8262
8300
|
var import_node_child_process10 = require("child_process");
|
|
8263
|
-
var
|
|
8264
|
-
var
|
|
8301
|
+
var import_node_fs31 = require("fs");
|
|
8302
|
+
var import_node_path21 = require("path");
|
|
8265
8303
|
var import_node_os8 = require("os");
|
|
8266
8304
|
|
|
8267
8305
|
// src/commands/monitor.ts
|
|
8268
|
-
var
|
|
8306
|
+
var import_node_fs5 = require("fs");
|
|
8269
8307
|
var import_node_readline = require("readline");
|
|
8270
8308
|
|
|
8271
8309
|
// src/core/logger.ts
|
|
@@ -8473,9 +8511,9 @@ async function monitorCommand(options) {
|
|
|
8473
8511
|
const unreadable = [];
|
|
8474
8512
|
for (const s of LOG_SOURCES) {
|
|
8475
8513
|
if (moduleFilter && !moduleFilter.includes(s.name)) continue;
|
|
8476
|
-
if (!(0,
|
|
8514
|
+
if (!(0, import_node_fs5.existsSync)(s.path)) continue;
|
|
8477
8515
|
try {
|
|
8478
|
-
(0,
|
|
8516
|
+
(0, import_node_fs5.accessSync)(s.path, import_node_fs5.constants.R_OK);
|
|
8479
8517
|
availableSources.push(s);
|
|
8480
8518
|
} catch {
|
|
8481
8519
|
unreadable.push(s);
|
|
@@ -8492,11 +8530,11 @@ async function monitorCommand(options) {
|
|
|
8492
8530
|
logger.warn("No readable log files found to monitor.");
|
|
8493
8531
|
logger.info("Available log paths checked:");
|
|
8494
8532
|
for (const s of LOG_SOURCES) {
|
|
8495
|
-
const exists = (0,
|
|
8533
|
+
const exists = (0, import_node_fs5.existsSync)(s.path);
|
|
8496
8534
|
let readable = false;
|
|
8497
8535
|
if (exists) {
|
|
8498
8536
|
try {
|
|
8499
|
-
(0,
|
|
8537
|
+
(0, import_node_fs5.accessSync)(s.path, import_node_fs5.constants.R_OK);
|
|
8500
8538
|
readable = true;
|
|
8501
8539
|
} catch {
|
|
8502
8540
|
readable = false;
|
|
@@ -8528,16 +8566,16 @@ async function monitorCommand(options) {
|
|
|
8528
8566
|
}
|
|
8529
8567
|
function tailLog(source) {
|
|
8530
8568
|
const { path, name, category } = source;
|
|
8531
|
-
const stat = (0,
|
|
8569
|
+
const stat = (0, import_node_fs5.statSync)(path);
|
|
8532
8570
|
let position = stat.size;
|
|
8533
8571
|
const checkForNewData = () => {
|
|
8534
8572
|
try {
|
|
8535
|
-
const currentStat = (0,
|
|
8573
|
+
const currentStat = (0, import_node_fs5.statSync)(path);
|
|
8536
8574
|
if (currentStat.size <= position) {
|
|
8537
8575
|
if (currentStat.size < position) position = 0;
|
|
8538
8576
|
return;
|
|
8539
8577
|
}
|
|
8540
|
-
const stream = (0,
|
|
8578
|
+
const stream = (0, import_node_fs5.createReadStream)(path, { start: position, encoding: "utf-8" });
|
|
8541
8579
|
stream.on("error", (err) => {
|
|
8542
8580
|
logger.warn(`stopped tailing ${path}: ${err.code || err.message}`);
|
|
8543
8581
|
position = currentStat.size;
|
|
@@ -8639,8 +8677,8 @@ async function runDemoMode() {
|
|
|
8639
8677
|
}
|
|
8640
8678
|
|
|
8641
8679
|
// src/commands/scan.ts
|
|
8642
|
-
var
|
|
8643
|
-
var
|
|
8680
|
+
var import_node_fs8 = require("fs");
|
|
8681
|
+
var import_node_path6 = require("path");
|
|
8644
8682
|
|
|
8645
8683
|
// ../../node_modules/.pnpm/ora@8.2.0/node_modules/ora/index.js
|
|
8646
8684
|
var import_node_process7 = __toESM(require("process"), 1);
|
|
@@ -9797,7 +9835,17 @@ var NODE_RULES = [
|
|
|
9797
9835
|
// arrangement of nearby lines that makes an echoed origin safe — except a
|
|
9798
9836
|
// membership test on the value, which is what the guard looks for.
|
|
9799
9837
|
inherent: true,
|
|
9800
|
-
guard: ORIGIN_ALLOWLIST_GUARD
|
|
9838
|
+
guard: ORIGIN_ALLOWLIST_GUARD,
|
|
9839
|
+
/**
|
|
9840
|
+
* Logging the origin is not reflecting it.
|
|
9841
|
+
*
|
|
9842
|
+
* `origin: request.headers.origin` is the CORS mistake *and* the ordinary
|
|
9843
|
+
* way to record who called — the two are character-for-character
|
|
9844
|
+
* identical, and only what encloses them differs. A response header goes
|
|
9845
|
+
* out to the browser; a log line goes to stdout, where it grants nobody
|
|
9846
|
+
* anything.
|
|
9847
|
+
*/
|
|
9848
|
+
enclosingCallGuard: /(?:^|\.)(?:log|debug|info|warn|error|trace|verbose|fatal)$/
|
|
9801
9849
|
},
|
|
9802
9850
|
// ── Server-side request forgery ──────────────────────────────────────────
|
|
9803
9851
|
{
|
|
@@ -10046,7 +10094,8 @@ var CODE_RULES = [
|
|
|
10046
10094
|
cwe: "CWE-78",
|
|
10047
10095
|
severity: "critical",
|
|
10048
10096
|
languages: ["javascript", "typescript"],
|
|
10049
|
-
pattern: /\b(?:exec|execSync|spawn|spawnSync)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+|[a-zA-Z_$][\w$]*\s*\+)
|
|
10097
|
+
pattern: /\b(?:exec|execSync|spawn|spawnSync)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+|[a-zA-Z_$][\w$]*\s*\+)/,
|
|
10098
|
+
constantInterpolationGuard: true
|
|
10050
10099
|
},
|
|
10051
10100
|
{
|
|
10052
10101
|
id: "py-shell-command-string",
|
|
@@ -10945,7 +10994,26 @@ var CODE_RULES = [
|
|
|
10945
10994
|
cwe: "CWE-346",
|
|
10946
10995
|
severity: "high",
|
|
10947
10996
|
languages: ["javascript", "typescript"],
|
|
10948
|
-
pattern: /\$\{[^}\n]*\breq(?:uest)?\s*\.\s*(?:headers\s*\.\s*host|hostname|host)\b|['"`]\s*\+\s*req(?:uest)?\s*\.\s*(?:headers\s*\.\s*host|hostname)\b
|
|
10997
|
+
pattern: /\$\{[^}\n]*\breq(?:uest)?\s*\.\s*(?:headers\s*\.\s*host|hostname|host)\b|['"`]\s*\+\s*req(?:uest)?\s*\.\s*(?:headers\s*\.\s*host|hostname)\b/,
|
|
10998
|
+
/**
|
|
10999
|
+
* Parsing the request's own URL is not building a link from the Host
|
|
11000
|
+
* header, even though it is spelled with one.
|
|
11001
|
+
*
|
|
11002
|
+
* const url = new URL(request.url, `http://${request.headers.host}`);
|
|
11003
|
+
* const token = url.searchParams.get('token');
|
|
11004
|
+
*
|
|
11005
|
+
* `request.url` on a Node server is a path — `/ws?token=…` — and `new URL`
|
|
11006
|
+
* refuses a relative input without a base. The base exists to satisfy the
|
|
11007
|
+
* parser and is thrown away; only the path and query are ever read. Every
|
|
11008
|
+
* Node HTTP handler that wants a query parameter is written this way, so
|
|
11009
|
+
* the rule fired on the framework idiom rather than on the defect.
|
|
11010
|
+
*
|
|
11011
|
+
* Narrow on purpose: the first argument must be `req.url` itself. The
|
|
11012
|
+
* dangerous shape passes a *path* the application chose —
|
|
11013
|
+
* `new URL('/reset?t=…', `https://${req.headers.host}`)` — and that is what
|
|
11014
|
+
* produces an attacker-controlled link. It does not match this guard.
|
|
11015
|
+
*/
|
|
11016
|
+
lineGuard: /\bnew\s+URL\s*\(\s*(?:req|request|ctx)(?:uest)?\s*\.\s*url\b\s*,/
|
|
10949
11017
|
},
|
|
10950
11018
|
// A recursive-merge prototype-pollution rule (`target[key] = source[key]`
|
|
10951
11019
|
// with no `__proto__` guard) was built and dropped. The bare copy-by-key is
|
|
@@ -11013,6 +11081,55 @@ function fileTextOf(lines) {
|
|
|
11013
11081
|
function withoutSingleQuoted(text) {
|
|
11014
11082
|
return text.replace(/'[^'\n]*'/g, "''");
|
|
11015
11083
|
}
|
|
11084
|
+
function calleeEndingAt(text, open) {
|
|
11085
|
+
let end = open;
|
|
11086
|
+
while (end > 0 && (text[end - 1] === " " || text[end - 1] === " ")) end -= 1;
|
|
11087
|
+
let start = end;
|
|
11088
|
+
while (start > 0 && /[\w$.]/.test(text[start - 1])) start -= 1;
|
|
11089
|
+
return text.slice(start, end);
|
|
11090
|
+
}
|
|
11091
|
+
function enclosingCallees(lines, index, back) {
|
|
11092
|
+
const before = lines.slice(Math.max(0, index - back), index).join("\n");
|
|
11093
|
+
const stack = [];
|
|
11094
|
+
let quote = null;
|
|
11095
|
+
for (let i = 0; i < before.length; i += 1) {
|
|
11096
|
+
const ch = before[i];
|
|
11097
|
+
if (quote) {
|
|
11098
|
+
if (ch === "\\") i += 1;
|
|
11099
|
+
else if (ch === quote) quote = null;
|
|
11100
|
+
continue;
|
|
11101
|
+
}
|
|
11102
|
+
if (ch === '"' || ch === "'" || ch === "`") quote = ch;
|
|
11103
|
+
else if (ch === "(") stack.push(calleeEndingAt(before, i));
|
|
11104
|
+
else if (ch === ")") stack.pop();
|
|
11105
|
+
}
|
|
11106
|
+
return stack;
|
|
11107
|
+
}
|
|
11108
|
+
function interpolations(line) {
|
|
11109
|
+
const found = [];
|
|
11110
|
+
for (let i = 0; ; ) {
|
|
11111
|
+
const start = line.indexOf("${", i);
|
|
11112
|
+
if (start === -1) break;
|
|
11113
|
+
const end = line.indexOf("}", start + 2);
|
|
11114
|
+
if (end === -1) return null;
|
|
11115
|
+
found.push(line.slice(start + 2, end).trim());
|
|
11116
|
+
i = end + 1;
|
|
11117
|
+
}
|
|
11118
|
+
return found.length > 0 ? found : null;
|
|
11119
|
+
}
|
|
11120
|
+
function isConstantString(name, fileText) {
|
|
11121
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11122
|
+
return new RegExp(
|
|
11123
|
+
`\\bconst\\s+${escaped}\\s*(?::[^=\\n]+)?=\\s*(?:'[^'\\n]*'|"[^"\\n]*"|\`[^\`$\\n]*\`)`
|
|
11124
|
+
).test(fileText);
|
|
11125
|
+
}
|
|
11126
|
+
function interpolationsAreConstant(line, fileText) {
|
|
11127
|
+
const found = interpolations(line);
|
|
11128
|
+
if (!found) return false;
|
|
11129
|
+
return found.every(
|
|
11130
|
+
(expr) => /^[A-Za-z_$][\w$]*$/.test(expr) && isConstantString(expr, fileText)
|
|
11131
|
+
);
|
|
11132
|
+
}
|
|
11016
11133
|
function evaluateRule(rule, ctx) {
|
|
11017
11134
|
if (rule.languages && !rule.languages.includes(ctx.language)) return null;
|
|
11018
11135
|
const line = ctx.lines[ctx.index] ?? "";
|
|
@@ -11024,6 +11141,13 @@ function evaluateRule(rule, ctx) {
|
|
|
11024
11141
|
const context = windowText(ctx.lines, ctx.index, back, forward, ctx.prose);
|
|
11025
11142
|
if (rule.requires && !rule.requires.test(context)) return null;
|
|
11026
11143
|
if (rule.lineGuard?.test(line)) return null;
|
|
11144
|
+
if (rule.enclosingCallGuard) {
|
|
11145
|
+
const callees = enclosingCallees(ctx.lines, ctx.index, back);
|
|
11146
|
+
if (callees.some((callee) => rule.enclosingCallGuard.test(callee))) return null;
|
|
11147
|
+
}
|
|
11148
|
+
if (rule.constantInterpolationGuard && interpolationsAreConstant(line, fileTextOf(ctx.lines))) {
|
|
11149
|
+
return null;
|
|
11150
|
+
}
|
|
11027
11151
|
const guard = rule.guard === void 0 ? GENERIC_GUARD : rule.guard;
|
|
11028
11152
|
if (guard && (guard.test(line) || guard.test(context))) return null;
|
|
11029
11153
|
const untrusted = untrustedPatternFor(ctx.language);
|
|
@@ -11606,7 +11730,8 @@ var SECRET_RULES = [
|
|
|
11606
11730
|
pattern: /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_.+/=-]*/,
|
|
11607
11731
|
severity: "medium",
|
|
11608
11732
|
cwe: "CWE-798",
|
|
11609
|
-
consequence: "Session or service identity until it expires \u2014 and committed tokens are usually long-lived."
|
|
11733
|
+
consequence: "Session or service identity until it expires \u2014 and committed tokens are usually long-lived.",
|
|
11734
|
+
keywordShaped: true
|
|
11610
11735
|
},
|
|
11611
11736
|
{
|
|
11612
11737
|
id: "secret-generic-api-key",
|
|
@@ -11617,7 +11742,8 @@ var SECRET_RULES = [
|
|
|
11617
11742
|
pattern: /(?:api[_-]?key|apikey|access[_-]?token|auth[_-]?token)\s*[=:]\s*['"]([A-Za-z0-9\-_.]{20,})['"]/i,
|
|
11618
11743
|
severity: "high",
|
|
11619
11744
|
cwe: "CWE-798",
|
|
11620
|
-
consequence: "Whatever the third-party service lets the key do, for as long as it stays valid."
|
|
11745
|
+
consequence: "Whatever the third-party service lets the key do, for as long as it stays valid.",
|
|
11746
|
+
keywordShaped: true
|
|
11621
11747
|
},
|
|
11622
11748
|
{
|
|
11623
11749
|
id: "secret-generic-credential",
|
|
@@ -11625,7 +11751,8 @@ var SECRET_RULES = [
|
|
|
11625
11751
|
pattern: /(?:secret|password|passwd|pwd|token)\s*[=:]\s*['"]([^'"\s]{8,})['"]/i,
|
|
11626
11752
|
severity: "high",
|
|
11627
11753
|
cwe: "CWE-798",
|
|
11628
|
-
consequence: "A password in source is a password in every clone, fork and CI cache of that source."
|
|
11754
|
+
consequence: "A password in source is a password in every clone, fork and CI cache of that source.",
|
|
11755
|
+
keywordShaped: true
|
|
11629
11756
|
},
|
|
11630
11757
|
{
|
|
11631
11758
|
id: "secret-hex-token",
|
|
@@ -11633,7 +11760,8 @@ var SECRET_RULES = [
|
|
|
11633
11760
|
pattern: /(?:token|key|secret|auth|signing)\w*\s*[=:]\s*['"]?([0-9a-f]{32,})['"]?/i,
|
|
11634
11761
|
severity: "medium",
|
|
11635
11762
|
cwe: "CWE-798",
|
|
11636
|
-
consequence: "Signing secrets and session keys are usually hex; a leaked one forges anything they sign."
|
|
11763
|
+
consequence: "Signing secrets and session keys are usually hex; a leaked one forges anything they sign.",
|
|
11764
|
+
keywordShaped: true
|
|
11637
11765
|
}
|
|
11638
11766
|
];
|
|
11639
11767
|
var KNOWN_PLACEHOLDERS = [
|
|
@@ -11664,6 +11792,54 @@ var KNOWN_PLACEHOLDERS = [
|
|
|
11664
11792
|
function isKnownPlaceholder(text) {
|
|
11665
11793
|
return KNOWN_PLACEHOLDERS.some((pattern) => pattern.test(text));
|
|
11666
11794
|
}
|
|
11795
|
+
function isVariableReference(value) {
|
|
11796
|
+
const trimmed = value.trim();
|
|
11797
|
+
const braced = /^\$\{\s*([A-Za-z_][\w.]*)\s*(?::?[-=+?]([\s\S]*))?\}$/.exec(trimmed);
|
|
11798
|
+
if (braced) {
|
|
11799
|
+
const fallback2 = braced[2];
|
|
11800
|
+
if (fallback2 === void 0 || fallback2.trim() === "") return true;
|
|
11801
|
+
return /^\$\{?[A-Za-z_][\w.]*\}?$/.test(fallback2.trim());
|
|
11802
|
+
}
|
|
11803
|
+
return /^\$[A-Za-z_]\w*$/.test(trimmed) || // $VAR
|
|
11804
|
+
/^\$\([\s\S]*\)$/.test(trimmed) || // $(command substitution)
|
|
11805
|
+
/^%[A-Za-z_]\w*%$/.test(trimmed) || // %VAR% on Windows
|
|
11806
|
+
/^\{\{[\s\S]*\}\}$/.test(trimmed) || // {{ template }}
|
|
11807
|
+
/^#\{[\s\S]*\}$/.test(trimmed) || // #{ruby}
|
|
11808
|
+
/^<%=?[\s\S]*%>$/.test(trimmed);
|
|
11809
|
+
}
|
|
11810
|
+
var FIXTURE_STEMS = [
|
|
11811
|
+
"test",
|
|
11812
|
+
"mock",
|
|
11813
|
+
"fake",
|
|
11814
|
+
"dummy",
|
|
11815
|
+
"stub",
|
|
11816
|
+
"sample",
|
|
11817
|
+
"example",
|
|
11818
|
+
"placeholder",
|
|
11819
|
+
"fixture",
|
|
11820
|
+
"invalid",
|
|
11821
|
+
"expired",
|
|
11822
|
+
"forged",
|
|
11823
|
+
"bogus",
|
|
11824
|
+
"notreal",
|
|
11825
|
+
"nonexistent",
|
|
11826
|
+
"changeme",
|
|
11827
|
+
"foobar",
|
|
11828
|
+
"lorem"
|
|
11829
|
+
];
|
|
11830
|
+
var KEY_NOISE = /* @__PURE__ */ new Set(["const", "this", "return", "await", "async", "expect", "value"]);
|
|
11831
|
+
function words(text) {
|
|
11832
|
+
return text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[^A-Za-z]+/g, " ").toLowerCase().split(" ").filter(Boolean);
|
|
11833
|
+
}
|
|
11834
|
+
function isTestFixtureValue(line, value) {
|
|
11835
|
+
const valueWords = words(value);
|
|
11836
|
+
if (valueWords.some((word) => FIXTURE_STEMS.some((stem) => word.startsWith(stem)))) return true;
|
|
11837
|
+
if (value.length > 48) return false;
|
|
11838
|
+
const valueAt = line.lastIndexOf(value);
|
|
11839
|
+
const key = valueAt === -1 ? line : line.slice(0, valueAt);
|
|
11840
|
+
const flattened = valueWords.join("");
|
|
11841
|
+
return words(key).filter((word) => word.length >= 4 && !KEY_NOISE.has(word)).some((word) => flattened.includes(word));
|
|
11842
|
+
}
|
|
11667
11843
|
function redactSecret(line) {
|
|
11668
11844
|
return line.replace(/([A-Za-z0-9+/=_.-]{12,})/g, (match) => {
|
|
11669
11845
|
if (match.length <= 12) return match;
|
|
@@ -11882,6 +12058,9 @@ function scanText(relativePath, text, language = languageOf(relativePath)) {
|
|
|
11882
12058
|
if (!match) continue;
|
|
11883
12059
|
if (isKnownPlaceholder(match[0])) continue;
|
|
11884
12060
|
if (isSuppressed(suppressions, index, rule.id)) continue;
|
|
12061
|
+
const value = match[1] ?? match[0];
|
|
12062
|
+
if (isVariableReference(value)) continue;
|
|
12063
|
+
if (inTests && rule.keywordShaped && isTestFixtureValue(line, value)) continue;
|
|
11885
12064
|
const marked = foreignCredentialMark(line);
|
|
11886
12065
|
findings.push({
|
|
11887
12066
|
ruleId: rule.id,
|
|
@@ -11965,8 +12144,8 @@ function meetsFailThreshold(findings, threshold) {
|
|
|
11965
12144
|
}
|
|
11966
12145
|
|
|
11967
12146
|
// ../../packages/scan/src/node/walk.ts
|
|
11968
|
-
var
|
|
11969
|
-
var
|
|
12147
|
+
var import_node_fs6 = require("fs");
|
|
12148
|
+
var import_node_path3 = require("path");
|
|
11970
12149
|
function compileExcludes(patterns) {
|
|
11971
12150
|
const matchers = patterns.map((p) => p.trim()).filter((p) => p.length > 0 && !p.startsWith("#")).map(compilePattern);
|
|
11972
12151
|
if (matchers.length === 0) return () => false;
|
|
@@ -12037,7 +12216,7 @@ function matchPrefix(parts, segs) {
|
|
|
12037
12216
|
}
|
|
12038
12217
|
function readIgnoreFile(root) {
|
|
12039
12218
|
try {
|
|
12040
|
-
return (0,
|
|
12219
|
+
return (0, import_node_fs6.readFileSync)((0, import_node_path3.join)(root, ".threatcrushignore"), "utf-8").split("\n");
|
|
12041
12220
|
} catch {
|
|
12042
12221
|
return [];
|
|
12043
12222
|
}
|
|
@@ -12053,16 +12232,16 @@ function scanPath(targetPath, options = {}) {
|
|
|
12053
12232
|
let excluded = 0;
|
|
12054
12233
|
const rootIsDirectory = (() => {
|
|
12055
12234
|
try {
|
|
12056
|
-
return (0,
|
|
12235
|
+
return (0, import_node_fs6.statSync)(targetPath).isDirectory();
|
|
12057
12236
|
} catch {
|
|
12058
12237
|
return true;
|
|
12059
12238
|
}
|
|
12060
12239
|
})();
|
|
12061
|
-
const walkRoot = rootIsDirectory ? targetPath : (0,
|
|
12240
|
+
const walkRoot = rootIsDirectory ? targetPath : (0, import_node_path3.dirname)(targetPath);
|
|
12062
12241
|
const isExcluded = compileExcludes([...options.exclude ?? [], ...readIgnoreFile(walkRoot)]);
|
|
12063
12242
|
const scanFile = (fullPath, filename) => {
|
|
12064
12243
|
const relativePath = toRelative(walkRoot, fullPath);
|
|
12065
|
-
const extension = (0,
|
|
12244
|
+
const extension = (0, import_node_path3.extname)(filename).toLowerCase();
|
|
12066
12245
|
const isManifest = filename === "package.json" || filename === "requirements.txt";
|
|
12067
12246
|
const scannable = SCAN_EXTENSIONS.has(extension) || filename.startsWith(".env");
|
|
12068
12247
|
const mayDeclareInterpreter = !scannable && !isManifest && extension === "";
|
|
@@ -12074,26 +12253,26 @@ function scanPath(targetPath, options = {}) {
|
|
|
12074
12253
|
let handle;
|
|
12075
12254
|
let declared = null;
|
|
12076
12255
|
try {
|
|
12077
|
-
handle = (0,
|
|
12256
|
+
handle = (0, import_node_fs6.openSync)(fullPath, "r");
|
|
12078
12257
|
} catch {
|
|
12079
12258
|
unreadable.push(relativePath);
|
|
12080
12259
|
return;
|
|
12081
12260
|
}
|
|
12082
12261
|
try {
|
|
12083
|
-
if ((0,
|
|
12262
|
+
if ((0, import_node_fs6.fstatSync)(handle).size > maxFileBytes) return;
|
|
12084
12263
|
if (mayDeclareInterpreter) {
|
|
12085
12264
|
const prefix = Buffer.alloc(128);
|
|
12086
|
-
const read = (0,
|
|
12265
|
+
const read = (0, import_node_fs6.readSync)(handle, prefix, 0, prefix.length, 0);
|
|
12087
12266
|
declared = languageOfShebang(prefix.subarray(0, read).toString("utf-8").split("\n", 1)[0] ?? "");
|
|
12088
12267
|
if (!declared) return;
|
|
12089
12268
|
}
|
|
12090
|
-
text = (0,
|
|
12269
|
+
text = (0, import_node_fs6.readFileSync)(handle, "utf-8");
|
|
12091
12270
|
} catch {
|
|
12092
12271
|
unreadable.push(relativePath);
|
|
12093
12272
|
return;
|
|
12094
12273
|
} finally {
|
|
12095
12274
|
try {
|
|
12096
|
-
(0,
|
|
12275
|
+
(0, import_node_fs6.closeSync)(handle);
|
|
12097
12276
|
} catch {
|
|
12098
12277
|
}
|
|
12099
12278
|
}
|
|
@@ -12111,13 +12290,13 @@ function scanPath(targetPath, options = {}) {
|
|
|
12111
12290
|
const walk = (currentPath) => {
|
|
12112
12291
|
let entries;
|
|
12113
12292
|
try {
|
|
12114
|
-
entries = (0,
|
|
12293
|
+
entries = (0, import_node_fs6.readdirSync)(currentPath, { withFileTypes: true });
|
|
12115
12294
|
} catch {
|
|
12116
12295
|
unreadable.push(toRelative(walkRoot, currentPath));
|
|
12117
12296
|
return;
|
|
12118
12297
|
}
|
|
12119
12298
|
for (const entry of entries) {
|
|
12120
|
-
const fullPath = (0,
|
|
12299
|
+
const fullPath = (0, import_node_path3.join)(currentPath, entry.name);
|
|
12121
12300
|
const relativePath = toRelative(walkRoot, fullPath);
|
|
12122
12301
|
if (entry.isDirectory()) {
|
|
12123
12302
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
@@ -12141,7 +12320,7 @@ function scanPath(targetPath, options = {}) {
|
|
|
12141
12320
|
} else if (isExcluded(toRelative(walkRoot, targetPath))) {
|
|
12142
12321
|
excluded += 1;
|
|
12143
12322
|
} else {
|
|
12144
|
-
scanFile(targetPath, (0,
|
|
12323
|
+
scanFile(targetPath, (0, import_node_path3.basename)(targetPath));
|
|
12145
12324
|
}
|
|
12146
12325
|
if (options.missingControls) findings.push(...controls.findings());
|
|
12147
12326
|
const filtered = allowed ? findings.filter((f) => allowed.has(f.category)) : findings;
|
|
@@ -12173,32 +12352,37 @@ function recordSensitiveFile(filename, relativePath, sink, fileFindings) {
|
|
|
12173
12352
|
}
|
|
12174
12353
|
}
|
|
12175
12354
|
function toRelative(base, target) {
|
|
12176
|
-
const rel = (0,
|
|
12177
|
-
return (rel === "" ? target : rel).split(
|
|
12355
|
+
const rel = (0, import_node_path3.relative)(base, target);
|
|
12356
|
+
return (rel === "" ? target : rel).split(import_node_path3.sep).join("/");
|
|
12178
12357
|
}
|
|
12179
12358
|
|
|
12180
12359
|
// ../../packages/scan/src/node/dependencies.ts
|
|
12181
|
-
var
|
|
12182
|
-
var
|
|
12183
|
-
var LOCKFILES = [
|
|
12184
|
-
{ file: "package-lock.json", ecosystem: "npm" },
|
|
12185
|
-
{ file: "pnpm-lock.yaml", ecosystem: "npm" },
|
|
12186
|
-
{ file: "yarn.lock", ecosystem: "npm" },
|
|
12187
|
-
{ file: "requirements.txt", ecosystem: "PyPI" },
|
|
12188
|
-
{ file: "Pipfile.lock", ecosystem: "PyPI" }
|
|
12189
|
-
];
|
|
12360
|
+
var import_node_fs7 = require("fs");
|
|
12361
|
+
var import_node_path4 = require("path");
|
|
12190
12362
|
var MAX_DEPS_PER_LOCKFILE = 50;
|
|
12191
12363
|
async function scanDependencies(targetPath) {
|
|
12192
12364
|
const findings = [];
|
|
12193
|
-
for (const { file, ecosystem } of LOCKFILES) {
|
|
12194
|
-
const lockPath = (0,
|
|
12195
|
-
if (!(0,
|
|
12365
|
+
for (const { file, ecosystem, parse } of LOCKFILES) {
|
|
12366
|
+
const lockPath = (0, import_node_path4.join)(targetPath, file);
|
|
12367
|
+
if (!(0, import_node_fs7.existsSync)(lockPath)) continue;
|
|
12196
12368
|
let deps;
|
|
12197
12369
|
try {
|
|
12198
|
-
deps =
|
|
12370
|
+
deps = dedupe(parse((0, import_node_fs7.readFileSync)(lockPath, "utf-8")));
|
|
12199
12371
|
} catch {
|
|
12200
12372
|
continue;
|
|
12201
12373
|
}
|
|
12374
|
+
if (deps.length === 0) {
|
|
12375
|
+
findings.push(incompleteFinding(file, "No dependencies could be read from this lockfile."));
|
|
12376
|
+
continue;
|
|
12377
|
+
}
|
|
12378
|
+
if (deps.length > MAX_DEPS_PER_LOCKFILE) {
|
|
12379
|
+
findings.push(
|
|
12380
|
+
incompleteFinding(
|
|
12381
|
+
file,
|
|
12382
|
+
`Only the first ${MAX_DEPS_PER_LOCKFILE} of ${deps.length} locked packages were checked against OSV.`
|
|
12383
|
+
)
|
|
12384
|
+
);
|
|
12385
|
+
}
|
|
12202
12386
|
for (const dep of deps.slice(0, MAX_DEPS_PER_LOCKFILE)) {
|
|
12203
12387
|
let vulns;
|
|
12204
12388
|
try {
|
|
@@ -12225,6 +12409,20 @@ async function scanDependencies(targetPath) {
|
|
|
12225
12409
|
}
|
|
12226
12410
|
return findings;
|
|
12227
12411
|
}
|
|
12412
|
+
function incompleteFinding(file, message) {
|
|
12413
|
+
return {
|
|
12414
|
+
ruleId: "dependency-scan-incomplete",
|
|
12415
|
+
title: "Dependency scan incomplete",
|
|
12416
|
+
file,
|
|
12417
|
+
line: 1,
|
|
12418
|
+
severity: "low",
|
|
12419
|
+
confidence: "evidence",
|
|
12420
|
+
message,
|
|
12421
|
+
consequence: "Advisories affecting the unchecked packages would not appear in this report.",
|
|
12422
|
+
excerpt: file,
|
|
12423
|
+
category: "dependency"
|
|
12424
|
+
};
|
|
12425
|
+
}
|
|
12228
12426
|
function severityFromCvss(score) {
|
|
12229
12427
|
if (!score) return "medium";
|
|
12230
12428
|
const value = Number.parseFloat(score);
|
|
@@ -12234,26 +12432,131 @@ function severityFromCvss(score) {
|
|
|
12234
12432
|
if (value >= 4) return "medium";
|
|
12235
12433
|
return "low";
|
|
12236
12434
|
}
|
|
12237
|
-
function
|
|
12435
|
+
function dedupe(deps) {
|
|
12436
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12437
|
+
const unique = [];
|
|
12438
|
+
for (const dep of deps) {
|
|
12439
|
+
const key = `${dep.name}@${dep.version}`;
|
|
12440
|
+
if (seen.has(key)) continue;
|
|
12441
|
+
seen.add(key);
|
|
12442
|
+
unique.push(dep);
|
|
12443
|
+
}
|
|
12444
|
+
return unique;
|
|
12445
|
+
}
|
|
12446
|
+
function splitNameVersion(spec) {
|
|
12447
|
+
const at = spec.lastIndexOf("@");
|
|
12448
|
+
if (at <= 0) return null;
|
|
12449
|
+
const name = spec.slice(0, at);
|
|
12450
|
+
const version = spec.slice(at + 1);
|
|
12451
|
+
if (!name || !version) return null;
|
|
12452
|
+
return { name, version };
|
|
12453
|
+
}
|
|
12454
|
+
function exactVersion(raw) {
|
|
12455
|
+
const version = raw.trim().replace(/^[=v]+/, "");
|
|
12456
|
+
return /^[0-9][0-9a-zA-Z.+-]*$/.test(version) ? version : null;
|
|
12457
|
+
}
|
|
12458
|
+
function parsePackageLock(content) {
|
|
12459
|
+
const lock = JSON.parse(content);
|
|
12460
|
+
const packages = lock.packages ?? lock.dependencies ?? {};
|
|
12461
|
+
const deps = [];
|
|
12462
|
+
for (const [key, value] of Object.entries(packages)) {
|
|
12463
|
+
const name = key.replace(/^.*node_modules\//, "");
|
|
12464
|
+
const version = value?.version;
|
|
12465
|
+
if (name && version && !name.startsWith(".")) deps.push({ name, version });
|
|
12466
|
+
}
|
|
12467
|
+
return deps;
|
|
12468
|
+
}
|
|
12469
|
+
function parsePnpmLock(content) {
|
|
12470
|
+
const deps = [];
|
|
12471
|
+
let inPackages = false;
|
|
12472
|
+
for (const line of content.split("\n")) {
|
|
12473
|
+
if (/^[a-zA-Z]/.test(line)) {
|
|
12474
|
+
inPackages = line.startsWith("packages:");
|
|
12475
|
+
continue;
|
|
12476
|
+
}
|
|
12477
|
+
if (!inPackages) continue;
|
|
12478
|
+
const match = /^ {2}(?! )(.+):\s*$/.exec(line);
|
|
12479
|
+
if (!match?.[1]) continue;
|
|
12480
|
+
let key = match[1].trim().replace(/^['"]|['"]$/g, "");
|
|
12481
|
+
key = key.replace(/^\//, "");
|
|
12482
|
+
key = key.replace(/\(.*$/, "");
|
|
12483
|
+
let name;
|
|
12484
|
+
let rawVersion;
|
|
12485
|
+
const slashed = /^(@?[^@]+)\/([0-9][^/]*)$/.exec(key);
|
|
12486
|
+
if (slashed?.[1] && slashed[2]) {
|
|
12487
|
+
name = slashed[1];
|
|
12488
|
+
rawVersion = slashed[2];
|
|
12489
|
+
} else {
|
|
12490
|
+
const dep = splitNameVersion(key);
|
|
12491
|
+
if (!dep) continue;
|
|
12492
|
+
name = dep.name;
|
|
12493
|
+
rawVersion = dep.version;
|
|
12494
|
+
}
|
|
12495
|
+
const version = exactVersion(rawVersion.replace(/_.*$/, ""));
|
|
12496
|
+
if (version) deps.push({ name, version });
|
|
12497
|
+
}
|
|
12498
|
+
return deps;
|
|
12499
|
+
}
|
|
12500
|
+
function parseYarnLock(content) {
|
|
12238
12501
|
const deps = [];
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
const
|
|
12245
|
-
|
|
12502
|
+
let pendingName = null;
|
|
12503
|
+
for (const line of content.split("\n")) {
|
|
12504
|
+
if (line.startsWith("#") || line.trim() === "") continue;
|
|
12505
|
+
if (!/^\s/.test(line)) {
|
|
12506
|
+
pendingName = null;
|
|
12507
|
+
const header = line.replace(/:\s*$/, "");
|
|
12508
|
+
const first = header.split(",")[0]?.trim().replace(/^['"]|['"]$/g, "");
|
|
12509
|
+
if (!first) continue;
|
|
12510
|
+
if (!first.includes("@") || first === "__metadata") continue;
|
|
12511
|
+
if (/@(?:workspace|file|link|portal|exec|patch):/.test(first)) continue;
|
|
12512
|
+
const dep = splitNameVersion(first.replace(/@npm:/, "@"));
|
|
12513
|
+
if (dep) pendingName = dep.name;
|
|
12514
|
+
continue;
|
|
12246
12515
|
}
|
|
12247
|
-
|
|
12516
|
+
if (!pendingName) continue;
|
|
12517
|
+
const version = /^\s+version:?\s+["']?([^"'\s]+)["']?\s*$/.exec(line);
|
|
12518
|
+
if (!version?.[1]) continue;
|
|
12519
|
+
const exact = exactVersion(version[1]);
|
|
12520
|
+
if (exact) deps.push({ name: pendingName, version: exact });
|
|
12521
|
+
pendingName = null;
|
|
12248
12522
|
}
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12523
|
+
return deps;
|
|
12524
|
+
}
|
|
12525
|
+
function parsePipfileLock(content) {
|
|
12526
|
+
const lock = JSON.parse(content);
|
|
12527
|
+
const deps = [];
|
|
12528
|
+
for (const section of ["default", "develop"]) {
|
|
12529
|
+
const packages = lock[section];
|
|
12530
|
+
if (!packages || typeof packages !== "object") continue;
|
|
12531
|
+
for (const [name, value] of Object.entries(packages)) {
|
|
12532
|
+
const version = exactVersion(String(value?.version ?? "").replace(/^==/, ""));
|
|
12533
|
+
if (name && version) deps.push({ name, version });
|
|
12253
12534
|
}
|
|
12254
12535
|
}
|
|
12255
12536
|
return deps;
|
|
12256
12537
|
}
|
|
12538
|
+
function parseRequirementsTxt(content) {
|
|
12539
|
+
const deps = [];
|
|
12540
|
+
for (const raw of content.split("\n")) {
|
|
12541
|
+
const line = raw.split("#")[0]?.split(";")[0]?.trim();
|
|
12542
|
+
if (!line || line.startsWith("-")) continue;
|
|
12543
|
+
const match = /^([a-zA-Z0-9._-]+)\s*(?:\[[^\]]*\])?\s*==\s*([^\s,]+)/.exec(line);
|
|
12544
|
+
if (!match?.[1] || !match[2]) continue;
|
|
12545
|
+
const version = exactVersion(match[2]);
|
|
12546
|
+
if (version) deps.push({ name: match[1], version });
|
|
12547
|
+
}
|
|
12548
|
+
return deps;
|
|
12549
|
+
}
|
|
12550
|
+
var LOCKFILES = [
|
|
12551
|
+
{ file: "package-lock.json", ecosystem: "npm", parse: parsePackageLock },
|
|
12552
|
+
{ file: "pnpm-lock.yaml", ecosystem: "npm", parse: parsePnpmLock },
|
|
12553
|
+
{ file: "yarn.lock", ecosystem: "npm", parse: parseYarnLock },
|
|
12554
|
+
{ file: "requirements.txt", ecosystem: "PyPI", parse: parseRequirementsTxt },
|
|
12555
|
+
{ file: "Pipfile.lock", ecosystem: "PyPI", parse: parsePipfileLock }
|
|
12556
|
+
];
|
|
12557
|
+
var LOCKFILE_PARSERS = Object.fromEntries(
|
|
12558
|
+
LOCKFILES.map((entry) => [entry.file, entry.parse])
|
|
12559
|
+
);
|
|
12257
12560
|
function isValidPackageName(name) {
|
|
12258
12561
|
return /^[@a-zA-Z0-9_.\-/]{1,214}$/.test(name);
|
|
12259
12562
|
}
|
|
@@ -12278,12 +12581,12 @@ async function queryOsv(name, version, ecosystem) {
|
|
|
12278
12581
|
}
|
|
12279
12582
|
|
|
12280
12583
|
// ../../packages/scan/src/node/sarif.ts
|
|
12281
|
-
var
|
|
12282
|
-
var
|
|
12584
|
+
var import_node_crypto2 = require("crypto");
|
|
12585
|
+
var import_node_path5 = require("path");
|
|
12283
12586
|
var FINGERPRINT_KEY = "threatcrush/contentHash/v1";
|
|
12284
12587
|
function fingerprintOf(finding) {
|
|
12285
12588
|
const content = finding.excerpt.replace(/\s+/g, " ").trim();
|
|
12286
|
-
return (0,
|
|
12589
|
+
return (0, import_node_crypto2.createHash)("sha256").update(`${finding.ruleId}
|
|
12287
12590
|
${finding.file}
|
|
12288
12591
|
${content}`).digest("hex").slice(0, 32);
|
|
12289
12592
|
}
|
|
@@ -12317,11 +12620,11 @@ function securitySeverity(severity) {
|
|
|
12317
12620
|
}
|
|
12318
12621
|
}
|
|
12319
12622
|
function toArtifactUri(filePath, base, prefix = "", root = base) {
|
|
12320
|
-
const absolute = (0,
|
|
12321
|
-
const relativePath = (0,
|
|
12623
|
+
const absolute = (0, import_node_path5.isAbsolute)(filePath) ? filePath : (0, import_node_path5.resolve)(root, filePath);
|
|
12624
|
+
const relativePath = (0, import_node_path5.relative)(base, absolute);
|
|
12322
12625
|
const escapedOut = relativePath.startsWith("..") || relativePath === "";
|
|
12323
12626
|
const chosen = escapedOut ? absolute : relativePath;
|
|
12324
|
-
const posix = chosen.split(
|
|
12627
|
+
const posix = chosen.split(import_node_path5.sep).join("/").replace(/^\.\//, "");
|
|
12325
12628
|
if (!prefix || escapedOut) return posix;
|
|
12326
12629
|
const trimmed = prefix.replace(/^\/+|\/+$/g, "");
|
|
12327
12630
|
return trimmed ? `${trimmed}/${posix}` : posix;
|
|
@@ -12411,11 +12714,11 @@ ${finding.consequence}` : `**${finding.title}**`
|
|
|
12411
12714
|
// src/commands/scan.ts
|
|
12412
12715
|
function readVersion() {
|
|
12413
12716
|
for (const candidate of [
|
|
12414
|
-
(0,
|
|
12415
|
-
(0,
|
|
12717
|
+
(0, import_node_path6.join)(__dirname, "..", "package.json"),
|
|
12718
|
+
(0, import_node_path6.join)(__dirname, "..", "..", "package.json")
|
|
12416
12719
|
]) {
|
|
12417
12720
|
try {
|
|
12418
|
-
return JSON.parse((0,
|
|
12721
|
+
return JSON.parse((0, import_node_fs8.readFileSync)(candidate, "utf-8")).version ?? "0.0.0";
|
|
12419
12722
|
} catch {
|
|
12420
12723
|
}
|
|
12421
12724
|
}
|
|
@@ -12482,7 +12785,7 @@ async function scanCommand(targetPath, options = {}) {
|
|
|
12482
12785
|
const say = machineReadable ? (line) => process.stderr.write(`${line}
|
|
12483
12786
|
`) : (line) => process.stdout.write(`${line}
|
|
12484
12787
|
`);
|
|
12485
|
-
if (!(0,
|
|
12788
|
+
if (!(0, import_node_fs8.existsSync)(targetPath)) {
|
|
12486
12789
|
say(source_default.red(`Scan target does not exist: ${targetPath}`));
|
|
12487
12790
|
process.exitCode = 2;
|
|
12488
12791
|
return failedResult(targetPath, `no such path: ${targetPath}`);
|
|
@@ -12578,7 +12881,7 @@ function emitMachineReadable(format, outcome, targetPath, options, say) {
|
|
|
12578
12881
|
// and it fails silently. `--path-prefix` covers the remaining case:
|
|
12579
12882
|
// a scan run from inside the subdirectory it is scanning.
|
|
12580
12883
|
base: process.cwd(),
|
|
12581
|
-
root: (0,
|
|
12884
|
+
root: (0, import_node_path6.resolve)(outcome.root)
|
|
12582
12885
|
}) : {
|
|
12583
12886
|
tool: "threatcrush",
|
|
12584
12887
|
version: PKG_VERSION,
|
|
@@ -12592,8 +12895,8 @@ function emitMachineReadable(format, outcome, targetPath, options, say) {
|
|
|
12592
12895
|
const serialized = `${JSON.stringify(payload, null, 2)}
|
|
12593
12896
|
`;
|
|
12594
12897
|
if (options.output) {
|
|
12595
|
-
(0,
|
|
12596
|
-
(0,
|
|
12898
|
+
(0, import_node_fs8.mkdirSync)((0, import_node_path6.dirname)((0, import_node_path6.resolve)(options.output)), { recursive: true });
|
|
12899
|
+
(0, import_node_fs8.writeFileSync)(options.output, serialized, "utf-8");
|
|
12597
12900
|
say(
|
|
12598
12901
|
source_default.gray(
|
|
12599
12902
|
` ${format.toUpperCase()} written to ${options.output} (${outcome.findings.length} finding(s))`
|
|
@@ -12645,13 +12948,13 @@ function printHuman(outcome) {
|
|
|
12645
12948
|
}
|
|
12646
12949
|
|
|
12647
12950
|
// src/commands/init.ts
|
|
12648
|
-
var
|
|
12951
|
+
var import_node_fs11 = require("fs");
|
|
12649
12952
|
var import_node_child_process = require("child_process");
|
|
12650
12953
|
var import_node_readline3 = __toESM(require("readline"));
|
|
12651
12954
|
|
|
12652
12955
|
// src/core/config.ts
|
|
12653
|
-
var
|
|
12654
|
-
var
|
|
12956
|
+
var import_node_fs9 = require("fs");
|
|
12957
|
+
var import_node_path7 = require("path");
|
|
12655
12958
|
var import_toml = __toESM(require_toml());
|
|
12656
12959
|
var DEFAULT_CONFIG_PATH = "/etc/threatcrush/threatcrushd.conf";
|
|
12657
12960
|
var DEFAULT_CONFDIR = "/etc/threatcrush/threatcrushd.conf.d";
|
|
@@ -12677,11 +12980,11 @@ var DEFAULT_CONFIG = {
|
|
|
12677
12980
|
};
|
|
12678
12981
|
function loadConfig(configPath) {
|
|
12679
12982
|
const path = configPath || DEFAULT_CONFIG_PATH;
|
|
12680
|
-
if (!(0,
|
|
12983
|
+
if (!(0, import_node_fs9.existsSync)(path)) {
|
|
12681
12984
|
return { ...DEFAULT_CONFIG };
|
|
12682
12985
|
}
|
|
12683
12986
|
try {
|
|
12684
|
-
const raw = (0,
|
|
12987
|
+
const raw = (0, import_node_fs9.readFileSync)(path, "utf-8");
|
|
12685
12988
|
const parsed = import_toml.default.parse(raw);
|
|
12686
12989
|
return {
|
|
12687
12990
|
daemon: { ...DEFAULT_CONFIG.daemon, ...parsed.daemon },
|
|
@@ -12697,13 +13000,13 @@ function loadConfig(configPath) {
|
|
|
12697
13000
|
function loadModuleConfigs(confDir) {
|
|
12698
13001
|
const dir = confDir || DEFAULT_CONFDIR;
|
|
12699
13002
|
const configs = /* @__PURE__ */ new Map();
|
|
12700
|
-
if (!(0,
|
|
13003
|
+
if (!(0, import_node_fs9.existsSync)(dir)) {
|
|
12701
13004
|
return configs;
|
|
12702
13005
|
}
|
|
12703
|
-
const files = (0,
|
|
13006
|
+
const files = (0, import_node_fs9.readdirSync)(dir).filter((f) => f.endsWith(".conf"));
|
|
12704
13007
|
for (const file of files) {
|
|
12705
13008
|
try {
|
|
12706
|
-
const raw = (0,
|
|
13009
|
+
const raw = (0, import_node_fs9.readFileSync)((0, import_node_path7.join)(dir, file), "utf-8");
|
|
12707
13010
|
const parsed = import_toml.default.parse(raw);
|
|
12708
13011
|
for (const [name, config] of Object.entries(parsed)) {
|
|
12709
13012
|
configs.set(name, config);
|
|
@@ -12732,23 +13035,23 @@ function generateModuleConfig(moduleName, defaults = {}) {
|
|
|
12732
13035
|
}
|
|
12733
13036
|
|
|
12734
13037
|
// src/core/cli-config.ts
|
|
12735
|
-
var
|
|
12736
|
-
var
|
|
13038
|
+
var import_node_fs10 = require("fs");
|
|
13039
|
+
var import_node_path8 = require("path");
|
|
12737
13040
|
var import_node_os4 = require("os");
|
|
12738
|
-
var CLI_CONFIG_DIR = (0,
|
|
12739
|
-
var CLI_CONFIG_PATH = (0,
|
|
13041
|
+
var CLI_CONFIG_DIR = (0, import_node_path8.join)((0, import_node_os4.homedir)(), ".threatcrush");
|
|
13042
|
+
var CLI_CONFIG_PATH = (0, import_node_path8.join)(CLI_CONFIG_DIR, "config.json");
|
|
12740
13043
|
function readCliConfig() {
|
|
12741
13044
|
try {
|
|
12742
|
-
return JSON.parse((0,
|
|
13045
|
+
return JSON.parse((0, import_node_fs10.readFileSync)(CLI_CONFIG_PATH, "utf-8"));
|
|
12743
13046
|
} catch {
|
|
12744
13047
|
return {};
|
|
12745
13048
|
}
|
|
12746
13049
|
}
|
|
12747
13050
|
function writeCliConfig(config) {
|
|
12748
|
-
if (!(0,
|
|
12749
|
-
(0,
|
|
13051
|
+
if (!(0, import_node_fs10.existsSync)(CLI_CONFIG_DIR)) (0, import_node_fs10.mkdirSync)(CLI_CONFIG_DIR, { recursive: true });
|
|
13052
|
+
(0, import_node_fs10.writeFileSync)(CLI_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
12750
13053
|
try {
|
|
12751
|
-
(0,
|
|
13054
|
+
(0, import_node_fs10.chmodSync)(CLI_CONFIG_PATH, 384);
|
|
12752
13055
|
} catch {
|
|
12753
13056
|
}
|
|
12754
13057
|
}
|
|
@@ -12993,7 +13296,7 @@ function binaryExists(name) {
|
|
|
12993
13296
|
}
|
|
12994
13297
|
}
|
|
12995
13298
|
function findLogPath(paths) {
|
|
12996
|
-
return paths.find((p) => (0,
|
|
13299
|
+
return paths.find((p) => (0, import_node_fs11.existsSync)(p));
|
|
12997
13300
|
}
|
|
12998
13301
|
async function promptYesNo(question, fallback2) {
|
|
12999
13302
|
const rl = import_node_readline3.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -13109,11 +13412,11 @@ async function initCommand() {
|
|
|
13109
13412
|
}
|
|
13110
13413
|
} else {
|
|
13111
13414
|
const spinner2 = ora({ text: "Writing configuration files...", color: "green" }).start();
|
|
13112
|
-
(0,
|
|
13113
|
-
(0,
|
|
13114
|
-
(0,
|
|
13415
|
+
(0, import_node_fs11.mkdirSync)(confDDir, { recursive: true });
|
|
13416
|
+
(0, import_node_fs11.mkdirSync)("/var/log/threatcrush", { recursive: true });
|
|
13417
|
+
(0, import_node_fs11.mkdirSync)("/var/lib/threatcrush", { recursive: true });
|
|
13115
13418
|
const mainConfig = generateDefaultConfig(detected.map((d) => d.name));
|
|
13116
|
-
(0,
|
|
13419
|
+
(0, import_node_fs11.writeFileSync)(`${configDir}/threatcrushd.conf`, mainConfig);
|
|
13117
13420
|
for (const svc of detected) {
|
|
13118
13421
|
const svcDef = SERVICES_TO_DETECT.find((s) => s.name === svc.name);
|
|
13119
13422
|
if (!svcDef) continue;
|
|
@@ -13122,7 +13425,7 @@ async function initCommand() {
|
|
|
13122
13425
|
...svcDef.moduleConfig,
|
|
13123
13426
|
log_path: svc.logPath || svcDef.logPaths[0]
|
|
13124
13427
|
});
|
|
13125
|
-
(0,
|
|
13428
|
+
(0, import_node_fs11.writeFileSync)(`${confDDir}/${modName}.conf`, modConfig);
|
|
13126
13429
|
}
|
|
13127
13430
|
spinner2.succeed("Configuration written successfully");
|
|
13128
13431
|
console.log();
|
|
@@ -13140,8 +13443,8 @@ async function initCommand() {
|
|
|
13140
13443
|
}
|
|
13141
13444
|
function checkWriteAccess(dir) {
|
|
13142
13445
|
try {
|
|
13143
|
-
if (!(0,
|
|
13144
|
-
(0,
|
|
13446
|
+
if (!(0, import_node_fs11.existsSync)(dir)) {
|
|
13447
|
+
(0, import_node_fs11.mkdirSync)(dir, { recursive: true });
|
|
13145
13448
|
}
|
|
13146
13449
|
return true;
|
|
13147
13450
|
} catch {
|
|
@@ -13150,8 +13453,8 @@ function checkWriteAccess(dir) {
|
|
|
13150
13453
|
}
|
|
13151
13454
|
|
|
13152
13455
|
// src/core/module-loader.ts
|
|
13153
|
-
var
|
|
13154
|
-
var
|
|
13456
|
+
var import_node_fs12 = require("fs");
|
|
13457
|
+
var import_node_path9 = require("path");
|
|
13155
13458
|
var import_toml2 = __toESM(require_toml());
|
|
13156
13459
|
init_paths();
|
|
13157
13460
|
function discoverModules(moduleDir, confDir) {
|
|
@@ -13159,22 +13462,22 @@ function discoverModules(moduleDir, confDir) {
|
|
|
13159
13462
|
const configs = loadModuleConfigs(confDir || PATHS.confD);
|
|
13160
13463
|
const searchPaths = [
|
|
13161
13464
|
moduleDir || PATHS.moduleDir,
|
|
13162
|
-
(0,
|
|
13465
|
+
(0, import_node_path9.resolve)(process.cwd(), "modules")
|
|
13163
13466
|
];
|
|
13164
|
-
const builtinDir = (0,
|
|
13165
|
-
if ((0,
|
|
13467
|
+
const builtinDir = (0, import_node_path9.resolve)(__dirname || ".", "..", "modules");
|
|
13468
|
+
if ((0, import_node_fs12.existsSync)(builtinDir)) {
|
|
13166
13469
|
searchPaths.push(builtinDir);
|
|
13167
13470
|
}
|
|
13168
13471
|
for (const basePath of searchPaths) {
|
|
13169
|
-
if (!(0,
|
|
13170
|
-
const entries = (0,
|
|
13472
|
+
if (!(0, import_node_fs12.existsSync)(basePath)) continue;
|
|
13473
|
+
const entries = (0, import_node_fs12.readdirSync)(basePath, { withFileTypes: true });
|
|
13171
13474
|
for (const entry of entries) {
|
|
13172
13475
|
if (!entry.isDirectory()) continue;
|
|
13173
|
-
const modPath = (0,
|
|
13174
|
-
const manifestPath = (0,
|
|
13175
|
-
if (!(0,
|
|
13476
|
+
const modPath = (0, import_node_path9.join)(basePath, entry.name);
|
|
13477
|
+
const manifestPath = (0, import_node_path9.join)(modPath, "mod.toml");
|
|
13478
|
+
if (!(0, import_node_fs12.existsSync)(manifestPath)) continue;
|
|
13176
13479
|
try {
|
|
13177
|
-
const raw = (0,
|
|
13480
|
+
const raw = (0, import_node_fs12.readFileSync)(manifestPath, "utf-8");
|
|
13178
13481
|
const manifest = import_toml2.default.parse(raw);
|
|
13179
13482
|
const config = configs.get(manifest.module.name) || { enabled: true };
|
|
13180
13483
|
modules.push({
|
|
@@ -13293,11 +13596,163 @@ function formatUptime(seconds) {
|
|
|
13293
13596
|
|
|
13294
13597
|
// src/commands/modules.ts
|
|
13295
13598
|
var import_node_child_process2 = require("child_process");
|
|
13296
|
-
var
|
|
13297
|
-
var
|
|
13599
|
+
var import_node_fs14 = require("fs");
|
|
13600
|
+
var import_node_path11 = require("path");
|
|
13298
13601
|
var import_toml3 = __toESM(require_toml());
|
|
13299
13602
|
init_paths();
|
|
13300
13603
|
init_pidfile();
|
|
13604
|
+
|
|
13605
|
+
// src/daemon/module-trust.ts
|
|
13606
|
+
var import_node_crypto3 = require("crypto");
|
|
13607
|
+
var import_node_fs13 = require("fs");
|
|
13608
|
+
var import_node_path10 = require("path");
|
|
13609
|
+
init_paths();
|
|
13610
|
+
var TRUST_FILE = (0, import_node_path10.join)(PATHS.configDir, "trusted-modules.json");
|
|
13611
|
+
var PUBLISHER_KEYS_FILE = (0, import_node_path10.join)(PATHS.configDir, "publisher-keys.json");
|
|
13612
|
+
var DIGEST_EXCLUDED_DIRS = /* @__PURE__ */ new Set([".git"]);
|
|
13613
|
+
function collectFiles(root, dir = root, out = []) {
|
|
13614
|
+
for (const entry of (0, import_node_fs13.readdirSync)(dir, { withFileTypes: true }).sort(
|
|
13615
|
+
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
13616
|
+
)) {
|
|
13617
|
+
const full = (0, import_node_path10.join)(dir, entry.name);
|
|
13618
|
+
if (entry.isSymbolicLink()) {
|
|
13619
|
+
out.push({ path: full, isSymlink: true });
|
|
13620
|
+
} else if (entry.isDirectory()) {
|
|
13621
|
+
if (DIGEST_EXCLUDED_DIRS.has(entry.name)) continue;
|
|
13622
|
+
collectFiles(root, full, out);
|
|
13623
|
+
} else if (entry.isFile()) {
|
|
13624
|
+
out.push({ path: full, isSymlink: false });
|
|
13625
|
+
}
|
|
13626
|
+
}
|
|
13627
|
+
return out;
|
|
13628
|
+
}
|
|
13629
|
+
function computeModuleDigest(modulePath) {
|
|
13630
|
+
const hash = (0, import_node_crypto3.createHash)("sha256");
|
|
13631
|
+
for (const entry of collectFiles(modulePath)) {
|
|
13632
|
+
const relPath = (0, import_node_path10.relative)(modulePath, entry.path).split(import_node_path10.sep).join("/");
|
|
13633
|
+
hash.update(relPath);
|
|
13634
|
+
hash.update("\0");
|
|
13635
|
+
if (entry.isSymlink) {
|
|
13636
|
+
hash.update("symlink:");
|
|
13637
|
+
hash.update((0, import_node_fs13.readlinkSync)(entry.path));
|
|
13638
|
+
} else {
|
|
13639
|
+
hash.update("file:");
|
|
13640
|
+
hash.update((0, import_node_fs13.readFileSync)(entry.path));
|
|
13641
|
+
}
|
|
13642
|
+
hash.update("\0");
|
|
13643
|
+
}
|
|
13644
|
+
return hash.digest("hex");
|
|
13645
|
+
}
|
|
13646
|
+
function readTrustFile() {
|
|
13647
|
+
if (!(0, import_node_fs13.existsSync)(TRUST_FILE)) return { version: 1, modules: {} };
|
|
13648
|
+
try {
|
|
13649
|
+
const parsed = JSON.parse((0, import_node_fs13.readFileSync)(TRUST_FILE, "utf-8"));
|
|
13650
|
+
if (parsed.version !== 1 || typeof parsed.modules !== "object" || !parsed.modules) {
|
|
13651
|
+
return { version: 1, modules: {} };
|
|
13652
|
+
}
|
|
13653
|
+
return { version: 1, modules: parsed.modules };
|
|
13654
|
+
} catch {
|
|
13655
|
+
return { version: 1, modules: {} };
|
|
13656
|
+
}
|
|
13657
|
+
}
|
|
13658
|
+
function writeTrustFile(file) {
|
|
13659
|
+
(0, import_node_fs13.writeFileSync)(TRUST_FILE, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
|
|
13660
|
+
try {
|
|
13661
|
+
(0, import_node_fs13.chmodSync)(TRUST_FILE, 384);
|
|
13662
|
+
} catch {
|
|
13663
|
+
}
|
|
13664
|
+
}
|
|
13665
|
+
function trustModule(name, modulePath, source) {
|
|
13666
|
+
const file = readTrustFile();
|
|
13667
|
+
const record = {
|
|
13668
|
+
digest: computeModuleDigest(modulePath),
|
|
13669
|
+
trustedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13670
|
+
source
|
|
13671
|
+
};
|
|
13672
|
+
file.modules[name] = record;
|
|
13673
|
+
writeTrustFile(file);
|
|
13674
|
+
return record;
|
|
13675
|
+
}
|
|
13676
|
+
function untrustModule(name) {
|
|
13677
|
+
const file = readTrustFile();
|
|
13678
|
+
if (!file.modules[name]) return false;
|
|
13679
|
+
delete file.modules[name];
|
|
13680
|
+
writeTrustFile(file);
|
|
13681
|
+
return true;
|
|
13682
|
+
}
|
|
13683
|
+
function listTrustedModules() {
|
|
13684
|
+
return readTrustFile().modules;
|
|
13685
|
+
}
|
|
13686
|
+
function readPublisherKeys() {
|
|
13687
|
+
if (!(0, import_node_fs13.existsSync)(PUBLISHER_KEYS_FILE)) return {};
|
|
13688
|
+
try {
|
|
13689
|
+
const parsed = JSON.parse((0, import_node_fs13.readFileSync)(PUBLISHER_KEYS_FILE, "utf-8"));
|
|
13690
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
13691
|
+
} catch {
|
|
13692
|
+
return {};
|
|
13693
|
+
}
|
|
13694
|
+
}
|
|
13695
|
+
function verifyModuleSignature(modulePath, digest) {
|
|
13696
|
+
const keys = readPublisherKeys();
|
|
13697
|
+
const pinnedKeyIds = Object.keys(keys);
|
|
13698
|
+
const sigPath = (0, import_node_path10.join)(modulePath, "mod.sig");
|
|
13699
|
+
if (pinnedKeyIds.length === 0) {
|
|
13700
|
+
return { ok: true };
|
|
13701
|
+
}
|
|
13702
|
+
if (!(0, import_node_fs13.existsSync)(sigPath)) {
|
|
13703
|
+
return { ok: false, reason: "publisher keys are pinned but the module ships no mod.sig" };
|
|
13704
|
+
}
|
|
13705
|
+
let parsed;
|
|
13706
|
+
try {
|
|
13707
|
+
parsed = JSON.parse((0, import_node_fs13.readFileSync)(sigPath, "utf-8"));
|
|
13708
|
+
} catch {
|
|
13709
|
+
return { ok: false, reason: "mod.sig is not valid JSON" };
|
|
13710
|
+
}
|
|
13711
|
+
if (!parsed.keyId || !parsed.signature) {
|
|
13712
|
+
return { ok: false, reason: "mod.sig is missing keyId or signature" };
|
|
13713
|
+
}
|
|
13714
|
+
const publicKey = keys[parsed.keyId];
|
|
13715
|
+
if (!publicKey) {
|
|
13716
|
+
return { ok: false, reason: `mod.sig references unpinned key "${parsed.keyId}"` };
|
|
13717
|
+
}
|
|
13718
|
+
try {
|
|
13719
|
+
const valid = (0, import_node_crypto3.verify)(
|
|
13720
|
+
null,
|
|
13721
|
+
Buffer.from(digest, "hex"),
|
|
13722
|
+
publicKey,
|
|
13723
|
+
Buffer.from(parsed.signature, "base64")
|
|
13724
|
+
);
|
|
13725
|
+
return valid ? { ok: true } : { ok: false, reason: "mod.sig signature does not match" };
|
|
13726
|
+
} catch (err) {
|
|
13727
|
+
return { ok: false, reason: `signature check failed: ${String(err.message || err)}` };
|
|
13728
|
+
}
|
|
13729
|
+
}
|
|
13730
|
+
function verifyModuleTrust(name, modulePath) {
|
|
13731
|
+
let digest;
|
|
13732
|
+
try {
|
|
13733
|
+
digest = computeModuleDigest(modulePath);
|
|
13734
|
+
} catch (err) {
|
|
13735
|
+
return { ok: false, reason: `could not hash module: ${String(err.message || err)}` };
|
|
13736
|
+
}
|
|
13737
|
+
const signature = verifyModuleSignature(modulePath, digest);
|
|
13738
|
+
if (!signature.ok) return signature;
|
|
13739
|
+
const record = readTrustFile().modules[name];
|
|
13740
|
+
if (!record) {
|
|
13741
|
+
return {
|
|
13742
|
+
ok: false,
|
|
13743
|
+
reason: `not trusted \u2014 review it, then run: threatcrush modules trust ${name}`
|
|
13744
|
+
};
|
|
13745
|
+
}
|
|
13746
|
+
if (record.digest !== digest) {
|
|
13747
|
+
return {
|
|
13748
|
+
ok: false,
|
|
13749
|
+
reason: `contents changed since it was trusted on ${record.trustedAt} \u2014 re-review it, then run: threatcrush modules trust ${name}`
|
|
13750
|
+
};
|
|
13751
|
+
}
|
|
13752
|
+
return { ok: true };
|
|
13753
|
+
}
|
|
13754
|
+
|
|
13755
|
+
// src/commands/modules.ts
|
|
13301
13756
|
var API_URL2 = process.env.THREATCRUSH_API_URL || "https://threatcrush.com";
|
|
13302
13757
|
function modulesDir() {
|
|
13303
13758
|
ensureRuntimeDirs();
|
|
@@ -13310,7 +13765,7 @@ function safeModuleDirName(name, label = "module name") {
|
|
|
13310
13765
|
return name;
|
|
13311
13766
|
}
|
|
13312
13767
|
function moduleDestination(dir, name, label) {
|
|
13313
|
-
return (0,
|
|
13768
|
+
return (0, import_node_path11.join)(dir, safeModuleDirName(name, label));
|
|
13314
13769
|
}
|
|
13315
13770
|
function assertSafeTarballEntries(tarPath) {
|
|
13316
13771
|
const listing = (0, import_node_child_process2.execFileSync)("tar", ["-tzf", tarPath], { encoding: "utf-8" });
|
|
@@ -13323,12 +13778,12 @@ function assertSafeTarballEntries(tarPath) {
|
|
|
13323
13778
|
}
|
|
13324
13779
|
}
|
|
13325
13780
|
function validateManifest(modPath) {
|
|
13326
|
-
const manifestPath = (0,
|
|
13327
|
-
if (!(0,
|
|
13781
|
+
const manifestPath = (0, import_node_path11.join)(modPath, "mod.toml");
|
|
13782
|
+
if (!(0, import_node_fs14.existsSync)(manifestPath)) {
|
|
13328
13783
|
return { ok: false, error: `mod.toml not found at ${manifestPath}` };
|
|
13329
13784
|
}
|
|
13330
13785
|
try {
|
|
13331
|
-
const raw = (0,
|
|
13786
|
+
const raw = (0, import_node_fs14.readFileSync)(manifestPath, "utf-8");
|
|
13332
13787
|
const parsed = import_toml3.default.parse(raw);
|
|
13333
13788
|
const name = parsed.module?.name;
|
|
13334
13789
|
const version = parsed.module?.version;
|
|
@@ -13339,6 +13794,12 @@ function validateManifest(modPath) {
|
|
|
13339
13794
|
return { ok: false, error: `Invalid mod.toml: ${err.message}` };
|
|
13340
13795
|
}
|
|
13341
13796
|
}
|
|
13797
|
+
function printTrustRequired(name) {
|
|
13798
|
+
console.log();
|
|
13799
|
+
console.log(source_default.yellow(` ! ${name} is installed but NOT trusted, so threatcrushd will not load it.`));
|
|
13800
|
+
console.log(source_default.dim(" Review the module source, then run:"));
|
|
13801
|
+
console.log(source_default.dim(` ${source_default.white(`threatcrush modules trust ${name}`)}`));
|
|
13802
|
+
}
|
|
13342
13803
|
function notifyDaemonIfRunning() {
|
|
13343
13804
|
if (!findRunningDaemon()) return;
|
|
13344
13805
|
console.log(source_default.dim(` \u2139 threatcrushd is running \u2014 restart it to load/unload modules:`));
|
|
@@ -13382,8 +13843,8 @@ async function modulesInstallCommand(source) {
|
|
|
13382
13843
|
console.log();
|
|
13383
13844
|
const dir = modulesDir();
|
|
13384
13845
|
if (source.startsWith("./") || source.startsWith("/") || source.startsWith("~")) {
|
|
13385
|
-
const absPath = (0,
|
|
13386
|
-
if (!(0,
|
|
13846
|
+
const absPath = (0, import_node_path11.resolve)(source.startsWith("~") ? source.replace("~", process.env.HOME || "") : source);
|
|
13847
|
+
if (!(0, import_node_fs14.existsSync)(absPath)) {
|
|
13387
13848
|
console.log(source_default.red(` \u2717 Path not found: ${absPath}
|
|
13388
13849
|
`));
|
|
13389
13850
|
return;
|
|
@@ -13402,7 +13863,7 @@ async function modulesInstallCommand(source) {
|
|
|
13402
13863
|
`));
|
|
13403
13864
|
return;
|
|
13404
13865
|
}
|
|
13405
|
-
if ((0,
|
|
13866
|
+
if ((0, import_node_fs14.existsSync)(dest2)) {
|
|
13406
13867
|
console.log(source_default.yellow(` ! ${check.name} is already installed at ${dest2}`));
|
|
13407
13868
|
console.log(source_default.dim(` Run ${source_default.white(`threatcrush modules remove ${check.name}`)} first.
|
|
13408
13869
|
`));
|
|
@@ -13410,12 +13871,13 @@ async function modulesInstallCommand(source) {
|
|
|
13410
13871
|
}
|
|
13411
13872
|
const spinner2 = ora({ text: `Copying module files...`, color: "green" }).start();
|
|
13412
13873
|
try {
|
|
13413
|
-
(0,
|
|
13874
|
+
(0, import_node_fs14.cpSync)(absPath, dest2, { recursive: true, dereference: true });
|
|
13414
13875
|
spinner2.succeed(`Installed ${source_default.white(check.name)} v${check.version} \u2192 ${dest2}`);
|
|
13415
13876
|
} catch (err) {
|
|
13416
13877
|
spinner2.fail(`Copy failed: ${err.message}`);
|
|
13417
13878
|
return;
|
|
13418
13879
|
}
|
|
13880
|
+
printTrustRequired(check.name);
|
|
13419
13881
|
notifyDaemonIfRunning();
|
|
13420
13882
|
console.log();
|
|
13421
13883
|
return;
|
|
@@ -13425,14 +13887,14 @@ async function modulesInstallCommand(source) {
|
|
|
13425
13887
|
let name;
|
|
13426
13888
|
let dest2;
|
|
13427
13889
|
try {
|
|
13428
|
-
name = safeModuleDirName((0,
|
|
13890
|
+
name = safeModuleDirName((0, import_node_path11.basename)(gitUrl.replace(/\.git$/, "")), "repository name");
|
|
13429
13891
|
dest2 = moduleDestination(dir, name);
|
|
13430
13892
|
} catch (err) {
|
|
13431
13893
|
console.log(source_default.red(` x ${err.message}
|
|
13432
13894
|
`));
|
|
13433
13895
|
return;
|
|
13434
13896
|
}
|
|
13435
|
-
if ((0,
|
|
13897
|
+
if ((0, import_node_fs14.existsSync)(dest2)) {
|
|
13436
13898
|
console.log(source_default.yellow(` ! ${name} is already installed at ${dest2}`));
|
|
13437
13899
|
console.log(source_default.dim(` Run ${source_default.white(`threatcrush modules remove ${name}`)} first.
|
|
13438
13900
|
`));
|
|
@@ -13449,12 +13911,13 @@ async function modulesInstallCommand(source) {
|
|
|
13449
13911
|
if (!check.ok) {
|
|
13450
13912
|
spinner2.fail(check.error);
|
|
13451
13913
|
try {
|
|
13452
|
-
(0,
|
|
13914
|
+
(0, import_node_fs14.rmSync)(dest2, { recursive: true, force: true });
|
|
13453
13915
|
} catch {
|
|
13454
13916
|
}
|
|
13455
13917
|
return;
|
|
13456
13918
|
}
|
|
13457
13919
|
spinner2.succeed(`Installed ${source_default.white(check.name)} v${check.version} \u2192 ${dest2}`);
|
|
13920
|
+
printTrustRequired(check.name);
|
|
13458
13921
|
notifyDaemonIfRunning();
|
|
13459
13922
|
console.log();
|
|
13460
13923
|
return;
|
|
@@ -13494,7 +13957,7 @@ async function modulesInstallCommand(source) {
|
|
|
13494
13957
|
`));
|
|
13495
13958
|
return;
|
|
13496
13959
|
}
|
|
13497
|
-
if ((0,
|
|
13960
|
+
if ((0, import_node_fs14.existsSync)(dest)) {
|
|
13498
13961
|
console.log(source_default.yellow(` ! ${mod.slug} is already installed at ${dest}
|
|
13499
13962
|
`));
|
|
13500
13963
|
return;
|
|
@@ -13520,7 +13983,7 @@ async function modulesInstallCommand(source) {
|
|
|
13520
13983
|
if (!check.ok) {
|
|
13521
13984
|
cloneSpinner.fail(check.error);
|
|
13522
13985
|
try {
|
|
13523
|
-
(0,
|
|
13986
|
+
(0, import_node_fs14.rmSync)(dest, { recursive: true, force: true });
|
|
13524
13987
|
} catch {
|
|
13525
13988
|
}
|
|
13526
13989
|
return;
|
|
@@ -13534,11 +13997,11 @@ async function modulesInstallCommand(source) {
|
|
|
13534
13997
|
dlSpinner.fail(`HTTP ${res.status}`);
|
|
13535
13998
|
return;
|
|
13536
13999
|
}
|
|
13537
|
-
const tar = (0,
|
|
13538
|
-
(0,
|
|
14000
|
+
const tar = (0, import_node_path11.join)(dir, `${mod.slug}.tar.gz`);
|
|
14001
|
+
(0, import_node_fs14.writeFileSync)(tar, Buffer.from(await res.arrayBuffer()));
|
|
13539
14002
|
assertSafeTarballEntries(tar);
|
|
13540
14003
|
(0, import_node_child_process2.execFileSync)("tar", ["-xzf", tar, "-C", dir], { stdio: "pipe" });
|
|
13541
|
-
(0,
|
|
14004
|
+
(0, import_node_fs14.rmSync)(tar, { force: true });
|
|
13542
14005
|
const check = validateManifest(dest);
|
|
13543
14006
|
if (!check.ok) {
|
|
13544
14007
|
dlSpinner.fail(check.error);
|
|
@@ -13553,6 +14016,7 @@ async function modulesInstallCommand(source) {
|
|
|
13553
14016
|
logger.info("No installable artifact provided for this module.");
|
|
13554
14017
|
return;
|
|
13555
14018
|
}
|
|
14019
|
+
printTrustRequired(mod.slug);
|
|
13556
14020
|
notifyDaemonIfRunning();
|
|
13557
14021
|
console.log();
|
|
13558
14022
|
}
|
|
@@ -13569,7 +14033,7 @@ async function modulesRemoveCommand(name) {
|
|
|
13569
14033
|
`));
|
|
13570
14034
|
return;
|
|
13571
14035
|
}
|
|
13572
|
-
if (!(0,
|
|
14036
|
+
if (!(0, import_node_fs14.existsSync)(target)) {
|
|
13573
14037
|
console.log(source_default.yellow(` ! No module named "${name}" in ${dir}
|
|
13574
14038
|
`));
|
|
13575
14039
|
return;
|
|
@@ -13579,7 +14043,8 @@ async function modulesRemoveCommand(name) {
|
|
|
13579
14043
|
console.log(source_default.yellow(` ! Directory name "${name}" does not match manifest name "${check.name}"`));
|
|
13580
14044
|
}
|
|
13581
14045
|
try {
|
|
13582
|
-
(0,
|
|
14046
|
+
(0, import_node_fs14.rmSync)(target, { recursive: true, force: true });
|
|
14047
|
+
untrustModule(name);
|
|
13583
14048
|
console.log(source_default.green(` \u2713 Removed ${name} from ${dir}
|
|
13584
14049
|
`));
|
|
13585
14050
|
} catch (err) {
|
|
@@ -13589,6 +14054,72 @@ async function modulesRemoveCommand(name) {
|
|
|
13589
14054
|
}
|
|
13590
14055
|
notifyDaemonIfRunning();
|
|
13591
14056
|
}
|
|
14057
|
+
async function modulesTrustCommand(name) {
|
|
14058
|
+
banner();
|
|
14059
|
+
console.log(source_default.green.bold(" Trust Module"));
|
|
14060
|
+
console.log(source_default.gray(" " + "\u2500".repeat(50)));
|
|
14061
|
+
console.log();
|
|
14062
|
+
const dir = modulesDir();
|
|
14063
|
+
let target;
|
|
14064
|
+
try {
|
|
14065
|
+
target = moduleDestination(dir, name);
|
|
14066
|
+
} catch (err) {
|
|
14067
|
+
console.log(source_default.red(` x ${err.message}
|
|
14068
|
+
`));
|
|
14069
|
+
return;
|
|
14070
|
+
}
|
|
14071
|
+
if (!(0, import_node_fs14.existsSync)(target)) {
|
|
14072
|
+
console.log(source_default.yellow(` ! No module named "${name}" in ${dir}
|
|
14073
|
+
`));
|
|
14074
|
+
return;
|
|
14075
|
+
}
|
|
14076
|
+
const check = validateManifest(target);
|
|
14077
|
+
if (!check.ok) {
|
|
14078
|
+
console.log(source_default.red(` \u2717 ${check.error}
|
|
14079
|
+
`));
|
|
14080
|
+
return;
|
|
14081
|
+
}
|
|
14082
|
+
const record = trustModule(name, target, target);
|
|
14083
|
+
console.log(source_default.green(` \u2713 Trusted ${source_default.white(name)} v${check.version}`));
|
|
14084
|
+
console.log(source_default.dim(` digest ${record.digest.slice(0, 16)}\u2026`));
|
|
14085
|
+
console.log(
|
|
14086
|
+
source_default.dim(" threatcrushd will refuse to load it again if its contents change.\n")
|
|
14087
|
+
);
|
|
14088
|
+
notifyDaemonIfRunning();
|
|
14089
|
+
}
|
|
14090
|
+
async function modulesUntrustCommand(name) {
|
|
14091
|
+
banner();
|
|
14092
|
+
console.log(source_default.green.bold(" Revoke Module Trust"));
|
|
14093
|
+
console.log(source_default.gray(" " + "\u2500".repeat(50)));
|
|
14094
|
+
console.log();
|
|
14095
|
+
if (untrustModule(name)) {
|
|
14096
|
+
console.log(source_default.green(` \u2713 Revoked trust for ${source_default.white(name)}
|
|
14097
|
+
`));
|
|
14098
|
+
notifyDaemonIfRunning();
|
|
14099
|
+
} else {
|
|
14100
|
+
console.log(source_default.yellow(` ! ${name} was not trusted
|
|
14101
|
+
`));
|
|
14102
|
+
}
|
|
14103
|
+
}
|
|
14104
|
+
async function modulesTrustedCommand() {
|
|
14105
|
+
banner();
|
|
14106
|
+
console.log(source_default.green.bold(" Trusted Modules"));
|
|
14107
|
+
console.log(source_default.gray(" " + "\u2500".repeat(50)));
|
|
14108
|
+
console.log();
|
|
14109
|
+
const trusted = Object.entries(listTrustedModules());
|
|
14110
|
+
if (trusted.length === 0) {
|
|
14111
|
+
console.log(source_default.yellow(" No modules are trusted."));
|
|
14112
|
+
console.log(source_default.dim(" Installed modules stay dormant until you run:"));
|
|
14113
|
+
console.log(source_default.dim(` ${source_default.white("threatcrush modules trust <name>")}
|
|
14114
|
+
`));
|
|
14115
|
+
return;
|
|
14116
|
+
}
|
|
14117
|
+
for (const [name, record] of trusted) {
|
|
14118
|
+
console.log(` ${source_default.white(name)} ${source_default.dim(record.digest.slice(0, 16) + "\u2026")}`);
|
|
14119
|
+
console.log(source_default.dim(` trusted ${record.trustedAt}`));
|
|
14120
|
+
}
|
|
14121
|
+
console.log();
|
|
14122
|
+
}
|
|
13592
14123
|
async function modulesCommand(opts) {
|
|
13593
14124
|
const action = opts.action || "list";
|
|
13594
14125
|
switch (action) {
|
|
@@ -13622,10 +14153,31 @@ async function modulesCommand(opts) {
|
|
|
13622
14153
|
}
|
|
13623
14154
|
await modulesRemoveCommand(opts.name);
|
|
13624
14155
|
break;
|
|
14156
|
+
case "trust":
|
|
14157
|
+
if (!opts.name) {
|
|
14158
|
+
banner();
|
|
14159
|
+
console.log(source_default.red(" Module name required."));
|
|
14160
|
+
console.log(source_default.gray(" Usage: threatcrush modules trust <name>\n"));
|
|
14161
|
+
return;
|
|
14162
|
+
}
|
|
14163
|
+
await modulesTrustCommand(opts.name);
|
|
14164
|
+
break;
|
|
14165
|
+
case "untrust":
|
|
14166
|
+
if (!opts.name) {
|
|
14167
|
+
banner();
|
|
14168
|
+
console.log(source_default.red(" Module name required."));
|
|
14169
|
+
console.log(source_default.gray(" Usage: threatcrush modules untrust <name>\n"));
|
|
14170
|
+
return;
|
|
14171
|
+
}
|
|
14172
|
+
await modulesUntrustCommand(opts.name);
|
|
14173
|
+
break;
|
|
14174
|
+
case "trusted":
|
|
14175
|
+
await modulesTrustedCommand();
|
|
14176
|
+
break;
|
|
13625
14177
|
default:
|
|
13626
14178
|
banner();
|
|
13627
14179
|
console.log(source_default.yellow(` Unknown action: ${action}`));
|
|
13628
|
-
console.log(source_default.gray(" Available actions: list, install, remove\n"));
|
|
14180
|
+
console.log(source_default.gray(" Available actions: list, install, remove, trust, untrust, trusted\n"));
|
|
13629
14181
|
await modulesListCommand();
|
|
13630
14182
|
break;
|
|
13631
14183
|
}
|
|
@@ -13952,21 +14504,21 @@ async function pentestCommand(targetUrl) {
|
|
|
13952
14504
|
|
|
13953
14505
|
// src/commands/orgs.ts
|
|
13954
14506
|
var import_node_os5 = require("os");
|
|
13955
|
-
var
|
|
13956
|
-
var
|
|
14507
|
+
var import_node_fs15 = require("fs");
|
|
14508
|
+
var import_node_path12 = require("path");
|
|
13957
14509
|
var API_URL3 = process.env.THREATCRUSH_API_URL || "https://threatcrush.com";
|
|
13958
|
-
var CONFIG_PATH = (0,
|
|
14510
|
+
var CONFIG_PATH = (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".threatcrush", "config.json");
|
|
13959
14511
|
function readConfig() {
|
|
13960
14512
|
try {
|
|
13961
|
-
return JSON.parse((0,
|
|
14513
|
+
return JSON.parse((0, import_node_fs15.readFileSync)(CONFIG_PATH, "utf-8"));
|
|
13962
14514
|
} catch {
|
|
13963
14515
|
return {};
|
|
13964
14516
|
}
|
|
13965
14517
|
}
|
|
13966
14518
|
function writeConfig(config) {
|
|
13967
|
-
const dir = (0,
|
|
13968
|
-
if (!(0,
|
|
13969
|
-
(0,
|
|
14519
|
+
const dir = (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".threatcrush");
|
|
14520
|
+
if (!(0, import_node_fs15.existsSync)(dir)) (0, import_node_fs15.mkdirSync)(dir, { recursive: true });
|
|
14521
|
+
(0, import_node_fs15.writeFileSync)(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
13970
14522
|
}
|
|
13971
14523
|
function getAuthHeaders() {
|
|
13972
14524
|
const config = readConfig();
|
|
@@ -14102,13 +14654,13 @@ async function useOrganization(slug) {
|
|
|
14102
14654
|
|
|
14103
14655
|
// src/commands/servers.ts
|
|
14104
14656
|
var import_node_os6 = require("os");
|
|
14105
|
-
var
|
|
14106
|
-
var
|
|
14657
|
+
var import_node_fs16 = require("fs");
|
|
14658
|
+
var import_node_path13 = require("path");
|
|
14107
14659
|
var API_URL4 = process.env.THREATCRUSH_API_URL || "https://threatcrush.com";
|
|
14108
|
-
var CONFIG_PATH2 = (0,
|
|
14660
|
+
var CONFIG_PATH2 = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".threatcrush", "config.json");
|
|
14109
14661
|
function readConfig2() {
|
|
14110
14662
|
try {
|
|
14111
|
-
return JSON.parse((0,
|
|
14663
|
+
return JSON.parse((0, import_node_fs16.readFileSync)(CONFIG_PATH2, "utf-8"));
|
|
14112
14664
|
} catch {
|
|
14113
14665
|
return {};
|
|
14114
14666
|
}
|
|
@@ -14211,14 +14763,14 @@ function timeAgo(dateStr) {
|
|
|
14211
14763
|
|
|
14212
14764
|
// src/commands/connect.ts
|
|
14213
14765
|
var import_node_os7 = require("os");
|
|
14214
|
-
var
|
|
14215
|
-
var
|
|
14766
|
+
var import_node_fs17 = require("fs");
|
|
14767
|
+
var import_node_path14 = require("path");
|
|
14216
14768
|
var import_node_child_process3 = require("child_process");
|
|
14217
14769
|
var API_URL5 = process.env.THREATCRUSH_API_URL || "https://threatcrush.com";
|
|
14218
|
-
var CONFIG_PATH3 = (0,
|
|
14770
|
+
var CONFIG_PATH3 = (0, import_node_path14.join)((0, import_node_os7.homedir)(), ".threatcrush", "config.json");
|
|
14219
14771
|
function readConfig3() {
|
|
14220
14772
|
try {
|
|
14221
|
-
return JSON.parse((0,
|
|
14773
|
+
return JSON.parse((0, import_node_fs17.readFileSync)(CONFIG_PATH3, "utf-8"));
|
|
14222
14774
|
} catch {
|
|
14223
14775
|
return {};
|
|
14224
14776
|
}
|
|
@@ -14373,20 +14925,21 @@ async function sshConnect(options) {
|
|
|
14373
14925
|
|
|
14374
14926
|
// src/commands/daemon.ts
|
|
14375
14927
|
var import_node_child_process7 = require("child_process");
|
|
14376
|
-
var
|
|
14377
|
-
var
|
|
14378
|
-
var
|
|
14928
|
+
var import_node_fs26 = require("fs");
|
|
14929
|
+
var import_node_path18 = require("path");
|
|
14930
|
+
var import_node_fs27 = require("fs");
|
|
14379
14931
|
|
|
14380
14932
|
// src/daemon/index.ts
|
|
14381
|
-
var
|
|
14382
|
-
var
|
|
14933
|
+
var import_node_fs25 = require("fs");
|
|
14934
|
+
var import_node_path17 = require("path");
|
|
14383
14935
|
init_paths();
|
|
14384
14936
|
init_pidfile();
|
|
14385
14937
|
|
|
14386
14938
|
// src/daemon/ipc-server.ts
|
|
14387
14939
|
var import_node_net2 = require("net");
|
|
14388
|
-
var
|
|
14940
|
+
var import_node_fs18 = require("fs");
|
|
14389
14941
|
init_paths();
|
|
14942
|
+
init_control_token();
|
|
14390
14943
|
|
|
14391
14944
|
// src/daemon/event-bus.ts
|
|
14392
14945
|
var import_node_events = require("events");
|
|
@@ -14431,10 +14984,12 @@ var IpcServer = class {
|
|
|
14431
14984
|
nextClientId = 1;
|
|
14432
14985
|
startedAt = /* @__PURE__ */ new Date();
|
|
14433
14986
|
counters = { events: 0, threats: 0, alerts: 0 };
|
|
14987
|
+
controlToken = "";
|
|
14434
14988
|
async start() {
|
|
14435
|
-
|
|
14989
|
+
this.controlToken = issueControlToken();
|
|
14990
|
+
if ((0, import_node_fs18.existsSync)(PATHS.socket)) {
|
|
14436
14991
|
try {
|
|
14437
|
-
(0,
|
|
14992
|
+
(0, import_node_fs18.unlinkSync)(PATHS.socket);
|
|
14438
14993
|
} catch {
|
|
14439
14994
|
}
|
|
14440
14995
|
}
|
|
@@ -14470,14 +15025,14 @@ var IpcServer = class {
|
|
|
14470
15025
|
return new Promise((resolve5) => {
|
|
14471
15026
|
if (!this.server) {
|
|
14472
15027
|
try {
|
|
14473
|
-
if ((0,
|
|
15028
|
+
if ((0, import_node_fs18.existsSync)(PATHS.socket)) (0, import_node_fs18.unlinkSync)(PATHS.socket);
|
|
14474
15029
|
} catch {
|
|
14475
15030
|
}
|
|
14476
15031
|
return resolve5();
|
|
14477
15032
|
}
|
|
14478
15033
|
this.server.close(() => {
|
|
14479
15034
|
try {
|
|
14480
|
-
if ((0,
|
|
15035
|
+
if ((0, import_node_fs18.existsSync)(PATHS.socket)) (0, import_node_fs18.unlinkSync)(PATHS.socket);
|
|
14481
15036
|
} catch {
|
|
14482
15037
|
}
|
|
14483
15038
|
resolve5();
|
|
@@ -14563,6 +15118,13 @@ var IpcServer = class {
|
|
|
14563
15118
|
for (const ch of req.params.channels) client.subscriptions.add(ch);
|
|
14564
15119
|
return this.send(client, { id: req.id, ok: true, result: { subscribed: [...client.subscriptions] } });
|
|
14565
15120
|
case "shutdown":
|
|
15121
|
+
if (!tokensMatch(this.controlToken, req.params?.token)) {
|
|
15122
|
+
return this.send(client, {
|
|
15123
|
+
id: req.id,
|
|
15124
|
+
ok: false,
|
|
15125
|
+
error: "shutdown requires the daemon control token (run as root, or use systemctl)"
|
|
15126
|
+
});
|
|
15127
|
+
}
|
|
14566
15128
|
this.send(client, { id: req.id, ok: true, result: "shutting down" });
|
|
14567
15129
|
setTimeout(() => process.emit("SIGTERM"), 50);
|
|
14568
15130
|
return;
|
|
@@ -14583,14 +15145,14 @@ var IpcServer = class {
|
|
|
14583
15145
|
};
|
|
14584
15146
|
|
|
14585
15147
|
// src/daemon/module-host.ts
|
|
14586
|
-
var
|
|
14587
|
-
var
|
|
15148
|
+
var import_node_fs22 = require("fs");
|
|
15149
|
+
var import_node_path15 = require("path");
|
|
14588
15150
|
var import_node_url = require("url");
|
|
14589
15151
|
var import_toml4 = __toESM(require_toml());
|
|
14590
15152
|
init_paths();
|
|
14591
15153
|
|
|
14592
15154
|
// src/daemon/watchers/log-watcher.ts
|
|
14593
|
-
var
|
|
15155
|
+
var import_node_fs19 = require("fs");
|
|
14594
15156
|
var import_node_readline4 = require("readline");
|
|
14595
15157
|
init_state();
|
|
14596
15158
|
var DEFAULT_SOURCES = [
|
|
@@ -14612,9 +15174,9 @@ var LogWatcher = class {
|
|
|
14612
15174
|
start() {
|
|
14613
15175
|
const started = [];
|
|
14614
15176
|
for (const src of this.sources) {
|
|
14615
|
-
if (!(0,
|
|
15177
|
+
if (!(0, import_node_fs19.existsSync)(src.path)) continue;
|
|
14616
15178
|
try {
|
|
14617
|
-
(0,
|
|
15179
|
+
(0, import_node_fs19.accessSync)(src.path, import_node_fs19.constants.R_OK);
|
|
14618
15180
|
} catch {
|
|
14619
15181
|
continue;
|
|
14620
15182
|
}
|
|
@@ -14634,7 +15196,7 @@ var LogWatcher = class {
|
|
|
14634
15196
|
}
|
|
14635
15197
|
tail(src) {
|
|
14636
15198
|
try {
|
|
14637
|
-
this.positions.set(src.path, (0,
|
|
15199
|
+
this.positions.set(src.path, (0, import_node_fs19.statSync)(src.path).size);
|
|
14638
15200
|
} catch {
|
|
14639
15201
|
this.positions.set(src.path, 0);
|
|
14640
15202
|
}
|
|
@@ -14645,7 +15207,7 @@ var LogWatcher = class {
|
|
|
14645
15207
|
poll(src) {
|
|
14646
15208
|
let stat;
|
|
14647
15209
|
try {
|
|
14648
|
-
stat = (0,
|
|
15210
|
+
stat = (0, import_node_fs19.statSync)(src.path);
|
|
14649
15211
|
} catch {
|
|
14650
15212
|
return;
|
|
14651
15213
|
}
|
|
@@ -14655,7 +15217,7 @@ var LogWatcher = class {
|
|
|
14655
15217
|
return;
|
|
14656
15218
|
}
|
|
14657
15219
|
if (stat.size === prev) return;
|
|
14658
|
-
const stream = (0,
|
|
15220
|
+
const stream = (0, import_node_fs19.createReadStream)(src.path, { start: prev, encoding: "utf-8" });
|
|
14659
15221
|
stream.on("error", () => this.positions.set(src.path, stat.size));
|
|
14660
15222
|
const rl = (0, import_node_readline4.createInterface)({ input: stream });
|
|
14661
15223
|
rl.on("error", () => {
|
|
@@ -14848,7 +15410,7 @@ function realtimeToDate(rt) {
|
|
|
14848
15410
|
|
|
14849
15411
|
// src/modules/network-monitor/index.ts
|
|
14850
15412
|
var import_node_child_process5 = require("child_process");
|
|
14851
|
-
var
|
|
15413
|
+
var import_node_fs20 = require("fs");
|
|
14852
15414
|
init_state();
|
|
14853
15415
|
var NetworkMonitor = class {
|
|
14854
15416
|
constructor(bus2) {
|
|
@@ -14887,7 +15449,7 @@ var NetworkMonitor = class {
|
|
|
14887
15449
|
hasConntrackOrSs() {
|
|
14888
15450
|
const ss = (0, import_node_child_process5.spawnSync)("ss", ["--version"], { stdio: "pipe" });
|
|
14889
15451
|
if (ss.status === 0) return true;
|
|
14890
|
-
return (0,
|
|
15452
|
+
return (0, import_node_fs20.existsSync)("/proc/net/tcp");
|
|
14891
15453
|
}
|
|
14892
15454
|
poll() {
|
|
14893
15455
|
try {
|
|
@@ -15026,7 +15588,7 @@ var NetworkMonitor = class {
|
|
|
15026
15588
|
};
|
|
15027
15589
|
|
|
15028
15590
|
// src/modules/dns-monitor/index.ts
|
|
15029
|
-
var
|
|
15591
|
+
var import_node_fs21 = require("fs");
|
|
15030
15592
|
var import_node_readline5 = require("readline");
|
|
15031
15593
|
init_state();
|
|
15032
15594
|
var DNS_LOG_SOURCES = [
|
|
@@ -15061,9 +15623,9 @@ var DnsMonitor = class {
|
|
|
15061
15623
|
entropyThreshold = 3.5;
|
|
15062
15624
|
start() {
|
|
15063
15625
|
const sources = DNS_LOG_SOURCES.filter((p) => {
|
|
15064
|
-
if (!(0,
|
|
15626
|
+
if (!(0, import_node_fs21.existsSync)(p)) return false;
|
|
15065
15627
|
try {
|
|
15066
|
-
(0,
|
|
15628
|
+
(0, import_node_fs21.accessSync)(p, import_node_fs21.constants.R_OK);
|
|
15067
15629
|
return true;
|
|
15068
15630
|
} catch {
|
|
15069
15631
|
return false;
|
|
@@ -15087,7 +15649,7 @@ var DnsMonitor = class {
|
|
|
15087
15649
|
}
|
|
15088
15650
|
tailLog(path) {
|
|
15089
15651
|
try {
|
|
15090
|
-
this.positions.set(path, (0,
|
|
15652
|
+
this.positions.set(path, (0, import_node_fs21.statSync)(path).size);
|
|
15091
15653
|
} catch {
|
|
15092
15654
|
this.positions.set(path, 0);
|
|
15093
15655
|
}
|
|
@@ -15097,7 +15659,7 @@ var DnsMonitor = class {
|
|
|
15097
15659
|
pollLog(path) {
|
|
15098
15660
|
let stat;
|
|
15099
15661
|
try {
|
|
15100
|
-
stat = (0,
|
|
15662
|
+
stat = (0, import_node_fs21.statSync)(path);
|
|
15101
15663
|
} catch {
|
|
15102
15664
|
return;
|
|
15103
15665
|
}
|
|
@@ -15107,7 +15669,7 @@ var DnsMonitor = class {
|
|
|
15107
15669
|
return;
|
|
15108
15670
|
}
|
|
15109
15671
|
if (stat.size === prev) return;
|
|
15110
|
-
const stream = (0,
|
|
15672
|
+
const stream = (0, import_node_fs21.createReadStream)(path, { start: prev, encoding: "utf-8" });
|
|
15111
15673
|
stream.on("error", () => this.positions.set(path, stat.size));
|
|
15112
15674
|
const rl = (0, import_node_readline5.createInterface)({ input: stream });
|
|
15113
15675
|
rl.on("line", (line) => this.parseDnsLine(line));
|
|
@@ -15339,15 +15901,15 @@ var ModuleHost = class {
|
|
|
15339
15901
|
for (const m of builtins) this.modules.set(m.name, m);
|
|
15340
15902
|
}
|
|
15341
15903
|
async discoverAndStartInstalled() {
|
|
15342
|
-
if (!(0,
|
|
15904
|
+
if (!(0, import_node_fs22.existsSync)(PATHS.moduleDir)) return;
|
|
15343
15905
|
const configs = loadModuleConfigs(PATHS.confD);
|
|
15344
|
-
const entries = (0,
|
|
15906
|
+
const entries = (0, import_node_fs22.readdirSync)(PATHS.moduleDir, { withFileTypes: true });
|
|
15345
15907
|
for (const entry of entries) {
|
|
15346
15908
|
if (!entry.isDirectory()) continue;
|
|
15347
|
-
const manifestPath = (0,
|
|
15348
|
-
if (!(0,
|
|
15909
|
+
const manifestPath = (0, import_node_path15.join)(PATHS.moduleDir, entry.name, "mod.toml");
|
|
15910
|
+
if (!(0, import_node_fs22.existsSync)(manifestPath)) continue;
|
|
15349
15911
|
try {
|
|
15350
|
-
const manifest = import_toml4.default.parse((0,
|
|
15912
|
+
const manifest = import_toml4.default.parse((0, import_node_fs22.readFileSync)(manifestPath, "utf-8"));
|
|
15351
15913
|
const name = manifest.module?.name || entry.name;
|
|
15352
15914
|
const defaults = manifest.module?.config?.defaults || {};
|
|
15353
15915
|
const config = {
|
|
@@ -15361,7 +15923,7 @@ var ModuleHost = class {
|
|
|
15361
15923
|
source: "installed",
|
|
15362
15924
|
status: config.enabled === false ? "disabled" : "loaded",
|
|
15363
15925
|
events: 0,
|
|
15364
|
-
path: (0,
|
|
15926
|
+
path: (0, import_node_path15.join)(PATHS.moduleDir, entry.name),
|
|
15365
15927
|
config
|
|
15366
15928
|
};
|
|
15367
15929
|
this.modules.set(name, hosted);
|
|
@@ -15376,7 +15938,7 @@ var ModuleHost = class {
|
|
|
15376
15938
|
status: "error",
|
|
15377
15939
|
events: 0,
|
|
15378
15940
|
detail: `manifest load failed: ${String(err.message || err)}`,
|
|
15379
|
-
path: (0,
|
|
15941
|
+
path: (0, import_node_path15.join)(PATHS.moduleDir, entry.name)
|
|
15380
15942
|
});
|
|
15381
15943
|
}
|
|
15382
15944
|
}
|
|
@@ -15388,6 +15950,13 @@ var ModuleHost = class {
|
|
|
15388
15950
|
hosted.detail = "no built entrypoint found; run npm install && npm run build in the module directory";
|
|
15389
15951
|
return;
|
|
15390
15952
|
}
|
|
15953
|
+
const trust = verifyModuleTrust(hosted.name, hosted.path);
|
|
15954
|
+
if (!trust.ok) {
|
|
15955
|
+
hosted.status = "error";
|
|
15956
|
+
hosted.detail = `refusing to load: ${trust.reason}`;
|
|
15957
|
+
this.bus.announceModule(hosted.name, "error", hosted.detail);
|
|
15958
|
+
return;
|
|
15959
|
+
}
|
|
15391
15960
|
try {
|
|
15392
15961
|
const imported = await import((0, import_node_url.pathToFileURL)(entrypoint).href);
|
|
15393
15962
|
const exported = imported.default || imported.module || imported;
|
|
@@ -15408,17 +15977,17 @@ var ModuleHost = class {
|
|
|
15408
15977
|
}
|
|
15409
15978
|
}
|
|
15410
15979
|
installedEntrypoint(modulePath) {
|
|
15411
|
-
const packageJson = (0,
|
|
15980
|
+
const packageJson = (0, import_node_path15.join)(modulePath, "package.json");
|
|
15412
15981
|
const candidates = [];
|
|
15413
|
-
if ((0,
|
|
15982
|
+
if ((0, import_node_fs22.existsSync)(packageJson)) {
|
|
15414
15983
|
try {
|
|
15415
|
-
const pkg = JSON.parse((0,
|
|
15416
|
-
if (pkg.main) candidates.push((0,
|
|
15984
|
+
const pkg = JSON.parse((0, import_node_fs22.readFileSync)(packageJson, "utf-8"));
|
|
15985
|
+
if (pkg.main) candidates.push((0, import_node_path15.join)(modulePath, pkg.main));
|
|
15417
15986
|
} catch {
|
|
15418
15987
|
}
|
|
15419
15988
|
}
|
|
15420
|
-
candidates.push((0,
|
|
15421
|
-
return candidates.find((candidate) => (0,
|
|
15989
|
+
candidates.push((0, import_node_path15.join)(modulePath, "dist", "index.js"), (0, import_node_path15.join)(modulePath, "index.js"));
|
|
15990
|
+
return candidates.find((candidate) => (0, import_node_fs22.existsSync)(candidate)) || null;
|
|
15422
15991
|
}
|
|
15423
15992
|
isThreatCrushModule(value) {
|
|
15424
15993
|
return Boolean(
|
|
@@ -15939,8 +16508,8 @@ var RuleEngine = class {
|
|
|
15939
16508
|
};
|
|
15940
16509
|
|
|
15941
16510
|
// src/daemon/rules/loader.ts
|
|
15942
|
-
var
|
|
15943
|
-
var
|
|
16511
|
+
var import_node_fs23 = require("fs");
|
|
16512
|
+
var import_node_path16 = require("path");
|
|
15944
16513
|
|
|
15945
16514
|
// src/daemon/rules/default-rules.ts
|
|
15946
16515
|
var DEFAULT_RULES = [
|
|
@@ -16224,11 +16793,11 @@ var RULES_DIR = "/etc/threatcrush/rules.d";
|
|
|
16224
16793
|
function loadAllRules(customDir) {
|
|
16225
16794
|
const rules = [...DEFAULT_RULES];
|
|
16226
16795
|
const dir = customDir || RULES_DIR;
|
|
16227
|
-
if ((0,
|
|
16228
|
-
const files = (0,
|
|
16796
|
+
if ((0, import_node_fs23.existsSync)(dir)) {
|
|
16797
|
+
const files = (0, import_node_fs23.readdirSync)(dir).filter((f) => f.endsWith(".json"));
|
|
16229
16798
|
for (const file of files) {
|
|
16230
16799
|
try {
|
|
16231
|
-
const raw = (0,
|
|
16800
|
+
const raw = (0, import_node_fs23.readFileSync)((0, import_node_path16.join)(dir, file), "utf-8");
|
|
16232
16801
|
const parsed = JSON.parse(raw);
|
|
16233
16802
|
const customRules = Array.isArray(parsed) ? parsed : [parsed];
|
|
16234
16803
|
for (const rule of customRules) {
|
|
@@ -16391,7 +16960,7 @@ function detectFirewallAdapter() {
|
|
|
16391
16960
|
}
|
|
16392
16961
|
|
|
16393
16962
|
// src/daemon/firewall/remediation.ts
|
|
16394
|
-
var
|
|
16963
|
+
var import_node_fs24 = require("fs");
|
|
16395
16964
|
init_state();
|
|
16396
16965
|
init_paths();
|
|
16397
16966
|
var DEFAULT_CONFIG2 = {
|
|
@@ -16548,7 +17117,7 @@ var RemediationManager = class {
|
|
|
16548
17117
|
}
|
|
16549
17118
|
logLine(line) {
|
|
16550
17119
|
try {
|
|
16551
|
-
(0,
|
|
17120
|
+
(0, import_node_fs24.appendFileSync)(PATHS.logFile, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
16552
17121
|
`);
|
|
16553
17122
|
} catch {
|
|
16554
17123
|
}
|
|
@@ -16607,7 +17176,7 @@ async function flushTelemetry(timeoutMs = 2e3) {
|
|
|
16607
17176
|
// src/daemon/index.ts
|
|
16608
17177
|
function readVersion2() {
|
|
16609
17178
|
try {
|
|
16610
|
-
const pkg = JSON.parse((0,
|
|
17179
|
+
const pkg = JSON.parse((0, import_node_fs25.readFileSync)((0, import_node_path17.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
16611
17180
|
return pkg.version || "0.0.0";
|
|
16612
17181
|
} catch {
|
|
16613
17182
|
return "0.0.0";
|
|
@@ -16615,7 +17184,7 @@ function readVersion2() {
|
|
|
16615
17184
|
}
|
|
16616
17185
|
function logLine(line) {
|
|
16617
17186
|
try {
|
|
16618
|
-
(0,
|
|
17187
|
+
(0, import_node_fs25.appendFileSync)(PATHS.logFile, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
16619
17188
|
`);
|
|
16620
17189
|
} catch {
|
|
16621
17190
|
}
|
|
@@ -16643,7 +17212,7 @@ async function runDaemon() {
|
|
|
16643
17212
|
} catch (err) {
|
|
16644
17213
|
logLine(`[daemon] state db unavailable: ${err.message}`);
|
|
16645
17214
|
}
|
|
16646
|
-
const config = loadConfig((0,
|
|
17215
|
+
const config = loadConfig((0, import_node_fs25.existsSync)(PATHS.configFile) ? PATHS.configFile : void 0);
|
|
16647
17216
|
bus.on("event", (event) => {
|
|
16648
17217
|
logLine(`[event] ${event.severity} ${event.module} ${event.message}`);
|
|
16649
17218
|
});
|
|
@@ -16735,7 +17304,7 @@ async function runDaemon() {
|
|
|
16735
17304
|
init_paths();
|
|
16736
17305
|
init_pidfile();
|
|
16737
17306
|
init_ipc_client();
|
|
16738
|
-
var DAEMON_ENTRY = (0,
|
|
17307
|
+
var DAEMON_ENTRY = (0, import_node_path18.join)(__dirname, "daemon.js");
|
|
16739
17308
|
async function daemonForeground() {
|
|
16740
17309
|
await runDaemon();
|
|
16741
17310
|
}
|
|
@@ -16746,7 +17315,7 @@ async function daemonStart() {
|
|
|
16746
17315
|
return;
|
|
16747
17316
|
}
|
|
16748
17317
|
ensureRuntimeDirs();
|
|
16749
|
-
if (!(0,
|
|
17318
|
+
if (!(0, import_node_fs26.existsSync)(DAEMON_ENTRY)) {
|
|
16750
17319
|
console.log(source_default.red(` Daemon entry not found at ${DAEMON_ENTRY}.`));
|
|
16751
17320
|
console.log(source_default.dim(" Reinstall with `threatcrush update` or `pnpm run build` in the cli package."));
|
|
16752
17321
|
return;
|
|
@@ -16754,8 +17323,8 @@ async function daemonStart() {
|
|
|
16754
17323
|
let out;
|
|
16755
17324
|
let err;
|
|
16756
17325
|
try {
|
|
16757
|
-
out = (0,
|
|
16758
|
-
err = (0,
|
|
17326
|
+
out = (0, import_node_fs27.openSync)(PATHS.logFile, "a");
|
|
17327
|
+
err = (0, import_node_fs27.openSync)(PATHS.logFile, "a");
|
|
16759
17328
|
} catch (e) {
|
|
16760
17329
|
const code = e.code;
|
|
16761
17330
|
console.log(source_default.red(` \u2717 Cannot write daemon log at ${PATHS.logFile} (${code ?? "error"}).`));
|
|
@@ -16834,19 +17403,19 @@ async function daemonStop() {
|
|
|
16834
17403
|
|
|
16835
17404
|
// src/commands/service.ts
|
|
16836
17405
|
var import_node_child_process8 = require("child_process");
|
|
16837
|
-
var
|
|
16838
|
-
var
|
|
17406
|
+
var import_node_fs28 = require("fs");
|
|
17407
|
+
var import_node_path19 = require("path");
|
|
16839
17408
|
var UNIT_PATH = "/etc/systemd/system/threatcrushd.service";
|
|
16840
17409
|
function resolveTemplate() {
|
|
16841
|
-
const templatePath = (0,
|
|
16842
|
-
if (!(0,
|
|
17410
|
+
const templatePath = (0, import_node_path19.join)(__dirname, "systemd", "threatcrushd.service");
|
|
17411
|
+
if (!(0, import_node_fs28.existsSync)(templatePath)) {
|
|
16843
17412
|
throw new Error(`systemd unit template not found at ${templatePath}`);
|
|
16844
17413
|
}
|
|
16845
|
-
return (0,
|
|
17414
|
+
return (0, import_node_fs28.readFileSync)(templatePath, "utf-8");
|
|
16846
17415
|
}
|
|
16847
17416
|
function resolveBinPath() {
|
|
16848
17417
|
const arg = process.argv[1];
|
|
16849
|
-
if (arg && (0,
|
|
17418
|
+
if (arg && (0, import_node_fs28.existsSync)(arg)) return arg;
|
|
16850
17419
|
try {
|
|
16851
17420
|
return (0, import_node_child_process8.execSync)("command -v threatcrush", { encoding: "utf-8" }).trim();
|
|
16852
17421
|
} catch {
|
|
@@ -16867,7 +17436,7 @@ async function installServiceCommand() {
|
|
|
16867
17436
|
return;
|
|
16868
17437
|
}
|
|
16869
17438
|
const unit = resolveTemplate().replace("{{BIN_PATH}}", resolveBinPath());
|
|
16870
|
-
(0,
|
|
17439
|
+
(0, import_node_fs28.writeFileSync)(UNIT_PATH, unit, { mode: 420 });
|
|
16871
17440
|
console.log(source_default.green(` \u2713 Installed unit file: ${UNIT_PATH}`));
|
|
16872
17441
|
ensureSystemDirs();
|
|
16873
17442
|
try {
|
|
@@ -16882,29 +17451,31 @@ async function installServiceCommand() {
|
|
|
16882
17451
|
}
|
|
16883
17452
|
function ensureSystemDirs() {
|
|
16884
17453
|
const dirs = [
|
|
16885
|
-
{ path: "/etc/threatcrush" },
|
|
16886
|
-
{ path: "/etc/threatcrush/modules", sticky: true },
|
|
16887
|
-
{ path: "/etc/threatcrush/threatcrushd.conf.d" },
|
|
16888
|
-
{ path: "/var/log/threatcrush" },
|
|
16889
|
-
{ path: "/var/lib/threatcrush" },
|
|
16890
|
-
{ path: "/var/run/threatcrush" }
|
|
17454
|
+
{ path: "/etc/threatcrush", groupWritable: true },
|
|
17455
|
+
{ path: "/etc/threatcrush/modules", groupWritable: true, sticky: true },
|
|
17456
|
+
{ path: "/etc/threatcrush/threatcrushd.conf.d", groupWritable: true },
|
|
17457
|
+
{ path: "/var/log/threatcrush", groupWritable: false },
|
|
17458
|
+
{ path: "/var/lib/threatcrush", groupWritable: false },
|
|
17459
|
+
{ path: "/var/run/threatcrush", groupWritable: false }
|
|
16891
17460
|
];
|
|
16892
17461
|
let admGid = null;
|
|
16893
17462
|
try {
|
|
16894
|
-
admGid = (0,
|
|
17463
|
+
admGid = (0, import_node_fs28.statSync)("/var/log/auth.log").gid;
|
|
16895
17464
|
} catch {
|
|
16896
17465
|
}
|
|
16897
|
-
for (const { path, sticky } of dirs) {
|
|
17466
|
+
for (const { path, groupWritable, sticky } of dirs) {
|
|
16898
17467
|
try {
|
|
16899
|
-
(0,
|
|
17468
|
+
(0, import_node_fs28.mkdirSync)(path, { recursive: true });
|
|
16900
17469
|
} catch {
|
|
16901
17470
|
}
|
|
16902
|
-
|
|
16903
|
-
|
|
16904
|
-
(0,
|
|
17471
|
+
try {
|
|
17472
|
+
if (groupWritable && admGid !== null) {
|
|
17473
|
+
(0, import_node_fs28.chmodSync)(path, sticky ? 1533 : 509);
|
|
16905
17474
|
(0, import_node_child_process8.execSync)(`chgrp adm ${path}`, { stdio: "ignore" });
|
|
16906
|
-
}
|
|
17475
|
+
} else {
|
|
17476
|
+
(0, import_node_fs28.chmodSync)(path, 493);
|
|
16907
17477
|
}
|
|
17478
|
+
} catch {
|
|
16908
17479
|
}
|
|
16909
17480
|
}
|
|
16910
17481
|
console.log(source_default.green(" \u2713 Runtime dirs prepared (group `adm` may install modules / edit config without sudo)."));
|
|
@@ -16928,7 +17499,7 @@ async function uninstallServiceCommand() {
|
|
|
16928
17499
|
} catch {
|
|
16929
17500
|
}
|
|
16930
17501
|
try {
|
|
16931
|
-
if ((0,
|
|
17502
|
+
if ((0, import_node_fs28.existsSync)(UNIT_PATH)) {
|
|
16932
17503
|
(0, import_node_child_process8.execSync)(`rm -f ${UNIT_PATH}`);
|
|
16933
17504
|
console.log(source_default.green(` \u2713 Removed unit file: ${UNIT_PATH}`));
|
|
16934
17505
|
}
|
|
@@ -17027,8 +17598,8 @@ function welcomeCommand() {
|
|
|
17027
17598
|
}
|
|
17028
17599
|
|
|
17029
17600
|
// src/commands/properties.ts
|
|
17030
|
-
var
|
|
17031
|
-
var
|
|
17601
|
+
var import_node_fs29 = require("fs");
|
|
17602
|
+
var import_node_path20 = require("path");
|
|
17032
17603
|
var import_node_readline6 = __toESM(require("readline"));
|
|
17033
17604
|
var API_URL7 = process.env.THREATCRUSH_API_URL || "https://threatcrush.com";
|
|
17034
17605
|
var KINDS = ["url", "api", "domain", "ip", "repo"];
|
|
@@ -17357,8 +17928,8 @@ async function propertiesRunsCommand(opts) {
|
|
|
17357
17928
|
}
|
|
17358
17929
|
}
|
|
17359
17930
|
function parseImportFile(path) {
|
|
17360
|
-
const ext = (0,
|
|
17361
|
-
const raw = (0,
|
|
17931
|
+
const ext = (0, import_node_path20.extname)(path).toLowerCase();
|
|
17932
|
+
const raw = (0, import_node_fs29.readFileSync)(path, "utf-8");
|
|
17362
17933
|
if (ext === ".json") {
|
|
17363
17934
|
const parsed = JSON.parse(raw);
|
|
17364
17935
|
if (!Array.isArray(parsed)) throw new Error("JSON must be an array of objects");
|
|
@@ -17548,7 +18119,7 @@ async function rulesCommand(opts) {
|
|
|
17548
18119
|
}
|
|
17549
18120
|
|
|
17550
18121
|
// src/commands/harden.ts
|
|
17551
|
-
var
|
|
18122
|
+
var import_node_fs30 = require("fs");
|
|
17552
18123
|
var import_node_child_process9 = require("child_process");
|
|
17553
18124
|
function tryExec(cmd) {
|
|
17554
18125
|
try {
|
|
@@ -17559,7 +18130,7 @@ function tryExec(cmd) {
|
|
|
17559
18130
|
}
|
|
17560
18131
|
function tryRead(path) {
|
|
17561
18132
|
try {
|
|
17562
|
-
return (0,
|
|
18133
|
+
return (0, import_node_fs30.readFileSync)(path, "utf-8");
|
|
17563
18134
|
} catch {
|
|
17564
18135
|
return null;
|
|
17565
18136
|
}
|
|
@@ -17663,8 +18234,8 @@ function checkSshWeakConfig() {
|
|
|
17663
18234
|
};
|
|
17664
18235
|
}
|
|
17665
18236
|
function checkAutoUpdates() {
|
|
17666
|
-
const unattended = (0,
|
|
17667
|
-
const dnfAuto = (0,
|
|
18237
|
+
const unattended = (0, import_node_fs30.existsSync)("/etc/apt/apt.conf.d/20auto-upgrades") || (0, import_node_fs30.existsSync)("/etc/apt/apt.conf.d/50unattended-upgrades");
|
|
18238
|
+
const dnfAuto = (0, import_node_fs30.existsSync)("/etc/dnf/automatic.conf");
|
|
17668
18239
|
if (unattended || dnfAuto) {
|
|
17669
18240
|
return {
|
|
17670
18241
|
key: "auto-updates",
|
|
@@ -17790,7 +18361,7 @@ function checkFail2ban() {
|
|
|
17790
18361
|
explanation: "fail2ban is installed and running."
|
|
17791
18362
|
};
|
|
17792
18363
|
}
|
|
17793
|
-
if ((0,
|
|
18364
|
+
if ((0, import_node_fs30.existsSync)("/etc/fail2ban/fail2ban.conf")) {
|
|
17794
18365
|
return {
|
|
17795
18366
|
key: checkKey,
|
|
17796
18367
|
status: "warn",
|
|
@@ -18021,7 +18592,7 @@ async function allowlistCommand(opts) {
|
|
|
18021
18592
|
init_paths();
|
|
18022
18593
|
var PKG_VERSION2 = "0.1.8";
|
|
18023
18594
|
try {
|
|
18024
|
-
const pkg = JSON.parse((0,
|
|
18595
|
+
const pkg = JSON.parse((0, import_node_fs31.readFileSync)((0, import_node_path21.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
18025
18596
|
PKG_VERSION2 = pkg.version;
|
|
18026
18597
|
} catch {
|
|
18027
18598
|
}
|
|
@@ -18037,7 +18608,7 @@ ${source_default.dim(" C R U S H")}
|
|
|
18037
18608
|
var API_URL8 = process.env.THREATCRUSH_API_URL || "https://threatcrush.com";
|
|
18038
18609
|
var PKG_NAME = "@profullstack/threatcrush";
|
|
18039
18610
|
var DESKTOP_PKG_NAME = "@profullstack/threatcrush-desktop";
|
|
18040
|
-
var INSTALL_CONFIG_PATH = (0,
|
|
18611
|
+
var INSTALL_CONFIG_PATH = (0, import_node_path21.join)((0, import_node_os8.homedir)(), ".threatcrush", "install.json");
|
|
18041
18612
|
function detectPackageManager() {
|
|
18042
18613
|
try {
|
|
18043
18614
|
const npmGlobal = (0, import_node_child_process10.execSync)("npm ls -g --depth=0 --json 2>/dev/null", { encoding: "utf-8" });
|
|
@@ -18063,7 +18634,7 @@ function detectPackageManager() {
|
|
|
18063
18634
|
}
|
|
18064
18635
|
function readInstallConfig() {
|
|
18065
18636
|
try {
|
|
18066
|
-
return JSON.parse((0,
|
|
18637
|
+
return JSON.parse((0, import_node_fs31.readFileSync)(INSTALL_CONFIG_PATH, "utf-8"));
|
|
18067
18638
|
} catch {
|
|
18068
18639
|
return {};
|
|
18069
18640
|
}
|
|
@@ -18248,7 +18819,7 @@ program2.command("uninstall-service").description("Remove the threatcrushd syste
|
|
|
18248
18819
|
program2.command("logs").description("Tail daemon logs").action(async () => {
|
|
18249
18820
|
console.log(LOGO2);
|
|
18250
18821
|
const logPath = PATHS.logFile;
|
|
18251
|
-
if (!(0,
|
|
18822
|
+
if (!(0, import_node_fs31.existsSync)(logPath)) {
|
|
18252
18823
|
console.log(source_default.yellow(` No log file found at ${logPath}`));
|
|
18253
18824
|
console.log(source_default.dim(" Start the daemon first with `threatcrush start`\n"));
|
|
18254
18825
|
return;
|
|
@@ -18453,10 +19024,10 @@ storeCmd.command("search <query>").description("Search for modules in the store"
|
|
|
18453
19024
|
});
|
|
18454
19025
|
storeCmd.command("publish <url>").description("Publish a module from a git URL or web URL").action(async (url) => {
|
|
18455
19026
|
console.log(LOGO2);
|
|
18456
|
-
const configPath = (0,
|
|
19027
|
+
const configPath = (0, import_node_path21.join)((0, import_node_os8.homedir)(), ".threatcrush", "config.json");
|
|
18457
19028
|
let email = "";
|
|
18458
19029
|
try {
|
|
18459
|
-
const config = JSON.parse((0,
|
|
19030
|
+
const config = JSON.parse((0, import_node_fs31.readFileSync)(configPath, "utf-8"));
|
|
18460
19031
|
email = config.email || "";
|
|
18461
19032
|
} catch {
|
|
18462
19033
|
}
|
|
@@ -18473,9 +19044,9 @@ storeCmd.command("publish <url>").description("Publish a module from a git URL o
|
|
|
18473
19044
|
return;
|
|
18474
19045
|
}
|
|
18475
19046
|
try {
|
|
18476
|
-
const dir = (0,
|
|
18477
|
-
if (!(0,
|
|
18478
|
-
(0,
|
|
19047
|
+
const dir = (0, import_node_path21.join)((0, import_node_os8.homedir)(), ".threatcrush");
|
|
19048
|
+
if (!(0, import_node_fs31.existsSync)(dir)) (0, import_node_fs31.mkdirSync)(dir, { recursive: true });
|
|
19049
|
+
(0, import_node_fs31.writeFileSync)(configPath, JSON.stringify({ email }, null, 2));
|
|
18479
19050
|
console.log(source_default.dim(` Saved email to ${configPath}`));
|
|
18480
19051
|
} catch {
|
|
18481
19052
|
}
|