@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.
@@ -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"}
@@ -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
@@ -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;AAenF,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,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,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACzC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,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,CAGpE;AAED,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,UAAU,GAAG,4BAA4B,EAAE,CAQnG;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,yBAAyB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAEnE;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,EAAE,CAc1E"}
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
- return dedupeMatches(findRawSqlConcatNodes(sourceFile).map((node) => matchFromNode(file, sourceFile, node)));
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
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ast-utils.js","sourceRoot":"","sources":["../src/ast-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ast-utils.js","sourceRoot":"","sources":["../src/ast-utils.ts"],"names":[],"mappings":"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"}
@@ -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>): CommandFlowFacts;
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;AAgB5B,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;CAClD,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,GAC1C,gBAAgB,CAiBlB"}
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"}
@@ -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 sourcePaths = new Map();
12
- const safeCommandCalls = new Set();
13
- analyzeCommandScope(sourceFile, sourceFile, commandIdentifiers, childProcessNamespaces, sourcePaths, safeCommandCalls);
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, sourcePaths, safeCommandCalls);
21
+ analyzeCommandScope(body, body, commandIdentifiers, childProcessNamespaces, factsBuilder, callbacks);
21
22
  }
22
23
  });
23
- return { sourcePaths, safeCommandCalls };
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, sourcePaths, safeCommandCalls, state = createCommandFlowState()) {
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
- if (isGuardedCommandSink(node, state)) {
83
- safeCommandCalls.add(node);
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
- sourcePaths.set(node, sourcePath);
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, sourcePaths, safeCommandCalls, state));
123
+ ts.forEachChild(node, (child) => analyzeCommandScope(scopeRoot, child, commandIdentifiers, childProcessNamespaces, factsBuilder, callbacks, state));
98
124
  }
99
- function isGuardedCommandSink(node, state) {
125
+ function guardedCommandIdentifier(node, state) {
100
126
  const [commandArgument] = node.arguments;
101
127
  if (!commandArgument) {
102
- return false;
128
+ return undefined;
103
129
  }
104
130
  const normalized = unwrapCommandExpression(commandArgument);
105
131
  if (!ts.isIdentifier(normalized) || !sourcePathForExpression(normalized, state)) {
106
- return false;
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
- trackCommandValue(node.name.text, sourcePathForAssignment(node.initializer, state, node.name.text), state);
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
- trackCommandValue(target, sourcePathForAssignment(node.right, state, target), state);
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
- return state.tracked.get(normalized.text) ?? (ROUTE_PARAMS_NAMES.test(normalized.text) ? { path: normalized.text, aliasDepth: 0 } : undefined);
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
  }