@node9/policy-engine 1.0.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +325 -3
- package/dist/index.d.ts +325 -3
- package/dist/index.js +312 -14
- package/dist/index.mjs +299 -14
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -31,15 +31,24 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
BUILTIN_SHIELDS: () => BUILTIN_SHIELDS,
|
|
34
|
+
COST_PER_LOOP_ITER_USD: () => COST_PER_LOOP_ITER_USD,
|
|
34
35
|
DLP_PATTERNS: () => DLP_PATTERNS,
|
|
35
36
|
ENGINE_VERSION: () => ENGINE_VERSION,
|
|
36
37
|
FLAGS_WITH_VALUES: () => FLAGS_WITH_VALUES,
|
|
37
38
|
LOOP_MAX_RECORDS: () => LOOP_MAX_RECORDS,
|
|
39
|
+
LOOP_THRESHOLD_FOR_WASTE: () => LOOP_THRESHOLD_FOR_WASTE,
|
|
40
|
+
SCAN_SIGNAL_WEIGHTS: () => SCAN_SIGNAL_WEIGHTS,
|
|
38
41
|
SENSITIVE_PATH_REGEXES: () => SENSITIVE_PATH_REGEXES,
|
|
39
42
|
analyzePipeChain: () => analyzePipeChain,
|
|
40
43
|
analyzeShellCommand: () => analyzeShellCommand,
|
|
41
44
|
checkDangerousSql: () => checkDangerousSql,
|
|
45
|
+
classifyAuditEntry: () => classifyAuditEntry,
|
|
46
|
+
classifyRuleSeverity: () => classifyRuleSeverity,
|
|
47
|
+
classifyScanSignal: () => classifyScanSignal,
|
|
42
48
|
computeArgsHash: () => computeArgsHash,
|
|
49
|
+
computeBlendedSecurityScore: () => computeBlendedSecurityScore,
|
|
50
|
+
computeScanScore: () => computeScanScore,
|
|
51
|
+
computeSecurityScore: () => computeSecurityScore,
|
|
43
52
|
detectDangerousEval: () => detectDangerousEval,
|
|
44
53
|
detectDangerousShellExec: () => detectDangerousShellExec,
|
|
45
54
|
evaluateLoopWindow: () => evaluateLoopWindow,
|
|
@@ -54,12 +63,16 @@ __export(src_exports, {
|
|
|
54
63
|
isShieldVerdict: () => isShieldVerdict,
|
|
55
64
|
matchSensitivePath: () => matchSensitivePath,
|
|
56
65
|
matchesPattern: () => matchesPattern,
|
|
66
|
+
narrativeRuleLabel: () => narrativeRuleLabel,
|
|
57
67
|
normalizeCommandForPolicy: () => normalizeCommandForPolicy,
|
|
58
68
|
parseAllSshHostsFromCommand: () => parseAllSshHostsFromCommand,
|
|
59
69
|
redactText: () => redactText,
|
|
60
70
|
scanArgs: () => scanArgs,
|
|
61
71
|
scanText: () => scanText,
|
|
62
72
|
sensitivePathMatch: () => sensitivePathMatch,
|
|
73
|
+
summarizeBlast: () => summarizeBlast,
|
|
74
|
+
summarizeScan: () => summarizeScan,
|
|
75
|
+
truncateBlastPath: () => truncateBlastPath,
|
|
63
76
|
validateOverrides: () => validateOverrides,
|
|
64
77
|
validateRegex: () => validateRegex,
|
|
65
78
|
validateShieldDefinition: () => validateShieldDefinition
|
|
@@ -67,6 +80,7 @@ __export(src_exports, {
|
|
|
67
80
|
module.exports = __toCommonJS(src_exports);
|
|
68
81
|
|
|
69
82
|
// src/dlp/index.ts
|
|
83
|
+
var import_safe_regex2 = __toESM(require("safe-regex2"));
|
|
70
84
|
var ASSIGNMENT_CONTEXT_RE = /\b(?:password|passwd|secret|token|api[_-]?key|auth(?:_key|_token)?|credential|private[_-]?key|access[_-]?key|client[_-]?secret)\s*[=:]\s*/i;
|
|
71
85
|
function isAssignmentContext(text) {
|
|
72
86
|
return ASSIGNMENT_CONTEXT_RE.test(text);
|
|
@@ -550,6 +564,23 @@ function sensitivePathMatch(originalPath) {
|
|
|
550
564
|
};
|
|
551
565
|
}
|
|
552
566
|
var SENSITIVE_PATH_REGEXES = SENSITIVE_PATH_PATTERNS;
|
|
567
|
+
function assertBuiltinPatternsAreSafe() {
|
|
568
|
+
for (const p of DLP_PATTERNS) {
|
|
569
|
+
if (!(0, import_safe_regex2.default)(p.regex.source)) {
|
|
570
|
+
throw new Error(
|
|
571
|
+
`[node9 engine] Builtin DLP pattern '${p.name}' is vulnerable to ReDoS: ${p.regex.source}`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
for (const re of SENSITIVE_PATH_PATTERNS) {
|
|
576
|
+
if (!(0, import_safe_regex2.default)(re.source)) {
|
|
577
|
+
throw new Error(
|
|
578
|
+
`[node9 engine] Builtin sensitive-path pattern is vulnerable to ReDoS: ${re.source}`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
assertBuiltinPatternsAreSafe();
|
|
553
584
|
function maskSecret(raw, pattern) {
|
|
554
585
|
const match = raw.match(pattern);
|
|
555
586
|
if (!match) return "****";
|
|
@@ -1141,7 +1172,7 @@ function parseAllSshHostsFromCommand(command) {
|
|
|
1141
1172
|
var import_picomatch = __toESM(require("picomatch"));
|
|
1142
1173
|
|
|
1143
1174
|
// src/utils/regex.ts
|
|
1144
|
-
var
|
|
1175
|
+
var import_safe_regex22 = __toESM(require("safe-regex2"));
|
|
1145
1176
|
var MAX_REGEX_LENGTH = 100;
|
|
1146
1177
|
var REGEX_CACHE_MAX = 500;
|
|
1147
1178
|
var regexCache = /* @__PURE__ */ new Map();
|
|
@@ -1154,7 +1185,7 @@ function validateRegex(pattern) {
|
|
|
1154
1185
|
return `Invalid regex syntax: ${e.message}`;
|
|
1155
1186
|
}
|
|
1156
1187
|
if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
|
|
1157
|
-
if (!(0,
|
|
1188
|
+
if (!(0, import_safe_regex22.default)(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
|
|
1158
1189
|
return null;
|
|
1159
1190
|
}
|
|
1160
1191
|
function getCompiledRegex(pattern, flags = "") {
|
|
@@ -1191,9 +1222,14 @@ function matchesPattern(text, patterns) {
|
|
|
1191
1222
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1192
1223
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1193
1224
|
}
|
|
1225
|
+
var FORBIDDEN_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1194
1226
|
function getNestedValue(obj, path) {
|
|
1195
1227
|
if (!obj || typeof obj !== "object") return null;
|
|
1196
|
-
|
|
1228
|
+
const segments = path.split(".");
|
|
1229
|
+
for (const seg of segments) {
|
|
1230
|
+
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
1231
|
+
}
|
|
1232
|
+
return segments.reduce((prev, curr) => prev?.[curr], obj);
|
|
1197
1233
|
}
|
|
1198
1234
|
function evaluateSmartConditions(args, rule) {
|
|
1199
1235
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
@@ -1479,6 +1515,9 @@ function isIgnoredTool(toolName, config) {
|
|
|
1479
1515
|
return matchesPattern(toolName, config.policy.ignoredTools);
|
|
1480
1516
|
}
|
|
1481
1517
|
|
|
1518
|
+
// src/shields/index.ts
|
|
1519
|
+
var import_safe_regex23 = __toESM(require("safe-regex2"));
|
|
1520
|
+
|
|
1482
1521
|
// src/shields/builtin/aws.json
|
|
1483
1522
|
var aws_default = {
|
|
1484
1523
|
name: "aws",
|
|
@@ -1910,15 +1949,6 @@ var k8s_default = {
|
|
|
1910
1949
|
dangerousWords: []
|
|
1911
1950
|
};
|
|
1912
1951
|
|
|
1913
|
-
// src/shields/builtin/mcp-tool-gating.json
|
|
1914
|
-
var mcp_tool_gating_default = {
|
|
1915
|
-
name: "mcp-tool-gating",
|
|
1916
|
-
description: "Intercept MCP tool lists and require user approval before the agent can use any tools from a new server",
|
|
1917
|
-
aliases: ["mcp-gating", "mcp-tools"],
|
|
1918
|
-
smartRules: [],
|
|
1919
|
-
dangerousWords: []
|
|
1920
|
-
};
|
|
1921
|
-
|
|
1922
1952
|
// src/shields/builtin/mongodb.json
|
|
1923
1953
|
var mongodb_default = {
|
|
1924
1954
|
name: "mongodb",
|
|
@@ -2233,12 +2263,29 @@ var BUILTIN_SHIELDS = {
|
|
|
2233
2263
|
[filesystem_default.name]: filesystem_default,
|
|
2234
2264
|
[github_default.name]: github_default,
|
|
2235
2265
|
[k8s_default.name]: k8s_default,
|
|
2236
|
-
[mcp_tool_gating_default.name]: mcp_tool_gating_default,
|
|
2237
2266
|
[mongodb_default.name]: mongodb_default,
|
|
2238
2267
|
[postgres_default.name]: postgres_default,
|
|
2239
2268
|
[project_jail_default.name]: project_jail_default,
|
|
2240
2269
|
[redis_default.name]: redis_default
|
|
2241
2270
|
};
|
|
2271
|
+
function assertBuiltinShieldRegexesAreSafe() {
|
|
2272
|
+
for (const shield of Object.values(BUILTIN_SHIELDS)) {
|
|
2273
|
+
for (const rule of shield.smartRules) {
|
|
2274
|
+
const conditions = rule.conditions ?? [];
|
|
2275
|
+
for (const cond of conditions) {
|
|
2276
|
+
if (cond.op !== "matches" && cond.op !== "notMatches") continue;
|
|
2277
|
+
const pattern = cond.value;
|
|
2278
|
+
if (!pattern) continue;
|
|
2279
|
+
if (!(0, import_safe_regex23.default)(pattern)) {
|
|
2280
|
+
throw new Error(
|
|
2281
|
+
`[node9 engine] Shield '${shield.name}' rule '${rule.name ?? rule.tool}' has unsafe regex: ${pattern}`
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
assertBuiltinShieldRegexesAreSafe();
|
|
2242
2289
|
|
|
2243
2290
|
// src/loop/index.ts
|
|
2244
2291
|
var import_crypto = __toESM(require("crypto"));
|
|
@@ -2257,20 +2304,267 @@ function evaluateLoopWindow(records, tool, args, threshold, windowMs, now) {
|
|
|
2257
2304
|
return { nextRecords, count, looping: count >= threshold };
|
|
2258
2305
|
}
|
|
2259
2306
|
|
|
2307
|
+
// src/scan/index.ts
|
|
2308
|
+
function emptySignals() {
|
|
2309
|
+
return {
|
|
2310
|
+
dlpFindings: 0,
|
|
2311
|
+
piiFindings: 0,
|
|
2312
|
+
sensitiveFileReads: 0,
|
|
2313
|
+
privilegeEscalation: 0,
|
|
2314
|
+
networkExfil: 0,
|
|
2315
|
+
pipeToShell: 0,
|
|
2316
|
+
evalOfRemote: 0,
|
|
2317
|
+
destructiveOps: 0,
|
|
2318
|
+
loops: 0,
|
|
2319
|
+
longOutputRedactions: 0
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
var FINDING_TO_SIGNAL = {
|
|
2323
|
+
dlp: "dlpFindings",
|
|
2324
|
+
pii: "piiFindings",
|
|
2325
|
+
"sensitive-file-read": "sensitiveFileReads",
|
|
2326
|
+
"privilege-escalation": "privilegeEscalation",
|
|
2327
|
+
"network-exfil": "networkExfil",
|
|
2328
|
+
"pipe-to-shell": "pipeToShell",
|
|
2329
|
+
"eval-of-remote": "evalOfRemote",
|
|
2330
|
+
"destructive-op": "destructiveOps",
|
|
2331
|
+
loop: "loops",
|
|
2332
|
+
"long-output-redacted": "longOutputRedactions"
|
|
2333
|
+
};
|
|
2334
|
+
var SCAN_SIGNAL_WEIGHTS = {
|
|
2335
|
+
dlpFindings: 30,
|
|
2336
|
+
piiFindings: 10,
|
|
2337
|
+
sensitiveFileReads: 20,
|
|
2338
|
+
privilegeEscalation: 15,
|
|
2339
|
+
networkExfil: 25,
|
|
2340
|
+
pipeToShell: 30,
|
|
2341
|
+
evalOfRemote: 30,
|
|
2342
|
+
destructiveOps: 15,
|
|
2343
|
+
loops: 3,
|
|
2344
|
+
longOutputRedactions: 1
|
|
2345
|
+
};
|
|
2346
|
+
function computeScanScore(signals) {
|
|
2347
|
+
let deduction = 0;
|
|
2348
|
+
for (const key of Object.keys(signals)) {
|
|
2349
|
+
deduction += signals[key] * SCAN_SIGNAL_WEIGHTS[key];
|
|
2350
|
+
}
|
|
2351
|
+
return Math.max(0, Math.min(100, 100 - deduction));
|
|
2352
|
+
}
|
|
2353
|
+
var LOOP_THRESHOLD_FOR_WASTE = 3;
|
|
2354
|
+
var COST_PER_LOOP_ITER_USD = 6e-3;
|
|
2355
|
+
function summarizeScan(findings, opts = {}) {
|
|
2356
|
+
const totalToolCalls = opts.totalToolCalls ?? 0;
|
|
2357
|
+
const topN = opts.topN ?? 10;
|
|
2358
|
+
const signals = emptySignals();
|
|
2359
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
2360
|
+
const patternCounts = /* @__PURE__ */ new Map();
|
|
2361
|
+
for (const f of findings) {
|
|
2362
|
+
sessionIds.add(f.sessionId);
|
|
2363
|
+
const key = FINDING_TO_SIGNAL[f.type];
|
|
2364
|
+
signals[key]++;
|
|
2365
|
+
if (f.patternName) {
|
|
2366
|
+
patternCounts.set(f.patternName, (patternCounts.get(f.patternName) ?? 0) + 1);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
const topPatterns = [...patternCounts.entries()].sort((a, b) => {
|
|
2370
|
+
if (b[1] !== a[1]) return b[1] - a[1];
|
|
2371
|
+
return a[0].localeCompare(b[0]);
|
|
2372
|
+
}).slice(0, topN).map(([patternName, count]) => ({ patternName, count }));
|
|
2373
|
+
return {
|
|
2374
|
+
totalSessions: sessionIds.size,
|
|
2375
|
+
totalToolCalls,
|
|
2376
|
+
signals,
|
|
2377
|
+
topPatterns,
|
|
2378
|
+
score: computeScanScore(signals)
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
// src/severity/index.ts
|
|
2383
|
+
function classifyRuleSeverity(name, verdict) {
|
|
2384
|
+
const n = name.toLowerCase();
|
|
2385
|
+
const criticalPatterns = [
|
|
2386
|
+
"rm-rf",
|
|
2387
|
+
"eval-remote",
|
|
2388
|
+
"eval-curl",
|
|
2389
|
+
"read-aws",
|
|
2390
|
+
"read-ssh",
|
|
2391
|
+
"read-gcp",
|
|
2392
|
+
"read-cred",
|
|
2393
|
+
"delete-repo",
|
|
2394
|
+
"helm-uninstall",
|
|
2395
|
+
"drop-table",
|
|
2396
|
+
"drop-database",
|
|
2397
|
+
"drop-collection",
|
|
2398
|
+
"truncate",
|
|
2399
|
+
"flushall",
|
|
2400
|
+
"flushdb",
|
|
2401
|
+
"pipe-shell"
|
|
2402
|
+
];
|
|
2403
|
+
if (criticalPatterns.some((p) => n.includes(p))) return "critical";
|
|
2404
|
+
const highPatterns = [
|
|
2405
|
+
"force-push",
|
|
2406
|
+
"force_push",
|
|
2407
|
+
"git-destructive",
|
|
2408
|
+
"reset-hard",
|
|
2409
|
+
"rebase",
|
|
2410
|
+
"delete-branch",
|
|
2411
|
+
"delete-remote"
|
|
2412
|
+
];
|
|
2413
|
+
if (highPatterns.some((p) => n.includes(p))) return "high";
|
|
2414
|
+
if (verdict === "block") return "high";
|
|
2415
|
+
return "medium";
|
|
2416
|
+
}
|
|
2417
|
+
function narrativeRuleLabel(name) {
|
|
2418
|
+
const stripped = stripRulePrefixes(name);
|
|
2419
|
+
const map = {
|
|
2420
|
+
"read-aws": "AWS credentials read",
|
|
2421
|
+
"read-ssh": "SSH private key read",
|
|
2422
|
+
"read-gcp": "GCP credentials read",
|
|
2423
|
+
"read-cred": "credential file read",
|
|
2424
|
+
"delete-repo": "GitHub repository deletion",
|
|
2425
|
+
"helm-uninstall": "helm uninstall",
|
|
2426
|
+
"rm-rf-home": "rm -rf on home directory",
|
|
2427
|
+
"rm-rf": "rm -rf",
|
|
2428
|
+
"eval-remote": "eval of remote download",
|
|
2429
|
+
"eval-curl": "eval of curl output",
|
|
2430
|
+
"pipe-shell": "curl | bash",
|
|
2431
|
+
"drop-table": "DROP TABLE",
|
|
2432
|
+
"drop-database": "DROP DATABASE",
|
|
2433
|
+
"drop-collection": "DROP COLLECTION",
|
|
2434
|
+
truncate: "TRUNCATE",
|
|
2435
|
+
flushall: "Redis FLUSHALL",
|
|
2436
|
+
flushdb: "Redis FLUSHDB",
|
|
2437
|
+
"force-push": "force pushes",
|
|
2438
|
+
force_push: "force pushes",
|
|
2439
|
+
"reset-hard": "git reset --hard",
|
|
2440
|
+
"git-destructive": "destructive git operations",
|
|
2441
|
+
"delete-branch": "branch deletion",
|
|
2442
|
+
"delete-remote": "remote deletion",
|
|
2443
|
+
rebase: "git rebase",
|
|
2444
|
+
rm: "rm calls",
|
|
2445
|
+
sudo: "sudo calls",
|
|
2446
|
+
"eval-dynamic": "dynamic eval",
|
|
2447
|
+
"config-set": "Redis CONFIG SET"
|
|
2448
|
+
};
|
|
2449
|
+
for (const [key, label] of Object.entries(map)) {
|
|
2450
|
+
if (stripped.includes(key)) return label;
|
|
2451
|
+
}
|
|
2452
|
+
return stripped;
|
|
2453
|
+
}
|
|
2454
|
+
function stripRulePrefixes(name) {
|
|
2455
|
+
let n = name.toLowerCase();
|
|
2456
|
+
if (n.startsWith("org:")) n = n.slice(4);
|
|
2457
|
+
const shieldMatch = /^shield:[^:]+:(.+)$/.exec(n);
|
|
2458
|
+
if (shieldMatch) n = shieldMatch[1];
|
|
2459
|
+
n = n.replace(/^(block|review|allow)-/, "");
|
|
2460
|
+
return n;
|
|
2461
|
+
}
|
|
2462
|
+
function classifyAuditEntry(entry) {
|
|
2463
|
+
const ruleName = entry.riskMetadata?.ruleName;
|
|
2464
|
+
if (typeof ruleName === "string" && ruleName.length > 0) {
|
|
2465
|
+
const verdict = entry.action === "AUTO_BLOCKED" || entry.action === "DENIED" ? "block" : entry.action === "APPROVED" || entry.action === "AUTO_ALLOWED" ? "allow" : "review";
|
|
2466
|
+
return classifyRuleSeverity(ruleName, verdict);
|
|
2467
|
+
}
|
|
2468
|
+
const cb = entry.checkedBy ?? "";
|
|
2469
|
+
if (cb === "dlp-block" || cb.startsWith("dlp-saas:")) return "critical";
|
|
2470
|
+
if (cb.startsWith("eval-saas") || cb === "pipe-chain-saas:critical") {
|
|
2471
|
+
return "critical";
|
|
2472
|
+
}
|
|
2473
|
+
if (cb === "loop-detected") return "medium";
|
|
2474
|
+
const isBlocked = entry.action === "AUTO_BLOCKED" || entry.action === "DENIED";
|
|
2475
|
+
if (isBlocked) return "high";
|
|
2476
|
+
return null;
|
|
2477
|
+
}
|
|
2478
|
+
function computeSecurityScore(opts) {
|
|
2479
|
+
const { critical, high, medium, total } = opts;
|
|
2480
|
+
if (total === 0) return { score: 100, tier: "good" };
|
|
2481
|
+
const criticalRate = critical / total;
|
|
2482
|
+
const highRate = high / total;
|
|
2483
|
+
const mediumRate = medium / total;
|
|
2484
|
+
const deduction = Math.min(criticalRate * 3e3, 60) + Math.min(highRate * 500, 30) + Math.min(mediumRate * 100, 15);
|
|
2485
|
+
const score = Math.max(0, Math.min(100, Math.round(100 - deduction)));
|
|
2486
|
+
const tier = score >= 80 ? "good" : score >= 50 ? "at-risk" : "critical";
|
|
2487
|
+
return { score, tier };
|
|
2488
|
+
}
|
|
2489
|
+
function classifyScanSignal(key) {
|
|
2490
|
+
const w = SCAN_SIGNAL_WEIGHTS[key];
|
|
2491
|
+
if (w >= 25) return "critical";
|
|
2492
|
+
if (w >= 11) return "high";
|
|
2493
|
+
return "medium";
|
|
2494
|
+
}
|
|
2495
|
+
function computeBlendedSecurityScore(opts) {
|
|
2496
|
+
const { audit, scan } = opts;
|
|
2497
|
+
let critical = audit.critical;
|
|
2498
|
+
let high = audit.high;
|
|
2499
|
+
let medium = audit.medium;
|
|
2500
|
+
let total = audit.total;
|
|
2501
|
+
if (scan) {
|
|
2502
|
+
let scanFindingSum = 0;
|
|
2503
|
+
for (const key of Object.keys(scan.signals)) {
|
|
2504
|
+
const count = scan.signals[key];
|
|
2505
|
+
if (count <= 0) continue;
|
|
2506
|
+
const tier = classifyScanSignal(key);
|
|
2507
|
+
if (tier === "critical") critical += count;
|
|
2508
|
+
else if (tier === "high") high += count;
|
|
2509
|
+
else medium += count;
|
|
2510
|
+
scanFindingSum += count;
|
|
2511
|
+
}
|
|
2512
|
+
const scanContribution = Math.max(scan.totalToolCalls ?? 0, scanFindingSum);
|
|
2513
|
+
total += scanContribution;
|
|
2514
|
+
}
|
|
2515
|
+
return computeSecurityScore({ critical, high, medium, total });
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
// src/blast/index.ts
|
|
2519
|
+
function truncateBlastPath(full) {
|
|
2520
|
+
if (!full) return "";
|
|
2521
|
+
const cleaned = full.replace(/[/\\]+$/, "");
|
|
2522
|
+
const parts = cleaned.split(/[/\\]+/).filter((p) => p.length > 0);
|
|
2523
|
+
if (parts.length <= 2) {
|
|
2524
|
+
return cleaned.startsWith("~") && !cleaned.startsWith("~/") ? cleaned : cleaned.startsWith("~/") ? cleaned : parts.join("/");
|
|
2525
|
+
}
|
|
2526
|
+
return parts.slice(-2).join("/");
|
|
2527
|
+
}
|
|
2528
|
+
function summarizeBlast(result, opts = {}) {
|
|
2529
|
+
const topN = opts.topN ?? 5;
|
|
2530
|
+
const sorted = [...result.reachable].sort((a, b) => {
|
|
2531
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
2532
|
+
return a.label.localeCompare(b.label);
|
|
2533
|
+
});
|
|
2534
|
+
return {
|
|
2535
|
+
score: result.score,
|
|
2536
|
+
exposureCount: result.reachable.length + result.envFindings.length,
|
|
2537
|
+
envExposureCount: result.envFindings.length,
|
|
2538
|
+
worstPaths: sorted.slice(0, topN).map((f) => ({
|
|
2539
|
+
path: truncateBlastPath(f.label),
|
|
2540
|
+
score: f.score
|
|
2541
|
+
}))
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2260
2545
|
// src/index.ts
|
|
2261
|
-
var ENGINE_VERSION = "1.
|
|
2546
|
+
var ENGINE_VERSION = "1.4.0";
|
|
2262
2547
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2263
2548
|
0 && (module.exports = {
|
|
2264
2549
|
BUILTIN_SHIELDS,
|
|
2550
|
+
COST_PER_LOOP_ITER_USD,
|
|
2265
2551
|
DLP_PATTERNS,
|
|
2266
2552
|
ENGINE_VERSION,
|
|
2267
2553
|
FLAGS_WITH_VALUES,
|
|
2268
2554
|
LOOP_MAX_RECORDS,
|
|
2555
|
+
LOOP_THRESHOLD_FOR_WASTE,
|
|
2556
|
+
SCAN_SIGNAL_WEIGHTS,
|
|
2269
2557
|
SENSITIVE_PATH_REGEXES,
|
|
2270
2558
|
analyzePipeChain,
|
|
2271
2559
|
analyzeShellCommand,
|
|
2272
2560
|
checkDangerousSql,
|
|
2561
|
+
classifyAuditEntry,
|
|
2562
|
+
classifyRuleSeverity,
|
|
2563
|
+
classifyScanSignal,
|
|
2273
2564
|
computeArgsHash,
|
|
2565
|
+
computeBlendedSecurityScore,
|
|
2566
|
+
computeScanScore,
|
|
2567
|
+
computeSecurityScore,
|
|
2274
2568
|
detectDangerousEval,
|
|
2275
2569
|
detectDangerousShellExec,
|
|
2276
2570
|
evaluateLoopWindow,
|
|
@@ -2285,12 +2579,16 @@ var ENGINE_VERSION = "1.0.0";
|
|
|
2285
2579
|
isShieldVerdict,
|
|
2286
2580
|
matchSensitivePath,
|
|
2287
2581
|
matchesPattern,
|
|
2582
|
+
narrativeRuleLabel,
|
|
2288
2583
|
normalizeCommandForPolicy,
|
|
2289
2584
|
parseAllSshHostsFromCommand,
|
|
2290
2585
|
redactText,
|
|
2291
2586
|
scanArgs,
|
|
2292
2587
|
scanText,
|
|
2293
2588
|
sensitivePathMatch,
|
|
2589
|
+
summarizeBlast,
|
|
2590
|
+
summarizeScan,
|
|
2591
|
+
truncateBlastPath,
|
|
2294
2592
|
validateOverrides,
|
|
2295
2593
|
validateRegex,
|
|
2296
2594
|
validateShieldDefinition
|