coretrace-python-analyzer 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

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.
@@ -1,4 +1,4 @@
1
1
  """CoreTrace's Python static analysis frontend."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.5.0"
4
4
 
@@ -7,7 +7,7 @@ from typing import ClassVar
7
7
 
8
8
  from coretrace_python.analysis import AnyAnalysis
9
9
  from coretrace_python.dependency import DependencyAnalysis
10
- from coretrace_python.dependency.correlation import affected_symbols, evidence
10
+ from coretrace_python.dependency.correlation import affected_symbols, check_conditions, evidence
11
11
  from coretrace_python.findings import Confidence, Finding
12
12
  from coretrace_python.interprocedural import CallGraphAnalysis, ExternalSymbol
13
13
  from coretrace_python.plugins import ProjectContext, ProjectPlugin
@@ -29,6 +29,9 @@ class ReachableVulnerabilityPlugin(ProjectPlugin):
29
29
  if not isinstance(site.target, ExternalSymbol):
30
30
  continue
31
31
  for advisory in affected.get(site.target.symbol, ()):
32
+ check = check_conditions(advisory.entry_point(site.target.symbol), site.arguments)
33
+ if check.contradicted is not None:
34
+ continue
32
35
  findings.append(
33
36
  Finding(
34
37
  rule_id="reachable-vulnerability",
@@ -40,7 +43,7 @@ class ReachableVulnerabilityPlugin(ProjectPlugin):
40
43
  confidence=Confidence.HIGH,
41
44
  span=site.location,
42
45
  function=function,
43
- metadata=evidence(advisory, site.target.symbol, "reachable"),
46
+ metadata=evidence(advisory, site.target.symbol, "reachable", check),
44
47
  )
45
48
  )
46
49
  return findings
@@ -1,7 +1,7 @@
1
1
  name = "vulnerable-dependency"
2
2
  version = "1.0.0"
3
3
  plugin_api = ">=1,<2"
4
- requires = ["dependency.graph"]
4
+ requires = ["dependency.graph", "interprocedural.callgraph"]
5
5
  provides = ["vulnerability.vulnerable-dependency"]
6
6
 
7
7
  [entrypoint]
@@ -7,23 +7,34 @@ from typing import ClassVar
7
7
 
8
8
  from coretrace_python.analysis import AnyAnalysis
9
9
  from coretrace_python.dependency import DependencyAnalysis
10
+ from coretrace_python.dependency.correlation import affected_symbols, check_conditions, ruled_out
10
11
  from coretrace_python.findings import Confidence, Finding
12
+ from coretrace_python.interprocedural import CallGraphAnalysis, ExternalSymbol
11
13
  from coretrace_python.plugins import ProjectContext, ProjectPlugin
12
14
 
13
15
 
14
16
  class VulnerableDependencyPlugin(ProjectPlugin):
15
17
  name: ClassVar[str] = "vulnerable-dependency"
16
- requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({DependencyAnalysis})
18
+ requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({DependencyAnalysis, CallGraphAnalysis})
17
19
 
18
20
  def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
19
21
  findings: list[Finding] = []
20
22
  imported = [s for module in ctx.modules for s in ctx.imports(module).all_symbols()]
23
+ excluded = _ruled_out(ctx)
21
24
  for requirement in ctx.dependencies.requirements:
22
25
  for advisory in ctx.advisories:
23
26
  if not advisory.affects(requirement):
24
27
  continue
25
28
  pinned = requirement.pinned is not None
26
29
  level = "imported" if advisory.imported_by(imported) else "declared"
30
+ metadata = {
31
+ "advisory": advisory.id,
32
+ "package": advisory.package,
33
+ "specifier": requirement.specifier,
34
+ "level": level,
35
+ }
36
+ if excluded.get(advisory.id):
37
+ metadata["ruled_out"] = "; ".join(excluded[advisory.id])
27
38
  findings.append(
28
39
  Finding(
29
40
  rule_id="vulnerable-dependency",
@@ -35,12 +46,26 @@ class VulnerableDependencyPlugin(ProjectPlugin):
35
46
  severity=advisory.severity,
36
47
  confidence=Confidence.HIGH if pinned else Confidence.MEDIUM,
37
48
  span=requirement.span,
38
- metadata={
39
- "advisory": advisory.id,
40
- "package": advisory.package,
41
- "specifier": requirement.specifier,
42
- "level": level,
43
- },
49
+ metadata=metadata,
44
50
  )
45
51
  )
46
52
  return findings
53
+
54
+
55
+ def _ruled_out(ctx: ProjectContext) -> dict[str, list[str]]:
56
+ """By advisory, the calls to its entry points whose arguments contradict one of its
57
+ conditions: the evidence that the requirement, imported, is not reached there."""
58
+
59
+ affected = affected_symbols(ctx.dependencies, ctx.advisories)
60
+ excluded: dict[str, list[str]] = {}
61
+ for module in sorted(ctx.modules):
62
+ graph = ctx.call_graph(module)
63
+ for function in graph.functions:
64
+ for site in graph.sites(function):
65
+ if not isinstance(site.target, ExternalSymbol):
66
+ continue
67
+ for advisory in affected.get(site.target.symbol, ()):
68
+ check = check_conditions(advisory.entry_point(site.target.symbol), site.arguments)
69
+ if check.contradicted is not None:
70
+ excluded.setdefault(advisory.id, []).append(ruled_out(module, site, check))
71
+ return excluded
coretrace_python/cache.py CHANGED
@@ -22,11 +22,13 @@ from typing import Any
22
22
 
23
23
  from coretrace_python.findings import Confidence, Finding, Severity
24
24
  from coretrace_python.interprocedural import (
25
+ Arguments,
25
26
  CallSite,
26
27
  ExternalCall,
27
28
  ExternalSymbol,
28
29
  FunctionSummary,
29
30
  KnownFunction,
31
+ ModuleFunction,
30
32
  ModuleGraph,
31
33
  Mutation,
32
34
  NonlocalWrite,
@@ -37,14 +39,14 @@ from coretrace_python.interprocedural import (
37
39
  from coretrace_python.semantic.symbols import SymbolId
38
40
  from coretrace_python.source import SourceId, SourceSpan
39
41
 
40
- CACHE_FORMAT = 5
42
+ CACHE_FORMAT = 7
41
43
 
42
44
 
43
45
  @dataclass(frozen=True)
44
46
  class CachedModule:
45
47
  """Everything a later run needs from one module without lowering it again."""
46
48
 
47
- functions: tuple[str, ...]
49
+ functions: tuple[ModuleFunction, ...]
48
50
  summaries: Mapping[str, FunctionSummary]
49
51
  sites: tuple[CallSite, ...]
50
52
  findings: tuple[Finding, ...]
@@ -128,7 +130,7 @@ class ProjectCache:
128
130
  def encode(module: CachedModule) -> dict[str, Any]:
129
131
  return {
130
132
  "format": CACHE_FORMAT,
131
- "functions": list(module.functions),
133
+ "functions": [[f.name, _encode_span(f.span), f.entry_point] for f in module.functions],
132
134
  "summaries": {name: _encode_summary(s) for name, s in module.summaries.items()},
133
135
  "sites": [_encode_site(site) for site in module.sites],
134
136
  "findings": [_encode_finding(finding) for finding in module.findings],
@@ -139,7 +141,7 @@ def decode(data: Mapping[str, Any]) -> CachedModule:
139
141
  if data["format"] != CACHE_FORMAT:
140
142
  raise ValueError(f"unsupported cache format {data['format']!r}")
141
143
  return CachedModule(
142
- tuple(_string(name) for name in data["functions"]),
144
+ tuple(_decode_function(function) for function in data["functions"]),
143
145
  {_string(name): _decode_summary(s) for name, s in data["summaries"].items()},
144
146
  tuple(_decode_site(site) for site in data["sites"]),
145
147
  tuple(_decode_finding(finding) for finding in data["findings"]),
@@ -187,6 +189,11 @@ def _decode_span(data: Any) -> SourceSpan:
187
189
  )
188
190
 
189
191
 
192
+ def _decode_function(data: Any) -> ModuleFunction:
193
+ name, span, entry_point = data
194
+ return ModuleFunction(_string(name), _decode_span(span), None if entry_point is None else _string(entry_point))
195
+
196
+
190
197
  def _encode_finding(finding: Finding) -> dict[str, Any]:
191
198
  return {
192
199
  "rule": finding.rule_id,
@@ -219,6 +226,7 @@ def _encode_call(call: ExternalCall) -> dict[str, Any]:
219
226
  "keywords": sorted(call.keyword_dependencies),
220
227
  "location": _encode_span(call.location),
221
228
  "call_site": None if call.call_site is None else _encode_span(call.call_site),
229
+ "given": _encode_arguments(call.arguments),
222
230
  }
223
231
 
224
232
 
@@ -230,6 +238,7 @@ def _decode_call(data: Mapping[str, Any]) -> ExternalCall:
230
238
  _indices(data["keywords"]),
231
239
  _decode_span(data["location"]),
232
240
  None if site is None else _decode_span(site),
241
+ _decode_arguments(data["given"]),
233
242
  )
234
243
 
235
244
 
@@ -311,8 +320,7 @@ def _encode_site(site: CallSite) -> dict[str, Any]:
311
320
  "caller": site.caller,
312
321
  "location": _encode_span(site.location),
313
322
  "target": _encode_target(site.target),
314
- "arguments": site.arguments,
315
- "keywords": site.keywords,
323
+ "arguments": _encode_arguments(site.arguments),
316
324
  }
317
325
 
318
326
 
@@ -321,6 +329,27 @@ def _decode_site(data: Mapping[str, Any]) -> CallSite:
321
329
  _string(data["caller"]),
322
330
  _decode_span(data["location"]),
323
331
  _decode_target(data["target"]),
324
- _integer(data["arguments"]),
325
- _integer(data["keywords"]),
332
+ _decode_arguments(data["arguments"]),
333
+ )
334
+
335
+
336
+ def _encode_arguments(arguments: Arguments) -> dict[str, Any]:
337
+ return {
338
+ "positional": list(arguments.positional),
339
+ "keywords": [[name, value] for name, value in arguments.keywords],
340
+ "unpacked": arguments.unpacked,
341
+ }
342
+
343
+
344
+ def _decode_arguments(data: Mapping[str, Any]) -> Arguments:
345
+ def denoted(value: Any) -> str | None:
346
+ return None if value is None else _string(value)
347
+
348
+ unpacked = data["unpacked"]
349
+ if not isinstance(unpacked, bool):
350
+ raise TypeError(f"expected a boolean, got {unpacked!r}")
351
+ return Arguments(
352
+ tuple(denoted(value) for value in data["positional"]),
353
+ tuple((_string(name), denoted(value)) for name, value in data["keywords"]),
354
+ unpacked,
326
355
  )
@@ -160,6 +160,10 @@ def _condition_entry(condition: Condition) -> dict[str, Any]:
160
160
  entry["argument"] = condition.argument
161
161
  if condition.values:
162
162
  entry["values"] = list(condition.values)
163
+ if condition.position is not None:
164
+ entry["position"] = condition.position
165
+ if condition.default:
166
+ entry["default"] = True
163
167
  return entry
164
168
 
165
169
 
@@ -193,13 +197,22 @@ def _entry_point(entry: Mapping[str, Any]) -> AdvisoryEntryPoint:
193
197
  return AdvisoryEntryPoint(
194
198
  SymbolId(str(entry["symbol"])),
195
199
  str(entry["justification"]),
196
- tuple(
197
- Condition(
198
- str(c["kind"]),
199
- str(c["text"]),
200
- None if c.get("argument") is None else str(c["argument"]),
201
- tuple(str(v) for v in c.get("values") or []),
202
- )
203
- for c in entry.get("conditions") or []
204
- ),
200
+ tuple(_condition(c) for c in entry.get("conditions") or []),
201
+ )
202
+
203
+
204
+ def _condition(entry: Mapping[str, Any]) -> Condition:
205
+ position = entry.get("position")
206
+ if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
207
+ raise TypeError(f"condition position must be a non-negative integer, got {position!r}")
208
+ default = entry.get("default", False)
209
+ if not isinstance(default, bool):
210
+ raise TypeError(f"condition default must be true or false, got {default!r}")
211
+ return Condition(
212
+ str(entry["kind"]),
213
+ str(entry["text"]),
214
+ None if entry.get("argument") is None else str(entry["argument"]),
215
+ tuple(str(v) for v in entry.get("values") or []),
216
+ position,
217
+ default,
205
218
  )
@@ -10,10 +10,18 @@ are correlated here into one high-confidence ``exploitable-vulnerability`` findi
10
10
  from __future__ import annotations
11
11
 
12
12
  from collections.abc import Iterable, Mapping
13
-
14
- from coretrace_python.dependency.graph import DIRECT, Advisory, DependencyGraph
13
+ from dataclasses import dataclass
14
+
15
+ from coretrace_python.dependency.graph import (
16
+ DIRECT,
17
+ Advisory,
18
+ AdvisoryEntryPoint,
19
+ Condition,
20
+ DependencyGraph,
21
+ )
15
22
  from coretrace_python.findings import Confidence, Finding, Severity
16
23
  from coretrace_python.findings.refutation import Status, Verdict, Verdicts
24
+ from coretrace_python.interprocedural import Arguments, CallSite, ExternalSymbol
17
25
  from coretrace_python.semantic.symbols import SymbolId
18
26
  from coretrace_python.taint import Sink, TaintFlow, TaintKind
19
27
 
@@ -40,10 +48,68 @@ def advisory_sinks(affected: Affected) -> tuple[Sink, ...]:
40
48
  return tuple(Sink(symbol, TaintKind.ADVISORY) for symbol in affected)
41
49
 
42
50
 
43
- def evidence(advisory: Advisory, symbol: SymbolId, level: str) -> dict[str, str]:
51
+ @dataclass(frozen=True)
52
+ class ConditionCheck:
53
+ """What one call tells of an entry point's conditions: those it meets, those left to
54
+ review (every semantic one, and every argument one the call does not decide), and
55
+ the argument condition it contradicts with what it passes instead (None: absent),
56
+ in which case the call does not reach the vulnerability."""
57
+
58
+ met: tuple[Condition, ...] = ()
59
+ pending: tuple[Condition, ...] = ()
60
+ contradicted: Condition | None = None
61
+ passed: str | None = None
62
+
63
+
64
+ def check_conditions(entry: AdvisoryEntryPoint | None, arguments: Arguments | None) -> ConditionCheck:
65
+ """Decide ``entry``'s conditions against what the call's ``arguments`` denote."""
66
+
67
+ if entry is None:
68
+ return ConditionCheck()
69
+ met: list[Condition] = []
70
+ pending: list[Condition] = []
71
+ for condition in entry.conditions:
72
+ given = _given(condition, arguments) if condition.checkable and arguments is not None else None
73
+ if given is None:
74
+ pending.append(condition)
75
+ continue
76
+ explicit, value = given
77
+ if explicit and value is None:
78
+ pending.append(condition)
79
+ continue
80
+ affected = value in condition.values if explicit else condition.default
81
+ if not affected:
82
+ return ConditionCheck(tuple(met), tuple(pending), condition, value)
83
+ met.append(condition)
84
+ return ConditionCheck(tuple(met), tuple(pending))
85
+
86
+
87
+ def _given(condition: Condition, arguments: Arguments) -> tuple[bool, str | None] | None:
88
+ """Whether the call gives the condition's argument, and what it denotes: ``(True,
89
+ value)`` when given, ``(False, None)`` when surely absent, None when it cannot tell."""
90
+
91
+ for name, value in arguments.keywords:
92
+ if name == condition.argument:
93
+ return True, value
94
+ if condition.position is not None and condition.position < len(arguments.positional):
95
+ return True, arguments.positional[condition.position]
96
+ return None if arguments.unpacked else (False, None)
97
+
98
+
99
+ def ruled_out(module: str, site: CallSite, check: ConditionCheck) -> str:
100
+ """Which call a contradicted condition rules out, and why: ``app:12
101
+ python.yaml.load(Loader=python.yaml.SafeLoader)``."""
102
+
103
+ assert check.contradicted is not None and isinstance(site.target, ExternalSymbol)
104
+ argument = check.contradicted.argument or f"#{check.contradicted.position}"
105
+ passed = f"{argument} absent" if check.passed is None else f"{argument}={check.passed}"
106
+ return f"{module}:{site.location.start_line} {site.target.symbol}({passed})"
107
+
108
+
109
+ def evidence(advisory: Advisory, symbol: SymbolId, level: str, check: ConditionCheck) -> dict[str, str]:
44
110
  """What a finding keeps of the advisory for ``symbol``: the level of evidence
