coretrace-python-analyzer 0.1.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 +4 -0
- coretrace_python/__main__.py +4 -0
- coretrace_python/abstract/__init__.py +45 -0
- coretrace_python/abstract/constants.py +226 -0
- coretrace_python/abstract/heap.py +252 -0
- coretrace_python/abstract/ranges.py +285 -0
- coretrace_python/abstract/values.py +56 -0
- coretrace_python/analysis/__init__.py +31 -0
- coretrace_python/analysis/manager.py +165 -0
- coretrace_python/analysis/provider.py +73 -0
- coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +46 -0
- coretrace_python/bundled/dependency/dependency_policy/plugin.toml +9 -0
- coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +9 -0
- coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +56 -0
- coretrace_python/bundled/dependency/sample_advisories/plugin.toml +9 -0
- coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +109 -0
- coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +9 -0
- coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +43 -0
- coretrace_python/bundled/models/cli/cli_models.py +35 -0
- coretrace_python/bundled/models/cli/plugin.toml +9 -0
- coretrace_python/bundled/models/credentials/credential_models.py +43 -0
- coretrace_python/bundled/models/credentials/plugin.toml +9 -0
- coretrace_python/bundled/models/django/django_models.py +123 -0
- coretrace_python/bundled/models/django/plugin.toml +9 -0
- coretrace_python/bundled/models/fastapi/fastapi_models.py +29 -0
- coretrace_python/bundled/models/fastapi/plugin.toml +9 -0
- coretrace_python/bundled/models/flask/flask_models.py +53 -0
- coretrace_python/bundled/models/flask/plugin.toml +9 -0
- coretrace_python/bundled/models/http_clients/http_client_models.py +43 -0
- coretrace_python/bundled/models/http_clients/plugin.toml +9 -0
- coretrace_python/bundled/models/python_stdlib/plugin.toml +9 -0
- coretrace_python/bundled/models/python_stdlib/python_stdlib.py +68 -0
- coretrace_python/bundled/models/sqlalchemy/plugin.toml +9 -0
- coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +47 -0
- coretrace_python/bundled/secrets/config_secrets/config_secrets.py +38 -0
- coretrace_python/bundled/secrets/config_secrets/plugin.toml +9 -0
- coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +19 -0
- coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +9 -0
- coretrace_python/bundled/security/command_injection/command_injection.py +17 -0
- coretrace_python/bundled/security/command_injection/plugin.toml +9 -0
- coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +17 -0
- coretrace_python/bundled/security/insecure_deserialization/plugin.toml +9 -0
- coretrace_python/bundled/security/open_redirect/open_redirect.py +17 -0
- coretrace_python/bundled/security/open_redirect/plugin.toml +9 -0
- coretrace_python/bundled/security/path_traversal/path_traversal.py +17 -0
- coretrace_python/bundled/security/path_traversal/plugin.toml +9 -0
- coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +21 -0
- coretrace_python/bundled/security/plaintext_credentials/plugin.toml +9 -0
- coretrace_python/bundled/security/sql_injection/plugin.toml +9 -0
- coretrace_python/bundled/security/sql_injection/sql_injection.py +17 -0
- coretrace_python/bundled/security/ssrf/plugin.toml +9 -0
- coretrace_python/bundled/security/ssrf/ssrf.py +17 -0
- coretrace_python/bundled/security/xss/plugin.toml +9 -0
- coretrace_python/bundled/security/xss/xss.py +17 -0
- coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +19 -0
- coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +9 -0
- coretrace_python/bundled/syntax/flask_debug/flask_debug.py +63 -0
- coretrace_python/bundled/syntax/flask_debug/plugin.toml +9 -0
- coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +51 -0
- coretrace_python/bundled/syntax/missing_timeout/plugin.toml +9 -0
- coretrace_python/bundled/syntax/weak_crypto/plugin.toml +9 -0
- coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +19 -0
- coretrace_python/cache.py +310 -0
- coretrace_python/cfg/__init__.py +46 -0
- coretrace_python/cfg/builder.py +589 -0
- coretrace_python/cfg/dominance.py +183 -0
- coretrace_python/cfg/model.py +166 -0
- coretrace_python/cli.py +216 -0
- coretrace_python/dataflow/__init__.py +29 -0
- coretrace_python/dataflow/lattice.py +78 -0
- coretrace_python/dataflow/solver.py +96 -0
- coretrace_python/dependency/__init__.py +44 -0
- coretrace_python/dependency/advisories.py +168 -0
- coretrace_python/dependency/correlation.py +87 -0
- coretrace_python/dependency/graph.py +274 -0
- coretrace_python/dependency/policy.py +65 -0
- coretrace_python/dependency/sbom.py +65 -0
- coretrace_python/engine.py +688 -0
- coretrace_python/findings/__init__.py +13 -0
- coretrace_python/findings/coverage.py +45 -0
- coretrace_python/findings/model.py +49 -0
- coretrace_python/findings/refutation.py +427 -0
- coretrace_python/frontend/__init__.py +18 -0
- coretrace_python/frontend/ast_adapter.py +447 -0
- coretrace_python/frontend/parser.py +23 -0
- coretrace_python/hir/__init__.py +5 -0
- coretrace_python/hir/nodes.py +584 -0
- coretrace_python/hir/visitors.py +36 -0
- coretrace_python/interprocedural/__init__.py +49 -0
- coretrace_python/interprocedural/callgraph.py +291 -0
- coretrace_python/interprocedural/modulegraph.py +218 -0
- coretrace_python/interprocedural/summaries.py +463 -0
- coretrace_python/ir/__init__.py +5 -0
- coretrace_python/ir/defuse.py +74 -0
- coretrace_python/ir/lowering.py +575 -0
- coretrace_python/ir/model.py +481 -0
- coretrace_python/ir/printer.py +201 -0
- coretrace_python/ir/ssa.py +277 -0
- coretrace_python/plugins/__init__.py +58 -0
- coretrace_python/plugins/api.py +153 -0
- coretrace_python/plugins/detectors.py +114 -0
- coretrace_python/plugins/loader.py +84 -0
- coretrace_python/plugins/manifest.py +112 -0
- coretrace_python/plugins/registry.py +34 -0
- coretrace_python/plugins/secrets.py +330 -0
- coretrace_python/reporters/__init__.py +22 -0
- coretrace_python/reporters/json_format.py +48 -0
- coretrace_python/reporters/report.py +23 -0
- coretrace_python/reporters/sarif.py +70 -0
- coretrace_python/reporters/text.py +24 -0
- coretrace_python/semantic/__init__.py +9 -0
- coretrace_python/semantic/identity.py +39 -0
- coretrace_python/semantic/imports.py +131 -0
- coretrace_python/semantic/scopes.py +473 -0
- coretrace_python/semantic/symbols.py +87 -0
- coretrace_python/source/__init__.py +13 -0
- coretrace_python/source/manager.py +81 -0
- coretrace_python/source/model.py +57 -0
- coretrace_python/taint/__init__.py +55 -0
- coretrace_python/taint/engine.py +800 -0
- coretrace_python/taint/models.py +317 -0
- coretrace_python/taint/routes.py +99 -0
- coretrace_python_analyzer-0.1.0.dist-info/METADATA +74 -0
- coretrace_python_analyzer-0.1.0.dist-info/RECORD +126 -0
- coretrace_python_analyzer-0.1.0.dist-info/WHEEL +4 -0
- coretrace_python_analyzer-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Symbol analysis: resolve names to canonical identities (architecture §4.3)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from types import MappingProxyType
|
|
8
|
+
from typing import ClassVar
|
|
9
|
+
|
|
10
|
+
from coretrace_python.analysis import Analysis, AnalysisContext, AnyAnalysis
|
|
11
|
+
from coretrace_python.hir import nodes
|
|
12
|
+
from coretrace_python.semantic.identity import SymbolId
|
|
13
|
+
from coretrace_python.semantic.imports import ImportAnalysis, ImportTable
|
|
14
|
+
from coretrace_python.semantic.scopes import ResolutionKind, ScopeAnalysis, ScopeId, ScopeTable
|
|
15
|
+
|
|
16
|
+
BUILTIN_NAMES = frozenset(name for name in dir(builtins) if not name.startswith("_"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SymbolTable:
|
|
20
|
+
"""Resolve names to canonical symbols through scopes, imports, builtins and the
|
|
21
|
+
module-level instances created by calling a symbol (``app = Flask(__name__)``)."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
scopes: ScopeTable,
|
|
26
|
+
imports: ImportTable,
|
|
27
|
+
instances: Mapping[str, SymbolId] | None = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
self._scopes = scopes
|
|
30
|
+
self._imports = imports
|
|
31
|
+
self._instances = MappingProxyType(dict(instances or {}))
|
|
32
|
+
|
|
33
|
+
def resolve(self, scope_id: ScopeId, name: str) -> SymbolId | None:
|
|
34
|
+
resolution = self._scopes.resolve(scope_id, name)
|
|
35
|
+
if resolution.kind is ResolutionKind.UNBOUND:
|
|
36
|
+
return SymbolId(f"python.builtins.{name}") if name in BUILTIN_NAMES else None
|
|
37
|
+
assert resolution.scope is not None
|
|
38
|
+
imported = self._imports.bindings(resolution.scope).get(name)
|
|
39
|
+
if imported is not None:
|
|
40
|
+
return imported
|
|
41
|
+
if resolution.scope == self._scopes.module_scope.id:
|
|
42
|
+
return self._instances.get(name)
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def resolve_expression(self, scope_id: ScopeId, node: nodes.Expression) -> SymbolId | None:
|
|
46
|
+
"""The symbol of a name or attribute chain, or of the callee of a call."""
|
|
47
|
+
|
|
48
|
+
if isinstance(node, nodes.Name):
|
|
49
|
+
return self.resolve(scope_id, node.identifier)
|
|
50
|
+
if isinstance(node, nodes.Attribute):
|
|
51
|
+
parent = self.resolve_expression(scope_id, node.value)
|
|
52
|
+
return parent.attribute(node.name) if parent is not None else None
|
|
53
|
+
if isinstance(node, nodes.Call):
|
|
54
|
+
return self.resolve_expression(scope_id, node.callee)
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def analyze_symbols(
|
|
59
|
+
scopes: ScopeTable, imports: ImportTable, module: nodes.Module | None = None
|
|
60
|
+
) -> SymbolTable:
|
|
61
|
+
table = SymbolTable(scopes, imports)
|
|
62
|
+
if module is None:
|
|
63
|
+
return table
|
|
64
|
+
instances: dict[str, SymbolId] = {}
|
|
65
|
+
module_scope = scopes.module_scope.id
|
|
66
|
+
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)
|
|
73
|
+
if symbol is not None:
|
|
74
|
+
instances[statement.target.identifier] = symbol
|
|
75
|
+
return SymbolTable(scopes, imports, instances)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class SymbolAnalysis(Analysis[SymbolTable]):
|
|
79
|
+
name: ClassVar[str] = "semantic.symbols"
|
|
80
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({ScopeAnalysis, ImportAnalysis})
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
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"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Source storage and location primitives."""
|
|
2
|
+
|
|
3
|
+
from coretrace_python.source.manager import SourceManager, decode_text
|
|
4
|
+
from coretrace_python.source.model import SourceFile, SourceId, SourceSpan
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"SourceFile",
|
|
8
|
+
"SourceId",
|
|
9
|
+
"SourceManager",
|
|
10
|
+
"SourceSpan",
|
|
11
|
+
"decode_text",
|
|
12
|
+
]
|
|
13
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Ownership and loading of source files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from coretrace_python.source.model import SourceFile, SourceId
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def module_name_for(path: Path) -> str:
|
|
11
|
+
"""Dotted module name of ``path``, walking up through ``__init__.py`` packages.
|
|
12
|
+
|
|
13
|
+
A package's ``__init__.py`` is named after the package itself."""
|
|
14
|
+
|
|
15
|
+
parts = [] if path.name == "__init__.py" else [path.stem]
|
|
16
|
+
directory = path.parent
|
|
17
|
+
while (directory / "__init__.py").is_file():
|
|
18
|
+
parts.append(directory.name)
|
|
19
|
+
directory = directory.parent
|
|
20
|
+
return ".".join(reversed(parts)) or path.stem
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def decode_text(data: bytes) -> str:
|
|
24
|
+
"""Decode a text file, honouring a UTF-8 or UTF-16 byte order mark; Windows tools
|
|
25
|
+
such as ``pip freeze`` write UTF-16 with one."""
|
|
26
|
+
|
|
27
|
+
for mark, encoding in ((b"\xef\xbb\xbf", "utf-8-sig"), (b"\xff\xfe", "utf-16"), (b"\xfe\xff", "utf-16")):
|
|
28
|
+
if data.startswith(mark):
|
|
29
|
+
return data.decode(encoding)
|
|
30
|
+
return data.decode("utf-8")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SourceManager:
|
|
34
|
+
"""Own source text and return one canonical object for each source ID."""
|
|
35
|
+
|
|
36
|
+
def __init__(self) -> None:
|
|
37
|
+
self._sources: dict[SourceId, SourceFile] = {}
|
|
38
|
+
|
|
39
|
+
def add_source(self, name: str, text: str, module_name: str | None = None) -> SourceFile:
|
|
40
|
+
source_id = SourceId(name)
|
|
41
|
+
existing = self._sources.get(source_id)
|
|
42
|
+
if existing is not None:
|
|
43
|
+
if existing.text != text:
|
|
44
|
+
raise ValueError(f"source {name!r} already exists with different text")
|
|
45
|
+
return existing
|
|
46
|
+
|
|
47
|
+
source = SourceFile(
|
|
48
|
+
source_id=source_id,
|
|
49
|
+
text=text,
|
|
50
|
+
module_name=module_name if module_name is not None else Path(name).stem,
|
|
51
|
+
)
|
|
52
|
+
self._sources[source_id] = source
|
|
53
|
+
return source
|
|
54
|
+
|
|
55
|
+
def load_file(self, path: str | Path) -> SourceFile:
|
|
56
|
+
resolved_path = Path(path).resolve()
|
|
57
|
+
source_id = SourceId(str(resolved_path))
|
|
58
|
+
existing = self._sources.get(source_id)
|
|
59
|
+
if existing is not None:
|
|
60
|
+
return existing
|
|
61
|
+
|
|
62
|
+
text = decode_text(resolved_path.read_bytes())
|
|
63
|
+
source = SourceFile(
|
|
64
|
+
source_id=source_id,
|
|
65
|
+
text=text,
|
|
66
|
+
module_name=module_name_for(resolved_path),
|
|
67
|
+
path=resolved_path,
|
|
68
|
+
is_package=resolved_path.name == "__init__.py",
|
|
69
|
+
)
|
|
70
|
+
self._sources[source_id] = source
|
|
71
|
+
return source
|
|
72
|
+
|
|
73
|
+
def get(self, source_id: SourceId) -> SourceFile:
|
|
74
|
+
try:
|
|
75
|
+
return self._sources[source_id]
|
|
76
|
+
except KeyError as error:
|
|
77
|
+
raise KeyError(f"unknown source ID: {source_id}") from error
|
|
78
|
+
|
|
79
|
+
def __len__(self) -> int:
|
|
80
|
+
return len(self._sources)
|
|
81
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Immutable source files and locations shared by engine layers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, order=True)
|
|
10
|
+
class SourceId:
|
|
11
|
+
"""Stable identity for a source unit within an analysis invocation."""
|
|
12
|
+
|
|
13
|
+
value: str
|
|
14
|
+
|
|
15
|
+
def __post_init__(self) -> None:
|
|
16
|
+
if not self.value:
|
|
17
|
+
raise ValueError("source ID cannot be empty")
|
|
18
|
+
|
|
19
|
+
def __str__(self) -> str:
|
|
20
|
+
return self.value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class SourceSpan:
|
|
25
|
+
"""One-based, half-open source range."""
|
|
26
|
+
|
|
27
|
+
source_id: SourceId
|
|
28
|
+
start_line: int
|
|
29
|
+
start_column: int
|
|
30
|
+
end_line: int | None = None
|
|
31
|
+
end_column: int | None = None
|
|
32
|
+
|
|
33
|
+
def __post_init__(self) -> None:
|
|
34
|
+
if self.start_line < 1 or self.start_column < 1:
|
|
35
|
+
raise ValueError("source span start must be one-based")
|
|
36
|
+
if (self.end_line is None) != (self.end_column is None):
|
|
37
|
+
raise ValueError("source span end line and column must be provided together")
|
|
38
|
+
if self.end_line is not None and self.end_column is not None:
|
|
39
|
+
if self.end_line < self.start_line:
|
|
40
|
+
raise ValueError("source span cannot end before it starts")
|
|
41
|
+
if self.end_line == self.start_line and self.end_column < self.start_column:
|
|
42
|
+
raise ValueError("source span cannot end before it starts")
|
|
43
|
+
|
|
44
|
+
def display(self) -> str:
|
|
45
|
+
return f"{self.source_id}:{self.start_line}:{self.start_column}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class SourceFile:
|
|
50
|
+
"""Decoded source text, its identity and its dotted module name."""
|
|
51
|
+
|
|
52
|
+
source_id: SourceId
|
|
53
|
+
text: str
|
|
54
|
+
module_name: str
|
|
55
|
+
path: Path | None = None
|
|
56
|
+
is_package: bool = False
|
|
57
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Security models and the global multi-kind taint engine (architecture §16, §17)."""
|
|
2
|
+
|
|
3
|
+
from coretrace_python.taint.engine import (
|
|
4
|
+
Taint,
|
|
5
|
+
TaintAnalysis,
|
|
6
|
+
TaintFacts,
|
|
7
|
+
TaintFlow,
|
|
8
|
+
propagate_taint,
|
|
9
|
+
)
|
|
10
|
+
from coretrace_python.taint.models import (
|
|
11
|
+
AuthorizationGuard,
|
|
12
|
+
EntryPoint,
|
|
13
|
+
Model,
|
|
14
|
+
ModelError,
|
|
15
|
+
ModelTable,
|
|
16
|
+
NamedParameter,
|
|
17
|
+
RouteRegistrar,
|
|
18
|
+
Sanitizer,
|
|
19
|
+
SecurityModelAnalysis,
|
|
20
|
+
SecurityModelRegistry,
|
|
21
|
+
Sink,
|
|
22
|
+
Source,
|
|
23
|
+
SuffixSink,
|
|
24
|
+
TaintKind,
|
|
25
|
+
TypedParameter,
|
|
26
|
+
Validator,
|
|
27
|
+
)
|
|
28
|
+
from coretrace_python.taint.routes import RegisteredRoutes, Routes, registered_routes
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"AuthorizationGuard",
|
|
32
|
+
"EntryPoint",
|
|
33
|
+
"Model",
|
|
34
|
+
"ModelError",
|
|
35
|
+
"ModelTable",
|
|
36
|
+
"NamedParameter",
|
|
37
|
+
"RegisteredRoutes",
|
|
38
|
+
"RouteRegistrar",
|
|
39
|
+
"Routes",
|
|
40
|
+
"Sanitizer",
|
|
41
|
+
"SecurityModelAnalysis",
|
|
42
|
+
"SecurityModelRegistry",
|
|
43
|
+
"Sink",
|
|
44
|
+
"Source",
|
|
45
|
+
"SuffixSink",
|
|
46
|
+
"Taint",
|
|
47
|
+
"TaintAnalysis",
|
|
48
|
+
"TaintFacts",
|
|
49
|
+
"TaintFlow",
|
|
50
|
+
"TaintKind",
|
|
51
|
+
"TypedParameter",
|
|
52
|
+
"Validator",
|
|
53
|
+
"propagate_taint",
|
|
54
|
+
"registered_routes",
|
|
55
|
+
]
|