@swestash/agent-lens 0.5.1 → 0.5.3

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.
@@ -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 = 5;
2322
+ var DETECTOR_VERSION = 6;
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;
@@ -2381,6 +2381,33 @@ function outsideProject(filePath, projectPath, configDirs) {
2381
2381
  return false;
2382
2382
  return filePath !== projectPath && !filePath.startsWith(projectPath.replace(/\/$/, "") + "/");
2383
2383
  }
2384
+ var SCOPE_RANK = { unknown: 0, loopback: 1, internal: 2, external: 3 };
2385
+ function netTargetScope(rawCommand) {
2386
+ const s = rawCommand.replace(/(?:-H|--header)\s+(['"]).*?\1/gi, " ").replace(/(?:-H|--header)\s+[^\s'"]+/gi, " ");
2387
+ const authorities = [];
2388
+ for (const m of s.matchAll(/\bhttps?:\/\/(\[[0-9a-fA-F:]+\]|[^/\s"'<>|]+)/gi))
2389
+ authorities.push(m[1]);
2390
+ for (const m of s.matchAll(/(?:^|[\s"'=(])(\[[0-9a-fA-F:]+\]|[A-Za-z0-9.-]+):\d{2,5}(?=[/\s"']|$)/g))
2391
+ authorities.push(m[1]);
2392
+ let scope = "unknown";
2393
+ for (let host of authorities) {
2394
+ host = host.replace(/^\[|\]$/g, "").replace(/^[^@/]*@/, "").toLowerCase();
2395
+ if (!host.includes("::"))
2396
+ host = host.replace(/:\d+$/, "");
2397
+ let hs;
2398
+ if (host === "localhost" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host) || host === "0.0.0.0" || host === "::1")
2399
+ hs = "loopback";
2400
+ else if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host) || // RFC1918
2401
+ /^192\.168\.\d{1,3}\.\d{1,3}$/.test(host) || /^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(host) || /^169\.254\.\d{1,3}\.\d{1,3}$/.test(host) || // link-local
2402
+ host.endsWith(".local") || host.endsWith(".internal") || !host.includes("."))
2403
+ hs = "internal";
2404
+ else
2405
+ hs = "external";
2406
+ if (SCOPE_RANK[hs] > SCOPE_RANK[scope])
2407
+ scope = hs;
2408
+ }
2409
+ return scope;
2410
+ }
2384
2411
  var RULES = [
2385
2412
  // --- Destructive / data-loss (OWASP ASI02 Tool Misuse) -------------------
2386
2413
  {
@@ -2407,7 +2434,8 @@ var RULES = [
2407
2434
  framework_ref: "OWASP ASI02",
2408
2435
  title: "Discards local changes (git reset --hard)",
2409
2436
  tools: ["Bash"],
2410
- baseSeverity: "medium",
2437
+ baseSeverity: "low",
2438
+ // routine dev op — only uncommitted working-tree changes are lost; commits survive in reflog
2411
2439
  test(ctx) {
2412
2440
  const c = ctx.commandBare;
2413
2441
  if (!c || !/\bgit\s+reset\s+--hard\b/i.test(c))
@@ -2421,13 +2449,14 @@ var RULES = [
2421
2449
  framework_ref: "OWASP ASI02",
2422
2450
  title: "Force-push rewrites remote history",
2423
2451
  tools: ["Bash"],
2424
- baseSeverity: "medium",
2452
+ baseSeverity: "low",
2453
+ // feature-branch force-push is routine; only a protected branch (below) is high
2425
2454
  test(ctx) {
2426
2455
  const c = ctx.commandBare;
2427
2456
  if (!c || !/\bgit\s+push\b[^|;&\n]*(--force\b|--force-with-lease\b|\s-f\b)/i.test(c))
2428
2457
  return null;
2429
2458
  const mainBranch = /\b(main|master|release)\b/.test(c);
2430
- return { evidence: evidence(ctx.command), severity: mainBranch ? "high" : "medium", mods: { protected_branch: mainBranch } };
2459
+ return { evidence: evidence(ctx.command), severity: mainBranch ? "high" : "low", mods: { protected_branch: mainBranch } };
2431
2460
  }
2432
2461
  },
2433
2462
  {
@@ -2465,14 +2494,16 @@ var RULES = [
2465
2494
  framework_ref: "OWASP ASI02",
2466
2495
  title: "Overwrites a critical project file",
2467
2496
  tools: ["Write", "Edit", "MultiEdit"],
2468
- baseSeverity: "medium",
2497
+ baseSeverity: "low",
2469
2498
  test(ctx) {
2470
2499
  const f = ctx.filePath;
2471
2500
  if (!f)
2472
2501
  return null;
2473
- if (!/(^|\/)(package-lock\.json|pnpm-lock\.ya?ml|yarn\.lock|Cargo\.lock|go\.sum|\.github\/workflows\/|\.gitlab-ci\.ya?ml)\b/.test(f))
2502
+ const lock = /(^|\/)(package-lock\.json|pnpm-lock\.ya?ml|yarn\.lock|Cargo\.lock|go\.sum)\b/.test(f);
2503
+ const ci = /(^|\/)(\.github\/workflows\/|\.gitlab-ci\.ya?ml)\b/.test(f);
2504
+ if (!lock && !ci)
2474
2505
  return null;
2475
- return { evidence: evidence(f) };
2506
+ return { evidence: evidence(f), severity: ci ? "medium" : "low", mods: { kind: ci ? "ci-config" : "lockfile" } };
2476
2507
  }
2477
2508
  },
2478
2509
  // --- Credential & secret access ------------------------------------------
@@ -2540,22 +2571,29 @@ var RULES = [
2540
2571
  id: "exfil.network_upload",
2541
2572
  category: "exfiltration",
2542
2573
  framework_ref: "MITRE ATLAS AML.T0086",
2543
- title: "Uploads data to an external host",
2574
+ title: "Uploads data to a network host",
2544
2575
  tools: ["Bash"],
2545
2576
  baseSeverity: "high",
2546
2577
  test(ctx) {
2547
2578
  const c = ctx.commandBare;
2548
2579
  if (!c)
2549
2580
  return null;
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);
2581
+ 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)/.test(c);
2551
2582
  if (!upload)
2552
2583
  return null;
2553
- const external = /https?:\/\/(?!(localhost|127\.0\.0\.1|0\.0\.0\.0))/i.test(c);
2554
2584
  const withFile = /(@[^\s'"]+|--upload-file|\s-T\b)/.test(c);
2585
+ const scope = netTargetScope(ctx.command);
2586
+ let severity;
2587
+ if (scope === "external")
2588
+ severity = withFile ? "critical" : "high";
2589
+ else if (scope === "unknown")
2590
+ severity = "high";
2591
+ else
2592
+ severity = withFile ? bumpSeverity(scope === "loopback" ? "info" : "low", 1) : scope === "loopback" ? "info" : "low";
2555
2593
  return {
2556
2594
  evidence: evidence(ctx.command),
2557
- severity: withFile && external ? "critical" : "high",
2558
- mods: { external, with_file: withFile }
2595
+ severity,
2596
+ mods: { target_scope: scope, with_file: withFile }
2559
2597
  };
2560
2598
  }
2561
2599
  },
@@ -2599,7 +2637,14 @@ var RULES = [
2599
2637
  baseSeverity: "high",
2600
2638
  test(ctx) {
2601
2639
  const c = ctx.commandBare;
2602
- if (!c || !/\b(curl|wget)\b[^|]*\|\s*(sudo\s+)?(sh|bash|zsh|python\d?|node)\b/i.test(c))
2640
+ if (!c)
2641
+ return null;
2642
+ const m = /\b(?:curl|wget)\b[^|]*\|\s*(?:sudo\s+)?(sh|bash|zsh|python\d?|node)\b([^|;&]*)/i.exec(c);
2643
+ if (!m)
2644
+ return null;
2645
+ const interp = m[1].toLowerCase();
2646
+ const args = m[2] || "";
2647
+ if (/^(?:node|python)/.test(interp) && /(?:^|\s)-(?:e|c|m|p)\b|(?:^|\s)--(?:eval|command|module|print)\b/.test(args))
2603
2648
  return null;
2604
2649
  return { evidence: evidence(ctx.command) };
2605
2650
  }
@@ -2673,13 +2718,14 @@ var RULES = [
2673
2718
  framework_ref: "OWASP LLM06",
2674
2719
  title: "Writes a file outside the working project",
2675
2720
  tools: ["Write", "Edit", "MultiEdit"],
2676
- baseSeverity: "medium",
2721
+ baseSeverity: "low",
2722
+ // writing to /tmp, scratch dirs, adjacent repos is routine; only a system path (below) is high
2677
2723
  test(ctx) {
2678
2724
  const f = ctx.filePath;
2679
2725
  if (!outsideProject(f, ctx.projectPath, ctx.ownedConfigDirs))
2680
2726
  return null;
2681
2727
  const system = !!f && SYSTEM_PATH.test(f);
2682
- return { evidence: evidence(f), severity: system ? "high" : "medium", mods: { project: ctx.projectPath, system_path: system } };
2728
+ return { evidence: evidence(f), severity: system ? "high" : "low", mods: { project: ctx.projectPath, system_path: system } };
2683
2729
  }
2684
2730
  }
2685
2731
  ];