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.
Files changed (126) hide show
  1. coretrace_python/__init__.py +4 -0
  2. coretrace_python/__main__.py +4 -0
  3. coretrace_python/abstract/__init__.py +45 -0
  4. coretrace_python/abstract/constants.py +226 -0
  5. coretrace_python/abstract/heap.py +252 -0
  6. coretrace_python/abstract/ranges.py +285 -0
  7. coretrace_python/abstract/values.py +56 -0
  8. coretrace_python/analysis/__init__.py +31 -0
  9. coretrace_python/analysis/manager.py +165 -0
  10. coretrace_python/analysis/provider.py +73 -0
  11. coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +46 -0
  12. coretrace_python/bundled/dependency/dependency_policy/plugin.toml +9 -0
  13. coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +9 -0
  14. coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +56 -0
  15. coretrace_python/bundled/dependency/sample_advisories/plugin.toml +9 -0
  16. coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +109 -0
  17. coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +9 -0
  18. coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +43 -0
  19. coretrace_python/bundled/models/cli/cli_models.py +35 -0
  20. coretrace_python/bundled/models/cli/plugin.toml +9 -0
  21. coretrace_python/bundled/models/credentials/credential_models.py +43 -0
  22. coretrace_python/bundled/models/credentials/plugin.toml +9 -0
  23. coretrace_python/bundled/models/django/django_models.py +123 -0
  24. coretrace_python/bundled/models/django/plugin.toml +9 -0
  25. coretrace_python/bundled/models/fastapi/fastapi_models.py +29 -0
  26. coretrace_python/bundled/models/fastapi/plugin.toml +9 -0
  27. coretrace_python/bundled/models/flask/flask_models.py +53 -0
  28. coretrace_python/bundled/models/flask/plugin.toml +9 -0
  29. coretrace_python/bundled/models/http_clients/http_client_models.py +43 -0
  30. coretrace_python/bundled/models/http_clients/plugin.toml +9 -0
  31. coretrace_python/bundled/models/python_stdlib/plugin.toml +9 -0
  32. coretrace_python/bundled/models/python_stdlib/python_stdlib.py +68 -0
  33. coretrace_python/bundled/models/sqlalchemy/plugin.toml +9 -0
  34. coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +47 -0
  35. coretrace_python/bundled/secrets/config_secrets/config_secrets.py +38 -0
  36. coretrace_python/bundled/secrets/config_secrets/plugin.toml +9 -0
  37. coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +19 -0
  38. coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +9 -0
  39. coretrace_python/bundled/security/command_injection/command_injection.py +17 -0
  40. coretrace_python/bundled/security/command_injection/plugin.toml +9 -0
  41. coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +17 -0
  42. coretrace_python/bundled/security/insecure_deserialization/plugin.toml +9 -0
  43. coretrace_python/bundled/security/open_redirect/open_redirect.py +17 -0
  44. coretrace_python/bundled/security/open_redirect/plugin.toml +9 -0
  45. coretrace_python/bundled/security/path_traversal/path_traversal.py +17 -0
  46. coretrace_python/bundled/security/path_traversal/plugin.toml +9 -0
  47. coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +21 -0
  48. coretrace_python/bundled/security/plaintext_credentials/plugin.toml +9 -0
  49. coretrace_python/bundled/security/sql_injection/plugin.toml +9 -0
  50. coretrace_python/bundled/security/sql_injection/sql_injection.py +17 -0
  51. coretrace_python/bundled/security/ssrf/plugin.toml +9 -0
  52. coretrace_python/bundled/security/ssrf/ssrf.py +17 -0
  53. coretrace_python/bundled/security/xss/plugin.toml +9 -0
  54. coretrace_python/bundled/security/xss/xss.py +17 -0
  55. coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +19 -0
  56. coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +9 -0
  57. coretrace_python/bundled/syntax/flask_debug/flask_debug.py +63 -0
  58. coretrace_python/bundled/syntax/flask_debug/plugin.toml +9 -0
  59. coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +51 -0
  60. coretrace_python/bundled/syntax/missing_timeout/plugin.toml +9 -0
  61. coretrace_python/bundled/syntax/weak_crypto/plugin.toml +9 -0
  62. coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +19 -0
  63. coretrace_python/cache.py +310 -0
  64. coretrace_python/cfg/__init__.py +46 -0
  65. coretrace_python/cfg/builder.py +589 -0
  66. coretrace_python/cfg/dominance.py +183 -0
  67. coretrace_python/cfg/model.py +166 -0
  68. coretrace_python/cli.py +216 -0
  69. coretrace_python/dataflow/__init__.py +29 -0
  70. coretrace_python/dataflow/lattice.py +78 -0
  71. coretrace_python/dataflow/solver.py +96 -0
  72. coretrace_python/dependency/__init__.py +44 -0
  73. coretrace_python/dependency/advisories.py +168 -0
  74. coretrace_python/dependency/correlation.py +87 -0
  75. coretrace_python/dependency/graph.py +274 -0
  76. coretrace_python/dependency/policy.py +65 -0
  77. coretrace_python/dependency/sbom.py +65 -0
  78. coretrace_python/engine.py +688 -0
  79. coretrace_python/findings/__init__.py +13 -0
  80. coretrace_python/findings/coverage.py +45 -0
  81. coretrace_python/findings/model.py +49 -0
  82. coretrace_python/findings/refutation.py +427 -0
  83. coretrace_python/frontend/__init__.py +18 -0
  84. coretrace_python/frontend/ast_adapter.py +447 -0
  85. coretrace_python/frontend/parser.py +23 -0
  86. coretrace_python/hir/__init__.py +5 -0
  87. coretrace_python/hir/nodes.py +584 -0
  88. coretrace_python/hir/visitors.py +36 -0
  89. coretrace_python/interprocedural/__init__.py +49 -0
  90. coretrace_python/interprocedural/callgraph.py +291 -0
  91. coretrace_python/interprocedural/modulegraph.py +218 -0
  92. coretrace_python/interprocedural/summaries.py +463 -0
  93. coretrace_python/ir/__init__.py +5 -0
  94. coretrace_python/ir/defuse.py +74 -0
  95. coretrace_python/ir/lowering.py +575 -0
  96. coretrace_python/ir/model.py +481 -0
  97. coretrace_python/ir/printer.py +201 -0
  98. coretrace_python/ir/ssa.py +277 -0
  99. coretrace_python/plugins/__init__.py +58 -0
  100. coretrace_python/plugins/api.py +153 -0
  101. coretrace_python/plugins/detectors.py +114 -0
  102. coretrace_python/plugins/loader.py +84 -0
  103. coretrace_python/plugins/manifest.py +112 -0
  104. coretrace_python/plugins/registry.py +34 -0
  105. coretrace_python/plugins/secrets.py +330 -0
  106. coretrace_python/reporters/__init__.py +22 -0
  107. coretrace_python/reporters/json_format.py +48 -0
  108. coretrace_python/reporters/report.py +23 -0
  109. coretrace_python/reporters/sarif.py +70 -0
  110. coretrace_python/reporters/text.py +24 -0
  111. coretrace_python/semantic/__init__.py +9 -0
  112. coretrace_python/semantic/identity.py +39 -0
  113. coretrace_python/semantic/imports.py +131 -0
  114. coretrace_python/semantic/scopes.py +473 -0
  115. coretrace_python/semantic/symbols.py +87 -0
  116. coretrace_python/source/__init__.py +13 -0
  117. coretrace_python/source/manager.py +81 -0
  118. coretrace_python/source/model.py +57 -0
  119. coretrace_python/taint/__init__.py +55 -0
  120. coretrace_python/taint/engine.py +800 -0
  121. coretrace_python/taint/models.py +317 -0
  122. coretrace_python/taint/routes.py +99 -0
  123. coretrace_python_analyzer-0.1.0.dist-info/METADATA +74 -0
  124. coretrace_python_analyzer-0.1.0.dist-info/RECORD +126 -0
  125. coretrace_python_analyzer-0.1.0.dist-info/WHEEL +4 -0
  126. 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
+ ]