45
- established, how the symbol relates to the vulnerability and under which
46
- conditions, the ones the engine could not check listed as pending review."""
111
+ established, how the symbol relates to the vulnerability, and its conditions — those
112
+ the call meets and those left to review."""
47
113
 
48
114
  metadata = {"advisory": advisory.id, "package": advisory.package, "symbol": str(symbol), "level": level}
49
115
  entry = advisory.entry_point(symbol)
@@ -54,9 +120,10 @@ def evidence(advisory: Advisory, symbol: SymbolId, level: str) -> dict[str, str]
54
120
  metadata["justification"] = entry.justification
55
121
  if entry.conditions:
56
122
  metadata["conditions"] = "; ".join(c.text for c in entry.conditions)
57
- # ponytail: no condition is checked yet, so every one awaits review; argument
58
- # conditions get checked at the call site once call sites carry argument symbols.
59
- metadata["conditions_pending_review"] = "; ".join(c.text for c in entry.conditions)
123
+ if check.met:
124
+ metadata["conditions_met"] = "; ".join(c.text for c in check.met)
125
+ if check.pending:
126
+ metadata["conditions_pending_review"] = "; ".join(c.text for c in check.pending)
60
127
  return metadata
61
128
 
62
129
 
@@ -77,21 +144,22 @@ def correlate(
77
144
  if verdict is not None and verdict.status is Status.REFUTED:
78
145
  continue
79
146
  hotspot = verdict is not None and verdict.status is Status.HOTSPOT
80
- findings.extend(
81
- _exploitable(function, flow, advisory, verdict, hotspot) for advisory in affected.get(flow.sink.symbol, ())
82
- )
147
+ for advisory in affected.get(flow.sink.symbol, ()):
148
+ check = check_conditions(advisory.entry_point(flow.sink.symbol), flow.sink_arguments)
149
+ if check.contradicted is None:
150
+ findings.append(_exploitable(function, flow, advisory, verdict, hotspot, check))
83
151
  return tuple(findings)
84
152
 
85
153
 
86
154
  def _exploitable(
87
- function: str, flow: TaintFlow, advisory: Advisory, verdict: Verdict | None, hotspot: bool
155
+ function: str, flow: TaintFlow, advisory: Advisory, verdict: Verdict | None, hotspot: bool, check: ConditionCheck
88
156
  ) -> Finding:
89
157
  message = (
90
158
  f"{advisory.id}: {flow.source.label} input reaches {flow.sink.symbol}, affected in "
91
159
  f"the required {advisory.package} {advisory.vulnerable}: {advisory.summary}"
92
160
  )
93
161
  metadata = {
94
- **evidence(advisory, flow.sink.symbol, "exploitable"),
162
+ **evidence(advisory, flow.sink.symbol, "exploitable", check),
95
163
  "source": str(flow.source.symbol),
96
164
  "source_label": flow.source.label,
97
165
  "verdict": "hotspot" if hotspot else "vulnerability",
@@ -147,13 +147,18 @@ class Condition:
147
147
  """What must hold for an entry point to reach, or exploit, the affected code: the
148
148
  value of an argument, the loader or algorithm used, the format decoded, the kind of
149
149
  input, a configuration. ``kind`` says whether the engine can check it: an
150
- ``argument`` condition names the argument and the values that satisfy it; a
151
- ``semantic`` condition cannot be checked yet and is reported as pending review."""
150
+ ``argument`` condition names the argument by keyword, and by ``position`` when it may
151
+ be passed positionally, and lists the ``values`` that satisfy it — symbols
152
+ (``python.yaml.FullLoader``) or constants as Python writes them (``True``);
153
+ ``default`` says an absent argument means a vulnerable value. A ``semantic``
154
+ condition cannot be checked and is reported as pending review."""
152
155
 
153
156
  kind: str
154
157
  text: str
155
158
  argument: str | None = None
156
159
  values: tuple[str, ...] = ()
160
+ position: int | None = None
161
+ default: bool = False
157
162
 
158
163
  @property
159
164
  def checkable(self) -> bool:
@@ -71,6 +71,7 @@ from coretrace_python.interprocedural import (
71
71
  CallGraphAnalysis,
72
72
  CallSite,
73
73
  FunctionSummary,
74
+ ModuleFunction,
74
75
  ModuleGraph,
75
76
  ProjectSummaries,
76
77
  SummaryAnalysis,
@@ -106,6 +107,7 @@ from coretrace_python.semantic.symbols import SymbolAnalysis, SymbolId
106
107
  from coretrace_python.source import SourceFile, SourceId, SourceManager, SourceSpan, decode_text
107
108
  from coretrace_python.taint import (
108
109
  EntryPoint,
110
+ EntryPointAnalysis,
109
111
  ModelTable,
110
112
  RegisteredRoutes,
111
113
  Routes,
@@ -139,6 +141,7 @@ ALL_ANALYSES: tuple[AnyAnalysis, ...] = (
139
141
  ProjectSummaries,
140
142
  SecurityModelAnalysis,
141
143
  TaintAnalysis,
144
+ EntryPointAnalysis,
142
145
  RefutationAnalysis,
143
146
  DependencyAnalysis,
144
147
  RegisteredRoutes,
@@ -147,7 +150,7 @@ ALL_ANALYSES: tuple[AnyAnalysis, ...] = (
147
150
 
148
151
  # Named so the set stays right whichever analyses a build registers.
149
152
  _PROJECT_DEPENDANT_NAMES = frozenset(
150
- {"interprocedural.project", "interprocedural.summaries", "taint.flows", "findings.refutation"}
153
+ {"interprocedural.project", "interprocedural.summaries", "taint.flows", "taint.entry_points", "findings.refutation"}
151
154
  )
152
155
  _PROJECT_DEPENDANTS: frozenset[AnyAnalysis] = frozenset(
153
156
  a for a in ALL_ANALYSES if a.name in _PROJECT_DEPENDANT_NAMES
@@ -451,6 +454,7 @@ def analyze_project(
451
454
 
452
455
  index = _seed(results, graph, frozenset())
453
456
  call_graphs: dict[str, CallGraph] = {}
457
+ functions: dict[str, tuple[ModuleFunction, ...]] = {}
454
458
  for name in sorted(analysable):
455
459
  entry = results[name]
456
460
  findings.extend(entry.findings)
@@ -458,11 +462,12 @@ def analyze_project(
458
462
  coverage.append(
459
463
  FileCoverage(str(files[name].source_id), "analysed", len(entry.functions), len(entry.functions) - unsupported)
460
464
  )
461
- sites: dict[str, list[CallSite]] = {function: [] for function in entry.functions}
465
+ functions[name] = entry.functions
466
+ sites: dict[str, list[CallSite]] = {function.name: [] for function in entry.functions}
462
467
  for site in entry.sites:
463
468
  sites.setdefault(site.caller, []).append(site)
464
469
  call_graphs[name] = CallGraph({}, {f: tuple(s) for f, s in sites.items()}, frozenset())
465
- context = ProjectContext(graph, dependencies, advisories, analysable, call_graphs, policy, root)
470
+ context = ProjectContext(graph, dependencies, advisories, analysable, call_graphs, policy, root, functions)
466
471
  for plugin in all_plugins:
467
472
  if isinstance(plugin, ProjectPlugin):
468
473
  findings.extend(plugin.analyze_project(context))
@@ -662,7 +667,7 @@ def _analyse_module(
662
667
  )
663
668
  )
664
669
  return CachedModule(
665
- graph.functions,
670
+ manager.get(EntryPointAnalysis),
666
671
  _summaries_of(manager),
667
672
  tuple(site for function in graph.functions for site in graph.sites(function)),
668
673
  (*findings, *correlated),
@@ -1,11 +1,13 @@
1
1
  """Call graph and function summaries (architecture §19, §20)."""
2
2
 
3
3
  from coretrace_python.interprocedural.callgraph import (
4
+ Arguments,
4
5
  CallGraph,
5
6
  CallGraphAnalysis,
6
7
  CallSite,
7
8
  ExternalSymbol,
8
9
  KnownFunction,
10
+ ModuleFunction,
9
11
  Target,
10
12
  UnknownTarget,
11
13
  )
@@ -28,6 +30,7 @@ from coretrace_python.interprocedural.summaries import (
28
30
  )
29
31
 
30
32
  __all__ = [
33
+ "Arguments",
31
34
  "CallGraph",
32
35
  "CallGraphAnalysis",
33
36
  "CallSite",
@@ -35,6 +38,7 @@ __all__ = [
35
38
  "ExternalSymbol",
36
39
  "FunctionSummary",
37
40
  "KnownFunction",
41
+ "ModuleFunction",
38
42
  "ModuleGraph",
39
43
  "Mutation",
40
44
  "NonlocalWrite",
@@ -9,7 +9,7 @@ and framework models narrow it down.
9
9
  from __future__ import annotations
10
10
 
11
11
  from collections.abc import Mapping
12
- from dataclasses import dataclass
12
+ from dataclasses import dataclass, field
13
13
  from types import MappingProxyType
14
14
  from typing import ClassVar
15
15
 
@@ -25,10 +25,12 @@ from coretrace_python.ir.lowering import (
25
25
  from coretrace_python.ir.model import (
26
26
  Await,
27
27
  Call,
28
+ Constant,
28
29
  FunctionIR,
29
30
  GetAttr,
30
31
  GetItem,
31
32
  Global,
33
+ Instruction,
32
34
  MakeFunction,
33
35
  Symbol,
34
36
  Value,
@@ -58,13 +60,36 @@ class UnknownTarget:
58
60
  Target = KnownFunction | ExternalSymbol | UnknownTarget
59
61
 
60
62
 
63
+ @dataclass(frozen=True)
64
+ class Arguments:
65
+ """What the arguments of a call denote: a symbol's canonical name
66
+ (``python.yaml.FullLoader``), a constant as Python writes it (``True``, ``'/static'``),
67
+ or None for anything else (a parameter, an expression). ``unpacked`` says the call
68
+ unpacks ``*args`` or ``**kwargs``, so an argument it does not give explicitly may
69
+ still be given; after ``*args`` no position is known, and ``positional`` is empty."""
70
+
71
+ positional: tuple[str | None, ...] = ()
72
+ keywords: tuple[tuple[str, str | None], ...] = ()
73
+ unpacked: bool = False
74
+
75
+
61
76
  @dataclass(frozen=True)
62
77
  class CallSite:
63
78
  caller: str
64
79
  location: SourceSpan
65
80
  target: Target
66
- arguments: int
67
- keywords: int
81
+ arguments: Arguments = field(default_factory=Arguments)
82
+
83
+
84
+ @dataclass(frozen=True)
85
+ class ModuleFunction:
86
+ """One function of a module as a project plugin sees it: the name the call graph
87
+ gives it (``Class.method``, ``outer.inner``, ``<module>``), where it is, and the label
88
+ of the entry point it is (``http`` for a route, ``argv`` for a command), if any."""
89
+
90
+ name: str
91
+ span: SourceSpan
92
+ entry_point: str | None = None
68
93
 
69
94
 
70
95
  class CallGraph:
@@ -82,9 +107,7 @@ class CallGraph:
82
107
  self._names = {function.span: name for name, function in definitions.items()}
83
108
  self.unsupported = unsupported
84
109
  self._sites = MappingProxyType(dict(sites))
85
- self._targets = {
86
- (site.caller, site.location): site.target for found in sites.values() for site in found
87
- }
110
+ self._at = {(site.caller, site.location): site for found in sites.values() for site in found}
88
111
  callers: dict[str, set[str]] = {name: set() for name in definitions}
89
112
  for found in sites.values():
90
113
  for site in found:
@@ -104,7 +127,14 @@ class CallGraph:
104
127
  return self._sites.get(caller, ())
105
128
 
106
129
  def target_at(self, caller: str, location: SourceSpan) -> Target:
107
- return self._targets.get((caller, location), UnknownTarget())
130
+ site = self._at.get((caller, location))
131
+ return site.target if site is not None else UnknownTarget()
132
+
133
+ def arguments_at(self, caller: str, location: SourceSpan) -> Arguments:
134
+ """What the arguments of the call at ``location`` in ``caller`` denote."""
135
+
136
+ site = self._at.get((caller, location))
137
+ return site.arguments if site is not None else Arguments()
108
138
 
109
139
  def callees(self, caller: str) -> frozenset[str]:
110
140
  return frozenset(
@@ -154,6 +184,29 @@ def derive_symbols(
154
184
  return symbols
155
185
 
156
186
 
187
+ # Conditions name short constants (``True``, ``None``, a mode); a log message or a query
188
+ # passed as an argument is not recorded, so call sites stay small in the cache.
189
+ MAX_CONSTANT_LENGTH = 64
190
+
191
+
192
+ def _arguments(call: Call, symbols: Mapping[Value, SymbolId], defs: Mapping[Value, Instruction]) -> Arguments:
193
+ def denoted(value: Value) -> str | None:
194
+ symbol = symbols.get(value)
195
+ if symbol is not None:
196
+ return str(symbol)
197
+ made = defs.get(value)
198
+ if not isinstance(made, Constant):
199
+ return None
200
+ written = repr(made.value)
201
+ return written if len(written) <= MAX_CONSTANT_LENGTH else None
202
+
203
+ return Arguments(
204
+ () if call.starred else tuple(denoted(value) for value in call.arguments),
205
+ tuple((name, denoted(value)) for name, value in call.keywords if name is not None),
206
+ bool(call.starred) or any(name is None for name, _ in call.keywords),
207
+ )
208
+
209
+
157
210
  def resolve_targets(
158
211
  function: FunctionIR,
159
212
  scopes: ScopeTable,
@@ -328,6 +381,7 @@ class CallGraphAnalysis(Analysis[CallGraph]):
328
381
  _typed_with_module_classes(function, ssa, classes),
329
382
  bases,
330
383
  )
384
+ defs = {i.result: i for block in ssa.blocks for i in block.instructions if i.result is not None}
331
385
  found: list[CallSite] = []
332
386
  for block in ssa.blocks:
333
387
  for instruction in block.instructions:
@@ -337,8 +391,7 @@ class CallGraphAnalysis(Analysis[CallGraph]):
337
391
  name,
338
392
  instruction.location,
339
393
  targets.get(instruction.callee, UnknownTarget()),
340
- len(instruction.arguments),
341
- len(instruction.keywords),
394
+ _arguments(instruction, symbols[name], defs),
342
395
  )
343
396
  )
344
397
  sites[name] = tuple(found)
@@ -9,6 +9,7 @@ security knowledge: the taint engine decides which external symbols matter.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
+ import dataclasses
12
13
  from collections.abc import Mapping
13
14
  from dataclasses import dataclass
14
15
  from types import MappingProxyType
@@ -26,6 +27,7 @@ from coretrace_python.analysis import Analysis, AnalysisContext, AnyAnalysis
26
27
  from coretrace_python.cfg import CFG, BlockId, CFGAnalysis
27
28
  from coretrace_python.dataflow import DataflowProblem, Direction, solve
28
29
  from coretrace_python.interprocedural.callgraph import (
30
+ Arguments,
29
31
  CallGraph,
30
32
  CallGraphAnalysis,
31
33
  ExternalSymbol,
@@ -92,6 +94,8 @@ class ExternalCall:
92
94
  keyword_dependencies: Dependencies
93
95
  location: SourceSpan
94
96
  call_site: SourceSpan | None
97
+ # What the arguments of the call at ``location`` denote, where it is made.
98
+ arguments: Arguments = dataclasses.field(default_factory=Arguments)
95
99
 
96
100
 
97
101
  @dataclass(frozen=True)
@@ -334,6 +338,7 @@ class _DependenceProblem(DataflowProblem[State]):
334
338
  keywords.parameters,
335
339
  call.location,
336
340
  None,
341
+ self.graph.arguments_at(self.name, call.location),
337
342
  )
338
343
  return everything | Dep(externals=frozenset({target.symbol}))
339
344
  if isinstance(target, KnownFunction):
@@ -416,6 +421,7 @@ class _DependenceProblem(DataflowProblem[State]):
416
421
  mapped(reached.keyword_dependencies).parameters,
417
422
  reached.location,
418
423
  call.location,
424
+ reached.arguments,
419
425
  )
420
426
  made = self.defs.get(call.callee)
421
427
  values = (*receiver, *call.arguments, *(made.captured if isinstance(made, MakeFunction) else ()))
@@ -432,6 +438,7 @@ class _DependenceProblem(DataflowProblem[State]):
432
438
  keywords: Dependencies,
433
439
  location: SourceSpan,
434
440
  call_site: SourceSpan | None,
441
+ given: Arguments,
435
442
  ) -> None:
436
443
  key = (symbol, location, call_site)
437
444
  previous = self.external.get(key)
@@ -440,7 +447,7 @@ class _DependenceProblem(DataflowProblem[State]):
440
447
  a | b for a, b in zip(previous.argument_dependencies, arguments, strict=False)
441
448
  )
442
449
  keywords |= previous.keyword_dependencies
443
- self.external[key] = ExternalCall(symbol, arguments, keywords, location, call_site)
450
+ self.external[key] = ExternalCall(symbol, arguments, keywords, location, call_site, given)
444
451
 
445
452
 
446
453
  def summarize(
@@ -25,10 +25,15 @@ from coretrace_python.analysis.provider import R
25
25
  from coretrace_python.dependency import Advisory, DependencyGraph, Policy
26
26
  from coretrace_python.findings import Finding
27
27
  from coretrace_python.hir import nodes
28
- from coretrace_python.interprocedural import CallGraph, CallGraphAnalysis, ModuleGraph
28
+ from coretrace_python.interprocedural import (
29
+ CallGraph,
30
+ CallGraphAnalysis,
31
+ ModuleFunction,
32
+ ModuleGraph,
33
+ )
29
34
  from coretrace_python.ir.lowering import analyzable_functions
30
35
  from coretrace_python.semantic.imports import ImportAnalysis, ImportTable
31
- from coretrace_python.taint import Model
36
+ from coretrace_python.taint import EntryPointAnalysis, Model
32
37
 
33
38
  PLUGIN_API_VERSION = 1
34
39
 
@@ -57,8 +62,8 @@ class ModelPlugin(Plugin):
57
62
  class ProjectContext:
58
63
  """What a project-scoped plugin sees: the module graph, the dependency graph, the
59
64
  advisories every plugin and advisory file contributed, the dependency policy, and
60
- each module's imports and call graph. The engine passes the call graphs of modules
61
- it served from its cache."""
65
+ each module's imports, call graph and functions. The engine passes the call graphs
66
+ and functions of modules it served from its cache."""
62
67
 
63
68
  def __init__(
64
69
  self,
@@ -69,6 +74,7 @@ class ProjectContext:
69
74
  call_graphs: Mapping[str, CallGraph] | None = None,
70
75
  policy: Policy | None = None,
71
76
  root: Path | None = None,
77
+ functions: Mapping[str, tuple[ModuleFunction, ...]] | None = None,
72
78
  ) -> None:
73
79
  self.root = root
74
80
  self.graph = graph
@@ -77,6 +83,7 @@ class ProjectContext:
77
83
  self.policy = policy or Policy()
78
84
  self._managers = managers
79
85
  self._call_graphs = dict(call_graphs or {})
86
+ self._functions = dict(functions or {})
80
87
 
81
88
  @property
82
89
  def modules(self) -> tuple[str, ...]:
@@ -89,6 +96,13 @@ class ProjectContext:
89
96
  cached = self._call_graphs.get(module)
90
97
  return cached if cached is not None else self._managers[module].get(CallGraphAnalysis)
91
98
 
99
+ def functions(self, module: str) -> tuple[ModuleFunction, ...]:
100
+ """The module's functions as its call graph names them, each with its span, so a
101
+ finding can be placed in one, and the label of the entry point it is, if any."""
102
+
103
+ cached = self._functions.get(module)
104
+ return cached if cached is not None else self._managers[module].get(EntryPointAnalysis)
105
+
92
106
 
93
107
  class ProjectPlugin(Plugin):
94
108
  """A plugin that runs once per project rather than once per module (§26)."""
@@ -1,6 +1,7 @@
1
1
  """Security models and the global multi-kind taint engine (architecture §16, §17)."""
2
2
 
3
3
  from coretrace_python.taint.engine import (
4
+ EntryPointAnalysis,
4
5
  Taint,
5
6
  TaintAnalysis,
6
7
  TaintFacts,
@@ -30,6 +31,7 @@ from coretrace_python.taint.routes import RegisteredRoutes, Routes, registered_r
30
31
  __all__ = [
31
32
  "AuthorizationGuard",
32
33
  "EntryPoint",
34
+ "EntryPointAnalysis",
33
35
  "Model",
34
36
  "ModelError",
35
37
  "ModelTable",
@@ -22,16 +22,18 @@ from coretrace_python.abstract import (
22
22
  HeapLocation,
23
23
  mutated_by,
24
24
  )
25
- from coretrace_python.analysis import AnalysisContext, AnyAnalysis, FunctionAnalysis
25
+ from coretrace_python.analysis import Analysis, AnalysisContext, AnyAnalysis, FunctionAnalysis
26
26
  from coretrace_python.cfg import CFG, BlockId, CFGAnalysis
27
27
  from coretrace_python.dataflow import DataflowProblem, Direction, solve
28
28
  from coretrace_python.hir import nodes
29
29
  from coretrace_python.interprocedural import (
30
+ Arguments,
30
31
  CallGraph,
31
32
  CallGraphAnalysis,
32
33
  ExternalSymbol,
33
34
  FunctionSummary,
34
35
  KnownFunction,
36
+ ModuleFunction,
35
37
  ProjectSummaries,
36
38
  SummaryAnalysis,
37
39
  SummaryIndex,
@@ -111,6 +113,8 @@ class TaintFlow:
111
113
  location: SourceSpan
112
114
  through: str | None = None
113
115
  sink_location: SourceSpan | None = None
116
+ # What the arguments of the sink call denote, to decide the conditions on them.
117
+ sink_arguments: Arguments | None = None
114
118
 
115
119
 
116
120
  Key = Value | HeapLocation
@@ -394,10 +398,11 @@ class _TaintProblem(DataflowProblem[State]):
394
398
 
395
399
  sink = self.models.sink(symbol)
396
400
  if sink is not None:
401
+ given = self.graph.arguments_at(self.name, call.location)
397
402
  for position, argument in enumerate(call.arguments):
398
- self.report(flows, sink, self.rendered(argument, state), argument, call, None, None, position)
403
+ self.report(flows, sink, self.rendered(argument, state), argument, call, None, None, position, given)
399
404
  for argument in (*call.starred, *(value for _, value in call.keywords)):
400
- self.report(flows, sink, self.rendered(argument, state), argument, call, None, None, None)
405
+ self.report(flows, sink, self.rendered(argument, state), argument, call, None, None, None, given)
401
406
  sanitizer = self.models.sanitizer(symbol)
402
407
  if sanitizer is not None:
403
408
  return everything.without(sanitizer.kinds)
@@ -483,7 +488,7 @@ class _TaintProblem(DataflowProblem[State]):
483
488
  for position, deps in zip(positions, (*reached.argument_dependencies, reached.keyword_dependencies), strict=True):
484
489
  taint, witness = mapped(deps)
485
490
  if witness is not None:
486
- self.report(flows, sink, taint, witness, call, through, reached.location, position)
491
+ self.report(flows, sink, taint, witness, call, through, reached.location, position, reached.arguments)
487
492
  for mutation in summary.mutations:
488
493
  if mutation.parameter < len(values):
489
494
  stored = mapped(mutation.dependencies)[0]
@@ -509,6 +514,7 @@ class _TaintProblem(DataflowProblem[State]):
509
514
  through: str | None,
510
515
  sink_location: SourceSpan | None,
511
516
  position: int | None = None,
517
+ arguments: Arguments | None = None,
512
518
  ) -> None:
513
519
  reaching = taint.kinds & sink.kinds_at(position)
514
520
  if not reaching:
@@ -523,6 +529,7 @@ class _TaintProblem(DataflowProblem[State]):
523
529
  call.location,
524
530
  through,
525
531
  call.location if sink_location is None else sink_location,
532
+ arguments,
526
533
  )
527
534
  )
528
535
 
@@ -684,7 +691,7 @@ def entry_point_of(
684
691
  return None
685
692
 
686
693
 
687
- def parameter_sources(
694
+ def function_entry_point(
688
695
  function: nodes.Function,
689
696
  module: nodes.Module,
690
697
  models: ModelTable,
@@ -692,16 +699,11 @@ def parameter_sources(
692
699
  symbols: SymbolTable,
693
700
  instances: Instances | None = None,
694
701
  routes: Routes | None = None,
695
- ) -> Mapping[int, Source]:
696
- """The attacker-controlled parameters of ``function``, by index: every parameter of an
697
- entry point (``self`` excepted for a method), including one registered elsewhere
698
- (``routes``), and every parameter annotated with a typed-parameter symbol."""
702
+ ) -> EntryPoint | None:
703
+ """The entry point ``function`` is: one of its decorators or, for a method, one of its
704
+ class's bases matching an entry-point model, or a registration elsewhere (``routes``)."""
699
705
 
700
- owner = next(
701
- (s for s in module.body if isinstance(s, nodes.Class) and any(f is function for f in s.body)),
702
- None,
703
- )
704
- sources: dict[int, Source] = {}
706
+ owner = _owner(module, function)
705
707
  enclosing_function = _enclosing(module, function)
706
708
  if enclosing_function is not None:
707
709
  # ``app = Flask(__name__)`` inside ``create_app``: routes defined there resolve.
@@ -712,6 +714,34 @@ def parameter_sources(
712
714
  entry = routes.get(project_symbol(module.name, qualified))
713
715
  if entry is None and owner is not None:
714
716
  entry = routes.get(project_symbol(module.name, owner.name))
717
+ return entry
718
+
719
+
720
+ def _owner(module: nodes.Module, function: nodes.Function) -> nodes.Class | None:
721
+ """The top-level class ``function`` is a method of, if any."""
722
+
723
+ return next(
724
+ (s for s in module.body if isinstance(s, nodes.Class) and any(f is function for f in s.body)),
725
+ None,
726
+ )
727
+
728
+
729
+ def parameter_sources(
730
+ function: nodes.Function,
731
+ module: nodes.Module,
732
+ models: ModelTable,
733
+ scopes: ScopeTable,
734
+ symbols: SymbolTable,
735
+ instances: Instances | None = None,
736
+ routes: Routes | None = None,
737
+ ) -> Mapping[int, Source]:
738
+ """The attacker-controlled parameters of ``function``, by index: every parameter of an
739
+ entry point (``self`` excepted for a method), including one registered elsewhere
740
+ (``routes``), and every parameter annotated with a typed-parameter symbol."""
741
+
742
+ owner = _owner(module, function)
743
+ sources: dict[int, Source] = {}
744
+ entry = function_entry_point(function, module, models, scopes, symbols, instances, routes)
715
745
  if entry is not None:
716
746
  first = 1 if owner is not None else 0
717
747
  for index in range(first, len(function.parameters)):
@@ -817,6 +847,38 @@ class TaintAnalysis(FunctionAnalysis[TaintFacts]):
817
847
  )
818
848
 
819
849
 
850
+ class EntryPointAnalysis(Analysis[tuple[ModuleFunction, ...]]):
851
+ """The module's functions as the call graph names them, each with its span and the
852
+ label of the entry point it is, decided as the taint engine decides which parameters
853
+ are attacker-controlled. What a project plugin sees of a module, cached with it."""
854
+
855
+ name: ClassVar[str] = "taint.entry_points"
856
+ requires: ClassVar[frozenset[AnyAnalysis]] = frozenset(
857
+ {
858
+ CallGraphAnalysis,
859
+ SecurityModelAnalysis,
860
+ ScopeAnalysis,
861
+ SymbolAnalysis,
862
+ SummaryAnalysis,
863
+ ProjectSummaries,
864
+ RegisteredRoutes,
865
+ }
866
+ )
867
+
868
+ @classmethod
869
+ def compute(cls, ctx: AnalysisContext) -> tuple[ModuleFunction, ...]:
870
+ graph = ctx.get(CallGraphAnalysis)
871
+ models = ctx.get(SecurityModelAnalysis)
872
+ scopes, symbols = ctx.get(ScopeAnalysis), ctx.get(SymbolAnalysis)
873
+ instances = factory_instances(ctx.module, scopes, symbols, ctx.get(SummaryAnalysis), ctx.get(ProjectSummaries))
874
+ routes = ctx.get(RegisteredRoutes)
875
+ functions: list[ModuleFunction] = []
876
+ for name, function in graph.definitions.items():
877
+ entry = function_entry_point(function, ctx.module, models, scopes, symbols, instances, routes)
878
+ functions.append(ModuleFunction(name, function.span, entry.label if entry is not None else None))
879
+ return tuple(functions)
880
+
881
+
820
882
  def self_seeds(
821
883
  function: nodes.Function,
822
884
  module: nodes.Module,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Static security analysis infrastructure for Python code
5
5
  Project-URL: Homepage, https://github.com/CoreTrace/coretrace-python-analyzer
6
6
  Project-URL: Repository, https://github.com/CoreTrace/coretrace-python-analyzer
@@ -1,8 +1,8 @@
1
- coretrace_python/__init__.py,sha256=uJFWBDpap961PWbZ01zYoAZ0LTF4lnI0UH11pEl6GEw,75
1
+ coretrace_python/__init__.py,sha256=utnxu8MbeZzYTN5zz5v2YJUPEiq63GzimvSef3LaKIo,75
2
2
  coretrace_python/__main__.py,sha256=V8DnYtRboISK2VCKm7DGHLREKvqLbqApyWlKBOs26wU,65
3
- coretrace_python/cache.py,sha256=Aj6o-PvOHoTjLRuD-rJqYZv-fnPnp7NmbNFmfWssOvk,11316
3
+ coretrace_python/cache.py,sha256=D14p9uzdise9HtkIEJVLZXICt5jUPcDYUMxo2iuh9g0,12427
4
4
  coretrace_python/cli.py,sha256=ZE6F8wqNcE-ogfldii-UgUq4nOcEMnHuMCzz0l--yuw,9792
5
- coretrace_python/engine.py,sha256=FGMR22fXmBnt_iafkM7-xSh9vYhw15OPKGvTTJu-xiQ,27403
5
+ coretrace_python/engine.py,sha256=ZXEPnio1cRSMme9HP2kmHJaYpychCRDYpZSyFosEZx0,27625
6
6
  coretrace_python/abstract/__init__.py,sha256=n5FNkV8Z4LmwG0r06KWdLj4N2CPSgAKwY3896tdmRgM,999
7
7
  coretrace_python/abstract/constants.py,sha256=z4-e7IzysxzGi58wFFff1MlEoV8l7AZsB9EL-q6b7vY,9071
8
8
  coretrace_python/abstract/heap.py,sha256=OaESXFGC0qzyhAr76xrCu4skeDVzZM4wlenQqfMTkdk,9642
@@ -14,11 +14,11 @@ coretrace_python/analysis/provider.py,sha256=Q_OlEcoLzBAsNdAGWijbj_n3E2Ep5UQ4Z8_
14
14
  coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py,sha256=_CJYHijv4DTgfdumcCqAVV5_MPWjfZOadxeM5pQdw_k,1845
15
15
  coretrace_python/bundled/dependency/dependency_policy/plugin.toml,sha256=4w1TA-UKsa82WTnd1xy4bJuU3_G2RNCAWNICLPcZLMk,210
16
16
  coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml,sha256=LuF9jQqZfplN_GBky69Xjq2pksaX1Fs8PYEir-7-vY8,275
17
- coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py,sha256=nPwOlbebAhMNmk4BHldNe-UMrsK6qUUs1bL4z8fObvY,2186
17
+ coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py,sha256=FwEqdDbkyYOUzF2piVteFfSzEm75tCnk2qRe8RvRNxA,2414
18
18
  coretrace_python/bundled/dependency/sample_advisories/plugin.toml,sha256=cME4pSFzkFsxmIJGGBSrmXqynAd0dg34X-rgEoZ_u6E,184
19
19
  coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py,sha256=jw98GK6hh5RAx05t8QPcUzsFMfGigAL8418vZLprKlc,3710
20
- coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml,sha256=FbmiXLEqtPUZEsDc5q_UqCzn6AecL5HCSdGVy7_2dy0,238
21
- coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py,sha256=ruwruejQ_NqYy7osxfaiPFRlus1DsMEluJ2obhxATkM,2092
20
+ coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml,sha256=4-OnbEb3lYVAoTfuz8mDPH4L9fHOPQ2KW1dDFAbClFk,267
21
+ coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py,sha256=THXqpuffQZ8nuv1b8zQrgkA7eTI1PxhODATMzqhWeyY,3393
22
22
  coretrace_python/bundled/models/aiohttp/aiohttp_models.py,sha256=EraiXpZih8yPRk5t0OKe2TMJO9iATv4Kj_lKLp_KpfA,2946
23
23
  coretrace_python/bundled/models/aiohttp/plugin.toml,sha256=YvU60RUBexYBtIxbXdmUhO83BoijmOe3MeQKwY8QDaM,220
24
24
  coretrace_python/bundled/models/bottle/bottle_models.py,sha256=0yt8-7g-Pxtas3qQcIdORkWLiqJu4O3JzCrSnhzbgHs,1885
@@ -89,9 +89,9 @@ coretrace_python/dataflow/__init__.py,sha256=N5uY8VTNjEk6-ARCgDkxr6uGqQIz2NbF-U9
89
89
  coretrace_python/dataflow/lattice.py,sha256=HgYGVcKVUlexvEc0yT47aQCudLxU_SWLpHhr17igkiE,1929
90
90
  coretrace_python/dataflow/solver.py,sha256=iDsW0wujhM2DXplu2JMKC46BRsroXlNETHPEoikPeG4,3475
91
91
  coretrace_python/dependency/__init__.py,sha256=sZdigKRbM4NkV0Dr86FDF3rFRQz35pICVX6obgO2Rxo,1065
92
- coretrace_python/dependency/advisories.py,sha256=RMEQ_9bvZ7OThjBncM5CbKzsmySPvQYuNMZqQVcrVnc,7765
93
- coretrace_python/dependency/correlation.py,sha256=nyysSoXPz9jg_X8zeweybDpJRXLmj4eH0Z0_WQw5rd0,4916
94
- coretrace_python/dependency/graph.py,sha256=Z0_u4v0hAYMCAyXEWMh7NwcG2FesIHsXUd3Fl8C1MF8,13617
92
+ coretrace_python/dependency/advisories.py,sha256=9g9teb2bzkHmnR9tB86AmtLLlVPhfedP8wITFwmXEXI,8381
93
+ coretrace_python/dependency/correlation.py,sha256=7a-i-1HbemzIfHFvdx0GcVuZ7gyI0o-NOT7-6uYoR_w,7797
94
+ coretrace_python/dependency/graph.py,sha256=0w6bWq7YNb_qcF7H-S-YGClhjS7JsK9Fh1i-w1ONpBQ,13910
95
95
  coretrace_python/dependency/policy.py,sha256=u6nUw2nX-kTuqmw_vooxN8eXZoC31ZMoMaxC9pCcwec,2317
96
96
  coretrace_python/dependency/sbom.py,sha256=h4K3Gb9O_D6uZyfpwpC5rPcNjH_Ib-3uvFxix6OKlfs,2520
97
97
  coretrace_python/findings/__init__.py,sha256=gnJI-uB2CkgZ8I5aFYP_N5yZb7FGNIy7LirVDh4NLwo,372
@@ -106,10 +106,10 @@ coretrace_python/frontend/parser.py,sha256=6-CG7rWxUYeOGeDP206SJWy4BMQUP9Mr6Jf-t
106
106
  coretrace_python/hir/__init__.py,sha256=xMpvj-RSp6bd78a_hhkqbe99-snjERkH-_Lv5o9ASbw,180
107
107
  coretrace_python/hir/nodes.py,sha256=4BI0f3UhKbnz9ZIxMoKXM7ujav0BmRpD0OXaoblRxIk,12364
108
108
  coretrace_python/hir/visitors.py,sha256=JFnX09hK6trLyRcWgZ5ehOG2ywX3VvkqQ1h82nahYNg,885
109
- coretrace_python/interprocedural/__init__.py,sha256=57nLUicdsnX0uIub--EUcLt3TRazzZxy2LQ8TRwlOS0,1054
110
- coretrace_python/interprocedural/callgraph.py,sha256=5YJ3W5JsKf-uSHkwDvSPBRS1X1ZTyjPaz-wJ8aJV3zE,15232
109
+ coretrace_python/interprocedural/__init__.py,sha256=TCX98DRbO0rXFcohwixpDZUVo_9Mdnkbn8wtlBeiu7Q,1128
110
+ coretrace_python/interprocedural/callgraph.py,sha256=NuXogZcdcJwRB_UYiGHGsIss0REjfiQueel7Tfo_4fI,17565
111
111
  coretrace_python/interprocedural/modulegraph.py,sha256=4sB7OKjBYfxtm7aMtHm2jRlqFVUNUsdnLhnv9UZ1BWA,8846
112
- coretrace_python/interprocedural/summaries.py,sha256=6JFrTaMkG34g_nLhfmckjxKWvexCGvXuXozLliw6xQ8,20427
112
+ coretrace_python/interprocedural/summaries.py,sha256=q--xpp1euaf5utxbK4CZtS8CSyLJ6p3MLel5Iu80X3w,20747
113
113
  coretrace_python/ir/__init__.py,sha256=ShBPYYqoQFePP-mgBjGezQ2acel0-ReOHF9vCC-G_4Q,232
114
114
  coretrace_python/ir/defuse.py,sha256=CZEPnlgntRp4MqwR7kLldd2ZlIlBnr-V9VJAxXxwm0c,2616
115
115
  coretrace_python/ir/lowering.py,sha256=V7r_4tEOi801NBOsPdG6pwrRHZIeVTahygYENneLQ4U,31428
@@ -117,7 +117,7 @@ coretrace_python/ir/model.py,sha256=i7LvHEnl5pmWYUcSui17ZGS1rdPVavMpnjt7Tog7P4c,
117
117
  coretrace_python/ir/printer.py,sha256=otBIhZUvB_UfB68gid_FbPcSjBUSmxH3v7vBMZZQVkg,9125
118
118
  coretrace_python/ir/ssa.py,sha256=cIYbcv90PI6AOeMrw2ZWp88fy7E2ZO4P3tqjtPHra7g,11116
119
119
  coretrace_python/plugins/__init__.py,sha256=xRKqkvkvQMmfQACHHoBioYhIyyK4HoMAVogy6EZG8tg,1398
120
- coretrace_python/plugins/api.py,sha256=Fjjy3FjnkS9OoQ55eLQZ-YJgykd4TgKNAYXySb0uSXY,7588
120
+ coretrace_python/plugins/api.py,sha256=NjeDRRwy2K6esSEkg1Ofmv09mqa0El6MlTmRDCWiOmQ,8182
121
121
  coretrace_python/plugins/detectors.py,sha256=ej5Z_MS0CBqrR4b9xycjTVUIo5arpArpdbr7nL732iI,5700
122
122
  coretrace_python/plugins/loader.py,sha256=Use-lHTL4-IJYQBZmODC89zhJbYKGvDSLZHWjZa5NaI,3885
123
123
  coretrace_python/plugins/manifest.py,sha256=F24pgQSwApRUL6ZihRSLv9wL_M03A_pZjrj9Zi4Ku3Q,3438
@@ -136,13 +136,13 @@ coretrace_python/semantic/symbols.py,sha256=3JptnnX5_KBlSd00L_wcGlYZQdnmBvSBAhfi
136
136
  coretrace_python/source/__init__.py,sha256=0Y8fvtbkETnNCJR9Zq7RS5TSgF9c22pz3SSk1KdAXF0,301
137
137
  coretrace_python/source/manager.py,sha256=JRmMOcWfS1nocbVrdcvfQTqEfIkmnb5JP857TpGywN4,2736
138
138
  coretrace_python/source/model.py,sha256=FZnN5mlir3tzbIpWoxnn6YtRi9UcJ_y0sZM60tKr8U4,1727
139
- coretrace_python/taint/__init__.py,sha256=D-NH3kx9wKTNeWASewawGAx7ChOkGMGhmjf-E4crHl0,1111
140
- coretrace_python/taint/engine.py,sha256=tj28lfqWZSyddXsJ2c0Q7zbVSmk8ceZ-FccZDoBuhd4,35605
139
+ coretrace_python/taint/__init__.py,sha256=BuZVlVE4h7Kr873dW3PZ7miBfHQvlRRcqvEaJVvJAas,1161
140
+ coretrace_python/taint/engine.py,sha256=4IFD5uFXG6QhPRBM9lY87hVCpfPLTAd7oTJZbveY5ZI,38180
141
141
  coretrace_python/taint/models.py,sha256=H-Q_nChzwqm-MEGfbfw5jMegpXlIfwUIunFiRXbE90I,12005
142
142
  coretrace_python/taint/routes.py,sha256=UAJKXcw_sM9shf58FfDroxVO9bNSm4qrQdZknwUB0lo,6110
143
- coretrace_python_analyzer-0.4.0.dist-info/METADATA,sha256=4CTPKMus5dgilu4I3lpROK-gLTL9HiQC5P75aZu8Kj8,3216
144
- coretrace_python_analyzer-0.4.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
145
- coretrace_python_analyzer-0.4.0.dist-info/entry_points.txt,sha256=wdozemiraOkbt65g_Y_zgspuZpoLxJdaCVMJt1Ft3c0,72
146
- coretrace_python_analyzer-0.4.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
147
- coretrace_python_analyzer-0.4.0.dist-info/licenses/NOTICE,sha256=s3VHj5ngV0ozjW_kZJpg_35ne6JwoBK0ysOjbqVtA0w,113
148
- coretrace_python_analyzer-0.4.0.dist-info/RECORD,,
143
+ coretrace_python_analyzer-0.5.0.dist-info/METADATA,sha256=v8A0jSjku33W6bQ9nd7aWtBRxGh0GQziPYycy8G4Igc,3216
144
+ coretrace_python_analyzer-0.5.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
145
+ coretrace_python_analyzer-0.5.0.dist-info/entry_points.txt,sha256=wdozemiraOkbt65g_Y_zgspuZpoLxJdaCVMJt1Ft3c0,72
146
+ coretrace_python_analyzer-0.5.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
147
+ coretrace_python_analyzer-0.5.0.dist-info/licenses/NOTICE,sha256=s3VHj5ngV0ozjW_kZJpg_35ne6JwoBK0ysOjbqVtA0w,113
148
+ coretrace_python_analyzer-0.5.0.dist-info/RECORD,,