@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/daemon.js +32 -5
- package/dist/daemon.js.map +1 -1
- package/dist/index.js +32 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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 =
|
|
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
|
-
|
|
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),
|