@profullstack/threatcrush 0.3.0 → 0.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/daemon.js CHANGED
@@ -4426,7 +4426,7 @@ function severityFor(declared, confidence) {
4426
4426
  }
4427
4427
 
4428
4428
  // src/scan/code-rules.ts
4429
- var UNTRUSTED_JS = /\b(?:req|request|ctx|context)\s*\.\s*(?:body|query|params|param|headers|cookies|url|files)\b|\bprocess\.argv\b|\bwindow\.location\b|\bdocument\.location\b|\blocation\.(?:search|hash|href)\b|\bsearchParams\b|\bgetParameter\s*\(|\bgetQueryString\s*\(|\bgetInputStream\s*\(/;
4429
+ var UNTRUSTED_JS = /\b(?:req|request|ctx|context)\s*\.\s*(?:body|query|params|param|headers|cookies|url|files)\b|\bprocess\.argv\b|\bwindow\.location\b|\bdocument\.location\b|\blocation\.(?:search|hash|href)\b|\bsearchParams\s*\.\s*(?:get|getAll|has|entries|keys|values|forEach)\b|\bgetParameter\s*\(|\bgetQueryString\s*\(|\bgetInputStream\s*\(/;
4430
4430
  var UNTRUSTED_PY = /\brequest\b|\bparams\b|\bflask\b|\bsys\.argv\b|\bos\.environ\b\s*\[/;
4431
4431
  var UNTRUSTED_RB = /\bparams\s*\[|\brequest\b|\bcookies\s*\[/;
4432
4432
  var UNTRUSTED_GO = /\br\s*\.\s*(?:URL|Form|Body|Header|PostForm)\b|\bFormValue\s*\(|\bQuery\s*\(\s*\)\s*\.\s*Get\s*\(|\bmux\.Vars\s*\(/;
@@ -4445,7 +4445,17 @@ function untrustedPatternFor(language) {
4445
4445
  return UNTRUSTED_JS;
4446
4446
  }
4447
4447
  }
4448
- var GENERIC_GUARD = /\ballow(?:ed|list|_list|ed_hosts)?\b|\bwhitelist\b|\bescape(?:Html|Html4|Xml|Sql)?\s*\(|\bhtml_escape\b|\bhtmlspecialchars\s*\(|\bsanitiz\w*\b|\bencoded\b|\brealpath\b|\bcommonpath\b|\bresolve\(\)\.startsWith\b|\bprocess\.env\b|\bos\.environ\b|\bgetenv\b|\bENV\s*\[|setObjectInputFilter|ObjectInputFilter/i;
4448
+ var GENERIC_GUARD = (
4449
+ // `esc(`, `aEsc(`, `htmlEscape(`, `escapeHtml(` — the escaper is almost
4450
+ // never *named* `escapeHtml` in real code. It gets aliased to something
4451
+ // short because it is called on nearly every interpolation, so matching only
4452
+ // the long spellings reported the codebases that escape most rigorously.
4453
+ //
4454
+ // The identifier must END at the escaper (with at most a known output-context
4455
+ // suffix). An earlier, looser form also matched `describe(`, which would have
4456
+ // silenced findings across every test file in every repository.
4457
+ /\ballow(?:ed|list|_list|ed_hosts)?\b|\bwhitelist\b|\b\w{0,6}[Ee]sc(?:ape)?(?:[Hh]tml|HTML|[Xx]ml|XML|[Ss]ql|[Aa]ttr|[Jj]s|[Uu]ri|[Uu]rl)?\s*\(|\bhtml_escape\b|\bhtmlspecialchars\s*\(|\bsanitiz\w*\b|\bencoded\b|\brealpath\b|\bcommonpath\b|\bresolve\(\)\.startsWith\b|\bprocess\.env\b|\bos\.environ\b|\bgetenv\b|\bENV\s*\[|setObjectInputFilter|ObjectInputFilter/i
4458
+ );
4449
4459
  var XXE_GUARD = /FEATURE_SECURE_PROCESSING|setExpandEntityReferences|disallow-doctype-decl|external-general-entities|external-parameter-entities|setXIncludeAware\s*\(\s*false/;
4450
4460
  var CODE_SINK = /\bglobalThis\s*\[|\bconstructor\b|\beval\b|\bFunction\b|\brun\s*\(|\bvm\s*\.\s*run/;
4451
4461
  var EXFIL_SINK = /\bconsole\s*\.\s*(?:log|debug|info|warn|error)\s*\(|\bfetch\s*\(|\baxios\b|\brequest\s*\(|\.\s*send\s*\(/;
@@ -4596,7 +4606,17 @@ var CODE_RULES = [
4596
4606
  cwe: "CWE-79",
4597
4607
  severity: "high",
4598
4608
  languages: ["javascript", "typescript"],
4599
- pattern: /\bdangerouslySetInnerHTML\s*=|\.\s*(?:innerHTML|outerHTML)\s*=\s*(?!\s*['"`]\s*['"`]\s*;?\s*$)|\bdocument\s*\.\s*write(?:ln)?\s*\(|\.\s*insertAdjacentHTML\s*\(/
4609
+ pattern: /\bdangerouslySetInnerHTML\s*=|\.\s*(?:innerHTML|outerHTML)\s*=\s*(?!\s*['"`]\s*['"`]\s*;?\s*$)|\bdocument\s*\.\s*write(?:ln)?\s*\(|\.\s*insertAdjacentHTML\s*\(/,
4610
+ /**
4611
+ * A whole-statement assignment of a string with no interpolation and no
4612
+ * concatenation carries no data, so it cannot carry attacker data. This
4613
+ * was the single largest source of noise: a codebase that builds its UI
4614
+ * with innerHTML reports every static heading and spinner as XSS, and a
4615
+ * rule that flags 40 safe lines to catch one real one gets switched off.
4616
+ *
4617
+ * Line-scoped on purpose — see `lineGuard`.
4618
+ */
4619
+ lineGuard: /(?:innerHTML|outerHTML)\s*=\s*(?:'[^'\\]*'|"[^"\\]*"|`[^`$\\]*`)\s*;?\s*$/
4600
4620
  },
4601
4621
  {
4602
4622
  id: "java-html-writer-concatenation",
@@ -4903,6 +4923,7 @@ function evaluateRule(rule, ctx) {
4903
4923
  const forward = rule.guardForward ?? 0;
4904
4924
  const context = windowText(ctx.lines, ctx.index, back, forward, ctx.prose);
4905
4925
  if (rule.requires && !rule.requires.test(context)) return null;
4926
+ if (rule.lineGuard?.test(line)) return null;
4906
4927
  const guard = rule.guard === void 0 ? GENERIC_GUARD : rule.guard;
4907
4928
  if (guard && (guard.test(line) || guard.test(context))) return null;
4908
4929
  const untrusted = untrustedPatternFor(ctx.language);
@@ -5504,10 +5525,15 @@ function isSuppressed(suppressions, index, ruleId) {
5504
5525
  if (!rules) return false;
5505
5526
  return rules.has("*") || rules.has(ruleId);
5506
5527
  }
5528
+ function isTestPath(relativePath) {
5529
+ const p = relativePath.replace(/\\/g, "/");
5530
+ return /(?:^|\/)(?:tests?|__tests__|__mocks__|spec|specs|fixtures?|mocks?|e2e|testdata)\//i.test(p) || /(?:^|\/)(?:test|conftest)_[^/]+$/i.test(p) || /[._-](?:test|spec)\.[a-z]+$/i.test(p) || /_test\.[a-z]+$/i.test(p);
5531
+ }
5507
5532
  function scanText(relativePath, text, language = languageOf(relativePath)) {
5508
5533
  const findings = [];
5509
5534
  const lines = text.split("\n");
5510
5535
  const suppressions = collectSuppressions(lines);
5536
+ const inTests = isTestPath(relativePath);
5511
5537
  lines.forEach((line, index) => {
5512
5538
  for (const rule of SECRET_RULES) {
5513
5539
  const match = rule.pattern.exec(line);
@@ -5519,10 +5545,11 @@ function scanText(relativePath, text, language = languageOf(relativePath)) {
5519
5545
  title: rule.name,
5520
5546
  file: relativePath,
5521
5547
  line: index + 1,
5522
- severity: rule.severity,
5548
+ // Reported but not blocking in tests — see isTestPath.
5549
+ severity: inTests ? "low" : rule.severity,
5523
5550
  // A matched credential format is the finding, not a proxy for one.
5524
5551
  confidence: "evidence",
5525
- message: `Possible ${rule.name} detected`,
5552
+ message: inTests ? `Possible ${rule.name} detected in a test file \u2014 usually a fixture, still worth confirming it is not a live credential` : `Possible ${rule.name} detected`,
5526
5553
  consequence: rule.consequence,
5527
5554
  cwe: rule.cwe,
5528
5555
  excerpt: redactSecret(line.trim()).slice(0, 200),