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.
- coretrace_python/__init__.py +1 -1
- coretrace_python/bundled/models/bottle/bottle_models.py +10 -6
- coretrace_python/bundled/models/cli/cli_models.py +4 -4
- coretrace_python/bundled/models/flask/flask_models.py +12 -9
- coretrace_python/bundled/models/python_stdlib/python_stdlib.py +5 -3
- coretrace_python/bundled/models/tornado/tornado_models.py +10 -5
- coretrace_python/bundled/security/nosql_injection/nosql_injection.py +20 -0
- coretrace_python/bundled/security/nosql_injection/plugin.toml +9 -0
- coretrace_python/engine.py +7 -1
- coretrace_python/interprocedural/callgraph.py +14 -12
- coretrace_python/ir/lowering.py +1 -1
- coretrace_python/semantic/__init__.py +3 -3
- coretrace_python/semantic/symbols.py +106 -16
- coretrace_python/taint/__init__.py +4 -0
- coretrace_python/taint/engine.py +69 -6
- coretrace_python/taint/models.py +16 -1
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/METADATA +1 -1
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/RECORD +22 -20
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/WHEEL +0 -0
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/entry_points.txt +0 -0
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/licenses/NOTICE +0 -0
coretrace_python/__init__.py
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
29
|
-
*(EntryPoint(_sym(f"bottle.
|
|
30
|
-
*(
|
|
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
|
|
12
|
-
#
|
|
13
|
-
ARGV_KINDS =
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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 =
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
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", "
|
|
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
|
-
|
|
30
|
-
|
|
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"
|
coretrace_python/engine.py
CHANGED
|
@@ -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
|
|
183
|
-
and the values a ``with`` on such a result binds. Known
|
|
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]
|
|
198
|
+
symbol = table.attribute(symbols[instruction.object], instruction.attribute)
|
|
199
199
|
elif isinstance(instruction, GetItem) and instruction.object in symbols:
|
|
200
|
-
|
|
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] =
|
|
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] = []
|
coretrace_python/ir/lowering.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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",
|
coretrace_python/taint/engine.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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,
|
coretrace_python/taint/models.py
CHANGED
|
@@ -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
|
|
{coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 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
|
{coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/RECORD
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
coretrace_python/__init__.py,sha256=
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
144
|
-
coretrace_python/taint/engine.py,sha256=
|
|
145
|
-
coretrace_python/taint/models.py,sha256=
|
|
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.
|
|
149
|
-
coretrace_python_analyzer-0.
|
|
150
|
-
coretrace_python_analyzer-0.
|
|
151
|
-
coretrace_python_analyzer-0.
|
|
152
|
-
coretrace_python_analyzer-0.
|
|
153
|
-
coretrace_python_analyzer-0.
|
|
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,,
|
{coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.12.0.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|