@profullstack/threatcrush 0.3.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/index.js CHANGED
@@ -9696,7 +9696,7 @@ function severityFor(declared, confidence) {
9696
9696
  }
9697
9697
 
9698
9698
  // src/scan/code-rules.ts
9699
- 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*\(/;
9699
+ 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*\(/;
9700
9700
  var UNTRUSTED_PY = /\brequest\b|\bparams\b|\bflask\b|\bsys\.argv\b|\bos\.environ\b\s*\[/;
9701
9701
  var UNTRUSTED_RB = /\bparams\s*\[|\brequest\b|\bcookies\s*\[/;
9702
9702
  var UNTRUSTED_GO = /\br\s*\.\s*(?:URL|Form|Body|Header|PostForm)\b|\bFormValue\s*\(|\bQuery\s*\(\s*\)\s*\.\s*Get\s*\(|\bmux\.Vars\s*\(/;
@@ -9715,7 +9715,17 @@ function untrustedPatternFor(language) {
9715
9715
  return UNTRUSTED_JS;
9716
9716
  }
9717
9717
  }
9718
- 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;
9718
+ var GENERIC_GUARD = (
9719
+ // `esc(`, `aEsc(`, `htmlEscape(`, `escapeHtml(` — the escaper is almost
9720
+ // never *named* `escapeHtml` in real code. It gets aliased to something
9721
+ // short because it is called on nearly every interpolation, so matching only
9722
+ // the long spellings reported the codebases that escape most rigorously.
9723
+ //
9724
+ // The identifier must END at the escaper (with at most a known output-context
9725
+ // suffix). An earlier, looser form also matched `describe(`, which would have
9726
+ // silenced findings across every test file in every repository.
9727
+ /\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
9728
+ );
9719
9729
  var XXE_GUARD = /FEATURE_SECURE_PROCESSING|setExpandEntityReferences|disallow-doctype-decl|external-general-entities|external-parameter-entities|setXIncludeAware\s*\(\s*false/;
9720
9730
  var CODE_SINK = /\bglobalThis\s*\[|\bconstructor\b|\beval\b|\bFunction\b|\brun\s*\(|\bvm\s*\.\s*run/;
9721
9731
  var EXFIL_SINK = /\bconsole\s*\.\s*(?:log|debug|info|warn|error)\s*\(|\bfetch\s*\(|\baxios\b|\brequest\s*\(|\.\s*send\s*\(/;
@@ -9866,7 +9876,17 @@ var CODE_RULES = [
9866
9876
  cwe: "CWE-79",
9867
9877
  severity: "high",
9868
9878
  languages: ["javascript", "typescript"],
9869
- pattern: /\bdangerouslySetInnerHTML\s*=|\.\s*(?:innerHTML|outerHTML)\s*=\s*(?!\s*['"`]\s*['"`]\s*;?\s*$)|\bdocument\s*\.\s*write(?:ln)?\s*\(|\.\s*insertAdjacentHTML\s*\(/
9879
+ pattern: /\bdangerouslySetInnerHTML\s*=|\.\s*(?:innerHTML|outerHTML)\s*=\s*(?!\s*['"`]\s*['"`]\s*;?\s*$)|\bdocument\s*\.\s*write(?:ln)?\s*\(|\.\s*insertAdjacentHTML\s*\(/,
9880
+ /**
9881
+ * A whole-statement assignment of a string with no interpolation and no
9882
+ * concatenation carries no data, so it cannot carry attacker data. This
9883
+ * was the single largest source of noise: a codebase that builds its UI
9884
+ * with innerHTML reports every static heading and spinner as XSS, and a
9885
+ * rule that flags 40 safe lines to catch one real one gets switched off.
9886
+ *
9887
+ * Line-scoped on purpose — see `lineGuard`.
9888
+ */
9889
+ lineGuard: /(?:innerHTML|outerHTML)\s*=\s*(?:'[^'\\]*'|"[^"\\]*"|`[^`$\\]*`)\s*;?\s*$/
9870
9890
  },
9871
9891
  {
9872
9892
  id: "java-html-writer-concatenation",
@@ -10173,6 +10193,7 @@ function evaluateRule(rule, ctx) {
10173
10193
  const forward = rule.guardForward ?? 0;
10174
10194
  const context = windowText(ctx.lines, ctx.index, back, forward, ctx.prose);
10175
10195
  if (rule.requires && !rule.requires.test(context)) return null;
10196
+ if (rule.lineGuard?.test(line)) return null;
10176
10197
  const guard = rule.guard === void 0 ? GENERIC_GUARD : rule.guard;
10177
10198
  if (guard && (guard.test(line) || guard.test(context))) return null;
10178
10199
  const untrusted = untrustedPatternFor(ctx.language);
@@ -10774,10 +10795,15 @@ function isSuppressed(suppressions, index, ruleId) {
10774
10795
  if (!rules) return false;
10775
10796
  return rules.has("*") || rules.has(ruleId);
10776
10797
  }
10798
+ function isTestPath(relativePath) {
10799
+ const p = relativePath.replace(/\\/g, "/");
10800
+ 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);
10801
+ }
10777
10802
  function scanText(relativePath, text, language = languageOf(relativePath)) {
10778
10803
  const findings = [];
10779
10804
  const lines = text.split("\n");
10780
10805
  const suppressions = collectSuppressions(lines);
10806
+ const inTests = isTestPath(relativePath);
10781
10807
  lines.forEach((line, index) => {
10782
10808
  for (const rule of SECRET_RULES) {
10783
10809
  const match = rule.pattern.exec(line);
@@ -10789,10 +10815,11 @@ function scanText(relativePath, text, language = languageOf(relativePath)) {
10789
10815
  title: rule.name,
10790
10816
  file: relativePath,
10791
10817
  line: index + 1,
10792
- severity: rule.severity,
10818
+ // Reported but not blocking in tests — see isTestPath.
10819
+ severity: inTests ? "low" : rule.severity,
10793
10820
  // A matched credential format is the finding, not a proxy for one.
10794
10821
  confidence: "evidence",
10795
- message: `Possible ${rule.name} detected`,
10822
+ 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`,
10796
10823
  consequence: rule.consequence,
10797
10824
  cwe: rule.cwe,
10798
10825
  excerpt: redactSecret(line.trim()).slice(0, 200),