@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,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE NODE SHELL
|
|
3
|
+
* ==============
|
|
4
|
+
*
|
|
5
|
+
* Everything that only makes sense on a machine with a filesystem: walking a
|
|
6
|
+
* directory, honouring --exclude, reading bytes, and shortening paths against
|
|
7
|
+
* the working directory so terminal output stays readable.
|
|
8
|
+
*
|
|
9
|
+
* The analysis itself is NOT here. It moved to src/core/analyze.ts when the
|
|
10
|
+
* browser UI arrived, so the web version could run the same code rather than a
|
|
11
|
+
* lookalike. What is left is the pipeline seam:
|
|
12
|
+
*
|
|
13
|
+
* collectFiles -> read text -> analyze() -> a ScanResult
|
|
14
|
+
*
|
|
15
|
+
* If you find yourself wanting to change how a FINDING is produced, this is the
|
|
16
|
+
* wrong file - that belongs in core/analyze.ts, where the browser sees it too.
|
|
17
|
+
*/
|
|
18
|
+
import { type AnalysisResult } from '../core/analyze.js';
|
|
19
|
+
import { type CoverageReport } from '../core/coverage.js';
|
|
20
|
+
import type { Finding } from '../core/finding.js';
|
|
21
|
+
import { type RuntimeCheckResult } from '../platform/runtime-version.js';
|
|
22
|
+
import type { DependencyReport } from '../platform/dependency-rule.js';
|
|
23
|
+
export type { FileParseProblem, Suppression } from '../core/analyze.js';
|
|
24
|
+
export interface ScanOptions {
|
|
25
|
+
/** Only run these rule ids. Empty = all. */
|
|
26
|
+
readonly only?: readonly string[];
|
|
27
|
+
/** Glob-ish paths to skip entirely. Reported, never silently applied. */
|
|
28
|
+
readonly exclude?: readonly string[];
|
|
29
|
+
/** Turn off cross-file resolution (faster, less memory, fewer findings). */
|
|
30
|
+
readonly noCrossFile?: boolean;
|
|
31
|
+
/** Print progress to stderr. */
|
|
32
|
+
readonly onProgress?: (done: number, total: number, file: string) => void;
|
|
33
|
+
}
|
|
34
|
+
export interface ScanResult {
|
|
35
|
+
readonly target: string;
|
|
36
|
+
readonly findings: readonly Finding[];
|
|
37
|
+
/** Lines the tracer proved safe. Reported so "clean" can mean "checked". */
|
|
38
|
+
readonly verifiedClean: AnalysisResult['verifiedClean'];
|
|
39
|
+
/** Branches and cases not followed because they cannot run, with the reason. */
|
|
40
|
+
readonly unreachableCode: AnalysisResult['unreachableCode'];
|
|
41
|
+
/** Map and list reads answered by key or position, with the write that answers each. */
|
|
42
|
+
readonly collectionReadsResolved: AnalysisResult['collectionReadsResolved'];
|
|
43
|
+
/** Calls where a trace ended because the result is new data, not the argument. */
|
|
44
|
+
readonly tracesStoppedAtNewData: AnalysisResult['tracesStoppedAtNewData'];
|
|
45
|
+
/**
|
|
46
|
+
* Which PHP the project declares, and what php.net's facts say about it -
|
|
47
|
+
* platform/runtime-version.ts. Null when --only left the check out.
|
|
48
|
+
*/
|
|
49
|
+
readonly runtimeVersions: RuntimeCheckResult | null;
|
|
50
|
+
/**
|
|
51
|
+
* What `scan --deps` read and asked osv.dev. Absent when the check did not run -
|
|
52
|
+
* scan() never sets it; the CLI adds it, because it is the one network call.
|
|
53
|
+
*/
|
|
54
|
+
readonly dependencies?: DependencyReport;
|
|
55
|
+
/** Per-file participation, so a file a flow passes through is never "clean". */
|
|
56
|
+
readonly fileRoles: AnalysisResult['fileRoles'];
|
|
57
|
+
/** Where the tracer ran out, counted for this scan. */
|
|
58
|
+
readonly traceLimits: AnalysisResult['traceLimits'];
|
|
59
|
+
readonly stats: AnalysisResult['stats'] & {
|
|
60
|
+
readonly filesFound: number;
|
|
61
|
+
readonly filesWithParseErrors: number;
|
|
62
|
+
};
|
|
63
|
+
readonly byLanguage: AnalysisResult['byLanguage'];
|
|
64
|
+
readonly parseProblems: AnalysisResult['parseProblems'];
|
|
65
|
+
readonly oversizedFiles: readonly string[];
|
|
66
|
+
/** What --exclude removed. A gap the user chose is still a gap worth printing. */
|
|
67
|
+
readonly excluded: {
|
|
68
|
+
readonly patterns: readonly string[];
|
|
69
|
+
readonly fileCount: number;
|
|
70
|
+
readonly directories: readonly string[];
|
|
71
|
+
};
|
|
72
|
+
/** Findings the code explicitly asked us to ignore. Reported, never hidden. */
|
|
73
|
+
readonly suppressions: AnalysisResult['suppressions'];
|
|
74
|
+
readonly coverage: CoverageReport;
|
|
75
|
+
/** How cross-file resolution went. */
|
|
76
|
+
readonly crossFile: AnalysisResult['crossFile'];
|
|
77
|
+
/** What the scan spent on syntax trees, and whether it had to spill. */
|
|
78
|
+
readonly treeMemory: AnalysisResult['treeMemory'];
|
|
79
|
+
}
|
|
80
|
+
export declare function scan(target: string, options?: ScanOptions): Promise<ScanResult>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE NODE SHELL
|
|
3
|
+
* ==============
|
|
4
|
+
*
|
|
5
|
+
* Everything that only makes sense on a machine with a filesystem: walking a
|
|
6
|
+
* directory, honouring --exclude, reading bytes, and shortening paths against
|
|
7
|
+
* the working directory so terminal output stays readable.
|
|
8
|
+
*
|
|
9
|
+
* The analysis itself is NOT here. It moved to src/core/analyze.ts when the
|
|
10
|
+
* browser UI arrived, so the web version could run the same code rather than a
|
|
11
|
+
* lookalike. What is left is the pipeline seam:
|
|
12
|
+
*
|
|
13
|
+
* collectFiles -> read text -> analyze() -> a ScanResult
|
|
14
|
+
*
|
|
15
|
+
* If you find yourself wanting to change how a FINDING is produced, this is the
|
|
16
|
+
* wrong file - that belongs in core/analyze.ts, where the browser sees it too.
|
|
17
|
+
*/
|
|
18
|
+
import { readFile } from 'node:fs/promises';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { analyze } from '../core/analyze.js';
|
|
21
|
+
import { buildCoverageReport } from '../core/coverage.js';
|
|
22
|
+
import { SCANNABLE_EXTENSIONS } from '../parse/languages.js';
|
|
23
|
+
import { useNodeGrammars } from '../parse/node-grammars.js';
|
|
24
|
+
import { checkRuntimeVersions, RUNTIME_VERSION_RULE } from '../platform/runtime-version.js';
|
|
25
|
+
import { collectFiles } from './walk.js';
|
|
26
|
+
export async function scan(target, options = {}) {
|
|
27
|
+
// Tell the parser it is running on Node before anything loads a grammar.
|
|
28
|
+
// The browser worker makes the equivalent call with its own loader.
|
|
29
|
+
useNodeGrammars();
|
|
30
|
+
const walk = await collectFiles(target, {
|
|
31
|
+
scannable: SCANNABLE_EXTENSIONS,
|
|
32
|
+
exclude: options.exclude ?? [],
|
|
33
|
+
});
|
|
34
|
+
// Read the text. A file we cannot read simply never reaches the analyzer;
|
|
35
|
+
// it shows up as the gap between filesFound and filesParsed.
|
|
36
|
+
const inputs = [];
|
|
37
|
+
for (const filePath of walk.files) {
|
|
38
|
+
try {
|
|
39
|
+
inputs.push({ path: filePath, source: await readFile(filePath, 'utf8') });
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// permissions, a broken symlink, a race with a build tool
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* DEFUSE_TREE_BUDGET_MB - how much WebAssembly heap the scan may hold in syntax
|
|
47
|
+
* trees before it starts freeing the least recently used one.
|
|
48
|
+
*
|
|
49
|
+
* An environment variable rather than a flag, deliberately. It exists to be
|
|
50
|
+
* turned down hard in testing: the interesting question about the tree store
|
|
51
|
+
* is not "does it work when nothing is ever evicted" but "are the findings
|
|
52
|
+
* still identical when almost everything is". A budget of 1 MB forces a
|
|
53
|
+
* re-parse for nearly every cross-file hop, which is exactly the condition
|
|
54
|
+
* the identity check should be run under.
|
|
55
|
+
*
|
|
56
|
+
* Anyone who genuinely needs it in production can set it, and the value used
|
|
57
|
+
* is printed in the report either way.
|
|
58
|
+
*/
|
|
59
|
+
const budgetMb = Number(process.env['DEFUSE_TREE_BUDGET_MB']);
|
|
60
|
+
const treeBudget = Number.isFinite(budgetMb) && budgetMb > 0 ? Math.round(budgetMb * 1024 * 1024) : undefined;
|
|
61
|
+
const result = await analyze(inputs, {
|
|
62
|
+
...(options.only ? { only: options.only } : {}),
|
|
63
|
+
...(options.noCrossFile !== undefined ? { noCrossFile: options.noCrossFile } : {}),
|
|
64
|
+
...(options.onProgress ? { onProgress: options.onProgress } : {}),
|
|
65
|
+
...(treeBudget !== undefined ? { treeBudgetBytes: treeBudget } : {}),
|
|
66
|
+
toDisplay: (absolutePath) => path.relative(process.cwd(), absolutePath) || absolutePath,
|
|
67
|
+
});
|
|
68
|
+
/*
|
|
69
|
+
* THE RUNTIME VERSION CHECK reads declarations, not code, so it runs beside
|
|
70
|
+
* the analysis rather than inside it. Its findings join the same list - a
|
|
71
|
+
* PHP release with a known critical flaw belongs next to the code problems,
|
|
72
|
+
* not in a footnote - and they are all signature-based by construction.
|
|
73
|
+
*/
|
|
74
|
+
const runtimeVersions = !options.only || options.only.length === 0 || options.only.includes(RUNTIME_VERSION_RULE.id)
|
|
75
|
+
? await (async () => {
|
|
76
|
+
const files = [];
|
|
77
|
+
for (const filePath of walk.runtimeFiles) {
|
|
78
|
+
try {
|
|
79
|
+
files.push({
|
|
80
|
+
path: path.relative(process.cwd(), filePath) || filePath,
|
|
81
|
+
text: await readFile(filePath, 'utf8'),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// unreadable - same treatment as a source file
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return checkRuntimeVersions(files, {
|
|
89
|
+
today: new Date().toISOString().slice(0, 10),
|
|
90
|
+
phpFilesScanned: result.byLanguage['php'] ?? 0,
|
|
91
|
+
});
|
|
92
|
+
})()
|
|
93
|
+
: null;
|
|
94
|
+
return {
|
|
95
|
+
target,
|
|
96
|
+
findings: [...result.findings, ...(runtimeVersions?.findings ?? [])],
|
|
97
|
+
verifiedClean: result.verifiedClean,
|
|
98
|
+
unreachableCode: result.unreachableCode,
|
|
99
|
+
collectionReadsResolved: result.collectionReadsResolved,
|
|
100
|
+
tracesStoppedAtNewData: result.tracesStoppedAtNewData,
|
|
101
|
+
runtimeVersions,
|
|
102
|
+
fileRoles: result.fileRoles,
|
|
103
|
+
traceLimits: result.traceLimits,
|
|
104
|
+
stats: {
|
|
105
|
+
...result.stats,
|
|
106
|
+
filesFound: walk.files.length,
|
|
107
|
+
filesSkipped: walk.files.length - result.stats.filesParsed,
|
|
108
|
+
filesWithParseErrors: result.parseProblems.length,
|
|
109
|
+
},
|
|
110
|
+
byLanguage: result.byLanguage,
|
|
111
|
+
parseProblems: result.parseProblems,
|
|
112
|
+
oversizedFiles: walk.oversizedFiles,
|
|
113
|
+
excluded: {
|
|
114
|
+
patterns: options.exclude ?? [],
|
|
115
|
+
fileCount: walk.excludedCount,
|
|
116
|
+
directories: walk.excludedDirectories,
|
|
117
|
+
},
|
|
118
|
+
suppressions: result.suppressions,
|
|
119
|
+
crossFile: result.crossFile,
|
|
120
|
+
treeMemory: result.treeMemory,
|
|
121
|
+
// Rebuilt here because only the walker knows which file types it passed
|
|
122
|
+
// over - the analyzer never saw them.
|
|
123
|
+
coverage: buildCoverageReport([...walk.skippedByExtension.entries()]
|
|
124
|
+
.sort((a, b) => b[1] - a[1])
|
|
125
|
+
.slice(0, 12)
|
|
126
|
+
.map(([ext, count]) => `${ext} (${count} file${count === 1 ? '' : 's'})`)),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../src/engine/scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE SHAPE LAYER
|
|
3
|
+
* ===============
|
|
4
|
+
*
|
|
5
|
+
* This is the translator between "five different languages" and "one set of
|
|
6
|
+
* rules". It uses tree-sitter's QUERY LANGUAGE, which is worth understanding
|
|
7
|
+
* because it is the only unfamiliar syntax in this project.
|
|
8
|
+
*
|
|
9
|
+
* A tree-sitter query is a pattern written as nested parentheses that describes
|
|
10
|
+
* a shape of tree, with @names marking the parts you want back:
|
|
11
|
+
*
|
|
12
|
+
* (call_expression
|
|
13
|
+
* function: (member_expression
|
|
14
|
+
* object: (_) @receiver
|
|
15
|
+
* property: (property_identifier) @method)
|
|
16
|
+
* arguments: (arguments) @args) @call
|
|
17
|
+
*
|
|
18
|
+
* Read it as: "find a call_expression; its `function` child must be a
|
|
19
|
+
* member_expression (that's the `a.b` shape); give me whatever is on the left
|
|
20
|
+
* of the dot as @receiver, the name after the dot as @method, the argument list
|
|
21
|
+
* as @args, and the whole call as @call."
|
|
22
|
+
*
|
|
23
|
+
* `(_)` means "any node". Field names before the colon (`function:`,
|
|
24
|
+
* `object:`) are the ROLE labels you saw in the AST dump. Matching on roles
|
|
25
|
+
* instead of child positions is what makes these patterns stable.
|
|
26
|
+
*
|
|
27
|
+
* Every language below gets its own query because the grammars use different
|
|
28
|
+
* node names for the same idea - JavaScript says `call_expression`, Python says
|
|
29
|
+
* `call`, Java says `method_invocation`. The OUTPUT is identical for all five,
|
|
30
|
+
* and that output is what rules see.
|
|
31
|
+
*
|
|
32
|
+
* ADDING A LANGUAGE LATER means adding two query strings to this file. No rule
|
|
33
|
+
* changes.
|
|
34
|
+
*/
|
|
35
|
+
import type { Shape } from '../rules/contract.js';
|
|
36
|
+
import type { LanguageId } from '../parse/languages.js';
|
|
37
|
+
import { type ParsedFile } from '../parse/parser.js';
|
|
38
|
+
/**
|
|
39
|
+
* Lower one parsed file into the flat list of shapes rules will see.
|
|
40
|
+
* Order is source order, which makes reports read top-to-bottom like the file.
|
|
41
|
+
*/
|
|
42
|
+
export declare function extractShapes(file: ParsedFile, language: LanguageId): Promise<Shape[]>;
|
|
43
|
+
/** Exposed so a self-check can compile every query against every grammar. */
|
|
44
|
+
export declare const SHAPE_QUERY_SOURCES: {
|
|
45
|
+
readonly calls: Record<LanguageId, string>;
|
|
46
|
+
readonly assignments: Record<LanguageId, string>;
|
|
47
|
+
};
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE SHAPE LAYER
|
|
3
|
+
* ===============
|
|
4
|
+
*
|
|
5
|
+
* This is the translator between "five different languages" and "one set of
|
|
6
|
+
* rules". It uses tree-sitter's QUERY LANGUAGE, which is worth understanding
|
|
7
|
+
* because it is the only unfamiliar syntax in this project.
|
|
8
|
+
*
|
|
9
|
+
* A tree-sitter query is a pattern written as nested parentheses that describes
|
|
10
|
+
* a shape of tree, with @names marking the parts you want back:
|
|
11
|
+
*
|
|
12
|
+
* (call_expression
|
|
13
|
+
* function: (member_expression
|
|
14
|
+
* object: (_) @receiver
|
|
15
|
+
* property: (property_identifier) @method)
|
|
16
|
+
* arguments: (arguments) @args) @call
|
|
17
|
+
*
|
|
18
|
+
* Read it as: "find a call_expression; its `function` child must be a
|
|
19
|
+
* member_expression (that's the `a.b` shape); give me whatever is on the left
|
|
20
|
+
* of the dot as @receiver, the name after the dot as @method, the argument list
|
|
21
|
+
* as @args, and the whole call as @call."
|
|
22
|
+
*
|
|
23
|
+
* `(_)` means "any node". Field names before the colon (`function:`,
|
|
24
|
+
* `object:`) are the ROLE labels you saw in the AST dump. Matching on roles
|
|
25
|
+
* instead of child positions is what makes these patterns stable.
|
|
26
|
+
*
|
|
27
|
+
* Every language below gets its own query because the grammars use different
|
|
28
|
+
* node names for the same idea - JavaScript says `call_expression`, Python says
|
|
29
|
+
* `call`, Java says `method_invocation`. The OUTPUT is identical for all five,
|
|
30
|
+
* and that output is what rules see.
|
|
31
|
+
*
|
|
32
|
+
* ADDING A LANGUAGE LATER means adding two query strings to this file. No rule
|
|
33
|
+
* changes.
|
|
34
|
+
*/
|
|
35
|
+
import { Query } from 'web-tree-sitter';
|
|
36
|
+
import { getLanguage } from '../parse/parser.js';
|
|
37
|
+
/* ---------------------------------------------------------------------------
|
|
38
|
+
* CALL SITES: "something is being called with arguments"
|
|
39
|
+
* ------------------------------------------------------------------------ */
|
|
40
|
+
/** Shared by C and C++, because C++ is a superset of these two shapes. */
|
|
41
|
+
const C_CALL_QUERY = `
|
|
42
|
+
(call_expression
|
|
43
|
+
function: (identifier) @method
|
|
44
|
+
arguments: (argument_list) @args) @call
|
|
45
|
+
(call_expression
|
|
46
|
+
function: (field_expression argument: (_) @receiver field: (field_identifier) @method)
|
|
47
|
+
arguments: (argument_list) @args) @call
|
|
48
|
+
`;
|
|
49
|
+
const CALL_QUERIES = {
|
|
50
|
+
javascript: `
|
|
51
|
+
(call_expression
|
|
52
|
+
function: (member_expression object: (_) @receiver property: (property_identifier) @method)
|
|
53
|
+
arguments: (arguments) @args) @call
|
|
54
|
+
(call_expression
|
|
55
|
+
function: (identifier) @method
|
|
56
|
+
arguments: (arguments) @args) @call
|
|
57
|
+
(new_expression
|
|
58
|
+
constructor: (_) @method
|
|
59
|
+
arguments: (arguments) @args) @call
|
|
60
|
+
`,
|
|
61
|
+
// TypeScript's grammar is JavaScript's grammar plus types, so the call shapes
|
|
62
|
+
// are byte-for-byte identical. Kept as a separate entry rather than an alias
|
|
63
|
+
// so that a future TS-only pattern has an obvious home.
|
|
64
|
+
typescript: `
|
|
65
|
+
(call_expression
|
|
66
|
+
function: (member_expression object: (_) @receiver property: (property_identifier) @method)
|
|
67
|
+
arguments: (arguments) @args) @call
|
|
68
|
+
(call_expression
|
|
69
|
+
function: (identifier) @method
|
|
70
|
+
arguments: (arguments) @args) @call
|
|
71
|
+
(new_expression
|
|
72
|
+
constructor: (_) @method
|
|
73
|
+
arguments: (arguments) @args) @call
|
|
74
|
+
`,
|
|
75
|
+
python: `
|
|
76
|
+
(call
|
|
77
|
+
function: (attribute object: (_) @receiver attribute: (identifier) @method)
|
|
78
|
+
arguments: (argument_list) @args) @call
|
|
79
|
+
(call
|
|
80
|
+
function: (identifier) @method
|
|
81
|
+
arguments: (argument_list) @args) @call
|
|
82
|
+
`,
|
|
83
|
+
java: `
|
|
84
|
+
(method_invocation
|
|
85
|
+
object: (_) @receiver
|
|
86
|
+
name: (identifier) @method
|
|
87
|
+
arguments: (argument_list) @args) @call
|
|
88
|
+
(method_invocation
|
|
89
|
+
name: (identifier) @method
|
|
90
|
+
arguments: (argument_list) @args) @call
|
|
91
|
+
(object_creation_expression
|
|
92
|
+
type: (_) @method
|
|
93
|
+
arguments: (argument_list) @args) @call
|
|
94
|
+
`,
|
|
95
|
+
go: `
|
|
96
|
+
(call_expression
|
|
97
|
+
function: (selector_expression operand: (_) @receiver field: (field_identifier) @method)
|
|
98
|
+
arguments: (argument_list) @args) @call
|
|
99
|
+
(call_expression
|
|
100
|
+
function: (identifier) @method
|
|
101
|
+
arguments: (argument_list) @args) @call
|
|
102
|
+
`,
|
|
103
|
+
// PHP has three call shapes: a bare function, a method on an object, and a
|
|
104
|
+
// static method on a class. `echo` is deliberately NOT here - it is a
|
|
105
|
+
// statement, not a call, and the XSS rule handles it separately.
|
|
106
|
+
php: `
|
|
107
|
+
(function_call_expression
|
|
108
|
+
function: (name) @method
|
|
109
|
+
arguments: (arguments) @args) @call
|
|
110
|
+
(member_call_expression
|
|
111
|
+
object: (_) @receiver
|
|
112
|
+
name: (name) @method
|
|
113
|
+
arguments: (arguments) @args) @call
|
|
114
|
+
(scoped_call_expression
|
|
115
|
+
scope: (_) @receiver
|
|
116
|
+
name: (name) @method
|
|
117
|
+
arguments: (arguments) @args) @call
|
|
118
|
+
(object_creation_expression
|
|
119
|
+
(name) @method
|
|
120
|
+
(arguments) @args) @call
|
|
121
|
+
`,
|
|
122
|
+
/*
|
|
123
|
+
* C. Two shapes only, because C has two: a bare call, and a call through a
|
|
124
|
+
* struct member. Every node type and field name below was read off the real
|
|
125
|
+
* grammar with a query probe rather than assumed - the same discipline the
|
|
126
|
+
* PHP entry learned the hard way, when `if_statement` turned out to use
|
|
127
|
+
* `body` where every other grammar uses `consequence`.
|
|
128
|
+
*/
|
|
129
|
+
c: C_CALL_QUERY,
|
|
130
|
+
/*
|
|
131
|
+
* C++ is C plus namespaces. `std::system(cmd)` parses as a
|
|
132
|
+
* qualified_identifier, NOT an identifier, so a query that only matched the
|
|
133
|
+
* C shapes would miss it - and reaching system() through <cstdlib> is the
|
|
134
|
+
* stylistically CORRECT way to do it in C++, which means the well-written
|
|
135
|
+
* code would be the code that slipped through.
|
|
136
|
+
*/
|
|
137
|
+
cpp: `
|
|
138
|
+
${C_CALL_QUERY}
|
|
139
|
+
(call_expression
|
|
140
|
+
function: (qualified_identifier name: (identifier) @method)
|
|
141
|
+
arguments: (argument_list) @args) @call
|
|
142
|
+
`,
|
|
143
|
+
};
|
|
144
|
+
/* ---------------------------------------------------------------------------
|
|
145
|
+
* ASSIGNMENTS: "a name is being bound to a value"
|
|
146
|
+
*
|
|
147
|
+
* Deliberately broad: it covers `x = v`, `const x = v`, object/dict entries
|
|
148
|
+
* `{ key: v }`, and keyword arguments `f(key=v)`. Hardcoded credentials hide in
|
|
149
|
+
* all of those, and so does `element.innerHTML = ...`.
|
|
150
|
+
* ------------------------------------------------------------------------ */
|
|
151
|
+
const JS_ASSIGN_QUERY = `
|
|
152
|
+
(assignment_expression left: (_) @target right: (_) @value) @assign
|
|
153
|
+
(variable_declarator name: (_) @target value: (_) @value) @assign
|
|
154
|
+
(pair key: (_) @target value: (_) @value) @assign
|
|
155
|
+
(augmented_assignment_expression left: (_) @target right: (_) @value) @assign
|
|
156
|
+
`;
|
|
157
|
+
const C_ASSIGN_QUERY = `
|
|
158
|
+
(assignment_expression left: (_) @target right: (_) @value) @assign
|
|
159
|
+
(init_declarator declarator: (_) @target value: (_) @value) @assign
|
|
160
|
+
`;
|
|
161
|
+
const ASSIGNMENT_QUERIES = {
|
|
162
|
+
php: `
|
|
163
|
+
(assignment_expression left: (_) @target right: (_) @value) @assign
|
|
164
|
+
(property_element (variable_name) @target (_) @value) @assign
|
|
165
|
+
(echo_statement "echo" @target (_) @value) @assign
|
|
166
|
+
`,
|
|
167
|
+
javascript: JS_ASSIGN_QUERY,
|
|
168
|
+
typescript: `
|
|
169
|
+
${JS_ASSIGN_QUERY}
|
|
170
|
+
(public_field_definition name: (_) @target value: (_) @value) @assign
|
|
171
|
+
`,
|
|
172
|
+
python: `
|
|
173
|
+
(assignment left: (_) @target right: (_) @value) @assign
|
|
174
|
+
(pair key: (_) @target value: (_) @value) @assign
|
|
175
|
+
(keyword_argument name: (identifier) @target value: (_) @value) @assign
|
|
176
|
+
`,
|
|
177
|
+
java: `
|
|
178
|
+
(variable_declarator name: (identifier) @target value: (_) @value) @assign
|
|
179
|
+
(assignment_expression left: (_) @target right: (_) @value) @assign
|
|
180
|
+
`,
|
|
181
|
+
go: `
|
|
182
|
+
(short_var_declaration left: (expression_list) @target right: (expression_list) @value) @assign
|
|
183
|
+
(assignment_statement left: (expression_list) @target right: (expression_list) @value) @assign
|
|
184
|
+
(var_spec name: (identifier) @target value: (expression_list) @value) @assign
|
|
185
|
+
(const_spec name: (identifier) @target value: (expression_list) @value) @assign
|
|
186
|
+
(keyed_element (literal_element (identifier) @target) (literal_element (_) @value)) @assign
|
|
187
|
+
`,
|
|
188
|
+
/*
|
|
189
|
+
* C and C++ share these. `init_declarator` is the declaration-with-value
|
|
190
|
+
* form (`char *p = getenv(...)`), and its @target captures the DECLARATOR
|
|
191
|
+
* rather than a bare name - so `*name = getenv("USER")` yields a target of
|
|
192
|
+
* `*name`, pointer star included. The tracer strips it; see POINTER_PREFIX
|
|
193
|
+
* in taint/tracer.ts. Recorded here because it is the kind of detail that
|
|
194
|
+
* silently halves a language's coverage if nobody notices it.
|
|
195
|
+
*/
|
|
196
|
+
c: C_ASSIGN_QUERY,
|
|
197
|
+
cpp: C_ASSIGN_QUERY,
|
|
198
|
+
};
|
|
199
|
+
/* ------------------------------------------------------------------------ */
|
|
200
|
+
/** Compiled queries are expensive to build and cheap to reuse. Cache per grammar. */
|
|
201
|
+
const queryCache = new Map();
|
|
202
|
+
async function compile(grammar, key, source) {
|
|
203
|
+
const cacheKey = `${grammar}:${key}`;
|
|
204
|
+
const cached = queryCache.get(cacheKey);
|
|
205
|
+
if (cached)
|
|
206
|
+
return cached;
|
|
207
|
+
const language = await getLanguage(grammar);
|
|
208
|
+
const query = new Query(language, source);
|
|
209
|
+
queryCache.set(cacheKey, query);
|
|
210
|
+
return query;
|
|
211
|
+
}
|
|
212
|
+
function collapse(text) {
|
|
213
|
+
return (text ?? '').replace(/\s+/g, ' ').trim();
|
|
214
|
+
}
|
|
215
|
+
/** Pull one capture out of a match. Returns null when the capture is optional. */
|
|
216
|
+
function captured(match, name) {
|
|
217
|
+
for (const capture of match.captures) {
|
|
218
|
+
if (capture.name === name)
|
|
219
|
+
return capture.node;
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Argument lists contain punctuation as well as expressions. `namedChildren`
|
|
225
|
+
* gives us only the meaningful ones - see the "named vs anonymous" note in
|
|
226
|
+
* astDump.ts.
|
|
227
|
+
*/
|
|
228
|
+
function argumentNodes(argsNode) {
|
|
229
|
+
if (!argsNode)
|
|
230
|
+
return [];
|
|
231
|
+
return argsNode.namedChildren
|
|
232
|
+
.filter((n) => n !== null)
|
|
233
|
+
// PHP wraps every argument in an `argument` node, so the expression a rule
|
|
234
|
+
// wants to examine sits one level down. Without this unwrap every PHP
|
|
235
|
+
// argument reads as the node type `argument` and no content check matches.
|
|
236
|
+
.map((n) => (n.type === 'argument' ? (n.namedChildren.find((c) => c !== null) ?? n) : n));
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Go writes `a, b := f(), g()` - both sides are lists. When the lists line up
|
|
240
|
+
* we pair them; when they don't (a multi-return call) we bind every name to the
|
|
241
|
+
* single value node, which is honest enough for signature matching and is
|
|
242
|
+
* flagged as a known imprecision in the Go coverage notes.
|
|
243
|
+
*/
|
|
244
|
+
function goPairs(target, value) {
|
|
245
|
+
const targets = target.type === 'expression_list' ? target.namedChildren : [target];
|
|
246
|
+
const values = value.type === 'expression_list' ? value.namedChildren : [value];
|
|
247
|
+
const pairs = [];
|
|
248
|
+
for (let i = 0; i < targets.length; i++) {
|
|
249
|
+
const t = targets[i];
|
|
250
|
+
if (!t)
|
|
251
|
+
continue;
|
|
252
|
+
const v = values.length === targets.length ? values[i] : values[0];
|
|
253
|
+
if (v)
|
|
254
|
+
pairs.push({ t, v });
|
|
255
|
+
}
|
|
256
|
+
return pairs;
|
|
257
|
+
}
|
|
258
|
+
/** Last dotted segment: "config.db.password" -> "password". */
|
|
259
|
+
function lastSegment(text) {
|
|
260
|
+
const cleaned = text.replace(/\[["']?|["']?\]/g, '.').replace(/\.+$/, '');
|
|
261
|
+
const parts = cleaned.split('.');
|
|
262
|
+
return (parts[parts.length - 1] ?? cleaned).trim();
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Lower one parsed file into the flat list of shapes rules will see.
|
|
266
|
+
* Order is source order, which makes reports read top-to-bottom like the file.
|
|
267
|
+
*/
|
|
268
|
+
export async function extractShapes(file, language) {
|
|
269
|
+
const shapes = [];
|
|
270
|
+
const seen = new Set();
|
|
271
|
+
const callQuery = await compile(file.grammar, 'calls', CALL_QUERIES[language]);
|
|
272
|
+
for (const match of callQuery.matches(file.root)) {
|
|
273
|
+
const call = captured(match, 'call');
|
|
274
|
+
const method = captured(match, 'method');
|
|
275
|
+
if (!call || !method)
|
|
276
|
+
continue;
|
|
277
|
+
// Java's two method_invocation patterns overlap (object is optional), so the
|
|
278
|
+
// same node can match twice. Dedupe by node id.
|
|
279
|
+
if (seen.has(call.id))
|
|
280
|
+
continue;
|
|
281
|
+
seen.add(call.id);
|
|
282
|
+
const receiver = captured(match, 'receiver');
|
|
283
|
+
const receiverText = collapse(receiver?.text);
|
|
284
|
+
const calleeName = collapse(method.text);
|
|
285
|
+
// Java's `method_invocation` has no `function` field - the receiver and the
|
|
286
|
+
// name are separate children - so we rebuild "receiver.name" by hand there.
|
|
287
|
+
const functionNode = call.childForFieldName('function') ?? call.childForFieldName('constructor');
|
|
288
|
+
const site = {
|
|
289
|
+
kind: 'call',
|
|
290
|
+
node: call,
|
|
291
|
+
calleeName,
|
|
292
|
+
receiverText,
|
|
293
|
+
calleeText: collapse(functionNode?.text ?? (receiverText ? `${receiverText}.${calleeName}` : calleeName)),
|
|
294
|
+
args: argumentNodes(captured(match, 'args')),
|
|
295
|
+
};
|
|
296
|
+
shapes.push(site);
|
|
297
|
+
}
|
|
298
|
+
const assignQuery = await compile(file.grammar, 'assignments', ASSIGNMENT_QUERIES[language]);
|
|
299
|
+
for (const match of assignQuery.matches(file.root)) {
|
|
300
|
+
const node = captured(match, 'assign');
|
|
301
|
+
const target = captured(match, 'target');
|
|
302
|
+
const value = captured(match, 'value');
|
|
303
|
+
if (!node || !target || !value)
|
|
304
|
+
continue;
|
|
305
|
+
if (seen.has(node.id))
|
|
306
|
+
continue;
|
|
307
|
+
seen.add(node.id);
|
|
308
|
+
const pairs = language === 'go' ? goPairs(target, value) : [{ t: target, v: value }];
|
|
309
|
+
for (const pair of pairs) {
|
|
310
|
+
const targetText = collapse(pair.t.text);
|
|
311
|
+
shapes.push({
|
|
312
|
+
kind: 'assignment',
|
|
313
|
+
node,
|
|
314
|
+
targetText,
|
|
315
|
+
targetName: lastSegment(targetText).replace(/^["']|["']$/g, ''),
|
|
316
|
+
value: pair.v,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
shapes.sort((a, b) => a.node.startIndex - b.node.startIndex);
|
|
321
|
+
return shapes;
|
|
322
|
+
}
|
|
323
|
+
/** Exposed so a self-check can compile every query against every grammar. */
|
|
324
|
+
export const SHAPE_QUERY_SOURCES = {
|
|
325
|
+
calls: CALL_QUERIES,
|
|
326
|
+
assignments: ASSIGNMENT_QUERIES,
|
|
327
|
+
};
|
|
328
|
+
//# sourceMappingURL=shapes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shapes.js","sourceRoot":"","sources":["../../../src/engine/shapes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FILE DISCOVERY
|
|
3
|
+
*
|
|
4
|
+
* Turning "scan this path" into "here is the list of files". Boring code, but
|
|
5
|
+
* two decisions in it matter:
|
|
6
|
+
*
|
|
7
|
+
* 1. We skip dependency and build directories. Scanning node_modules finds
|
|
8
|
+
* thousands of "vulnerabilities" in other people's code that you cannot fix
|
|
9
|
+
* and did not write. Dependency risk is a real and separate problem
|
|
10
|
+
* (Software Composition Analysis) and it deserves its own tool, not noise
|
|
11
|
+
* inside this one.
|
|
12
|
+
*
|
|
13
|
+
* 2. We record the extensions we skipped. That list feeds the coverage report,
|
|
14
|
+
* so a scan of a repo that is 70% Ruby says "we did not look at 412 .rb
|
|
15
|
+
* files" instead of quietly reporting "no issues found".
|
|
16
|
+
*/
|
|
17
|
+
export interface WalkResult {
|
|
18
|
+
readonly files: readonly string[];
|
|
19
|
+
/** Extension -> how many files with it we passed over. Feeds the coverage report. */
|
|
20
|
+
readonly skippedByExtension: ReadonlyMap<string, number>;
|
|
21
|
+
readonly oversizedFiles: readonly string[];
|
|
22
|
+
/** How many files --exclude removed. Printed, never silent. */
|
|
23
|
+
readonly excludedCount: number;
|
|
24
|
+
/**
|
|
25
|
+
* Directories --exclude pruned. Counted separately because we stop walking
|
|
26
|
+
* them, so we honestly do not know how many files were inside. Saying
|
|
27
|
+
* "0 files excluded" after skipping a whole tree would be its own small lie.
|
|
28
|
+
*/
|
|
29
|
+
readonly excludedDirectories: readonly string[];
|
|
30
|
+
/**
|
|
31
|
+
* Files that declare which PHP the project runs on - Dockerfiles, compose
|
|
32
|
+
* files, composer.json, `.php-version`. Not source code, so not in `files`;
|
|
33
|
+
* the runtime version check reads them (platform/runtime-version.ts). The
|
|
34
|
+
* same directories are skipped as for code, so a dependency's composer.json
|
|
35
|
+
* under vendor/ is never mistaken for the project's own.
|
|
36
|
+
*/
|
|
37
|
+
readonly runtimeFiles: readonly string[];
|
|
38
|
+
}
|
|
39
|
+
export interface WalkOptions {
|
|
40
|
+
/** Extensions we are able to parse. Everything else is counted and skipped. */
|
|
41
|
+
readonly scannable: readonly string[];
|
|
42
|
+
readonly maxBytes?: number;
|
|
43
|
+
/** Glob-ish patterns from --exclude. */
|
|
44
|
+
readonly exclude?: readonly string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Turn a glob into a regular expression.
|
|
48
|
+
*
|
|
49
|
+
* Supported, deliberately a small subset:
|
|
50
|
+
* ** any number of path segments
|
|
51
|
+
* * anything except a path separator
|
|
52
|
+
* ? one character
|
|
53
|
+
* A pattern with no wildcards and no separator (e.g. `vendor`) matches any path
|
|
54
|
+
* SEGMENT with that name, which is what people mean by `--exclude=vendor`.
|
|
55
|
+
*
|
|
56
|
+
* Everything is matched against the path with forward slashes, so the same
|
|
57
|
+
* pattern works on Windows and Unix.
|
|
58
|
+
*/
|
|
59
|
+
export declare function globToRegExp(pattern: string): RegExp;
|
|
60
|
+
export declare function collectFiles(target: string, options: WalkOptions): Promise<WalkResult>;
|