@sikkuumiii/defuse 0.0.0-stage → 0.5.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/LICENSE +235 -0
- package/README.md +561 -2
- package/dist/src/cli.d.ts +20 -0
- package/dist/src/cli.js +619 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/core/analyze.d.ts +178 -0
- package/dist/src/core/analyze.js +834 -0
- package/dist/src/core/analyze.js.map +1 -0
- package/dist/src/core/coverage.d.ts +87 -0
- package/dist/src/core/coverage.js +181 -0
- package/dist/src/core/coverage.js.map +1 -0
- package/dist/src/core/finding.d.ts +196 -0
- package/dist/src/core/finding.js +178 -0
- package/dist/src/core/finding.js.map +1 -0
- package/dist/src/core/licence.d.ts +50 -0
- package/dist/src/core/licence.js +56 -0
- package/dist/src/core/licence.js.map +1 -0
- package/dist/src/core/paths.d.ts +21 -0
- package/dist/src/core/paths.js +58 -0
- package/dist/src/core/paths.js.map +1 -0
- package/dist/src/core/score.d.ts +137 -0
- package/dist/src/core/score.js +232 -0
- package/dist/src/core/score.js.map +1 -0
- package/dist/src/dast/targets.d.ts +30 -0
- package/dist/src/dast/targets.js +79 -0
- package/dist/src/dast/targets.js.map +1 -0
- package/dist/src/dast/zap.d.ts +42 -0
- package/dist/src/dast/zap.js +184 -0
- package/dist/src/dast/zap.js.map +1 -0
- package/dist/src/engine/scan.d.ts +80 -0
- package/dist/src/engine/scan.js +129 -0
- package/dist/src/engine/scan.js.map +1 -0
- package/dist/src/engine/shapes.d.ts +47 -0
- package/dist/src/engine/shapes.js +328 -0
- package/dist/src/engine/shapes.js.map +1 -0
- package/dist/src/engine/walk.d.ts +60 -0
- package/dist/src/engine/walk.js +156 -0
- package/dist/src/engine/walk.js.map +1 -0
- package/dist/src/parse/astDump.d.ts +31 -0
- package/dist/src/parse/astDump.js +66 -0
- package/dist/src/parse/astDump.js.map +1 -0
- package/dist/src/parse/languages.d.ts +70 -0
- package/dist/src/parse/languages.js +174 -0
- package/dist/src/parse/languages.js.map +1 -0
- package/dist/src/parse/location.d.ts +17 -0
- package/dist/src/parse/location.js +38 -0
- package/dist/src/parse/location.js.map +1 -0
- package/dist/src/parse/node-grammars.d.ts +12 -0
- package/dist/src/parse/node-grammars.js +35 -0
- package/dist/src/parse/node-grammars.js.map +1 -0
- package/dist/src/parse/parser.d.ts +126 -0
- package/dist/src/parse/parser.js +202 -0
- package/dist/src/parse/parser.js.map +1 -0
- package/dist/src/parse/tree-store.d.ts +139 -0
- package/dist/src/parse/tree-store.js +236 -0
- package/dist/src/parse/tree-store.js.map +1 -0
- package/dist/src/platform/dependency-rule.d.ts +27 -0
- package/dist/src/platform/dependency-rule.js +21 -0
- package/dist/src/platform/dependency-rule.js.map +1 -0
- package/dist/src/platform/deps.d.ts +86 -0
- package/dist/src/platform/deps.js +304 -0
- package/dist/src/platform/deps.js.map +1 -0
- package/dist/src/platform/php-facts.d.ts +49 -0
- package/dist/src/platform/php-facts.js +67 -0
- package/dist/src/platform/php-facts.js.map +1 -0
- package/dist/src/platform/runtime-version.d.ts +78 -0
- package/dist/src/platform/runtime-version.js +573 -0
- package/dist/src/platform/runtime-version.js.map +1 -0
- package/dist/src/report/colors.d.ts +50 -0
- package/dist/src/report/colors.js +97 -0
- package/dist/src/report/colors.js.map +1 -0
- package/dist/src/report/coverage-table.d.ts +90 -0
- package/dist/src/report/coverage-table.js +291 -0
- package/dist/src/report/coverage-table.js.map +1 -0
- package/dist/src/report/html-report.d.ts +66 -0
- package/dist/src/report/html-report.js +489 -0
- package/dist/src/report/html-report.js.map +1 -0
- package/dist/src/report/human.d.ts +44 -0
- package/dist/src/report/human.js +575 -0
- package/dist/src/report/human.js.map +1 -0
- package/dist/src/report/json.d.ts +24 -0
- package/dist/src/report/json.js +178 -0
- package/dist/src/report/json.js.map +1 -0
- package/dist/src/report/mark.d.ts +17 -0
- package/dist/src/report/mark.js +39 -0
- package/dist/src/report/mark.js.map +1 -0
- package/dist/src/report/pricing.d.ts +33 -0
- package/dist/src/report/pricing.js +343 -0
- package/dist/src/report/pricing.js.map +1 -0
- package/dist/src/report/sarif.d.ts +39 -0
- package/dist/src/report/sarif.js +219 -0
- package/dist/src/report/sarif.js.map +1 -0
- package/dist/src/report/site.d.ts +87 -0
- package/dist/src/report/site.js +742 -0
- package/dist/src/report/site.js.map +1 -0
- package/dist/src/rules/code-injection.d.ts +47 -0
- package/dist/src/rules/code-injection.js +326 -0
- package/dist/src/rules/code-injection.js.map +1 -0
- package/dist/src/rules/command-injection.d.ts +30 -0
- package/dist/src/rules/command-injection.js +286 -0
- package/dist/src/rules/command-injection.js.map +1 -0
- package/dist/src/rules/contract.d.ts +198 -0
- package/dist/src/rules/contract.js +39 -0
- package/dist/src/rules/contract.js.map +1 -0
- package/dist/src/rules/format-string.d.ts +46 -0
- package/dist/src/rules/format-string.js +146 -0
- package/dist/src/rules/format-string.js.map +1 -0
- package/dist/src/rules/hardcoded-secret.d.ts +38 -0
- package/dist/src/rules/hardcoded-secret.js +440 -0
- package/dist/src/rules/hardcoded-secret.js.map +1 -0
- package/dist/src/rules/lib/guards.d.ts +168 -0
- package/dist/src/rules/lib/guards.js +1179 -0
- package/dist/src/rules/lib/guards.js.map +1 -0
- package/dist/src/rules/lib/html-safety.d.ts +75 -0
- package/dist/src/rules/lib/html-safety.js +755 -0
- package/dist/src/rules/lib/html-safety.js.map +1 -0
- package/dist/src/rules/lib/local-build.d.ts +59 -0
- package/dist/src/rules/lib/local-build.js +320 -0
- package/dist/src/rules/lib/local-build.js.map +1 -0
- package/dist/src/rules/lib/strings.d.ts +208 -0
- package/dist/src/rules/lib/strings.js +1970 -0
- package/dist/src/rules/lib/strings.js.map +1 -0
- package/dist/src/rules/path-traversal.d.ts +33 -0
- package/dist/src/rules/path-traversal.js +96 -0
- package/dist/src/rules/path-traversal.js.map +1 -0
- package/dist/src/rules/registry.d.ts +19 -0
- package/dist/src/rules/registry.js +77 -0
- package/dist/src/rules/registry.js.map +1 -0
- package/dist/src/rules/sql-injection.d.ts +36 -0
- package/dist/src/rules/sql-injection.js +299 -0
- package/dist/src/rules/sql-injection.js.map +1 -0
- package/dist/src/rules/ssrf.d.ts +39 -0
- package/dist/src/rules/ssrf.js +111 -0
- package/dist/src/rules/ssrf.js.map +1 -0
- package/dist/src/rules/unbounded-copy.d.ts +37 -0
- package/dist/src/rules/unbounded-copy.js +147 -0
- package/dist/src/rules/unbounded-copy.js.map +1 -0
- package/dist/src/rules/unsafe-deserialization.d.ts +43 -0
- package/dist/src/rules/unsafe-deserialization.js +276 -0
- package/dist/src/rules/unsafe-deserialization.js.map +1 -0
- package/dist/src/rules/weak-hash.d.ts +37 -0
- package/dist/src/rules/weak-hash.js +197 -0
- package/dist/src/rules/weak-hash.js.map +1 -0
- package/dist/src/rules/xss.d.ts +38 -0
- package/dist/src/rules/xss.js +527 -0
- package/dist/src/rules/xss.js.map +1 -0
- package/dist/src/taint/content-type.d.ts +19 -0
- package/dist/src/taint/content-type.js +20 -0
- package/dist/src/taint/content-type.js.map +1 -0
- package/dist/src/taint/dictionaries.d.ts +23 -0
- package/dist/src/taint/dictionaries.js +1778 -0
- package/dist/src/taint/dictionaries.js.map +1 -0
- package/dist/src/taint/fold.d.ts +59 -0
- package/dist/src/taint/fold.js +1177 -0
- package/dist/src/taint/fold.js.map +1 -0
- package/dist/src/taint/java-scope.d.ts +61 -0
- package/dist/src/taint/java-scope.js +212 -0
- package/dist/src/taint/java-scope.js.map +1 -0
- package/dist/src/taint/local-map.d.ts +79 -0
- package/dist/src/taint/local-map.js +603 -0
- package/dist/src/taint/local-map.js.map +1 -0
- package/dist/src/taint/project.d.ts +172 -0
- package/dist/src/taint/project.js +617 -0
- package/dist/src/taint/project.js.map +1 -0
- package/dist/src/taint/tracer.d.ts +265 -0
- package/dist/src/taint/tracer.js +3817 -0
- package/dist/src/taint/tracer.js.map +1 -0
- package/dist/src/taint/types.d.ts +484 -0
- package/dist/src/taint/types.js +47 -0
- package/dist/src/taint/types.js.map +1 -0
- package/dist/src/taint/visibility.d.ts +25 -0
- package/dist/src/taint/visibility.js +96 -0
- package/dist/src/taint/visibility.js.map +1 -0
- package/docs/examples/defuse.json +20 -0
- package/docs/examples/dvwa-urls.json +14 -0
- package/docs/examples/github-code-scanning.yml +54 -0
- package/docs/examples/tiny.js +4 -0
- package/package.json +73 -4
- package/src/cli.ts +747 -0
- package/src/core/analyze.ts +1072 -0
- package/src/core/coverage.ts +269 -0
- package/src/core/finding.ts +311 -0
- package/src/core/licence.ts +61 -0
- package/src/core/paths.ts +59 -0
- package/src/core/score.ts +353 -0
- package/src/dast/targets.ts +121 -0
- package/src/dast/zap.ts +246 -0
- package/src/engine/scan.ts +200 -0
- package/src/engine/shapes.ts +348 -0
- package/src/engine/walk.ts +196 -0
- package/src/parse/astDump.ts +85 -0
- package/src/parse/languages.ts +232 -0
- package/src/parse/location.ts +44 -0
- package/src/parse/node-grammars.ts +41 -0
- package/src/parse/parser.ts +262 -0
- package/src/parse/tree-store.ts +301 -0
- package/src/platform/dependency-rule.ts +33 -0
- package/src/platform/deps.ts +374 -0
- package/src/platform/php-facts.ts +85 -0
- package/src/platform/runtime-version.ts +710 -0
- package/src/report/colors.ts +107 -0
- package/src/report/coverage-table.ts +359 -0
- package/src/report/html-report.ts +601 -0
- package/src/report/human.ts +811 -0
- package/src/report/json.ts +189 -0
- package/src/report/mark.ts +47 -0
- package/src/report/pricing.ts +374 -0
- package/src/report/sarif.ts +235 -0
- package/src/report/site.ts +760 -0
- package/src/rules/code-injection.ts +346 -0
- package/src/rules/command-injection.ts +296 -0
- package/src/rules/contract.ts +220 -0
- package/src/rules/format-string.ts +158 -0
- package/src/rules/hardcoded-secret.ts +485 -0
- package/src/rules/lib/guards.ts +1235 -0
- package/src/rules/lib/html-safety.ts +793 -0
- package/src/rules/lib/local-build.ts +329 -0
- package/src/rules/lib/strings.ts +2034 -0
- package/src/rules/path-traversal.ts +101 -0
- package/src/rules/registry.ts +90 -0
- package/src/rules/sql-injection.ts +311 -0
- package/src/rules/ssrf.ts +118 -0
- package/src/rules/unbounded-copy.ts +162 -0
- package/src/rules/unsafe-deserialization.ts +291 -0
- package/src/rules/weak-hash.ts +226 -0
- package/src/rules/xss.ts +572 -0
- package/src/taint/content-type.ts +20 -0
- package/src/taint/dictionaries.ts +1855 -0
- package/src/taint/fold.ts +1206 -0
- package/src/taint/java-scope.ts +200 -0
- package/src/taint/local-map.ts +594 -0
- package/src/taint/project.ts +774 -0
- package/src/taint/tracer.ts +4298 -0
- package/src/taint/types.ts +511 -0
- package/src/taint/visibility.ts +100 -0
- package/tsconfig.json +61 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VALIDATION GUARDS - cleanliness that lives in the control flow.
|
|
3
|
+
* ==============================================================
|
|
4
|
+
*
|
|
5
|
+
* Every sanitiser this engine knew about until now was value-in / value-out:
|
|
6
|
+
*
|
|
7
|
+
* $safe = esc_html($dirty); // takes a dirty value, returns a clean one
|
|
8
|
+
*
|
|
9
|
+
* A GUARD is a different shape. It transforms nothing and returns a boolean,
|
|
10
|
+
* and the fact it establishes is true only where the program can stand after
|
|
11
|
+
* that boolean came out a particular way:
|
|
12
|
+
*
|
|
13
|
+
* if (is_numeric($octet[0]) && is_numeric($octet[1])) {
|
|
14
|
+
* $target = $octet[0] . '.' . $octet[1];
|
|
15
|
+
* shell_exec('ping ' . $target); // <- cannot hold a metacharacter
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* That is DVWA's `exec/impossible.php`, the author's deliberate FIX, and this
|
|
19
|
+
* scanner reported it for weeks. It is the seventh time this project has
|
|
20
|
+
* punished a fix.
|
|
21
|
+
*
|
|
22
|
+
* WHY THE BAR FOR A GUARD IS SO HIGH.
|
|
23
|
+
*
|
|
24
|
+
* A sanitiser rule that fires too often costs precision. A GUARD rule that
|
|
25
|
+
* fires too often costs SILENCE, and silence is the failure this scanner
|
|
26
|
+
* cannot detect in itself - a tool that has gone quiet looks exactly like a
|
|
27
|
+
* tool that has been fixed. This project has over-corrected before: the
|
|
28
|
+
* lowercase-word rejection that dropped the corpus from 48 findings to 32, and
|
|
29
|
+
* the mathjs.eval regression that was caught only because dvna's flow count
|
|
30
|
+
* fell from 3 to 2.
|
|
31
|
+
*
|
|
32
|
+
* So a check earns a place here on exactly one condition: being on the
|
|
33
|
+
* passing side of it must prove what the value can contain. Three strengths:
|
|
34
|
+
*
|
|
35
|
+
* digits only digits (is_numeric, ctype_digit, preg_match('/^\d+$/'))
|
|
36
|
+
* word only letters, digits and underscore (ctype_alnum, /^\w+$/)
|
|
37
|
+
* fixed only values the programmer wrote down ($x === 'png',
|
|
38
|
+
* in_array($x, ['jpg', 'png'], true))
|
|
39
|
+
*
|
|
40
|
+
* and each one clears a different set of sinks - see guardClears(). A word is
|
|
41
|
+
* not enough for a SQL query unless it sits inside quotes (`WHERE id = id` is
|
|
42
|
+
* true for every row), so `word` hands SQL to the same quote check that
|
|
43
|
+
* escaping uses.
|
|
44
|
+
*
|
|
45
|
+
* These do NOT count, and they are the ones that look reassuring:
|
|
46
|
+
*
|
|
47
|
+
* strlen($x) < 100 a hundred characters of `; rm -rf /` fits fine
|
|
48
|
+
* $x !== '' proves only that something is there
|
|
49
|
+
* isset($x) proves only that it exists
|
|
50
|
+
* preg_match('/\d+/', $x) not anchored: "1; rm -rf /" contains a digit
|
|
51
|
+
* preg_match('/^\d+$/m') multi-line: "1\n; rm -rf /" has a line of digits
|
|
52
|
+
* preg_match(...) !== 0 preg_match returns FALSE on an error, and
|
|
53
|
+
* false !== 0 - the error would pass as a match
|
|
54
|
+
*
|
|
55
|
+
* WHERE A FACT HOLDS (guardFactsAt):
|
|
56
|
+
*
|
|
57
|
+
* if (CHECK) { here } the true branch
|
|
58
|
+
* if (!CHECK) { ... } else { here } the else of a negated check
|
|
59
|
+
* if (!CHECK) { exit; } here after a branch that always leaves
|
|
60
|
+
* CHECK ? here : not-here the matching arm of a ternary
|
|
61
|
+
*
|
|
62
|
+
* and only while the checked value is not written again: a write to it
|
|
63
|
+
* anywhere in that region - including the condition - drops the fact, because
|
|
64
|
+
* `if (is_numeric($x)) { $x = $_GET['y']; run($x); }` is a real injection. The
|
|
65
|
+
* walk also stops at a function boundary: a closure defined inside the branch
|
|
66
|
+
* may run long after the value changed.
|
|
67
|
+
*
|
|
68
|
+
* NOT covered, on purpose or not yet - each one only means a finding stays:
|
|
69
|
+
* Java (no checks recognised at all yet); Python's `abort()` and other calls
|
|
70
|
+
* that never return; compiled regex objects (`re.compile(...).fullmatch`,
|
|
71
|
+
* `const re = /.../; re.test(x)`); `switch` on the value; a check whose
|
|
72
|
+
* result was stored in a variable first; a closure or by-reference
|
|
73
|
+
* parameter that rewrites the value from inside a called function.
|
|
74
|
+
*
|
|
75
|
+
* See safe/validated-guard.php and safe/allow-list-guards.* for the shapes this
|
|
76
|
+
* recognises, and vulnerable/weak-guard.php and vulnerable/allow-list-fence.*
|
|
77
|
+
* for the fence around them.
|
|
78
|
+
*/
|
|
79
|
+
import type { Node } from 'web-tree-sitter';
|
|
80
|
+
import type { LanguageId } from '../../parse/languages.js';
|
|
81
|
+
import type { SinkKind } from '../../taint/types.js';
|
|
82
|
+
export type GuardStrength = 'digits' | 'word' | 'fixed' | 'quoted-literal';
|
|
83
|
+
export interface GuardFact {
|
|
84
|
+
readonly strength: GuardStrength;
|
|
85
|
+
/** For `fixed`: the values the check lets through. */
|
|
86
|
+
readonly values?: readonly string[];
|
|
87
|
+
/** For `fixed`: the check compared a lower- or upper-cased copy. */
|
|
88
|
+
readonly caseFolded?: boolean;
|
|
89
|
+
/** A regex `$` in PHP or Python also lets one line break through after the value. */
|
|
90
|
+
readonly newlineTail?: boolean;
|
|
91
|
+
/** The check itself, for the path a reader sees. */
|
|
92
|
+
readonly check: Node;
|
|
93
|
+
}
|
|
94
|
+
/** Does this language have any checks this module recognises? */
|
|
95
|
+
export declare const guardsApply: (language: LanguageId) => boolean;
|
|
96
|
+
/** Which sinks a fact makes safe, and which of those only when the value lands inside quotes. */
|
|
97
|
+
export declare const guardClears: (fact: GuardFact) => {
|
|
98
|
+
readonly kinds: readonly SinkKind[];
|
|
99
|
+
readonly insideQuotesOnly: readonly SinkKind[];
|
|
100
|
+
};
|
|
101
|
+
/** One sentence for the path: what the check proved. */
|
|
102
|
+
export declare function describeFact(fact: GuardFact): string;
|
|
103
|
+
/**
|
|
104
|
+
* The same expression written two ways must compare equal. DVWA writes
|
|
105
|
+
* `$_REQUEST[ 'ip' ]` with spaces and reads it back the same way, but a check
|
|
106
|
+
* written `$_GET["id"]` and a read written `$_GET['id']` are one value too.
|
|
107
|
+
*/
|
|
108
|
+
export declare function normalizeExpression(raw: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* What a whole-value regular expression lets through: 'digits', 'word',
|
|
111
|
+
* 'fixed' (a list of literal words, `/^(asc|desc)$/`), or null when anything
|
|
112
|
+
* else could pass - and null is the answer for everything this does not
|
|
113
|
+
* understand.
|
|
114
|
+
*
|
|
115
|
+
* The body may only be digits, letters, underscores, `\d`, `\w`, classes of
|
|
116
|
+
* those, groups, alternation INSIDE a group, and quantifiers. Top-level `|`
|
|
117
|
+
* is refused because it splits the anchors: `/^1|2$/` is "starts with 1" OR
|
|
118
|
+
* "ends with 2", and "1; rm -rf /" starts with 1.
|
|
119
|
+
*
|
|
120
|
+
* `$` lets one trailing newline through in PHP and Python (not in
|
|
121
|
+
* JavaScript, and not with PHP's D flag). A lone newline at the very end
|
|
122
|
+
* cannot open a quote or a tag, and is accepted - but it is recorded, so a
|
|
123
|
+
* fixed list is not vouched for inside a URL (see guardClears).
|
|
124
|
+
*/
|
|
125
|
+
export declare function regexAllowList(source: string, options: {
|
|
126
|
+
readonly flags?: string;
|
|
127
|
+
readonly implicitStart?: boolean;
|
|
128
|
+
readonly implicitEnd?: boolean;
|
|
129
|
+
readonly language: LanguageId;
|
|
130
|
+
}): {
|
|
131
|
+
readonly strength: GuardStrength;
|
|
132
|
+
readonly values?: readonly string[];
|
|
133
|
+
readonly caseFolded?: boolean;
|
|
134
|
+
readonly newlineTail?: boolean;
|
|
135
|
+
} | null;
|
|
136
|
+
/**
|
|
137
|
+
* Every value a check proves something about, at this node. Keys are
|
|
138
|
+
* normalised expression texts (see normalizeExpression).
|
|
139
|
+
*/
|
|
140
|
+
export declare function guardFactsAt(node: Node, language: LanguageId): ReadonlyMap<string, GuardFact>;
|
|
141
|
+
/**
|
|
142
|
+
* Are ALL the spliced parts of this expression proven safe by a check?
|
|
143
|
+
*
|
|
144
|
+
* Every part must be covered. That is deliberate and it is the lesson from the
|
|
145
|
+
* Jenkins escaper bug, where a sanitiser covering ONE value in an expression
|
|
146
|
+
* was allowed to vouch for a DIFFERENT value beside it. Here, one unchecked
|
|
147
|
+
* part means the whole expression still reports:
|
|
148
|
+
*
|
|
149
|
+
* if (is_numeric($port)) { shell_exec('nc ' . $host . ' ' . $port); }
|
|
150
|
+
* ^^^^^ never checked
|
|
151
|
+
*
|
|
152
|
+
* One level of local indirection is followed, because the shape that matters
|
|
153
|
+
* rebuilds the value inside the branch:
|
|
154
|
+
*
|
|
155
|
+
* $target = $octet[0] . '.' . $octet[1]; <- every piece checked here
|
|
156
|
+
* shell_exec('ping ' . $target); <- and used here
|
|
157
|
+
*
|
|
158
|
+
* A name counts when its value here certainly comes from one assignment: the
|
|
159
|
+
* last plain `name = ...` before this point, sitting directly in a block that
|
|
160
|
+
* also holds this point, with no other write to the name from that assignment
|
|
161
|
+
* to the end of the statement this point is in (so a loop cannot bring a
|
|
162
|
+
* different value round). And every piece of that one value was checked where
|
|
163
|
+
* it was assigned. DVWA writes `$target` twice - once from the request, once
|
|
164
|
+
* rebuilt from checked octets - so "assigned once in the function" was too
|
|
165
|
+
* strict, and "assigned somewhere in the branch" (the first version) never
|
|
166
|
+
* asked whether a later write replaced it.
|
|
167
|
+
*/
|
|
168
|
+
export declare function partsAreGuarded(node: Node, dynamicParts: readonly string[], language: LanguageId, kind: SinkKind): boolean;
|