@next-secure-check/rules 0.4.0 → 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/dist/analysis-facts.d.ts +62 -0
- package/dist/analysis-facts.d.ts.map +1 -0
- package/dist/analysis-facts.js +29 -0
- package/dist/analysis-facts.js.map +1 -0
- package/dist/ast-utils.d.ts +5 -0
- package/dist/ast-utils.d.ts.map +1 -1
- package/dist/ast-utils.js +24 -8
- package/dist/ast-utils.js.map +1 -1
- package/dist/command-flow.d.ts +17 -1
- package/dist/command-flow.d.ts.map +1 -1
- package/dist/command-flow.js +124 -34
- package/dist/command-flow.js.map +1 -1
- package/dist/route-ast.d.ts +1 -0
- package/dist/route-ast.d.ts.map +1 -1
- package/dist/route-ast.js +218 -43
- package/dist/route-ast.js.map +1 -1
- package/dist/security-rules.d.ts.map +1 -1
- package/dist/security-rules.js +26 -21
- package/dist/security-rules.js.map +1 -1
- package/dist/sql-ast.d.ts +3 -0
- package/dist/sql-ast.d.ts.map +1 -1
- package/dist/sql-ast.js +177 -5
- package/dist/sql-ast.js.map +1 -1
- package/dist/xss-ast.d.ts +4 -1
- package/dist/xss-ast.d.ts.map +1 -1
- package/dist/xss-ast.js +65 -17
- package/dist/xss-ast.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
export type BoundedFlowSourceFact = {
|
|
3
|
+
readonly node: ts.Node;
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly scope: ts.Node;
|
|
6
|
+
};
|
|
7
|
+
export type BoundedFlowSinkFact = {
|
|
8
|
+
readonly node: ts.CallExpression;
|
|
9
|
+
readonly scope: ts.Node;
|
|
10
|
+
readonly kind?: string;
|
|
11
|
+
};
|
|
12
|
+
export type BoundedFlowGuardFact = {
|
|
13
|
+
readonly node: ts.CallExpression;
|
|
14
|
+
readonly scope: ts.Node;
|
|
15
|
+
readonly kind: string;
|
|
16
|
+
readonly identifier?: string;
|
|
17
|
+
};
|
|
18
|
+
export type BoundedFlowAliasFact = {
|
|
19
|
+
readonly node: ts.VariableDeclaration | ts.BinaryExpression;
|
|
20
|
+
readonly scope: ts.Node;
|
|
21
|
+
readonly from: string;
|
|
22
|
+
readonly to: string;
|
|
23
|
+
readonly depth: number;
|
|
24
|
+
readonly path?: string;
|
|
25
|
+
};
|
|
26
|
+
export type BoundedFlowInvalidationReason = "reassignment" | "mutation" | "call-escape";
|
|
27
|
+
export type BoundedFlowInvalidationFact = {
|
|
28
|
+
readonly node: ts.Node;
|
|
29
|
+
readonly scope: ts.Node;
|
|
30
|
+
readonly identifier: string;
|
|
31
|
+
readonly reason: BoundedFlowInvalidationReason;
|
|
32
|
+
};
|
|
33
|
+
export type BoundedFlowFunctionBoundaryFact = {
|
|
34
|
+
readonly node: ts.Node;
|
|
35
|
+
};
|
|
36
|
+
export type BoundedFlowEvidencePathFact = {
|
|
37
|
+
readonly sink: ts.Node;
|
|
38
|
+
readonly path: string;
|
|
39
|
+
};
|
|
40
|
+
export type BoundedFlowFacts = {
|
|
41
|
+
readonly sources: readonly BoundedFlowSourceFact[];
|
|
42
|
+
readonly sinks: readonly BoundedFlowSinkFact[];
|
|
43
|
+
readonly guards: readonly BoundedFlowGuardFact[];
|
|
44
|
+
readonly aliases: readonly BoundedFlowAliasFact[];
|
|
45
|
+
readonly invalidations: readonly BoundedFlowInvalidationFact[];
|
|
46
|
+
readonly functionBoundaries: readonly BoundedFlowFunctionBoundaryFact[];
|
|
47
|
+
readonly evidencePaths: ReadonlyMap<ts.Node, string>;
|
|
48
|
+
readonly guardedSinks: ReadonlySet<ts.CallExpression>;
|
|
49
|
+
};
|
|
50
|
+
export type BoundedFlowFactsBuilder = {
|
|
51
|
+
readonly sourceFacts: Map<ts.Node, BoundedFlowSourceFact>;
|
|
52
|
+
readonly sinkFacts: Map<ts.CallExpression, BoundedFlowSinkFact>;
|
|
53
|
+
readonly guardFacts: Map<ts.CallExpression, BoundedFlowGuardFact>;
|
|
54
|
+
readonly aliasFacts: BoundedFlowAliasFact[];
|
|
55
|
+
readonly invalidationFacts: BoundedFlowInvalidationFact[];
|
|
56
|
+
readonly functionBoundaryFacts: BoundedFlowFunctionBoundaryFact[];
|
|
57
|
+
readonly evidencePaths: Map<ts.Node, string>;
|
|
58
|
+
readonly guardedSinks: Set<ts.CallExpression>;
|
|
59
|
+
};
|
|
60
|
+
export declare function createBoundedFlowFactsBuilder(): BoundedFlowFactsBuilder;
|
|
61
|
+
export declare function finalizeBoundedFlowFacts(builder: BoundedFlowFactsBuilder): BoundedFlowFacts;
|
|
62
|
+
//# sourceMappingURL=analysis-facts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis-facts.d.ts","sourceRoot":"","sources":["../src/analysis-facts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function createBoundedFlowFactsBuilder() {
|
|
2
|
+
return {
|
|
3
|
+
sourceFacts: new Map(),
|
|
4
|
+
sinkFacts: new Map(),
|
|
5
|
+
guardFacts: new Map(),
|
|
6
|
+
aliasFacts: [],
|
|
7
|
+
invalidationFacts: [],
|
|
8
|
+
functionBoundaryFacts: [],
|
|
9
|
+
evidencePaths: new Map(),
|
|
10
|
+
guardedSinks: new Set()
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function finalizeBoundedFlowFacts(builder) {
|
|
14
|
+
const invalidations = new Map();
|
|
15
|
+
for (const invalidation of builder.invalidationFacts) {
|
|
16
|
+
invalidations.set(`${invalidation.node.pos}:${invalidation.node.end}:${invalidation.identifier}:${invalidation.reason}`, invalidation);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
sources: Object.freeze([...builder.sourceFacts.values()]),
|
|
20
|
+
sinks: Object.freeze([...builder.sinkFacts.values()]),
|
|
21
|
+
guards: Object.freeze([...builder.guardFacts.values()]),
|
|
22
|
+
aliases: Object.freeze([...builder.aliasFacts]),
|
|
23
|
+
invalidations: Object.freeze([...invalidations.values()]),
|
|
24
|
+
functionBoundaries: Object.freeze([...builder.functionBoundaryFacts]),
|
|
25
|
+
evidencePaths: new Map(builder.evidencePaths),
|
|
26
|
+
guardedSinks: new Set(builder.guardedSinks)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=analysis-facts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis-facts.js","sourceRoot":"","sources":["../src/analysis-facts.ts"],"names":[],"mappings":"AAsEA,MAAM,UAAU,6BAA6B;IAC3C,OAAO;QACL,WAAW,EAAE,IAAI,GAAG,EAAE;QACtB,SAAS,EAAE,IAAI,GAAG,EAAE;QACpB,UAAU,EAAE,IAAI,GAAG,EAAE;QACrB,UAAU,EAAE,EAAE;QACd,iBAAiB,EAAE,EAAE;QACrB,qBAAqB,EAAE,EAAE;QACzB,aAAa,EAAE,IAAI,GAAG,EAAE;QACxB,YAAY,EAAE,IAAI,GAAG,EAAE;KACxB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,OAAgC;IACvE,MAAM,aAAa,GAAG,IAAI,GAAG,EAAuC,CAAC;IACrE,KAAK,MAAM,YAAY,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;QACrD,aAAa,CAAC,GAAG,CACf,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,YAAY,CAAC,UAAU,IAAI,YAAY,CAAC,MAAM,EAAE,EACrG,YAAY,CACb,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC;QACzD,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;QACrD,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QACvD,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QAC/C,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC;QACzD,kBAAkB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;QACrE,aAAa,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;QAC7C,YAAY,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC;KAC5C,CAAC;AACJ,CAAC"}
|
package/dist/ast-utils.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Severity, SourceFile } from "@next-secure-check/core";
|
|
2
2
|
import ts from "typescript";
|
|
3
3
|
import { type AnalysisCacheStats } from "./analysis-cache.js";
|
|
4
|
+
import type { BoundedFlowFacts } from "./analysis-facts.js";
|
|
4
5
|
export type AstMatch = {
|
|
5
6
|
line: number;
|
|
6
7
|
column: number;
|
|
@@ -13,6 +14,7 @@ export type DangerouslySetInnerHtmlMatch = AstMatch & {
|
|
|
13
14
|
};
|
|
14
15
|
export type AnalysisFacts = {
|
|
15
16
|
sourceFile: ts.SourceFile;
|
|
17
|
+
boundedFlow: BoundedFlowFacts;
|
|
16
18
|
commandIdentifiers: ReadonlySet<string>;
|
|
17
19
|
childProcessNamespaces: ReadonlySet<string>;
|
|
18
20
|
commandDeclarationNodes: readonly ts.Node[];
|
|
@@ -20,9 +22,11 @@ export type AnalysisFacts = {
|
|
|
20
22
|
safeCommandCalls: ReadonlySet<ts.CallExpression>;
|
|
21
23
|
routeHandlerNodes: readonly ts.Node[];
|
|
22
24
|
sanitizerIdentifiers: ReadonlySet<string>;
|
|
25
|
+
untrustedSanitizerIdentifiers: ReadonlySet<string>;
|
|
23
26
|
safeHtmlIdentifiers: ReadonlySet<string>;
|
|
24
27
|
hasPasswordHashing: boolean;
|
|
25
28
|
hasAuthIntent: boolean;
|
|
29
|
+
hasRateLimitIntent: boolean;
|
|
26
30
|
hasValidationIntent: boolean;
|
|
27
31
|
hasUploadHandling: boolean;
|
|
28
32
|
};
|
|
@@ -36,6 +40,7 @@ export declare function findDangerouslySetInnerHtmlMatches(file: SourceFile): Da
|
|
|
36
40
|
export declare function findPasswordHandlingMatches(file: SourceFile): AstMatch[];
|
|
37
41
|
export declare function findRouteHandlerExports(file: SourceFile): AstMatch[];
|
|
38
42
|
export declare function hasAuthIntentSignal(file: SourceFile): boolean;
|
|
43
|
+
export declare function hasRateLimitIntentSignal(file: SourceFile): boolean;
|
|
39
44
|
export declare function hasValidationIntentSignal(file: SourceFile): boolean;
|
|
40
45
|
export declare function findUploadRouteHandlerMatches(file: SourceFile): AstMatch[];
|
|
41
46
|
//# sourceMappingURL=ast-utils.d.ts.map
|
package/dist/ast-utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../src/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../src/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAgB5D,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,QAAQ,GAAG;IACpD,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,KAAK,GAAG,QAAQ,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC;IAC1B,WAAW,EAAE,gBAAgB,CAAC;IAC9B,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACxC,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C,uBAAuB,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;IAC5C,kBAAkB,EAAE,WAAW,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAC3D,gBAAgB,EAAE,WAAW,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC;IACjD,iBAAiB,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;IACtC,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,6BAA6B,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnD,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACzC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,iBAAiB,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,kBAAkB,CAAC;AAIzD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,aAAa,CAWhE;AAED,wBAAgB,0BAA0B,IAAI,uBAAuB,CAEpE;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,EAAE,CAmBxE;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,EAAE,CAepE;AAED,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,UAAU,GAAG,4BAA4B,EAAE,CAenG;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,EAAE,CAOxE;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,EAAE,CAGpE;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAE7D;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAElE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAEnE;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,EAAE,CAc1E"}
|
package/dist/ast-utils.js
CHANGED
|
@@ -3,8 +3,8 @@ import { SourceAnalysisCache } from "./analysis-cache.js";
|
|
|
3
3
|
import { collectCommandDiscovery } from "./command-discovery.js";
|
|
4
4
|
import { collectCommandFlowFacts, isAnalyzableCommandExecutionCall } from "./command-flow.js";
|
|
5
5
|
import { findPasswordHandlingNodes, hasPasswordHashingCall } from "./password-ast.js";
|
|
6
|
-
import { findRawSqlConcatNodes } from "./sql-ast.js";
|
|
7
|
-
import { ROUTE_HANDLER_NAMES, exportedRouteHandlerName, hasAuthIntentInSource, hasValidationIntentInSource, isApiRouteFilePath, isUploadHandlingNode } from "./route-ast.js";
|
|
6
|
+
import { createRawSqlFlowCallbacks, findRawSqlConcatNodes } from "./sql-ast.js";
|
|
7
|
+
import { ROUTE_HANDLER_NAMES, exportedRouteHandlerName, hasAuthIntentInSource, hasRateLimitIntentInSource, hasValidationIntentInSource, isApiRouteFilePath, isUploadHandlingNode } from "./route-ast.js";
|
|
8
8
|
import { createXssAnalysisFacts, findDangerouslySetInnerHtmlNodes } from "./xss-ast.js";
|
|
9
9
|
const analysisFactsCache = new SourceAnalysisCache();
|
|
10
10
|
export function getAnalysisFacts(file) {
|
|
@@ -35,14 +35,24 @@ export function findCommandExecutionMatches(file) {
|
|
|
35
35
|
return dedupeMatches(matches);
|
|
36
36
|
}
|
|
37
37
|
export function findRawSqlConcatMatches(file) {
|
|
38
|
-
const { sourceFile } = getAnalysisFacts(file);
|
|
39
|
-
|
|
38
|
+
const { sourceFile, boundedFlow } = getAnalysisFacts(file);
|
|
39
|
+
const directMatches = findRawSqlConcatNodes(sourceFile);
|
|
40
|
+
const boundedMatches = boundedFlow.sinks
|
|
41
|
+
.filter((sink) => sink.kind === "raw-sql")
|
|
42
|
+
.map((sink) => sink.node);
|
|
43
|
+
const nodes = [...directMatches, ...boundedMatches];
|
|
44
|
+
return dedupeMatches(nodes.map((node) => {
|
|
45
|
+
const match = matchFromNode(file, sourceFile, node);
|
|
46
|
+
const evidencePath = boundedFlow.evidencePaths.get(node);
|
|
47
|
+
return evidencePath ? { ...match, evidencePath } : match;
|
|
48
|
+
}));
|
|
40
49
|
}
|
|
41
50
|
export function findDangerouslySetInnerHtmlMatches(file) {
|
|
42
|
-
const { sourceFile, sanitizerIdentifiers, safeHtmlIdentifiers } = getAnalysisFacts(file);
|
|
43
|
-
return dedupeMatches(findDangerouslySetInnerHtmlNodes(sourceFile, sanitizerIdentifiers, safeHtmlIdentifiers).map(({ node, severity }) => ({
|
|
51
|
+
const { sourceFile, boundedFlow, sanitizerIdentifiers, untrustedSanitizerIdentifiers, safeHtmlIdentifiers } = getAnalysisFacts(file);
|
|
52
|
+
return dedupeMatches(findDangerouslySetInnerHtmlNodes(sourceFile, sanitizerIdentifiers, safeHtmlIdentifiers, boundedFlow, untrustedSanitizerIdentifiers).map(({ node, severity, evidencePath }) => ({
|
|
44
53
|
...matchFromNode(file, sourceFile, node),
|
|
45
|
-
severity
|
|
54
|
+
severity,
|
|
55
|
+
...(evidencePath ? { evidencePath } : {})
|
|
46
56
|
})));
|
|
47
57
|
}
|
|
48
58
|
export function findPasswordHandlingMatches(file) {
|
|
@@ -59,6 +69,9 @@ export function findRouteHandlerExports(file) {
|
|
|
59
69
|
export function hasAuthIntentSignal(file) {
|
|
60
70
|
return getAnalysisFacts(file).hasAuthIntent;
|
|
61
71
|
}
|
|
72
|
+
export function hasRateLimitIntentSignal(file) {
|
|
73
|
+
return getAnalysisFacts(file).hasRateLimitIntent;
|
|
74
|
+
}
|
|
62
75
|
export function hasValidationIntentSignal(file) {
|
|
63
76
|
return getAnalysisFacts(file).hasValidationIntent;
|
|
64
77
|
}
|
|
@@ -77,7 +90,7 @@ export function findUploadRouteHandlerMatches(file) {
|
|
|
77
90
|
}
|
|
78
91
|
function createAnalysisFacts(sourceFile) {
|
|
79
92
|
const commandDiscovery = collectCommandDiscovery(sourceFile);
|
|
80
|
-
const commandFlow = collectCommandFlowFacts(sourceFile, commandDiscovery.commandIdentifiers, commandDiscovery.childProcessNamespaces);
|
|
93
|
+
const commandFlow = collectCommandFlowFacts(sourceFile, commandDiscovery.commandIdentifiers, commandDiscovery.childProcessNamespaces, createRawSqlFlowCallbacks());
|
|
81
94
|
const routeHandlerNodes = [];
|
|
82
95
|
let hasUploadHandling = false;
|
|
83
96
|
visit(sourceFile, (node) => {
|
|
@@ -92,6 +105,7 @@ function createAnalysisFacts(sourceFile) {
|
|
|
92
105
|
const xssFacts = createXssAnalysisFacts(sourceFile);
|
|
93
106
|
return {
|
|
94
107
|
sourceFile,
|
|
108
|
+
boundedFlow: commandFlow.boundedFlow,
|
|
95
109
|
commandIdentifiers: commandDiscovery.commandIdentifiers,
|
|
96
110
|
childProcessNamespaces: commandDiscovery.childProcessNamespaces,
|
|
97
111
|
commandDeclarationNodes: commandDiscovery.commandDeclarationNodes,
|
|
@@ -99,9 +113,11 @@ function createAnalysisFacts(sourceFile) {
|
|
|
99
113
|
safeCommandCalls: commandFlow.safeCommandCalls,
|
|
100
114
|
routeHandlerNodes,
|
|
101
115
|
sanitizerIdentifiers: xssFacts.sanitizerIdentifiers,
|
|
116
|
+
untrustedSanitizerIdentifiers: xssFacts.untrustedSanitizerIdentifiers,
|
|
102
117
|
safeHtmlIdentifiers: xssFacts.safeHtmlIdentifiers,
|
|
103
118
|
hasPasswordHashing: hasPasswordHashingCall(sourceFile),
|
|
104
119
|
hasAuthIntent: hasAuthIntentInSource(sourceFile),
|
|
120
|
+
hasRateLimitIntent: hasRateLimitIntentInSource(sourceFile),
|
|
105
121
|
hasValidationIntent: hasValidationIntentInSource(sourceFile),
|
|
106
122
|
hasUploadHandling
|
|
107
123
|
};
|
package/dist/ast-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-utils.js","sourceRoot":"","sources":["../src/ast-utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAA2B,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"ast-utils.js","sourceRoot":"","sources":["../src/ast-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/command-flow.d.ts
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { type BoundedFlowFacts, type BoundedFlowInvalidationReason } from "./analysis-facts.js";
|
|
2
3
|
export type CommandFlowFacts = {
|
|
3
4
|
sourcePaths: ReadonlyMap<ts.CallExpression, string>;
|
|
4
5
|
safeCommandCalls: ReadonlySet<ts.CallExpression>;
|
|
6
|
+
boundedFlow: BoundedFlowFacts;
|
|
7
|
+
};
|
|
8
|
+
export type BoundedFlowContext = {
|
|
9
|
+
readonly scopeRoot: ts.Node;
|
|
10
|
+
readonly findSourcePathInExpression: (node: ts.Node) => string | undefined;
|
|
11
|
+
readonly findSourcePathInArguments: (node: ts.CallExpression) => string | undefined;
|
|
12
|
+
readonly recordSink: (node: ts.CallExpression, kind?: string) => void;
|
|
13
|
+
readonly recordEvidencePath: (node: ts.Node, path: string) => void;
|
|
14
|
+
};
|
|
15
|
+
export type BoundedFlowCallbacks = {
|
|
16
|
+
readonly onVariableDeclaration?: (node: ts.VariableDeclaration, context: BoundedFlowContext) => void;
|
|
17
|
+
readonly onAssignment?: (node: ts.BinaryExpression, context: BoundedFlowContext) => void;
|
|
18
|
+
readonly onCall?: (node: ts.CallExpression, context: BoundedFlowContext) => void;
|
|
19
|
+
readonly onTaggedTemplate?: (node: ts.TaggedTemplateExpression, context: BoundedFlowContext) => void;
|
|
20
|
+
readonly onInvalidation?: (identifier: string, reason: BoundedFlowInvalidationReason, context: BoundedFlowContext) => void;
|
|
5
21
|
};
|
|
6
22
|
export declare function isAnalyzableCommandExecutionCall(node: ts.CallExpression, commandIdentifiers: ReadonlySet<string>, childProcessNamespaces: ReadonlySet<string>): boolean;
|
|
7
23
|
export declare function collectCommandSourcePaths(sourceFile: ts.SourceFile, commandIdentifiers: ReadonlySet<string>, childProcessNamespaces: ReadonlySet<string>): ReadonlyMap<ts.CallExpression, string>;
|
|
8
|
-
export declare function collectCommandFlowFacts(sourceFile: ts.SourceFile, commandIdentifiers: ReadonlySet<string>, childProcessNamespaces: ReadonlySet<string
|
|
24
|
+
export declare function collectCommandFlowFacts(sourceFile: ts.SourceFile, commandIdentifiers: ReadonlySet<string>, childProcessNamespaces: ReadonlySet<string>, callbacks?: BoundedFlowCallbacks): CommandFlowFacts;
|
|
9
25
|
//# sourceMappingURL=command-flow.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-flow.d.ts","sourceRoot":"","sources":["../src/command-flow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"command-flow.d.ts","sourceRoot":"","sources":["../src/command-flow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAc5B,OAAO,EAGL,KAAK,gBAAgB,EAErB,KAAK,6BAA6B,EACnC,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACpD,gBAAgB,EAAE,WAAW,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC;IACjD,WAAW,EAAE,gBAAgB,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC;IAC5B,QAAQ,CAAC,0BAA0B,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,MAAM,GAAG,SAAS,CAAC;IAC3E,QAAQ,CAAC,yBAAyB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,KAAK,MAAM,GAAG,SAAS,CAAC;IACpF,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,kBAAkB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACpE,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,EAAE,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACrG,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,EAAE,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACzF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,EAAE,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACjF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,wBAAwB,EAAE,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACrG,QAAQ,CAAC,cAAc,CAAC,EAAE,CACxB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,6BAA6B,EACrC,OAAO,EAAE,kBAAkB,KACxB,IAAI,CAAC;CACX,CAAC;AAEF,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,EAAE,CAAC,cAAc,EACvB,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,EACvC,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAAC,GAC1C,OAAO,CAET;AAED,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,EACvC,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAAC,GAC1C,WAAW,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,CAAC,CAExC;AAED,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,EACvC,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAAC,EAC3C,SAAS,GAAE,oBAAyB,GACnC,gBAAgB,CA6BlB"}
|
package/dist/command-flow.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { COMMAND_ALIAS_LIMIT, REQUEST_SOURCE_NAMES, ROUTE_PARAMS_NAMES, SEARCH_PARAMS_NAME, bindingElementLocalName, bindingElementName, commandExecutionName, isCommandAssignmentOperator, isCommandExecutionCall, isCommandMutationOperator } from "./command-ast.js";
|
|
3
|
+
import { createBoundedFlowFactsBuilder, finalizeBoundedFlowFacts } from "./analysis-facts.js";
|
|
3
4
|
import { hasCommandAllowlistGuard, isCommandAllowlistMembershipCall } from "./command-guards.js";
|
|
4
5
|
export function isAnalyzableCommandExecutionCall(node, commandIdentifiers, childProcessNamespaces) {
|
|
5
6
|
return isCommandExecutionCall(node.expression, commandIdentifiers, childProcessNamespaces) && !isSafeStaticSpawnCall(node);
|
|
@@ -7,20 +8,31 @@ export function isAnalyzableCommandExecutionCall(node, commandIdentifiers, child
|
|
|
7
8
|
export function collectCommandSourcePaths(sourceFile, commandIdentifiers, childProcessNamespaces) {
|
|
8
9
|
return collectCommandFlowFacts(sourceFile, commandIdentifiers, childProcessNamespaces).sourcePaths;
|
|
9
10
|
}
|
|
10
|
-
export function collectCommandFlowFacts(sourceFile, commandIdentifiers, childProcessNamespaces) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
analyzeCommandScope(sourceFile, sourceFile, commandIdentifiers, childProcessNamespaces,
|
|
11
|
+
export function collectCommandFlowFacts(sourceFile, commandIdentifiers, childProcessNamespaces, callbacks = {}) {
|
|
12
|
+
const factsBuilder = createBoundedFlowFactsBuilder();
|
|
13
|
+
collectFunctionBoundaryFacts(sourceFile, factsBuilder);
|
|
14
|
+
analyzeCommandScope(sourceFile, sourceFile, commandIdentifiers, childProcessNamespaces, factsBuilder, callbacks);
|
|
14
15
|
visitCommandNodes(sourceFile, (node) => {
|
|
15
16
|
if (!isCommandFunctionLike(node)) {
|
|
16
17
|
return;
|
|
17
18
|
}
|
|
18
19
|
const body = commandFunctionBody(node);
|
|
19
20
|
if (body) {
|
|
20
|
-
analyzeCommandScope(body, body, commandIdentifiers, childProcessNamespaces,
|
|
21
|
+
analyzeCommandScope(body, body, commandIdentifiers, childProcessNamespaces, factsBuilder, callbacks);
|
|
21
22
|
}
|
|
22
23
|
});
|
|
23
|
-
|
|
24
|
+
const boundedFlow = finalizeBoundedFlowFacts(factsBuilder);
|
|
25
|
+
const sourcePaths = new Map();
|
|
26
|
+
for (const [node, path] of boundedFlow.evidencePaths) {
|
|
27
|
+
if (ts.isCallExpression(node)) {
|
|
28
|
+
sourcePaths.set(node, path);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
sourcePaths,
|
|
33
|
+
safeCommandCalls: boundedFlow.guardedSinks,
|
|
34
|
+
boundedFlow
|
|
35
|
+
};
|
|
24
36
|
}
|
|
25
37
|
function isSafeStaticSpawnCall(node) {
|
|
26
38
|
const methodName = commandExecutionName(node.expression);
|
|
@@ -59,53 +71,69 @@ function isSafeStaticSpawnCall(node) {
|
|
|
59
71
|
return property.initializer.kind === ts.SyntaxKind.FalseKeyword;
|
|
60
72
|
});
|
|
61
73
|
}
|
|
62
|
-
function analyzeCommandScope(scopeRoot, node, commandIdentifiers, childProcessNamespaces,
|
|
74
|
+
function analyzeCommandScope(scopeRoot, node, commandIdentifiers, childProcessNamespaces, factsBuilder, callbacks, state = createCommandFlowState(factsBuilder, scopeRoot, callbacks)) {
|
|
63
75
|
if (node !== scopeRoot && isCommandFunctionLike(node)) {
|
|
64
76
|
return;
|
|
65
77
|
}
|
|
66
78
|
if (ts.isVariableDeclaration(node)) {
|
|
67
79
|
recordCommandDeclaration(node, state);
|
|
80
|
+
state.callbacks.onVariableDeclaration?.(node, createBoundedFlowContext(state));
|
|
68
81
|
}
|
|
69
82
|
if (ts.isBinaryExpression(node) && isCommandAssignmentOperator(node.operatorToken.kind)) {
|
|
70
83
|
recordCommandAssignment(node, state);
|
|
84
|
+
state.callbacks.onAssignment?.(node, createBoundedFlowContext(state));
|
|
71
85
|
}
|
|
72
86
|
if (ts.isPrefixUnaryExpression(node) && isCommandMutationOperator(node.operator)) {
|
|
73
|
-
invalidateCommandTarget(node.operand, state);
|
|
87
|
+
invalidateCommandTarget(node.operand, state, "mutation");
|
|
74
88
|
}
|
|
75
89
|
if (ts.isPostfixUnaryExpression(node) && isCommandMutationOperator(node.operator)) {
|
|
76
|
-
invalidateCommandTarget(node.operand, state);
|
|
90
|
+
invalidateCommandTarget(node.operand, state, "mutation");
|
|
91
|
+
}
|
|
92
|
+
if (ts.isTaggedTemplateExpression(node)) {
|
|
93
|
+
state.callbacks.onTaggedTemplate?.(node, createBoundedFlowContext(state));
|
|
77
94
|
}
|
|
78
95
|
if (ts.isCallExpression(node)) {
|
|
96
|
+
state.callbacks.onCall?.(node, createBoundedFlowContext(state));
|
|
79
97
|
if (isAnalyzableCommandExecutionCall(node, commandIdentifiers, childProcessNamespaces)) {
|
|
98
|
+
state.factsBuilder.sinkFacts.set(node, { node, scope: scopeRoot, kind: commandExecutionName(node.expression) });
|
|
80
99
|
const sourcePath = findCommandSourcePathInArguments(node, state);
|
|
81
100
|
if (sourcePath) {
|
|
82
|
-
|
|
83
|
-
|
|
101
|
+
const guardIdentifier = guardedCommandIdentifier(node, state);
|
|
102
|
+
if (guardIdentifier) {
|
|
103
|
+
state.factsBuilder.guardedSinks.add(node);
|
|
104
|
+
state.factsBuilder.guardFacts.set(node, {
|
|
105
|
+
node,
|
|
106
|
+
scope: scopeRoot,
|
|
107
|
+
kind: "command-allowlist",
|
|
108
|
+
identifier: guardIdentifier
|
|
109
|
+
});
|
|
84
110
|
}
|
|
85
111
|
else {
|
|
86
|
-
|
|
112
|
+
state.factsBuilder.evidencePaths.set(node, sourcePath);
|
|
87
113
|
}
|
|
88
114
|
}
|
|
89
115
|
else {
|
|
90
|
-
invalidateTaintedReferences(node, state);
|
|
116
|
+
invalidateTaintedReferences(node, state, "call-escape");
|
|
91
117
|
}
|
|
92
118
|
}
|
|
93
119
|
else if (!isCommandAllowlistMembershipCall(node) && !sourcePathForExpression(node, state)) {
|
|
94
|
-
invalidateTaintedReferences(node, state);
|
|
120
|
+
invalidateTaintedReferences(node, state, "call-escape");
|
|
95
121
|
}
|
|
96
122
|
}
|
|
97
|
-
ts.forEachChild(node, (child) => analyzeCommandScope(scopeRoot, child, commandIdentifiers, childProcessNamespaces,
|
|
123
|
+
ts.forEachChild(node, (child) => analyzeCommandScope(scopeRoot, child, commandIdentifiers, childProcessNamespaces, factsBuilder, callbacks, state));
|
|
98
124
|
}
|
|
99
|
-
function
|
|
125
|
+
function guardedCommandIdentifier(node, state) {
|
|
100
126
|
const [commandArgument] = node.arguments;
|
|
101
127
|
if (!commandArgument) {
|
|
102
|
-
return
|
|
128
|
+
return undefined;
|
|
103
129
|
}
|
|
104
130
|
const normalized = unwrapCommandExpression(commandArgument);
|
|
105
131
|
if (!ts.isIdentifier(normalized) || !sourcePathForExpression(normalized, state)) {
|
|
106
|
-
return
|
|
132
|
+
return undefined;
|
|
107
133
|
}
|
|
108
|
-
return hasCommandAllowlistGuard(node, normalized.text) && !hasUntrustedSpawnArguments(node, state)
|
|
134
|
+
return hasCommandAllowlistGuard(node, normalized.text) && !hasUntrustedSpawnArguments(node, state)
|
|
135
|
+
? normalized.text
|
|
136
|
+
: undefined;
|
|
109
137
|
}
|
|
110
138
|
function hasUntrustedSpawnArguments(node, state) {
|
|
111
139
|
const methodName = commandExecutionName(node.expression);
|
|
@@ -114,14 +142,30 @@ function hasUntrustedSpawnArguments(node, state) {
|
|
|
114
142
|
}
|
|
115
143
|
return node.arguments.slice(1).some((argument) => Boolean(findCommandSourcePathInExpression(argument, state)));
|
|
116
144
|
}
|
|
117
|
-
function createCommandFlowState() {
|
|
145
|
+
function createCommandFlowState(factsBuilder, scopeRoot, callbacks) {
|
|
118
146
|
return {
|
|
147
|
+
factsBuilder,
|
|
148
|
+
scopeRoot,
|
|
149
|
+
callbacks,
|
|
119
150
|
declared: new Set(),
|
|
120
151
|
initialized: new Set(),
|
|
121
152
|
invalidated: new Set(),
|
|
122
153
|
tracked: new Map()
|
|
123
154
|
};
|
|
124
155
|
}
|
|
156
|
+
function createBoundedFlowContext(state) {
|
|
157
|
+
return {
|
|
158
|
+
scopeRoot: state.scopeRoot,
|
|
159
|
+
findSourcePathInExpression: (node) => findCommandSourcePathInExpression(node, state),
|
|
160
|
+
findSourcePathInArguments: (node) => findCommandSourcePathInArguments(node, state),
|
|
161
|
+
recordSink: (node, kind) => {
|
|
162
|
+
state.factsBuilder.sinkFacts.set(node, { node, scope: state.scopeRoot, kind });
|
|
163
|
+
},
|
|
164
|
+
recordEvidencePath: (node, path) => {
|
|
165
|
+
state.factsBuilder.evidencePaths.set(node, path);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
125
169
|
function recordCommandDeclaration(node, state) {
|
|
126
170
|
if (ts.isIdentifier(node.name)) {
|
|
127
171
|
state.declared.add(node.name.text);
|
|
@@ -129,7 +173,9 @@ function recordCommandDeclaration(node, state) {
|
|
|
129
173
|
return;
|
|
130
174
|
}
|
|
131
175
|
state.initialized.add(node.name.text);
|
|
132
|
-
|
|
176
|
+
const value = sourcePathForAssignment(node.initializer, state, node.name.text);
|
|
177
|
+
recordIdentifierAlias(node, node.name.text, node.initializer, value, state);
|
|
178
|
+
trackCommandValue(node.name.text, value, state);
|
|
133
179
|
return;
|
|
134
180
|
}
|
|
135
181
|
if (!ts.isObjectBindingPattern(node.name) || !node.initializer) {
|
|
@@ -150,20 +196,36 @@ function recordCommandDeclaration(node, state) {
|
|
|
150
196
|
function recordCommandAssignment(node, state) {
|
|
151
197
|
const target = commandTargetIdentifier(node.left);
|
|
152
198
|
if (!target) {
|
|
153
|
-
invalidateTaintedReferences(node.left, state);
|
|
199
|
+
invalidateTaintedReferences(node.left, state, "reassignment");
|
|
154
200
|
return;
|
|
155
201
|
}
|
|
156
202
|
if (node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) {
|
|
157
|
-
invalidateCommandTarget(node.left, state);
|
|
203
|
+
invalidateCommandTarget(node.left, state, "mutation");
|
|
158
204
|
return;
|
|
159
205
|
}
|
|
160
206
|
if (state.invalidated.has(target) || state.initialized.has(target)) {
|
|
161
|
-
invalidateCommandTarget(node.left, state);
|
|
207
|
+
invalidateCommandTarget(node.left, state, "reassignment");
|
|
162
208
|
return;
|
|
163
209
|
}
|
|
164
210
|
state.declared.add(target);
|
|
165
211
|
state.initialized.add(target);
|
|
166
|
-
|
|
212
|
+
const value = sourcePathForAssignment(node.right, state, target);
|
|
213
|
+
recordIdentifierAlias(node, target, node.right, value, state);
|
|
214
|
+
trackCommandValue(target, value, state);
|
|
215
|
+
}
|
|
216
|
+
function recordIdentifierAlias(node, target, expression, value, state) {
|
|
217
|
+
const normalized = unwrapCommandExpression(expression);
|
|
218
|
+
if (!value || value.aliasDepth > COMMAND_ALIAS_LIMIT || !ts.isIdentifier(normalized) || normalized.text === target) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
state.factsBuilder.aliasFacts.push({
|
|
222
|
+
node,
|
|
223
|
+
scope: state.scopeRoot,
|
|
224
|
+
from: normalized.text,
|
|
225
|
+
to: target,
|
|
226
|
+
depth: value.aliasDepth,
|
|
227
|
+
path: value.path
|
|
228
|
+
});
|
|
167
229
|
}
|
|
168
230
|
function trackCommandValue(name, value, state) {
|
|
169
231
|
if (!value || value.aliasDepth > COMMAND_ALIAS_LIMIT || state.invalidated.has(name)) {
|
|
@@ -209,7 +271,7 @@ function sourcePathForExpression(expression, state) {
|
|
|
209
271
|
return tracked;
|
|
210
272
|
}
|
|
211
273
|
if (ROUTE_PARAMS_NAMES.test(normalized.text)) {
|
|
212
|
-
return { path: normalized.text, aliasDepth: 0 };
|
|
274
|
+
return recordDirectSource(normalized, { path: normalized.text, aliasDepth: 0 }, state);
|
|
213
275
|
}
|
|
214
276
|
return undefined;
|
|
215
277
|
}
|
|
@@ -231,12 +293,19 @@ function sourcePathForReference(expression, state) {
|
|
|
231
293
|
if (state.invalidated.has(normalized.text)) {
|
|
232
294
|
return undefined;
|
|
233
295
|
}
|
|
234
|
-
|
|
296
|
+
const tracked = state.tracked.get(normalized.text);
|
|
297
|
+
if (tracked) {
|
|
298
|
+
return tracked;
|
|
299
|
+
}
|
|
300
|
+
if (ROUTE_PARAMS_NAMES.test(normalized.text)) {
|
|
301
|
+
return recordDirectSource(normalized, { path: normalized.text, aliasDepth: 0 }, state);
|
|
302
|
+
}
|
|
303
|
+
return undefined;
|
|
235
304
|
}
|
|
236
305
|
if (ts.isPropertyAccessExpression(normalized)) {
|
|
237
306
|
const directSource = directCommandPropertySource(normalized);
|
|
238
307
|
if (directSource) {
|
|
239
|
-
return directSource;
|
|
308
|
+
return recordDirectSource(normalized, directSource, state);
|
|
240
309
|
}
|
|
241
310
|
const base = sourcePathForReference(normalized.expression, state);
|
|
242
311
|
return base ? commandProperty(base, normalized.name.text) : undefined;
|
|
@@ -248,7 +317,7 @@ function sourcePathForReference(expression, state) {
|
|
|
248
317
|
}
|
|
249
318
|
const directSource = directCommandElementSource(normalized, propertyName);
|
|
250
319
|
if (directSource) {
|
|
251
|
-
return directSource;
|
|
320
|
+
return recordDirectSource(normalized, directSource, state);
|
|
252
321
|
}
|
|
253
322
|
const base = sourcePathForReference(normalized.expression, state);
|
|
254
323
|
return base ? commandProperty(base, propertyName) : undefined;
|
|
@@ -257,11 +326,11 @@ function sourcePathForReference(expression, state) {
|
|
|
257
326
|
const methodName = normalized.expression.name.text;
|
|
258
327
|
const receiver = normalized.expression.expression;
|
|
259
328
|
if ((methodName === "json" || methodName === "formData") && isRequestSourceReceiver(receiver)) {
|
|
260
|
-
return { path: `${commandExpressionLabel(receiver)}.${methodName}()`, aliasDepth: 0 };
|
|
329
|
+
return recordDirectSource(normalized, { path: `${commandExpressionLabel(receiver)}.${methodName}()`, aliasDepth: 0 }, state);
|
|
261
330
|
}
|
|
262
331
|
if (methodName === "get") {
|
|
263
332
|
if (isSearchParamsReceiver(receiver)) {
|
|
264
|
-
return { path: `${commandExpressionLabel(receiver)}.get()`, aliasDepth: 0 };
|
|
333
|
+
return recordDirectSource(normalized, { path: `${commandExpressionLabel(receiver)}.get()`, aliasDepth: 0 }, state);
|
|
265
334
|
}
|
|
266
335
|
const base = sourcePathForReference(receiver, state);
|
|
267
336
|
return base ? commandProperty(base, "get()") : undefined;
|
|
@@ -269,6 +338,14 @@ function sourcePathForReference(expression, state) {
|
|
|
269
338
|
}
|
|
270
339
|
return undefined;
|
|
271
340
|
}
|
|
341
|
+
function recordDirectSource(node, value, state) {
|
|
342
|
+
state.factsBuilder.sourceFacts.set(node, {
|
|
343
|
+
node,
|
|
344
|
+
path: value.path,
|
|
345
|
+
scope: state.scopeRoot
|
|
346
|
+
});
|
|
347
|
+
return value;
|
|
348
|
+
}
|
|
272
349
|
function directCommandPropertySource(node) {
|
|
273
350
|
if (!isRequestSourceReceiver(node.expression) || (node.name.text !== "body" && node.name.text !== "query")) {
|
|
274
351
|
return undefined;
|
|
@@ -293,15 +370,16 @@ function commandAlias(value, aliasName) {
|
|
|
293
370
|
aliasDepth: value.aliasDepth + 1
|
|
294
371
|
};
|
|
295
372
|
}
|
|
296
|
-
function invalidateCommandTarget(node, state) {
|
|
373
|
+
function invalidateCommandTarget(node, state, reason) {
|
|
297
374
|
const target = commandTargetIdentifier(node);
|
|
298
375
|
if (target) {
|
|
376
|
+
recordInvalidation(node, target, reason, state);
|
|
299
377
|
state.tracked.delete(target);
|
|
300
378
|
state.invalidated.add(target);
|
|
301
379
|
}
|
|
302
|
-
invalidateTaintedReferences(node, state);
|
|
380
|
+
invalidateTaintedReferences(node, state, reason);
|
|
303
381
|
}
|
|
304
|
-
function invalidateTaintedReferences(node, state) {
|
|
382
|
+
function invalidateTaintedReferences(node, state, reason) {
|
|
305
383
|
const identifiers = new Set();
|
|
306
384
|
visitCommandNodes(node, (child) => {
|
|
307
385
|
if (!ts.isIdentifier(child) || !state.tracked.has(child.text)) {
|
|
@@ -313,10 +391,15 @@ function invalidateTaintedReferences(node, state) {
|
|
|
313
391
|
identifiers.add(child.text);
|
|
314
392
|
});
|
|
315
393
|
for (const identifier of identifiers) {
|
|
394
|
+
recordInvalidation(node, identifier, reason, state);
|
|
316
395
|
state.tracked.delete(identifier);
|
|
317
396
|
state.invalidated.add(identifier);
|
|
318
397
|
}
|
|
319
398
|
}
|
|
399
|
+
function recordInvalidation(node, identifier, reason, state) {
|
|
400
|
+
state.factsBuilder.invalidationFacts.push({ node, scope: state.scopeRoot, identifier, reason });
|
|
401
|
+
state.callbacks.onInvalidation?.(identifier, reason, createBoundedFlowContext(state));
|
|
402
|
+
}
|
|
320
403
|
function commandTargetIdentifier(node) {
|
|
321
404
|
if (ts.isIdentifier(node)) {
|
|
322
405
|
return node.text;
|
|
@@ -367,6 +450,13 @@ function isCommandFunctionLike(node) {
|
|
|
367
450
|
ts.isMethodDeclaration(node) ||
|
|
368
451
|
ts.isSetAccessorDeclaration(node));
|
|
369
452
|
}
|
|
453
|
+
function collectFunctionBoundaryFacts(sourceFile, factsBuilder) {
|
|
454
|
+
visitCommandNodes(sourceFile, (node) => {
|
|
455
|
+
if (isCommandFunctionLike(node)) {
|
|
456
|
+
factsBuilder.functionBoundaryFacts.push({ node });
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
370
460
|
function commandFunctionBody(node) {
|
|
371
461
|
return node.body;
|
|
372
462
|
}
|