coretrace-python-analyzer 0.11.0__py3-none-any.whl → 0.12.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.
Files changed (22) hide show
  1. coretrace_python/__init__.py +1 -1
  2. coretrace_python/bundled/models/bottle/bottle_models.py +10 -6
  3. coretrace_python/bundled/models/cli/cli_models.py +4 -4
  4. coretrace_python/bundled/models/flask/flask_models.py +12 -9
  5. coretrace_python/bundled/models/python_stdlib/python_stdlib.py +5 -3
  6. coretrace_python/bundled/models/tornado/tornado_models.py +10 -5
  7. coretrace_python/bundled/security/nosql_injection/nosql_injection.py +20 -0
  8. coretrace_python/bundled/security/nosql_injection/plugin.toml +9 -0
  9. coretrace_python/engine.py +7 -1
  10. coretrace_python/interprocedural/callgraph.py +14 -12
  11. coretrace_python/ir/lowering.py +1 -1
  12. coretrace_python/semantic/__init__.py +3 -3
  13. coretrace_python/semantic/symbols.py +106 -16
  14. coretrace_python/taint/__init__.py +4 -0
  15. coretrace_python/taint/engine.py +69 -6
  16. coretrace_python/taint/models.py +16 -1
  17. {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/METADATA +1 -1
  18. {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/RECORD +22 -20
  19. {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/WHEEL +0 -0
  20. {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/entry_points.txt +0 -0
  21. {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/licenses/LICENSE +0 -0
  22. {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,4 +1,4 @@
1
1
  """CoreTrace's Python static analysis frontend."""
2
2
 
3
- __version__ = "0.11.0"
3
+ __version__ = "0.12.0"
4
4
 
@@ -8,13 +8,15 @@ from typing import ClassVar
8
8
 
9
9
  from coretrace_python.plugins import ModelPlugin
10
10
  from coretrace_python.semantic.symbols import SymbolId
11
- from coretrace_python.taint import EntryPoint, Model, Sanitizer, Sink, Source, TaintKind
11
+ from coretrace_python.taint import TEXT_KINDS, EntryPoint, Model, Sanitizer, Sink, Source, TaintKind
12
12
 
13
13
  _METHODS = ("route", "get", "post", "put", "delete", "patch")
14
- _REQUEST_ATTRIBUTES = (
15
- "query", "forms", "params", "json", "body", "headers", "cookies", "GET", "POST", "files",
14
+ # Text, which cannot hold a query operator; JSON, raw bodies and files may hold a structure.
15
+ _TEXT_ATTRIBUTES = (
16
+ "query", "forms", "params", "headers", "cookies", "GET", "POST",
16
17
  "url", "path", "fullpath", "query_string", "get_cookie", "get_header",
17
18
  )
19
+ _PAYLOAD_ATTRIBUTES = ("json", "body", "files")
18
20
  _FIRST_ONLY = lambda kind: ((kind, (0,)),)
19
21
 
20
22
 
@@ -25,9 +27,11 @@ def _sym(path: str) -> SymbolId:
25
27
  class BottleModels(ModelPlugin):
26
28
  name: ClassVar[str] = "bottle-models"
27
29
  models: ClassVar[tuple[Model, ...]] = (
28
- *(EntryPoint(_sym(f"bottle.{method}"), "http") for method in _METHODS),
29
- *(EntryPoint(_sym(f"bottle.Bottle.{method}"), "http") for method in _METHODS),
30
- *(Source(_sym(f"bottle.request.{attribute}"), "http") for attribute in _REQUEST_ATTRIBUTES),
30
+ # A route's parameters are segments of its URL.
31
+ *(EntryPoint(_sym(f"bottle.{method}"), "http", TEXT_KINDS) for method in _METHODS),
32
+ *(EntryPoint(_sym(f"bottle.Bottle.{method}"), "http", TEXT_KINDS) for method in _METHODS),
33
+ *(Source(_sym(f"bottle.request.{attribute}"), "http", TEXT_KINDS) for attribute in _TEXT_ATTRIBUTES),
34
+ *(Source(_sym(f"bottle.request.{attribute}"), "http") for attribute in _PAYLOAD_ATTRIBUTES),
31
35
  Sink(_sym("bottle.redirect"), TaintKind.REDIRECT, _FIRST_ONLY(TaintKind.REDIRECT)),
32
36
  # ``static_file(filename, root)``: the root is the application's own.
33
37
  Sink(_sym("bottle.static_file"), TaintKind.PATH, _FIRST_ONLY(TaintKind.PATH)),
@@ -6,11 +6,11 @@ from typing import ClassVar
6
6
 
7
7
  from coretrace_python.plugins import ModelPlugin
8
8
  from coretrace_python.semantic.symbols import SymbolId
9
- from coretrace_python.taint import EntryPoint, Model, TaintKind
9
+ from coretrace_python.taint import TEXT_KINDS, EntryPoint, Model, TaintKind
10
10
 
11
- # A command-line tool is expected to open the paths it is given: argv input carries every
12
- # kind but PATH.
13
- ARGV_KINDS = TaintKind.ALL & ~TaintKind.PATH
11
+ # A command-line tool is expected to open the paths it is given: argv input is text,
12
+ # without PATH.
13
+ ARGV_KINDS = TEXT_KINDS & ~TaintKind.PATH
14
14
 
15
15
  _COMMANDS = (
16
16
  "click.command",
@@ -7,6 +7,7 @@ from typing import ClassVar
7
7
  from coretrace_python.plugins import ModelPlugin
8
8
  from coretrace_python.semantic.symbols import SymbolId
9
9
  from coretrace_python.taint import (
10
+ TEXT_KINDS,
10
11
  AuthorizationGuard,
11
12
  EntryPoint,
12
13
  Model,
@@ -17,10 +18,10 @@ from coretrace_python.taint import (
17
18
  TaintKind,
18
19
  )
19
20
 
20
- _REQUEST_ATTRIBUTES = (
21
- "args", "form", "values", "json", "data", "cookies", "headers", "files",
22
- "get_json", "get_data", "url", "full_path", "path", "query_string", "stream",
23
- )
21
+ # Strings, or mappings of strings: text, which cannot hold a query operator.
22
+ _TEXT_ATTRIBUTES = ("args", "form", "values", "cookies", "headers", "url", "full_path", "path", "query_string")
23
+ # JSON, and the raw bodies and files a ``json.loads`` may decode into a structure.
24
+ _PAYLOAD_ATTRIBUTES = ("json", "get_json", "data", "get_data", "stream", "files")
24
25
 
25
26
 
26
27
  _TARGET_ONLY = ((TaintKind.REDIRECT, (0,)),)
@@ -33,11 +34,13 @@ def _sym(path: str) -> SymbolId:
33
34
  class FlaskModels(ModelPlugin):
34
35
  name: ClassVar[str] = "flask-models"
35
36
  models: ClassVar[tuple[Model, ...]] = (
36
- *(Source(_sym(f"flask.request.{attribute}"), "http") for attribute in _REQUEST_ATTRIBUTES),
37
- EntryPoint(_sym("flask.Flask.route"), "http"),
38
- EntryPoint(_sym("flask.Blueprint.route"), "http"),
39
- RouteRegistrar(_sym("flask.Flask.add_url_rule"), 2, "http", keyword="view_func"),
40
- RouteRegistrar(_sym("flask.Blueprint.add_url_rule"), 2, "http", keyword="view_func"),
37
+ *(Source(_sym(f"flask.request.{attribute}"), "http", TEXT_KINDS) for attribute in _TEXT_ATTRIBUTES),
38
+ *(Source(_sym(f"flask.request.{attribute}"), "http") for attribute in _PAYLOAD_ATTRIBUTES),
39
+ # A route's parameters are segments of its URL.
40
+ EntryPoint(_sym("flask.Flask.route"), "http", TEXT_KINDS),
41
+ EntryPoint(_sym("flask.Blueprint.route"), "http", TEXT_KINDS),
42
+ RouteRegistrar(_sym("flask.Flask.add_url_rule"), 2, "http", TEXT_KINDS, keyword="view_func"),
43
+ RouteRegistrar(_sym("flask.Blueprint.add_url_rule"), 2, "http", TEXT_KINDS, keyword="view_func"),
41
44
  Sink(_sym("flask.render_template_string"), TaintKind.HTML),
42
45
  Sink(_sym("flask.make_response"), TaintKind.HTML),
43
46
  Sink(_sym("flask.Response"), TaintKind.HTML),
@@ -7,6 +7,7 @@ from typing import ClassVar
7
7
  from coretrace_python.plugins import ModelPlugin
8
8
  from coretrace_python.semantic.symbols import SymbolId
9
9
  from coretrace_python.taint import (
10
+ TEXT_KINDS,
10
11
  Model,
11
12
  SafeArgument,
12
13
  Sanitizer,
@@ -16,7 +17,7 @@ from coretrace_python.taint import (
16
17
  Validator,
17
18
  )
18
19
 
19
- _ENVIRONMENT_KINDS = TaintKind.ALL & ~(TaintKind.COMMAND | TaintKind.PATH)
20
+ _ENVIRONMENT_KINDS = TEXT_KINDS & ~(TaintKind.COMMAND | TaintKind.PATH)
20
21
  # SafeLoader and BaseLoader build plain data only, in Python and in C, under every spelling.
21
22
  _SAFE_YAML_LOADERS = (
22
23
  "python.yaml.SafeLoader",
@@ -38,13 +39,14 @@ def _sym(path: str) -> SymbolId:
38
39
  class PythonStdlibModels(ModelPlugin):
39
40
  name: ClassVar[str] = "python-stdlib-models"
40
41
  models: ClassVar[tuple[Model, ...]] = (
41
- Source(_sym("builtins.input"), "stdin"),
42
+ # A line of text; the stream may be a payload ``json.load`` decodes.
43
+ Source(_sym("builtins.input"), "stdin", TEXT_KINDS),
42
44
  Source(_sym("sys.stdin"), "stdin"),
43
45
  # Operator-controlled inputs. A command-line tool is expected to open the paths
44
46
  # it is given; the environment is set by whoever runs the program, so a command
45
47
  # or a path built from it is not an injection; the output of a local process is
46
48
  # not a path either, but a downloaded script piped into a shell is a real flaw.
47
- Source(_sym("sys.argv"), "argv", TaintKind.ALL & ~TaintKind.PATH),
49
+ Source(_sym("sys.argv"), "argv", TEXT_KINDS & ~TaintKind.PATH),
48
50
  Source(_sym("os.environ"), "environment", _ENVIRONMENT_KINDS),
49
51
  Source(_sym("subprocess.run.stdout"), "process-output", _PROCESS_OUTPUT_KINDS),
50
52
  Source(_sym("subprocess.check_output"), "process-output", _PROCESS_OUTPUT_KINDS),
@@ -8,12 +8,13 @@ from typing import ClassVar
8
8
 
9
9
  from coretrace_python.plugins import ModelPlugin
10
10
  from coretrace_python.semantic.symbols import SymbolId
11
- from coretrace_python.taint import EntryPoint, Model, Sanitizer, Sink, Source, TaintKind
11
+ from coretrace_python.taint import TEXT_KINDS, EntryPoint, Model, Sanitizer, Sink, Source, TaintKind
12
12
 
13
13
  _HANDLERS = ("tornado.web.RequestHandler", "tornado.websocket.WebSocketHandler")
14
- _REQUEST_METHODS = (
14
+ # Text, which cannot hold a query operator; the request object holds the raw body too.
15
+ _TEXT_METHODS = (
15
16
  "get_argument", "get_arguments", "get_query_argument", "get_query_arguments",
16
- "get_body_argument", "get_body_arguments", "get_cookie", "request", "path_args", "path_kwargs",
17
+ "get_body_argument", "get_body_arguments", "get_cookie", "path_args", "path_kwargs",
17
18
  )
18
19
  _CLIENTS = ("tornado.httpclient.AsyncHTTPClient.fetch", "tornado.httpclient.HTTPClient.fetch")
19
20
  _TARGET_ONLY = ((TaintKind.REDIRECT, (0,)),)
@@ -26,8 +27,12 @@ def _sym(path: str) -> SymbolId:
26
27
  class TornadoModels(ModelPlugin):
27
28
  name: ClassVar[str] = "tornado-models"
28
29
  models: ClassVar[tuple[Model, ...]] = (
29
- *(EntryPoint(_sym(handler), "http") for handler in _HANDLERS),
30
- *(Source(_sym(f"{handler}.{method}"), "http") for handler in _HANDLERS for method in _REQUEST_METHODS),
30
+ # A request handler's parameters are segments of its URL; a WebSocket handler also
31
+ # receives the messages, raw payloads a ``json.loads`` may decode.
32
+ EntryPoint(_sym("tornado.web.RequestHandler"), "http", TEXT_KINDS),
33
+ EntryPoint(_sym("tornado.websocket.WebSocketHandler"), "http"),
34
+ *(Source(_sym(f"{handler}.{method}"), "http", TEXT_KINDS) for handler in _HANDLERS for method in _TEXT_METHODS),
35
+ *(Source(_sym(f"{handler}.request"), "http") for handler in _HANDLERS),
31
36
  *(Sink(_sym(f"{handler}.write"), TaintKind.HTML) for handler in _HANDLERS),
32
37
  *(Sink(_sym(f"{handler}.finish"), TaintKind.HTML) for handler in _HANDLERS),
33
38
  *(Sink(_sym(f"{handler}.redirect"), TaintKind.REDIRECT, _TARGET_ONLY) for handler in _HANDLERS),
@@ -0,0 +1,20 @@
1
+ """NoSQL injection: attacker input that may hold a structure reaching a NOSQL sink, such
2
+ as a MongoDB filter, where a JSON body giving ``{"$ne": null}`` matches any value. Text
3
+ input carries no ``NOSQL``: a string cannot hold an operator. The engine ships no NOSQL
4
+ sink; model plugins declare them."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import ClassVar
9
+
10
+ from coretrace_python.findings import Severity
11
+ from coretrace_python.plugins import TaintDetector
12
+ from coretrace_python.taint import TaintKind
13
+
14
+
15
+ class NoSqlInjectionPlugin(TaintDetector):
16
+ name: ClassVar[str] = "nosql-injection"
17
+ rule_id: ClassVar[str] = "nosql-injection"
18
+ kind: ClassVar[TaintKind] = TaintKind.NOSQL
19
+ severity: ClassVar[Severity] = Severity.HIGH
20
+ title: ClassVar[str] = "NoSQL injection"
@@ -0,0 +1,9 @@
1
+ name = "nosql-injection"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["taint.flows", "findings.refutation"]
5
+ provides = ["vulnerability.nosql-injection"]
6
+
7
+ [entrypoint]
8
+ module = "nosql_injection"
9
+ class = "NoSqlInjectionPlugin"
@@ -109,7 +109,7 @@ from coretrace_python.reporters import Report
109
109
  from coretrace_python.semantic import SEMANTIC_ANALYSES
110
110
  from coretrace_python.semantic.imports import ImportAnalysis, ImportResolutionError, ImportTable
111
111
  from coretrace_python.semantic.scopes import ScopeAnalysis, ScopeError
112
- from coretrace_python.semantic.symbols import SymbolAnalysis, SymbolId
112
+ from coretrace_python.semantic.symbols import MembersAnalysis, SymbolAnalysis, SymbolId
113
113
  from coretrace_python.source import SourceFile, SourceId, SourceManager, SourceSpan, decode_text
114
114
  from coretrace_python.taint import (
115
115
  EntryPoint,
@@ -257,6 +257,7 @@ def build_manager(
257
257
  manager = _register_all(module)
258
258
  table = (models or SecurityModelRegistry()).freeze()
259
259
  manager.provide(SecurityModelAnalysis, table)
260
+ manager.provide(MembersAnalysis, table.members_by_class())
260
261
  manager.provide(ClearingAnalysis, table.clearing())
261
262
  manager.provide(ProjectSummaries, SummaryIndex())
262
263
  manager.provide(RegisteredRoutes, _routes_of(manager))
@@ -311,6 +312,7 @@ def analyze_file(source: SourceFile, plugin_roots: Sequence[Path]) -> FileAnalys
311
312
  registry = load_plugins(plugin_roots, manager)
312
313
  table = plugin_models(loaded.plugin for loaded in registry)
313
314
  manager.provide(SecurityModelAnalysis, table)
315
+ manager.provide(MembersAnalysis, table.members_by_class())
314
316
  manager.provide(ClearingAnalysis, table.clearing())
315
317
  manager.provide(ProjectSummaries, SummaryIndex())
316
318
  manager.provide(RegisteredRoutes, _routes_of(manager))
@@ -402,8 +404,10 @@ def analyze_project(
402
404
  advisories, origins = _merge_advisories(_contributions(registry, plugins, root, file_advisories))
403
405
  affected = affected_symbols(dependencies, advisories)
404
406
  models = plugin_models(all_plugins, root).extended(*advisory_sinks(affected))
407
+ members = models.members_by_class()
405
408
  for manager in managers.values():
406
409
  manager.provide(SecurityModelAnalysis, models)
410
+ manager.provide(MembersAnalysis, members)
407
411
  manager.provide(DependencyAnalysis, dependencies)
408
412
 
409
413
  imports: dict[str, ImportTable] = {}
@@ -698,8 +702,10 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
698
702
  models = plugin_models(all_plugins, batch.root).extended(*advisory_sinks(affected))
699
703
  routes = _decode_routes(batch.routes)
700
704
  escaped = frozenset(batch.escaped)
705
+ members = models.members_by_class()
701
706
  for manager in managers.values():
702
707
  manager.provide(SecurityModelAnalysis, models)
708
+ manager.provide(MembersAnalysis, members)
703
709
  manager.provide(DependencyAnalysis, dependencies)
704
710
  manager.provide(RegisteredRoutes, routes)
705
711
  manager.provide(EscapedTemplates, escaped)
@@ -176,12 +176,12 @@ class CallGraph:
176
176
 
177
177
 
178
178
  def derive_symbols(
179
- function: FunctionIR, initial: Mapping[Value, SymbolId] | None = None
179
+ function: FunctionIR, table: SymbolTable, initial: Mapping[Value, SymbolId] | None = None
180
180
  ) -> dict[Value, SymbolId]:
181
- """Symbols of values: ``Symbol`` results, attributes and items of symbol values,
182
- results of calling a symbol (``sqlite3.connect(p)`` denotes ``python.sqlite3.connect``)
183
- and the values a ``with`` on such a result binds. Known functions derive nothing;
184
- parameters only through ``initial``, their annotated classes."""
181
+ """Symbols of values: ``Symbol`` results, attributes and items of symbol values, as
182
+ ``table`` types them, results of calling a symbol (``sqlite3.connect(p)`` denotes
183
+ ``python.sqlite3.connect``) and the values a ``with`` on such a result binds. Known
184
+ functions derive nothing; parameters only through ``initial``, their annotated classes."""
185
185
 
186
186
  symbols: dict[Value, SymbolId] = dict(initial or {})
187
187
  changed = True
@@ -195,11 +195,9 @@ def derive_symbols(
195
195
  if isinstance(instruction, Symbol):
196
196
  symbol = instruction.symbol_id
197
197
  elif isinstance(instruction, GetAttr) and instruction.object in symbols:
198
- symbol = symbols[instruction.object].attribute(instruction.attribute)
198
+ symbol = table.attribute(symbols[instruction.object], instruction.attribute)
199
199
  elif isinstance(instruction, GetItem) and instruction.object in symbols:
200
- # An item of a symbol-denoted container (``request.files['f']``)
201
- # carries the container's symbol, so its methods resolve.
202
- symbol = symbols[instruction.object]
200
+ symbol = table.item(symbols[instruction.object])
203
201
  elif isinstance(instruction, Call | WithEnter):
204
202
  origin = (
205
203
  instruction.callee if isinstance(instruction, Call) else instruction.context
@@ -263,6 +261,8 @@ def resolve_targets(
263
261
  owner: str | None = None,
264
262
  typed: Mapping[Value, str] | None = None,
265
263
  bases: Mapping[str, SymbolId] | None = None,
264
+ *,
265
+ table: SymbolTable,
266
266
  ) -> tuple[dict[Value, Target], dict[Value, SymbolId]]:
267
267
  """Map every callee value of ``function`` to its target, and every symbol value.
268
268
  ``nested`` names the functions defined inside this one (``outer.inner``);
@@ -271,7 +271,8 @@ def resolve_targets(
271
271
  call to ``App.__init__``, ``app.run()`` and ``self.run()`` calls to ``App.run``.
272
272
  ``bases`` maps a module class to the symbol of its base: an attribute the class does
273
273
  not define is inherited, so ``self.get_argument`` in a ``RequestHandler`` subclass
274
- denotes ``tornado.web.RequestHandler.get_argument``."""
274
+ denotes ``tornado.web.RequestHandler.get_argument``. ``table`` says what the
275
+ attributes and items of a class a ``Members`` model describes give."""
275
276
 
276
277
  module = scopes.module_scope
277
278
  classes = classes or {}
@@ -294,8 +295,8 @@ def resolve_targets(
294
295
  class_name = instance_of[instruction.object]
295
296
  base = (bases or {}).get(class_name)
296
297
  if base is not None and instruction.attribute not in classes.get(class_name, ()):
297
- inherited[instruction.result] = base.attribute(instruction.attribute)
298
- symbols = derive_symbols(function, inherited)
298
+ inherited[instruction.result] = table.attribute(base, instruction.attribute)
299
+ symbols = derive_symbols(function, table, inherited)
299
300
  targets: dict[Value, Target] = {
300
301
  value: ExternalSymbol(symbol) for value, symbol in symbols.items()
301
302
  }
@@ -427,6 +428,7 @@ class CallGraphAnalysis(Analysis[CallGraph]):
427
428
  owner,
428
429
  _typed_with_module_classes(function, ssa, classes),
429
430
  bases,
431
+ table=table,
430
432
  )
431
433
  defs = {i.result: i for block in ssa.blocks for i in block.instructions if i.result is not None}
432
434
  found: list[CallSite] = []
@@ -141,7 +141,7 @@ class _FunctionLowerer:
141
141
  return self.symbols.resolve(self.scope.id, node.identifier)
142
142
  if isinstance(node, nodes.Attribute):
143
143
  parent = self.imported_symbol(node.value)
144
- return parent.attribute(node.name) if parent is not None else None
144
+ return self.symbols.attribute(parent, node.name) if parent is not None else None
145
145
  return None
146
146
 
147
147
  def expression(self, node: nodes.Expression) -> Value:
@@ -2,8 +2,8 @@
2
2
 
3
3
  from coretrace_python.semantic.imports import ImportAnalysis
4
4
  from coretrace_python.semantic.scopes import ScopeAnalysis
5
- from coretrace_python.semantic.symbols import SymbolAnalysis
5
+ from coretrace_python.semantic.symbols import MembersAnalysis, SymbolAnalysis
6
6
 
7
- SEMANTIC_ANALYSES = (ScopeAnalysis, ImportAnalysis, SymbolAnalysis)
7
+ SEMANTIC_ANALYSES = (ScopeAnalysis, ImportAnalysis, MembersAnalysis, SymbolAnalysis)
8
8
 
9
- __all__ = ["SEMANTIC_ANALYSES", "ImportAnalysis", "ScopeAnalysis", "SymbolAnalysis"]
9
+ __all__ = ["SEMANTIC_ANALYSES", "ImportAnalysis", "MembersAnalysis", "ScopeAnalysis", "SymbolAnalysis"]
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import builtins
6
6
  from collections.abc import Mapping
7
+ from dataclasses import dataclass
7
8
  from types import MappingProxyType
8
9
  from typing import ClassVar
9
10
 
@@ -16,19 +17,69 @@ from coretrace_python.semantic.scopes import ResolutionKind, ScopeAnalysis, Scop
16
17
  BUILTIN_NAMES = frozenset(name for name in dir(builtins) if not name.startswith("_"))
17
18
 
18
19
 
20
+ @dataclass(frozen=True)
21
+ class Members:
22
+ """What the attributes and items of an instance of ``symbol`` give, for a class whose
23
+ instances give other objects by name, as a pymongo client gives the database it is
24
+ dotted or indexed with (``client.shop``, ``client['shop']``).
25
+
26
+ ``typed`` maps an attribute to the class of what it gives, called or not, since a
27
+ call denotes its callee: ``get_database`` gives a database, as a ``db`` property does.
28
+ ``dynamic``, when set, is the class of every item and of every attribute outside
29
+ ``defined`` and ``typed``. Otherwise an attribute keeps its path and an item its
30
+ container's symbol, as for any other class."""
31
+
32
+ symbol: SymbolId
33
+ dynamic: SymbolId | None = None
34
+ defined: tuple[str, ...] = ()
35
+ typed: tuple[tuple[str, SymbolId], ...] = ()
36
+
37
+ def __post_init__(self) -> None:
38
+ # Sorted: the models' repr is part of the cache key, the same in every process.
39
+ object.__setattr__(self, "defined", tuple(sorted(set(self.defined))))
40
+
41
+ def attribute(self, name: str) -> SymbolId:
42
+ for attribute, gives in self.typed:
43
+ if attribute == name:
44
+ return gives
45
+ if self.dynamic is not None and name not in self.defined:
46
+ return self.dynamic
47
+ return self.symbol.attribute(name)
48
+
49
+ def item(self) -> SymbolId:
50
+ return self.symbol if self.dynamic is None else self.dynamic
51
+
52
+
19
53
  class SymbolTable:
20
54
  """Resolve names to canonical symbols through scopes, imports, builtins and the
21
- module-level instances created by calling a symbol (``app = Flask(__name__)``)."""
55
+ module-level names bound to a value denoting a symbol (``app = Flask(__name__)``,
56
+ ``cursor = conn.cursor()``). ``members`` holds the ``Members`` models by class."""
22
57
 
23
58
  def __init__(
24
59
  self,
25
60
  scopes: ScopeTable,
26
61
  imports: ImportTable,
27
62
  instances: Mapping[str, SymbolId] | None = None,
63
+ members: Mapping[SymbolId, Members] | None = None,
28
64
  ) -> None:
29
65
  self._scopes = scopes
30
66
  self._imports = imports
31
67
  self._instances = MappingProxyType(dict(instances or {}))
68
+ self._members = MappingProxyType(dict(members or {}))
69
+
70
+ def attribute(self, symbol: SymbolId, name: str) -> SymbolId:
71
+ """The symbol of attribute ``name`` of what ``symbol`` denotes: its path, unless
72
+ a ``Members`` model describes the class."""
73
+
74
+ members = self._members.get(symbol)
75
+ return symbol.attribute(name) if members is None else members.attribute(name)
76
+
77
+ def item(self, symbol: SymbolId) -> SymbolId:
78
+ """The symbol of an item of what ``symbol`` denotes: the container's own, so its
79
+ methods resolve (``request.files['f'].save``), unless a ``Members`` model says."""
80
+
81
+ members = self._members.get(symbol)
82
+ return symbol if members is None else members.item()
32
83
 
33
84
  def resolve(self, scope_id: ScopeId, name: str) -> SymbolId | None:
34
85
  resolution = self._scopes.resolve(scope_id, name)
@@ -49,39 +100,78 @@ class SymbolTable:
49
100
  return self.resolve(scope_id, node.identifier)
50
101
  if isinstance(node, nodes.Attribute):
51
102
  parent = self.resolve_expression(scope_id, node.value)
52
- return parent.attribute(node.name) if parent is not None else None
103
+ return self.attribute(parent, node.name) if parent is not None else None
53
104
  if isinstance(node, nodes.Call):
54
105
  return self.resolve_expression(scope_id, node.callee)
55
106
  return None
56
107
 
57
108
 
58
109
  def analyze_symbols(
59
- scopes: ScopeTable, imports: ImportTable, module: nodes.Module | None = None
110
+ scopes: ScopeTable,
111
+ imports: ImportTable,
112
+ module: nodes.Module | None = None,
113
+ members: Mapping[SymbolId, Members] | None = None,
60
114
  ) -> SymbolTable:
61
- table = SymbolTable(scopes, imports)
115
+ table = SymbolTable(scopes, imports, members=members)
62
116
  if module is None:
63
117
  return table
64
118
  instances: dict[str, SymbolId] = {}
65
119
  module_scope = scopes.module_scope.id
66
120
  for statement in module.body:
67
- if (
68
- isinstance(statement, nodes.Assign)
69
- and isinstance(statement.target, nodes.Name)
70
- and isinstance(statement.value, nodes.Call)
71
- ):
72
- symbol = table.resolve_expression(module_scope, statement.value.callee)
121
+ if isinstance(statement, nodes.Assign) and isinstance(statement.target, nodes.Name):
122
+ symbol = _denoted(table, module_scope, statement.value)
73
123
  if symbol is not None:
74
124
  instances[statement.target.identifier] = symbol
75
- return SymbolTable(scopes, imports, instances)
125
+ # Later bindings resolve through this one, in statement order.
126
+ table = SymbolTable(scopes, imports, instances, members)
127
+ return table
128
+
129
+
130
+ def _denoted(table: SymbolTable, scope_id: ScopeId, node: nodes.Expression) -> SymbolId | None:
131
+ """What a value denotes, as a function body derives it (``derive_symbols``): a
132
+ resolved name or an attribute of one, the result of calling one, or an item of one,
133
+ which carries its container's symbol (``os.environ['HOME']``) unless a ``Members``
134
+ model says what its items are."""
135
+
136
+ if isinstance(node, nodes.Subscript):
137
+ container = _denoted(table, scope_id, node.value)
138
+ return table.item(container) if container is not None else None
139
+ if isinstance(node, nodes.Attribute):
140
+ parent = _denoted(table, scope_id, node.value)
141
+ return table.attribute(parent, node.name) if parent is not None else None
142
+ if isinstance(node, nodes.Call):
143
+ return _denoted(table, scope_id, node.callee)
144
+ return table.resolve_expression(scope_id, node)
145
+
146
+
147
+ class MembersAnalysis(Analysis[Mapping[SymbolId, Members]]):
148
+ """The ``Members`` models by class, provided by the engine from the security models;
149
+ none on its own, so every attribute keeps its path."""
150
+
151
+ name: ClassVar[str] = "semantic.members"
152
+
153
+ @classmethod
154
+ def compute(cls, ctx: AnalysisContext) -> Mapping[SymbolId, Members]:
155
+ return MappingProxyType({})
76
156
 
77
157
 
78
158
  class SymbolAnalysis(Analysis[SymbolTable]):
79
159
  name: ClassVar[str] = "semantic.symbols"
80
- requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({ScopeAnalysis, ImportAnalysis})
160
+ requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({ScopeAnalysis, ImportAnalysis, MembersAnalysis})
81
161
 
82
162
  @classmethod
83
163
  def compute(cls, ctx: AnalysisContext) -> SymbolTable:
84
- return analyze_symbols(ctx.get(ScopeAnalysis), ctx.get(ImportAnalysis), ctx.module)
85
-
86
-
87
- __all__ = ["BUILTIN_NAMES", "SymbolAnalysis", "SymbolId", "SymbolTable", "analyze_symbols"]
164
+ return analyze_symbols(
165
+ ctx.get(ScopeAnalysis), ctx.get(ImportAnalysis), ctx.module, ctx.get(MembersAnalysis)
166
+ )
167
+
168
+
169
+ __all__ = [
170
+ "BUILTIN_NAMES",
171
+ "Members",
172
+ "MembersAnalysis",
173
+ "SymbolAnalysis",
174
+ "SymbolId",
175
+ "SymbolTable",
176
+ "analyze_symbols",
177
+ ]
@@ -1,5 +1,6 @@
1
1
  """Security models and the global multi-kind taint engine (architecture §16, §17)."""
2
2
 
3
+ from coretrace_python.semantic.symbols import Members
3
4
  from coretrace_python.taint.engine import (
4
5
  EntryPointAnalysis,
5
6
  Taint,
@@ -9,6 +10,7 @@ from coretrace_python.taint.engine import (
9
10
  propagate_taint,
10
11
  )
11
12
  from coretrace_python.taint.models import (
13
+ TEXT_KINDS,
12
14
  AuthorizationGuard,
13
15
  EntryPoint,
14
16
  Model,
@@ -32,10 +34,12 @@ from coretrace_python.taint.routes import RegisteredRoutes, Routes, registered_r
32
34
  from coretrace_python.taint.templates import EscapedTemplates, escaped_templates
33
35
 
34
36
  __all__ = [
37
+ "TEXT_KINDS",
35
38
  "AuthorizationGuard",
36
39
  "EntryPoint",
37
40
  "EntryPointAnalysis",
38
41
  "EscapedTemplates",
42
+ "Members",
39
43
  "Model",
40
44
  "ModelError",
41
45
  "ModelTable",
@@ -70,7 +70,7 @@ from coretrace_python.ir.model import (
70
70
  Value,
71
71
  )
72
72
  from coretrace_python.ir.ssa import SSAAnalysis
73
- from coretrace_python.semantic.scopes import ScopeAnalysis, ScopeTable
73
+ from coretrace_python.semantic.scopes import ScopeAnalysis, ScopeId, ScopeTable
74
74
  from coretrace_python.semantic.symbols import SymbolAnalysis, SymbolId, SymbolTable
75
75
  from coretrace_python.source import SourceSpan
76
76
  from coretrace_python.taint.models import (
@@ -551,7 +551,12 @@ class _TaintProblem(DataflowProblem[State]):
551
551
  reaching = taint.kinds & sink.kinds_at(position, keyword)
552
552
  if not reaching:
553
553
  return
554
- for source in sorted(taint.sources, key=lambda s: str(s.symbol)):
554
+ # One flow per origin: the parameters of one entry point share its source, with
555
+ # the kinds each can carry (a scalar one no NOSQL); the flow's kinds are the taint's.
556
+ origins: dict[tuple[SymbolId, str], Source] = {}
557
+ for source in sorted(taint.sources, key=lambda s: (str(s.symbol), s.label, s.kinds.value)):
558
+ origins.setdefault((source.symbol, source.label), source)
559
+ for source in origins.values():
555
560
  flows.append(
556
561
  TaintFlow(
557
562
  source,
@@ -770,17 +775,22 @@ def parameter_sources(
770
775
  ) -> Mapping[int, Source]:
771
776
  """The attacker-controlled parameters of ``function``, by index: every parameter of an
772
777
  entry point (``self`` excepted for a method), including one registered elsewhere
773
- (``routes``), and every parameter annotated with a typed-parameter symbol."""
778
+ (``routes``), and every parameter annotated with a typed-parameter symbol. An entry
779
+ point's parameter annotated with scalars is text: FastAPI validates it before the
780
+ handler runs, and a scalar cannot hold a query operator."""
774
781
 
775
782
  owner = _owner(module, function)
776
783
  sources: dict[int, Source] = {}
784
+ scope = scopes.scope_for(function)
785
+ enclosing = scope.parent if scope.parent is not None else scope.id
777
786
  entry = function_entry_point(function, module, models, scopes, symbols, instances, routes)
778
787
  if entry is not None:
779
788
  first = 1 if owner is not None else 0
780
789
  for index in range(first, len(function.parameters)):
781
- sources[index] = Source(entry.symbol, entry.label, entry.kinds)
782
- scope = scopes.scope_for(function)
783
- enclosing = scope.parent if scope.parent is not None else scope.id
790
+ annotation = function.parameters[index].annotation
791
+ scalar = annotation is not None and _scalar(annotation, symbols, enclosing)
792
+ kinds = entry.kinds & ~TaintKind.NOSQL if scalar else entry.kinds
793
+ sources[index] = Source(entry.symbol, entry.label, kinds)
784
794
  for index, parameter in enumerate(function.parameters):
785
795
  if parameter.annotation is None:
786
796
  continue
@@ -800,6 +810,59 @@ def parameter_sources(
800
810
  return sources
801
811
 
802
812
 
813
+ def _typing(*names: str) -> frozenset[SymbolId]:
814
+ return frozenset(SymbolId(f"python.{module}.{name}") for module in ("typing", "typing_extensions") for name in names)
815
+
816
+
817
+ _SCALARS = frozenset(
818
+ SymbolId(f"python.{name}")
819
+ for name in (
820
+ "builtins.str",
821
+ "builtins.int",
822
+ "builtins.float",
823
+ "builtins.bool",
824
+ "builtins.bytes",
825
+ "uuid.UUID",
826
+ "datetime.date",
827
+ "datetime.datetime",
828
+ "datetime.time",
829
+ "datetime.timedelta",
830
+ "decimal.Decimal",
831
+ )
832
+ )
833
+ _CONTAINERS = frozenset(
834
+ SymbolId(f"python.builtins.{name}") for name in ("list", "set", "frozenset", "tuple")
835
+ ) | _typing("List", "Set", "FrozenSet", "Tuple", "Sequence")
836
+ _UNIONS = _typing("Optional", "Union")
837
+ _LITERAL = _typing("Literal")
838
+ _ANNOTATED = _typing("Annotated")
839
+
840
+
841
+ def _scalar(annotation: nodes.Expression, symbols: SymbolTable, scope: ScopeId) -> bool:
842
+ """Whether a value annotated with ``annotation`` holds scalars only: a scalar class,
843
+ a literal, a container of scalars, a union of them with ``None``, or one of them
844
+ ``Annotated`` with metadata. Anything unknown may hold a structure."""
845
+
846
+ if isinstance(annotation, nodes.Constant):
847
+ return annotation.value is None or annotation.value is Ellipsis
848
+ if isinstance(annotation, nodes.BinaryOp):
849
+ return annotation.operator == "bit_or" and all(
850
+ _scalar(side, symbols, scope) for side in (annotation.left, annotation.right)
851
+ )
852
+ if isinstance(annotation, nodes.Subscript):
853
+ origin = symbols.resolve_expression(scope, annotation.value)
854
+ key = annotation.key
855
+ arguments = key.elements if isinstance(key, nodes.Tuple) else (key,)
856
+ if origin in _LITERAL:
857
+ return True
858
+ if origin in _ANNOTATED:
859
+ return _scalar(arguments[0], symbols, scope)
860
+ return (origin in _UNIONS or origin in _CONTAINERS) and all(
861
+ _scalar(argument, symbols, scope) for argument in arguments
862
+ )
863
+ return symbols.resolve_expression(scope, annotation) in _SCALARS
864
+
865
+
803
866
  def propagate_taint(
804
867
  name: str,
805
868
  function: FunctionIR,
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  import hashlib
12
12
  import re
13
+ from collections.abc import Mapping
13
14
  from dataclasses import dataclass, field
14
15
  from enum import Flag, auto
15
16
  from types import MappingProxyType
@@ -17,7 +18,7 @@ from typing import ClassVar
17
18
 
18
19
  from coretrace_python.analysis import Analysis, AnalysisContext, MissingInputError
19
20
  from coretrace_python.interprocedural import Clearing
20
- from coretrace_python.semantic.symbols import SymbolId
21
+ from coretrace_python.semantic.symbols import Members, SymbolId
21
22
 
22
23
 
23
24
  class TaintKind(Flag):
@@ -41,6 +42,11 @@ class TaintKind(Flag):
41
42
  PII = auto()
42
43
 
43
44
 
45
+ # What text input carries: a string cannot hold a query operator, only a structure can,
46
+ # so a form field, a header or a URL parameter carries every kind but NOSQL.
47
+ TEXT_KINDS = TaintKind.ALL & ~TaintKind.NOSQL
48
+
49
+
44
50
  class ModelError(Exception):
45
51
  """Two models of the same kind claim the same symbol."""
46
52
 
@@ -221,6 +227,7 @@ Model = (
221
227
  | SuffixSink
222
228
  | SafeArgument
223
229
  | TemplateRender
230
+ | Members
224
231
  )
225
232
 
226
233
 
@@ -238,6 +245,7 @@ class ModelTable:
238
245
  suffix_sinks: tuple[SuffixSink, ...] = ()
239
246
  safe_arguments: tuple[SafeArgument, ...] = ()
240
247
  template_renders: tuple[TemplateRender, ...] = ()
248
+ members: tuple[Members, ...] = ()
241
249
  _by_symbol: dict[type[Model], dict[SymbolId, Model]] = field(
242
250
  init=False, repr=False, compare=False
243
251
  )
@@ -321,6 +329,7 @@ class ModelTable:
321
329
  self.suffix_sinks,
322
330
  self.safe_arguments,
323
331
  self.template_renders,
332
+ self.members,
324
333
  )
325
334
 
326
335
  def sanitizer(self, symbol: SymbolId) -> Sanitizer | None:
@@ -335,6 +344,11 @@ class ModelTable:
335
344
  found = self._by_symbol[TemplateRender].get(symbol)
336
345
  return found if isinstance(found, TemplateRender) else None
337
346
 
347
+ def members_by_class(self) -> Mapping[SymbolId, Members]:
348
+ """The ``Members`` models by class, as symbol resolution reads them."""
349
+
350
+ return MappingProxyType({m.symbol: m for m in self.members})
351
+
338
352
  def clearing(self, escaped: frozenset[str] = frozenset()) -> Clearing:
339
353
  """What calls clear from the data they return: every sanitizer its kinds, every
340
354
  template render ``HTML`` when it names one of the ``escaped`` templates."""
@@ -393,6 +407,7 @@ class SecurityModelRegistry:
393
407
  suffix_sinks=tuple(m for m in models if isinstance(m, SuffixSink)),
394
408
  safe_arguments=tuple(m for m in models if isinstance(m, SafeArgument)),
395
409
  template_renders=tuple(m for m in models if isinstance(m, TemplateRender)),
410
+ members=tuple(m for m in models if isinstance(m, Members)),
396
411
  )
397
412
 
398
413
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.11.0
3
+ Version: 0.12.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=lwZwSBKMQK9wIdXsR7HCxeMNGo0vXmvI-7UK0Q66xu4,76
1
+ coretrace_python/__init__.py,sha256=85TrDjaBuFgudnqeEtHn3Snm9YTbwzOR_D7xaGXhXG8,76
2
2
  coretrace_python/__main__.py,sha256=V8DnYtRboISK2VCKm7DGHLREKvqLbqApyWlKBOs26wU,65
3
3
  coretrace_python/cache.py,sha256=-pZv-ELm4jMCTXQWSZIobHaT9UHIjFu7IlMkOM0i4LI,13607
4
4
  coretrace_python/cli.py,sha256=G5jhb6g6RgSRSjjS7DBgziIjUM1vTFwalJGvFHVKxrg,11225
5
- coretrace_python/engine.py,sha256=-E4z-HvKffI0WMUXPKdQxm61anu-r-cYXHGqnppalYk,31953
5
+ coretrace_python/engine.py,sha256=nwK8fbcEOT0zKiBlK67aCl7yiQpE00j56LLknXn7Djg,32276
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
@@ -21,9 +21,9 @@ coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml,sha256=4-O
21
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
- coretrace_python/bundled/models/bottle/bottle_models.py,sha256=0yt8-7g-Pxtas3qQcIdORkWLiqJu4O3JzCrSnhzbgHs,1885
24
+ coretrace_python/bundled/models/bottle/bottle_models.py,sha256=Y0SUMCNGiGhzcMj0uxBLhCbLFgM7sBc4XBB-C5LEuDw,2200
25
25
  coretrace_python/bundled/models/bottle/plugin.toml,sha256=V2Ea1DcocKOWyOWm_KuzNCaPxN7ZOomSnCyo0e7WfXQ,196
26
- coretrace_python/bundled/models/cli/cli_models.py,sha256=BfeXGLbS-3EnzYrJMhZeViQO9ZUZB-EFtxobWj-oW8M,977
26
+ coretrace_python/bundled/models/cli/cli_models.py,sha256=jKCCpVsIWsOeG8-Q1P79bU6CENOAaxSQ_HZe6jLwd0c,980
27
27
  coretrace_python/bundled/models/cli/plugin.toml,sha256=D2gkrL1lWc6b4ME_C0hjzwZHJX1l_OChRs3_oapam_s,163
28
28
  coretrace_python/bundled/models/credentials/credential_models.py,sha256=Mw9ty8PDDyLNPtbPTTm-sIY9XE8gBsi02dPmLeum2kE,1423
29
29
  coretrace_python/bundled/models/credentials/plugin.toml,sha256=0ut7Jc7IdcvbsZKPu6ty-z3DBlMg2CUT7JX4y4nMybo,217
@@ -33,16 +33,16 @@ coretrace_python/bundled/models/django/django_models.py,sha256=1ftZ8tEBZJXrAiZr0
33
33
  coretrace_python/bundled/models/django/plugin.toml,sha256=_Qa2X_4IViORQkcLvLsAC5vI-2P5CxgG8VvnuNIWcFQ,214
34
34
  coretrace_python/bundled/models/fastapi/fastapi_models.py,sha256=K2Edlo1RzYKmAJAIChNVUDc8gorAS48MGEMKNlCIGrI,1251
35
35
  coretrace_python/bundled/models/fastapi/plugin.toml,sha256=V_R2WcEc-qkST0tTpL-cRf2FEbjK6Mk727WbJmVpWJM,200
36
- coretrace_python/bundled/models/flask/flask_models.py,sha256=XVZWH4u2SdbErk40L-JjTBXQI1hrVCDlMu5BeqE1Ibc,2319
36
+ coretrace_python/bundled/models/flask/flask_models.py,sha256=quR3hm4BqyFCNkBByAhaPVgeC-O-W-dnH_UFNFpgj_k,2717
37
37
  coretrace_python/bundled/models/flask/plugin.toml,sha256=kI53Xwf5aZKd2n3fEQEbBzNnOXRvuonqEazTOIXPJP4,192
38
38
  coretrace_python/bundled/models/http_clients/http_client_models.py,sha256=9XhHWZKq3XWcD0WexucuedAiwxp3fhXxGmo9R6DNtSQ,1954
39
39
  coretrace_python/bundled/models/http_clients/plugin.toml,sha256=vq9tWHgy8-m8LiU1StGhm9Lsy7vnj3w90P4AKDaKfc0,216
40
40
  coretrace_python/bundled/models/python_stdlib/plugin.toml,sha256=DWqjG68rx91HCJi_VEp6tUs4ujkzlR7Lq7saMvtK2HM,187
41
- coretrace_python/bundled/models/python_stdlib/python_stdlib.py,sha256=7S2OEcdY41hQiHm_FX8WIc2DEzl4NQUdZer7yyoRz1Y,4774
41
+ coretrace_python/bundled/models/python_stdlib/python_stdlib.py,sha256=IyCxTHjZcd81YA2-JalQE7OvVSZBisDVCo6330my3JY,4873
42
42
  coretrace_python/bundled/models/sqlalchemy/plugin.toml,sha256=2gRbglnMK5U2QWnzs2U2pDvmTeWmrlW7XpJ_8dRmIoY,182
43
43
  coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py,sha256=Gm2qjyXUrjzALy-MigUhPQyyDxYdfWlQKDZLp1C6Jqk,1697
44
44
  coretrace_python/bundled/models/tornado/plugin.toml,sha256=u_XjJK2uKBR11zg4yGxsCDHcx2pKEQ1MBvvB6Mm0siA,222
45
- coretrace_python/bundled/models/tornado/tornado_models.py,sha256=2j0UPHNBAmf7HvsHCbxKdW974L4rmantXapo8KNEppM,1805
45
+ coretrace_python/bundled/models/tornado/tornado_models.py,sha256=B09X57SFbkV_CnMEb4peseB0tgCnwm3fP8om8wQoAHU,2222
46
46
  coretrace_python/bundled/secrets/config_secrets/config_secrets.py,sha256=JfhWSyVbjlYD9pQYJN6qAc4Rp4IA8PP222akM4KTPU8,1283
47
47
  coretrace_python/bundled/secrets/config_secrets/plugin.toml,sha256=dRkA3zQ9wtT2EJyqZ5zGqJ5KYdyIQtQcSvesqrb4M0Q,177
48
48
  coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py,sha256=7TD-Nf5S4yfoRBRmJ6Ab6v4Q2zIC2n79FRfNS3I4xos,544
@@ -53,6 +53,8 @@ coretrace_python/bundled/security/command_injection/command_injection.py,sha256=
53
53
  coretrace_python/bundled/security/command_injection/plugin.toml,sha256=x9NUpwdVZsTQpaPNX3HoYcrZWk4sOGN-aSp9Bc8jvCA,240
54
54
  coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py,sha256=-4KIQDaYo-09gSceuNEs3eutmBn2OBuYBTqiUEYOatk,637
55
55
  coretrace_python/bundled/security/insecure_deserialization/plugin.toml,sha256=5OTVO9jZN-HBid8v__jYkxU-RnKX974Qqu7JQMFdj54,268
56
+ coretrace_python/bundled/security/nosql_injection/nosql_injection.py,sha256=0MCTZ3DjOf5DQrLgnPPgpa80CYUd5teQ_iau3s_82O0,789
57
+ coretrace_python/bundled/security/nosql_injection/plugin.toml,sha256=FP2qTPtH7ZCiu6vwfgadWlXfVnlKOigXJ43cv9PlorA,232
56
58
  coretrace_python/bundled/security/open_redirect/open_redirect.py,sha256=nUjVIgYhVh_2xBs8_1LOfcLDgQ3g3z1E7APqg2sBdpo,562
57
59
  coretrace_python/bundled/security/open_redirect/plugin.toml,sha256=8t4KLNG1DIpjjen6TuKxMjz2jv6GDcwVJDm_hJUalCg,224
58
60
  coretrace_python/bundled/security/path_traversal/path_traversal.py,sha256=MiTDZBNU6i3WWua1qaxg1swqDy3wlWwBwG6G4ClyDgw,552
@@ -111,12 +113,12 @@ coretrace_python/hir/__init__.py,sha256=xMpvj-RSp6bd78a_hhkqbe99-snjERkH-_Lv5o9A
111
113
  coretrace_python/hir/nodes.py,sha256=4BI0f3UhKbnz9ZIxMoKXM7ujav0BmRpD0OXaoblRxIk,12364
112
114
  coretrace_python/hir/visitors.py,sha256=JFnX09hK6trLyRcWgZ5ehOG2ywX3VvkqQ1h82nahYNg,885
113
115
  coretrace_python/interprocedural/__init__.py,sha256=rBASF3aAvl5CFAmIISu2gID9mqY_iyi-4Hx0GzDVZsg,1300
114
- coretrace_python/interprocedural/callgraph.py,sha256=E6enTawqcl4-TObdjvoFe6l1zEedvsWbnjEBD6omzy8,19827
116
+ coretrace_python/interprocedural/callgraph.py,sha256=3phhsJQ4pnpDullBluROSCbuz4AKaZsvycxrvq4UM7c,19898
115
117
  coretrace_python/interprocedural/modulegraph.py,sha256=4sB7OKjBYfxtm7aMtHm2jRlqFVUNUsdnLhnv9UZ1BWA,8846
116
118
  coretrace_python/interprocedural/summaries.py,sha256=Z_KT3ggH7fgPz1mJL6TBz6V5STka1koIuUBAuYMti7o,26113
117
119
  coretrace_python/ir/__init__.py,sha256=ShBPYYqoQFePP-mgBjGezQ2acel0-ReOHF9vCC-G_4Q,232
118
120
  coretrace_python/ir/defuse.py,sha256=CZEPnlgntRp4MqwR7kLldd2ZlIlBnr-V9VJAxXxwm0c,2616
119
- coretrace_python/ir/lowering.py,sha256=V7r_4tEOi801NBOsPdG6pwrRHZIeVTahygYENneLQ4U,31428
121
+ coretrace_python/ir/lowering.py,sha256=kIVB1HlIW6AMW7-2x9z7wcdhObWj8rjP_eKqOKtgxoM,31442
120
122
  coretrace_python/ir/model.py,sha256=i7LvHEnl5pmWYUcSui17ZGS1rdPVavMpnjt7Tog7P4c,11125
121
123
  coretrace_python/ir/printer.py,sha256=otBIhZUvB_UfB68gid_FbPcSjBUSmxH3v7vBMZZQVkg,9125
122
124
  coretrace_python/ir/ssa.py,sha256=cIYbcv90PI6AOeMrw2ZWp88fy7E2ZO4P3tqjtPHra7g,11116
@@ -132,22 +134,22 @@ coretrace_python/reporters/json_format.py,sha256=x1XRw6UOOX624LRw2rJFiZDBbIX-LVz
132
134
  coretrace_python/reporters/report.py,sha256=hEQPDtnEgDF0djXfg74vBM1B0uajZ_AWtNtu3eH7QI4,1777
133
135
  coretrace_python/reporters/sarif.py,sha256=0HF47hZ6F2WtrDDb2uICLPqYI0gLRiUgxBX69C5lbnU,3649
134
136
  coretrace_python/reporters/text.py,sha256=bHsHkMheyL0AB1iWtxBO5H9S14k1AqJvKkNsFro71xo,1043
135
- coretrace_python/semantic/__init__.py,sha256=_tjT9CgXfPbOMs8EFguhWrLeSmpyrqOHU_OSoQPiWYs,411
137
+ coretrace_python/semantic/__init__.py,sha256=tJOvNvMzJ9yjP2bgkLpPO29pl9gMQgICh6XVYJHrovw,464
136
138
  coretrace_python/semantic/identity.py,sha256=uQBdqEXrOtYmGIiR9hZu0Pimv52iW1rOOOvIdG0InNY,1472
137
139
  coretrace_python/semantic/imports.py,sha256=389MZs8iTUc3CyUHG0VAgWFsypd7VPFKkLHmFz9L-N0,5673
138
140
  coretrace_python/semantic/scopes.py,sha256=zg8ClITSAUbOMvLOV5BsfdG1oC4cd0VweUb9S5Jc4js,20241
139
- coretrace_python/semantic/symbols.py,sha256=3JptnnX5_KBlSd00L_wcGlYZQdnmBvSBAhfivBF6DLo,3513
141
+ coretrace_python/semantic/symbols.py,sha256=G0jwsWFo2346Y1R2sztsB8HuZZm_aHfHSJXXeZKPQLo,7354
140
142
  coretrace_python/source/__init__.py,sha256=0Y8fvtbkETnNCJR9Zq7RS5TSgF9c22pz3SSk1KdAXF0,301
141
143
  coretrace_python/source/manager.py,sha256=JRmMOcWfS1nocbVrdcvfQTqEfIkmnb5JP857TpGywN4,2736
142
144
  coretrace_python/source/model.py,sha256=FZnN5mlir3tzbIpWoxnn6YtRi9UcJ_y0sZM60tKr8U4,1727
143
- coretrace_python/taint/__init__.py,sha256=kfqCEXE5wKFLwz2XrT42_yEv9pW4vPfgg5yYBVVSr2I,1371
144
- coretrace_python/taint/engine.py,sha256=_uVUc2m29aljN_YR2iCQtEdvZFc-LJ5CDWiZF1Tz3WU,40524
145
- coretrace_python/taint/models.py,sha256=B84caPxmQvTdB9H_80nZOqcFlpNd9ofpKI2YL5Uc0UA,15269
145
+ coretrace_python/taint/__init__.py,sha256=A-DU2w7cnoX-ZbRzzC4YjbmBqjhNsrPW1JfmlCjyqGs,1474
146
+ coretrace_python/taint/engine.py,sha256=aJKYKNlt-cItwqukzF3XfAKDVHERNrVlaUuP-MtpC8Y,43353
147
+ coretrace_python/taint/models.py,sha256=orTt70VZhzpIXSzSa_hm-YxKnLmF81VCwE-CO_CTLfc,15890
146
148
  coretrace_python/taint/routes.py,sha256=UAJKXcw_sM9shf58FfDroxVO9bNSm4qrQdZknwUB0lo,6110
147
149
  coretrace_python/taint/templates.py,sha256=8Ojb43VXuWxOVifeXRpi3_2mjZfnvIC7GQ-AW80GUYk,11148
148
- coretrace_python_analyzer-0.11.0.dist-info/METADATA,sha256=s9XGnFo_DdCtjSWnMC3WL6Dj1KiPskmmE-xqE7H9M6Q,3217
149
- coretrace_python_analyzer-0.11.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
150
- coretrace_python_analyzer-0.11.0.dist-info/entry_points.txt,sha256=wdozemiraOkbt65g_Y_zgspuZpoLxJdaCVMJt1Ft3c0,72
151
- coretrace_python_analyzer-0.11.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
152
- coretrace_python_analyzer-0.11.0.dist-info/licenses/NOTICE,sha256=s3VHj5ngV0ozjW_kZJpg_35ne6JwoBK0ysOjbqVtA0w,113
153
- coretrace_python_analyzer-0.11.0.dist-info/RECORD,,
150
+ coretrace_python_analyzer-0.12.0.dist-info/METADATA,sha256=aI3xBMs5H9Ue5btuk_0XZnkA42jTzagtALNVCYCTKQA,3217
151
+ coretrace_python_analyzer-0.12.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
152
+ coretrace_python_analyzer-0.12.0.dist-info/entry_points.txt,sha256=wdozemiraOkbt65g_Y_zgspuZpoLxJdaCVMJt1Ft3c0,72
153
+ coretrace_python_analyzer-0.12.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
154
+ coretrace_python_analyzer-0.12.0.dist-info/licenses/NOTICE,sha256=s3VHj5ngV0ozjW_kZJpg_35ne6JwoBK0ysOjbqVtA0w,113
155
+ coretrace_python_analyzer-0.12.0.dist-info/RECORD,,