@swestash/agent-lens 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-lens.js +94 -43
- package/dist/agent-lens.js.map +1 -1
- package/package.json +1 -1
- package/web/assets/{CopyButton-WFGbfzdo.js → CopyButton-CAUFwuiX.js} +1 -1
- package/web/assets/{Dashboard-B0wpOYPt.js → Dashboard-5CwsbaWY.js} +1 -1
- package/web/assets/SecurityView-DCWVNFf5.js +1 -0
- package/web/assets/SessionView-BJywkNAY.js +12 -0
- package/web/assets/{SkillView-COo5sndg.js → SkillView-BDENXN1E.js} +1 -1
- package/web/assets/{SkillsView-C6f11mVw.js → SkillsView-C4SyADuV.js} +1 -1
- package/web/assets/{WorkflowView-B8jF6aE7.js → WorkflowView-BQCMn0Uf.js} +1 -1
- package/web/assets/index-3w3Rb7sW.css +1 -0
- package/web/assets/{index-CnYz-f0y.js → index-BR_zJBAm.js} +1 -1
- package/web/assets/{index-DTfBIYUK.js → index-dvmn2NT_.js} +2 -2
- package/web/assets/{severity-DoLHU1bt.js → severity-4pf1WhNq.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/SecurityView-D9MKBBkp.js +0 -1
- package/web/assets/SessionView-BkOEhK9L.js +0 -12
- package/web/assets/index-DfoRqKEF.css +0 -1
package/dist/agent-lens.js
CHANGED
|
@@ -2319,7 +2319,7 @@ function classify(db, dirty) {
|
|
|
2319
2319
|
|
|
2320
2320
|
// ../ingest/dist/detect.js
|
|
2321
2321
|
import { createHash } from "crypto";
|
|
2322
|
-
var DETECTOR_VERSION =
|
|
2322
|
+
var DETECTOR_VERSION = 5;
|
|
2323
2323
|
function evidence(s, max = 200) {
|
|
2324
2324
|
const one = s.replace(/\s+/g, " ").trim();
|
|
2325
2325
|
return one.length > max ? one.slice(0, max - 1) + "\u2026" : one;
|
|
@@ -2330,7 +2330,7 @@ function findingId(toolCallId, ruleId) {
|
|
|
2330
2330
|
var SYSTEM_PATH = /(^|\s)(\/etc\/|\/usr\/|\/bin\/|\/sbin\/|\/boot\/|\/var\/|\/root\/|~\/\.ssh\/|~\/\.aws\/)/;
|
|
2331
2331
|
var HOME_OR_ROOT_TARGET = /(\s|^)(~\/?|\/|\/\*|\$HOME\/?|\*)(\s|$)/;
|
|
2332
2332
|
var SECRET_FILE = /(^|\/|\s)(\.env(\.[\w.-]+)?|id_rsa|id_ed25519|id_ecdsa|\.ssh\/[\w.-]+|\.aws\/credentials|\.gcp\/|gcloud\/|\.npmrc|\.pypirc|\.netrc|\.pem|\.p12|\.pfx|secrets?\.(ya?ml|json|txt)|credentials(\.json)?)\b/i;
|
|
2333
|
-
var EXAMPLE_FILE = /\.(example|sample|template|dist)(
|
|
2333
|
+
var EXAMPLE_FILE = /\.(example|sample|template|dist)(?![\w-])/i;
|
|
2334
2334
|
var CONTENT_READ_CMD = /\b(cat|less|more|head|tail|bat|xxd|strings|base64|grep|egrep|rg|awk|sed|nl|tac|od|hexdump|cp|scp|sort)\b/i;
|
|
2335
2335
|
var SECRET_VALUE_PATTERNS = [
|
|
2336
2336
|
{ re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/, label: "private-key", critical: true },
|
|
@@ -2341,18 +2341,41 @@ var SECRET_VALUE_PATTERNS = [
|
|
|
2341
2341
|
{ re: /\bsk-[A-Za-z0-9]{20,}\b/, label: "api-secret-key" }
|
|
2342
2342
|
];
|
|
2343
2343
|
var isBash = (t) => t === "Bash";
|
|
2344
|
-
var
|
|
2344
|
+
var stripComments = (c) => c.replace(/(^|\s)#[^\n]*/g, "$1");
|
|
2345
|
+
var neutralizeEcho = (c) => c.replace(/((?:^|[;&|\n(])\s*)(?:echo|printf)\b(?:"(?:[^"\\]|\\.)*"|'[^']*'|[^;&|\n])*/gi, "$1echo");
|
|
2346
|
+
var blankHeredoc = (c) => c.replace(/<<-?\s*(['"]?)(\w+)\1[\s\S]*?\n\s*\2\b/g, "<<HEREDOC");
|
|
2347
|
+
var codeOf = (c) => neutralizeEcho(stripComments(blankHeredoc(c)));
|
|
2348
|
+
var blankQuoted = (c) => c.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'[^']*'/g, "''");
|
|
2349
|
+
var bareOf = (c) => blankQuoted(codeOf(c));
|
|
2350
|
+
function execGeneratedScript(cmd) {
|
|
2351
|
+
const targets = /* @__PURE__ */ new Set();
|
|
2352
|
+
for (const m of cmd.matchAll(/(?:>>?|\btee\b\s+(?:-a\s+)?)\s*["']?([A-Za-z0-9._/~-]+)/g)) {
|
|
2353
|
+
if (m[1] && !m[1].startsWith("/dev/"))
|
|
2354
|
+
targets.add(m[1]);
|
|
2355
|
+
}
|
|
2356
|
+
for (const t of targets) {
|
|
2357
|
+
const e = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2358
|
+
const interp = new RegExp(`\\b(?:sh|bash|zsh|ksh|dash|source)\\s+["']?${e}\\b|(?:^|[\\n;|&(])\\s*\\.\\s+["']?${e}\\b`);
|
|
2359
|
+
const hasPath = /^(\.\/|\/|~\/)/.test(t);
|
|
2360
|
+
const path = hasPath ? new RegExp(`(?:^|[\\n;|&(])\\s*${e}(?:\\s|$|[;&|<>])`) : new RegExp(`(?:^|[\\n;|&(])\\s*\\.\\/${e}\\b`);
|
|
2361
|
+
if (interp.test(cmd) || path.test(cmd))
|
|
2362
|
+
return t;
|
|
2363
|
+
}
|
|
2364
|
+
return null;
|
|
2365
|
+
}
|
|
2345
2366
|
var TEMP_PATH = /^(\/tmp\/|\/var\/folders\/|\/private\/var\/folders\/)/;
|
|
2346
|
-
function isAgentOwnedPath(p) {
|
|
2347
|
-
if (
|
|
2367
|
+
function isAgentOwnedPath(p, configDirs) {
|
|
2368
|
+
if (TEMP_PATH.test(p))
|
|
2369
|
+
return true;
|
|
2370
|
+
if (configDirs.some((d) => p === d || p.startsWith(d + "/")))
|
|
2348
2371
|
return true;
|
|
2349
2372
|
const tmp = process.env.TMPDIR;
|
|
2350
2373
|
return !!tmp && p.startsWith(tmp.replace(/\/$/, "") + "/");
|
|
2351
2374
|
}
|
|
2352
|
-
function outsideProject(filePath, projectPath) {
|
|
2375
|
+
function outsideProject(filePath, projectPath, configDirs) {
|
|
2353
2376
|
if (!filePath || !filePath.startsWith("/"))
|
|
2354
2377
|
return false;
|
|
2355
|
-
if (isAgentOwnedPath(filePath))
|
|
2378
|
+
if (isAgentOwnedPath(filePath, configDirs))
|
|
2356
2379
|
return false;
|
|
2357
2380
|
if (!projectPath)
|
|
2358
2381
|
return false;
|
|
@@ -2368,14 +2391,14 @@ var RULES = [
|
|
|
2368
2391
|
tools: ["Bash"],
|
|
2369
2392
|
baseSeverity: "high",
|
|
2370
2393
|
test(ctx) {
|
|
2371
|
-
const c = ctx.
|
|
2394
|
+
const c = ctx.commandBare;
|
|
2372
2395
|
if (!c)
|
|
2373
2396
|
return null;
|
|
2374
2397
|
const rf = /\brm\s+[^|;&\n]*(-[a-z]*r[a-z]*f|-[a-z]*f[a-z]*r|--recursive[^|;&\n]*--force|--force[^|;&\n]*--recursive)/i;
|
|
2375
2398
|
if (!rf.test(c))
|
|
2376
2399
|
return null;
|
|
2377
2400
|
const dangerousTarget = HOME_OR_ROOT_TARGET.test(c) || /\s\/(?:\s|$)/.test(c) || /\brm\b[^|;&\n]*\*/.test(c);
|
|
2378
|
-
return { evidence: evidence(
|
|
2401
|
+
return { evidence: evidence(ctx.command), severity: dangerousTarget ? "critical" : "high", mods: { dangerous_target: dangerousTarget } };
|
|
2379
2402
|
}
|
|
2380
2403
|
},
|
|
2381
2404
|
{
|
|
@@ -2386,10 +2409,10 @@ var RULES = [
|
|
|
2386
2409
|
tools: ["Bash"],
|
|
2387
2410
|
baseSeverity: "medium",
|
|
2388
2411
|
test(ctx) {
|
|
2389
|
-
const c = ctx.
|
|
2412
|
+
const c = ctx.commandBare;
|
|
2390
2413
|
if (!c || !/\bgit\s+reset\s+--hard\b/i.test(c))
|
|
2391
2414
|
return null;
|
|
2392
|
-
return { evidence: evidence(
|
|
2415
|
+
return { evidence: evidence(ctx.command) };
|
|
2393
2416
|
}
|
|
2394
2417
|
},
|
|
2395
2418
|
{
|
|
@@ -2400,11 +2423,11 @@ var RULES = [
|
|
|
2400
2423
|
tools: ["Bash"],
|
|
2401
2424
|
baseSeverity: "medium",
|
|
2402
2425
|
test(ctx) {
|
|
2403
|
-
const c = ctx.
|
|
2426
|
+
const c = ctx.commandBare;
|
|
2404
2427
|
if (!c || !/\bgit\s+push\b[^|;&\n]*(--force\b|--force-with-lease\b|\s-f\b)/i.test(c))
|
|
2405
2428
|
return null;
|
|
2406
2429
|
const mainBranch = /\b(main|master|release)\b/.test(c);
|
|
2407
|
-
return { evidence: evidence(
|
|
2430
|
+
return { evidence: evidence(ctx.command), severity: mainBranch ? "high" : "medium", mods: { protected_branch: mainBranch } };
|
|
2408
2431
|
}
|
|
2409
2432
|
},
|
|
2410
2433
|
{
|
|
@@ -2415,10 +2438,11 @@ var RULES = [
|
|
|
2415
2438
|
tools: "*",
|
|
2416
2439
|
baseSeverity: "high",
|
|
2417
2440
|
test(ctx) {
|
|
2418
|
-
const
|
|
2441
|
+
const query = typeof ctx.input?.query === "string" ? ctx.input.query : null;
|
|
2442
|
+
const hay = ctx.commandCode ?? query;
|
|
2419
2443
|
if (!hay || !/\b(DROP\s+(TABLE|DATABASE|SCHEMA)|TRUNCATE(\s+TABLE)?)\b/i.test(hay))
|
|
2420
2444
|
return null;
|
|
2421
|
-
return { evidence: evidence(
|
|
2445
|
+
return { evidence: evidence(ctx.command ?? query) };
|
|
2422
2446
|
}
|
|
2423
2447
|
},
|
|
2424
2448
|
{
|
|
@@ -2429,10 +2453,10 @@ var RULES = [
|
|
|
2429
2453
|
tools: ["Bash"],
|
|
2430
2454
|
baseSeverity: "high",
|
|
2431
2455
|
test(ctx) {
|
|
2432
|
-
const c = ctx.
|
|
2456
|
+
const c = ctx.commandBare;
|
|
2433
2457
|
if (!c || !/(\bdd\s+if=|\bmkfs(\.\w+)?\b|\bshred\b)/i.test(c))
|
|
2434
2458
|
return null;
|
|
2435
|
-
return { evidence: evidence(
|
|
2459
|
+
return { evidence: evidence(ctx.command) };
|
|
2436
2460
|
}
|
|
2437
2461
|
},
|
|
2438
2462
|
{
|
|
@@ -2460,12 +2484,18 @@ var RULES = [
|
|
|
2460
2484
|
tools: ["Read", "Bash"],
|
|
2461
2485
|
baseSeverity: "high",
|
|
2462
2486
|
test(ctx) {
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2487
|
+
if (isBash(ctx.toolName)) {
|
|
2488
|
+
const cmd = ctx.commandBare;
|
|
2489
|
+
if (!cmd)
|
|
2490
|
+
return null;
|
|
2491
|
+
const segments = cmd.split(/\|\||&&|[|;\n]/);
|
|
2492
|
+
const reads = segments.some((seg) => SECRET_FILE.test(seg) && !EXAMPLE_FILE.test(seg) && CONTENT_READ_CMD.test(seg));
|
|
2493
|
+
return reads ? { evidence: evidence(ctx.command) } : null;
|
|
2494
|
+
}
|
|
2495
|
+
const f = ctx.filePath;
|
|
2496
|
+
if (!f || !SECRET_FILE.test(f) || EXAMPLE_FILE.test(f))
|
|
2467
2497
|
return null;
|
|
2468
|
-
return { evidence: evidence(
|
|
2498
|
+
return { evidence: evidence(f) };
|
|
2469
2499
|
}
|
|
2470
2500
|
},
|
|
2471
2501
|
{
|
|
@@ -2496,14 +2526,14 @@ var RULES = [
|
|
|
2496
2526
|
tools: ["Bash"],
|
|
2497
2527
|
baseSeverity: "critical",
|
|
2498
2528
|
test(ctx) {
|
|
2499
|
-
const c = ctx.
|
|
2529
|
+
const c = ctx.commandBare;
|
|
2500
2530
|
if (!c)
|
|
2501
2531
|
return null;
|
|
2502
2532
|
if (!/(\||<)\s*(sudo\s+)?(curl|wget|nc|ncat|netcat|ssh|scp)\b/i.test(c))
|
|
2503
2533
|
return null;
|
|
2504
2534
|
if (!/\b(cat|tar|zip|gzip|base64|env|printenv|cat\s|<)\b/i.test(c))
|
|
2505
2535
|
return null;
|
|
2506
|
-
return { evidence: evidence(
|
|
2536
|
+
return { evidence: evidence(ctx.command) };
|
|
2507
2537
|
}
|
|
2508
2538
|
},
|
|
2509
2539
|
{
|
|
@@ -2514,7 +2544,7 @@ var RULES = [
|
|
|
2514
2544
|
tools: ["Bash"],
|
|
2515
2545
|
baseSeverity: "high",
|
|
2516
2546
|
test(ctx) {
|
|
2517
|
-
const c = ctx.
|
|
2547
|
+
const c = ctx.commandBare;
|
|
2518
2548
|
if (!c)
|
|
2519
2549
|
return null;
|
|
2520
2550
|
const upload = /\b(curl|wget)\b[^|;&\n]*(-X\s*POST|--data\b|--data-\w+\b|\s-d\b|-T\b|--upload-file\b|-F\b|--form\b)/i.test(c);
|
|
@@ -2523,7 +2553,7 @@ var RULES = [
|
|
|
2523
2553
|
const external = /https?:\/\/(?!(localhost|127\.0\.0\.1|0\.0\.0\.0))/i.test(c);
|
|
2524
2554
|
const withFile = /(@[^\s'"]+|--upload-file|\s-T\b)/.test(c);
|
|
2525
2555
|
return {
|
|
2526
|
-
evidence: evidence(
|
|
2556
|
+
evidence: evidence(ctx.command),
|
|
2527
2557
|
severity: withFile && external ? "critical" : "high",
|
|
2528
2558
|
mods: { external, with_file: withFile }
|
|
2529
2559
|
};
|
|
@@ -2537,12 +2567,12 @@ var RULES = [
|
|
|
2537
2567
|
tools: ["Bash"],
|
|
2538
2568
|
baseSeverity: "critical",
|
|
2539
2569
|
test(ctx) {
|
|
2540
|
-
const c = ctx.
|
|
2570
|
+
const c = ctx.commandBare;
|
|
2541
2571
|
if (!c)
|
|
2542
2572
|
return null;
|
|
2543
2573
|
if (!/\b(nc|ncat|netcat)\b[^|;&\n]*(-e\b|-c\b|\d{2,5})|\/dev\/tcp\/|bash\s+-i\b/i.test(c))
|
|
2544
2574
|
return null;
|
|
2545
|
-
return { evidence: evidence(
|
|
2575
|
+
return { evidence: evidence(ctx.command) };
|
|
2546
2576
|
}
|
|
2547
2577
|
},
|
|
2548
2578
|
// --- Privilege escalation / guardrail bypass (OWASP LLM06 Excessive Agency)
|
|
@@ -2554,10 +2584,10 @@ var RULES = [
|
|
|
2554
2584
|
tools: "*",
|
|
2555
2585
|
baseSeverity: "high",
|
|
2556
2586
|
test(ctx) {
|
|
2557
|
-
const c = ctx.
|
|
2587
|
+
const c = ctx.commandBare;
|
|
2558
2588
|
if (!c || !/--dangerously-skip-permissions\b|--yolo\b|--dangerously-skip\b/i.test(c))
|
|
2559
2589
|
return null;
|
|
2560
|
-
return { evidence: evidence(
|
|
2590
|
+
return { evidence: evidence(ctx.command) };
|
|
2561
2591
|
}
|
|
2562
2592
|
},
|
|
2563
2593
|
{
|
|
@@ -2568,10 +2598,10 @@ var RULES = [
|
|
|
2568
2598
|
tools: ["Bash"],
|
|
2569
2599
|
baseSeverity: "high",
|
|
2570
2600
|
test(ctx) {
|
|
2571
|
-
const c = ctx.
|
|
2601
|
+
const c = ctx.commandBare;
|
|
2572
2602
|
if (!c || !/\b(curl|wget)\b[^|]*\|\s*(sudo\s+)?(sh|bash|zsh|python\d?|node)\b/i.test(c))
|
|
2573
2603
|
return null;
|
|
2574
|
-
return { evidence: evidence(
|
|
2604
|
+
return { evidence: evidence(ctx.command) };
|
|
2575
2605
|
}
|
|
2576
2606
|
},
|
|
2577
2607
|
{
|
|
@@ -2583,10 +2613,28 @@ var RULES = [
|
|
|
2583
2613
|
baseSeverity: "high",
|
|
2584
2614
|
// root escalation removes a real safety barrier — higher than an in-project op
|
|
2585
2615
|
test(ctx) {
|
|
2586
|
-
const c = ctx.
|
|
2587
|
-
if (!c || !/(
|
|
2616
|
+
const c = ctx.commandBare;
|
|
2617
|
+
if (!c || !/(?:^|[\n;|&(])\s*sudo\b/i.test(c))
|
|
2618
|
+
return null;
|
|
2619
|
+
return { evidence: evidence(ctx.command) };
|
|
2620
|
+
}
|
|
2621
|
+
},
|
|
2622
|
+
{
|
|
2623
|
+
id: "privilege.exec_generated_script",
|
|
2624
|
+
category: "privilege-bypass",
|
|
2625
|
+
framework_ref: "OWASP LLM06",
|
|
2626
|
+
title: "Writes a shell script from inline output and executes it",
|
|
2627
|
+
tools: ["Bash"],
|
|
2628
|
+
baseSeverity: "high",
|
|
2629
|
+
test(ctx) {
|
|
2630
|
+
const raw = ctx.command;
|
|
2631
|
+
if (!raw)
|
|
2632
|
+
return null;
|
|
2633
|
+
const file = execGeneratedScript(stripComments(raw));
|
|
2634
|
+
if (!file)
|
|
2588
2635
|
return null;
|
|
2589
|
-
|
|
2636
|
+
const critical = /(?:^|["'\s;|&(])sudo\b|\brm\s+[^|;&\n]*-[a-z]*r[a-z]*f|\bchmod\s+0?777\b|\bmkfs|\bdd\s+if=/i.test(raw);
|
|
2637
|
+
return { evidence: evidence(raw), severity: critical ? "critical" : "high", mods: { script_file: file } };
|
|
2590
2638
|
}
|
|
2591
2639
|
},
|
|
2592
2640
|
{
|
|
@@ -2597,10 +2645,10 @@ var RULES = [
|
|
|
2597
2645
|
tools: ["Bash"],
|
|
2598
2646
|
baseSeverity: "medium",
|
|
2599
2647
|
test(ctx) {
|
|
2600
|
-
const c = ctx.
|
|
2648
|
+
const c = ctx.commandBare;
|
|
2601
2649
|
if (!c || !/\bchmod\s+(-[a-zA-Z]+\s+)*0?777\b/i.test(c))
|
|
2602
2650
|
return null;
|
|
2603
|
-
return { evidence: evidence(
|
|
2651
|
+
return { evidence: evidence(ctx.command) };
|
|
2604
2652
|
}
|
|
2605
2653
|
},
|
|
2606
2654
|
{
|
|
@@ -2611,12 +2659,12 @@ var RULES = [
|
|
|
2611
2659
|
tools: ["Bash"],
|
|
2612
2660
|
baseSeverity: "high",
|
|
2613
2661
|
test(ctx) {
|
|
2614
|
-
const c = ctx.
|
|
2662
|
+
const c = ctx.commandBare;
|
|
2615
2663
|
if (!c)
|
|
2616
2664
|
return null;
|
|
2617
2665
|
if (!/\bcrontab\s+-|\bsystemctl\s+enable\b|\blaunchctl\s+load\b|>>\s*~\/\.(bashrc|zshrc|profile|bash_profile)\b|\/etc\/(cron|systemd|rc\.local)/i.test(c))
|
|
2618
2666
|
return null;
|
|
2619
|
-
return { evidence: evidence(
|
|
2667
|
+
return { evidence: evidence(ctx.command) };
|
|
2620
2668
|
}
|
|
2621
2669
|
},
|
|
2622
2670
|
{
|
|
@@ -2628,7 +2676,7 @@ var RULES = [
|
|
|
2628
2676
|
baseSeverity: "medium",
|
|
2629
2677
|
test(ctx) {
|
|
2630
2678
|
const f = ctx.filePath;
|
|
2631
|
-
if (!outsideProject(f, ctx.projectPath))
|
|
2679
|
+
if (!outsideProject(f, ctx.projectPath, ctx.ownedConfigDirs))
|
|
2632
2680
|
return null;
|
|
2633
2681
|
const system = !!f && SYSTEM_PATH.test(f);
|
|
2634
2682
|
return { evidence: evidence(f), severity: system ? "high" : "medium", mods: { project: ctx.projectPath, system_path: system } };
|
|
@@ -2638,7 +2686,7 @@ var RULES = [
|
|
|
2638
2686
|
function appliesTo(rule, toolName) {
|
|
2639
2687
|
return rule.tools === "*" || rule.tools.includes(toolName);
|
|
2640
2688
|
}
|
|
2641
|
-
function buildContext(row, projectPath) {
|
|
2689
|
+
function buildContext(row, projectPath, ownedConfigDirs) {
|
|
2642
2690
|
let input = null;
|
|
2643
2691
|
try {
|
|
2644
2692
|
input = row.input_json ? JSON.parse(row.input_json) : null;
|
|
@@ -2646,8 +2694,10 @@ function buildContext(row, projectPath) {
|
|
|
2646
2694
|
input = null;
|
|
2647
2695
|
}
|
|
2648
2696
|
const command = typeof input?.command === "string" ? input.command : null;
|
|
2697
|
+
const commandCode = command != null ? codeOf(command) : null;
|
|
2698
|
+
const commandBare = command != null ? bareOf(command) : null;
|
|
2649
2699
|
const filePath = typeof input?.file_path === "string" ? input.file_path : null;
|
|
2650
|
-
return { toolName: row.tool_name, command, filePath, input, resultSummary: row.result_summary, status: row.status, projectPath };
|
|
2700
|
+
return { toolName: row.tool_name, command, commandCode, commandBare, filePath, input, resultSummary: row.result_summary, status: row.status, projectPath, ownedConfigDirs };
|
|
2651
2701
|
}
|
|
2652
2702
|
function detect(db, dirty) {
|
|
2653
2703
|
const incremental = dirty != null;
|
|
@@ -2661,6 +2711,7 @@ function detect(db, dirty) {
|
|
|
2661
2711
|
})(dirty);
|
|
2662
2712
|
}
|
|
2663
2713
|
const scope = incremental ? " WHERE tc.session_id IN (SELECT id FROM _dirty_sec)" : "";
|
|
2714
|
+
const ownedConfigDirs = db.prepare("SELECT config_dir FROM sources WHERE config_dir IS NOT NULL").all().map((r) => r.config_dir.replace(/\/$/, "")).filter(Boolean);
|
|
2664
2715
|
const projPath = /* @__PURE__ */ new Map();
|
|
2665
2716
|
for (const r of db.prepare(`SELECT s.id id, p.path path FROM sessions s JOIN projects p ON p.id = s.project_id${incremental ? " WHERE s.id IN (SELECT id FROM _dirty_sec)" : ""}`).all()) {
|
|
2666
2717
|
projPath.set(r.id, r.path);
|
|
@@ -2674,7 +2725,7 @@ function detect(db, dirty) {
|
|
|
2674
2725
|
const tx = db.transaction(() => {
|
|
2675
2726
|
db.exec(delScope);
|
|
2676
2727
|
for (const row of rows) {
|
|
2677
|
-
const ctx = buildContext(row, projPath.get(row.session_id) ?? null);
|
|
2728
|
+
const ctx = buildContext(row, projPath.get(row.session_id) ?? null, ownedConfigDirs);
|
|
2678
2729
|
for (const rule of RULES) {
|
|
2679
2730
|
if (!appliesTo(rule, ctx.toolName))
|
|
2680
2731
|
continue;
|