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,56 @@
1
+ """Calls, anywhere in the project, to an API affected by a vulnerable requirement."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import ClassVar
7
+
8
+ from coretrace_python.analysis import AnyAnalysis
9
+ from coretrace_python.dependency import DependencyAnalysis
10
+ from coretrace_python.findings import Confidence, Finding
11
+ from coretrace_python.interprocedural import CallGraphAnalysis, ExternalSymbol
12
+ from coretrace_python.plugins import ProjectContext, ProjectPlugin
13
+
14
+
15
+ class ReachableVulnerabilityPlugin(ProjectPlugin):
16
+ name: ClassVar[str] = "reachable-vulnerability"
17
+ requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({DependencyAnalysis, CallGraphAnalysis})
18
+
19
+ def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
20
+ affected = {}
21
+ for requirement in ctx.dependencies.requirements:
22
+ for advisory in ctx.advisories:
23
+ if advisory.affects(requirement):
24
+ for symbol in advisory.affected_symbols:
25
+ affected.setdefault(symbol, advisory)
26
+ if not affected:
27
+ return ()
28
+ findings: list[Finding] = []
29
+ for module in ctx.modules:
30
+ graph = ctx.call_graph(module)
31
+ for function in graph.functions:
32
+ for site in graph.sites(function):
33
+ if not isinstance(site.target, ExternalSymbol):
34
+ continue
35
+ advisory = affected.get(site.target.symbol)
36
+ if advisory is None:
37
+ continue
38
+ findings.append(
39
+ Finding(
40
+ rule_id="reachable-vulnerability",
41
+ message=(
42
+ f"{advisory.id}: {site.target.symbol} is affected in the required "
43
+ f"{advisory.package} {advisory.vulnerable}: {advisory.summary}"
44
+ ),
45
+ severity=advisory.severity,
46
+ confidence=Confidence.HIGH,
47
+ span=site.location,
48
+ function=function,
49
+ metadata={
50
+ "advisory": advisory.id,
51
+ "package": advisory.package,
52
+ "symbol": str(site.target.symbol),
53
+ },
54
+ )
55
+ )
56
+ return findings
@@ -0,0 +1,9 @@
1
+ name = "sample-advisories"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["advisories.sample"]
6
+
7
+ [entrypoint]
8
+ module = "sample_advisories"
9
+ class = "SampleAdvisories"
@@ -0,0 +1,109 @@
1
+ """A small, offline sample of published advisories (architecture §26).
2
+
3
+ This is a demonstration database: a handful of well-known CVEs with the APIs they
4
+ affect. A live OSV or GHSA feed belongs to a dedicated plugin.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import ClassVar
10
+
11
+ from coretrace_python.dependency import Advisory
12
+ from coretrace_python.findings import Severity
13
+ from coretrace_python.plugins import ModelPlugin
14
+ from coretrace_python.semantic.symbols import SymbolId
15
+
16
+
17
+ def _sym(path: str) -> SymbolId:
18
+ return SymbolId(f"python.{path}")
19
+
20
+
21
+ class SampleAdvisories(ModelPlugin):
22
+ name: ClassVar[str] = "sample-advisories"
23
+ advisories: ClassVar[tuple[Advisory, ...]] = (
24
+ Advisory(
25
+ "CVE-2020-1747",
26
+ "pyyaml",
27
+ "<5.4",
28
+ "yaml.load and full_load can execute arbitrary code from untrusted documents",
29
+ Severity.CRITICAL,
30
+ (_sym("yaml.load"), _sym("yaml.full_load"), _sym("yaml.unsafe_load")),
31
+ ),
32
+ Advisory(
33
+ "CVE-2018-18074",
34
+ "requests",
35
+ "<2.20.0",
36
+ "Authorization header is leaked on redirects to another host",
37
+ Severity.HIGH,
38
+ (_sym("requests.get"), _sym("requests.post"), _sym("requests.request"), _sym("requests.Session")),
39
+ ),
40
+ Advisory(
41
+ "CVE-2020-28493",
42
+ "jinja2",
43
+ "<2.11.3",
44
+ "regular expression denial of service in the urlize filter",
45
+ Severity.MEDIUM,
46
+ (_sym("jinja2.Environment"), _sym("jinja2.Template")),
47
+ ),
48
+ Advisory(
49
+ "CVE-2021-33503",
50
+ "urllib3",
51
+ "<1.26.5",
52
+ "regular expression denial of service when parsing authority in URLs",
53
+ Severity.HIGH,
54
+ (_sym("urllib3.PoolManager"), _sym("urllib3.request")),
55
+ ),
56
+ Advisory(
57
+ "CVE-2023-30861",
58
+ "flask",
59
+ "<2.2.5",
60
+ "session cookie may be leaked through caching proxies",
61
+ Severity.HIGH,
62
+ (_sym("flask.Flask"),),
63
+ ),
64
+ Advisory(
65
+ "CVE-2024-1135",
66
+ "gunicorn",
67
+ "<22.0.0",
68
+ "HTTP request smuggling through improper Transfer-Encoding validation",
69
+ Severity.HIGH,
70
+ (),
71
+ ("GHSA-w3h3-4rj7-4ph4",),
72
+ ),
73
+ Advisory(
74
+ "CVE-2023-4863",
75
+ "opencv-python",
76
+ "<4.8.1.78",
77
+ "heap buffer overflow in the bundled libwebp when decoding a crafted WebP image",
78
+ Severity.CRITICAL,
79
+ (_sym("cv2.imread"), _sym("cv2.imdecode"), _sym("cv2.imreadmulti")),
80
+ ("GHSA-qr4w-53vh-m672",),
81
+ ),
82
+ Advisory(
83
+ "CVE-2024-34069",
84
+ "werkzeug",
85
+ "<3.0.3",
86
+ "the debugger can be tricked into executing code through a crafted domain",
87
+ Severity.HIGH,
88
+ (_sym("werkzeug.debug.DebuggedApplication"),),
89
+ ("GHSA-2g68-c3qc-8985",),
90
+ ),
91
+ Advisory(
92
+ "CVE-2023-46136",
93
+ "werkzeug",
94
+ "<3.0.1",
95
+ "a crafted multipart body exhausts CPU and memory in the form parser",
96
+ Severity.HIGH,
97
+ (_sym("werkzeug.formparser.MultiPartParser"),),
98
+ ("GHSA-hrfv-mqp8-q5rw",),
99
+ ),
100
+ Advisory(
101
+ "CVE-2026-25990",
102
+ "pillow",
103
+ "<=11.2.1",
104
+ "out-of-bounds write when loading a crafted PSD image",
105
+ Severity.HIGH,
106
+ (_sym("PIL.Image.open"),),
107
+ ("GHSA-cfh3-3jmp-rvhc",),
108
+ ),
109
+ )
@@ -0,0 +1,9 @@
1
+ name = "vulnerable-dependency"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["dependency.graph"]
5
+ provides = ["vulnerability.vulnerable-dependency"]
6
+
7
+ [entrypoint]
8
+ module = "vulnerable_dependency"
9
+ class = "VulnerableDependencyPlugin"
@@ -0,0 +1,43 @@
1
+ """Requirements that allow a version an advisory marks as vulnerable."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import ClassVar
7
+
8
+ from coretrace_python.analysis import AnyAnalysis
9
+ from coretrace_python.dependency import DependencyAnalysis
10
+ from coretrace_python.findings import Confidence, Finding
11
+ from coretrace_python.plugins import ProjectContext, ProjectPlugin
12
+
13
+
14
+ class VulnerableDependencyPlugin(ProjectPlugin):
15
+ name: ClassVar[str] = "vulnerable-dependency"
16
+ requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({DependencyAnalysis})
17
+
18
+ def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
19
+ findings: list[Finding] = []
20
+ for requirement in ctx.dependencies.requirements:
21
+ for advisory in ctx.advisories:
22
+ if not advisory.affects(requirement):
23
+ continue
24
+ pinned = requirement.pinned is not None
25
+ findings.append(
26
+ Finding(
27
+ rule_id="vulnerable-dependency",
28
+ message=(
29
+ f"{advisory.id}: {advisory.package} {advisory.vulnerable} is "
30
+ f"{'required' if pinned else 'allowed'} by {requirement.name}"
31
+ f"{requirement.specifier or ''}: {advisory.summary}"
32
+ ),
33
+ severity=advisory.severity,
34
+ confidence=Confidence.HIGH if pinned else Confidence.MEDIUM,
35
+ span=requirement.span,
36
+ metadata={
37
+ "advisory": advisory.id,
38
+ "package": advisory.package,
39
+ "specifier": requirement.specifier,
40
+ },
41
+ )
42
+ )
43
+ return findings
@@ -0,0 +1,35 @@
1
+ """Command-line entry points: the parameters of click and Typer commands are argv input."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.plugins import ModelPlugin
8
+ from coretrace_python.semantic.symbols import SymbolId
9
+ from coretrace_python.taint import EntryPoint, Model, TaintKind
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
14
+
15
+ _COMMANDS = (
16
+ "click.command",
17
+ "click.group",
18
+ "click.command.command",
19
+ "click.group.command",
20
+ "click.group.group",
21
+ "click.Group.command",
22
+ "typer.Typer.command",
23
+ "typer.Typer.callback",
24
+ )
25
+
26
+
27
+ def _sym(path: str) -> SymbolId:
28
+ return SymbolId(f"python.{path}")
29
+
30
+
31
+ class CliModels(ModelPlugin):
32
+ name: ClassVar[str] = "cli-models"
33
+ models: ClassVar[tuple[Model, ...]] = tuple(
34
+ EntryPoint(_sym(command), "argv", ARGV_KINDS) for command in _COMMANDS
35
+ )
@@ -0,0 +1,9 @@
1
+ name = "cli-models"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["model.cli-sources"]
6
+
7
+ [entrypoint]
8
+ module = "cli_models"
9
+ class = "CliModels"
@@ -0,0 +1,43 @@
1
+ """Credential models: password-named parameters are credentials, password hashing clears
2
+ them, and the database write sinks of the other model plugins also carry the
3
+ ``CREDENTIAL`` kind, so a credential stored unhashed is a flow."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import ClassVar
8
+
9
+ from coretrace_python.plugins import ModelPlugin
10
+ from coretrace_python.semantic.symbols import SymbolId
11
+ from coretrace_python.taint import Model, NamedParameter, Sanitizer, TaintKind
12
+
13
+ _HASHERS = (
14
+ "hashlib.pbkdf2_hmac",
15
+ "hashlib.scrypt",
16
+ "hashlib.sha256",
17
+ "hashlib.sha512",
18
+ "hashlib.sha3_256",
19
+ "hashlib.blake2b",
20
+ "hashlib.new",
21
+ "bcrypt.hashpw",
22
+ "bcrypt.kdf",
23
+ "argon2.PasswordHasher.hash",
24
+ "argon2.low_level.hash_secret",
25
+ "werkzeug.security.generate_password_hash",
26
+ "passlib.hash.pbkdf2_sha256.hash",
27
+ "passlib.hash.bcrypt.hash",
28
+ "passlib.hash.argon2.hash",
29
+ "passlib.context.CryptContext.hash",
30
+ "django.contrib.auth.hashers.make_password",
31
+ )
32
+
33
+
34
+ def _sym(path: str) -> SymbolId:
35
+ return SymbolId(f"python.{path}")
36
+
37
+
38
+ class CredentialModels(ModelPlugin):
39
+ name: ClassVar[str] = "credential-models"
40
+ models: ClassVar[tuple[Model, ...]] = (
41
+ NamedParameter(r"(?i)^(password|passwd|pwd|new_password|plain_password|secret)$", "credential", TaintKind.CREDENTIAL),
42
+ *(Sanitizer(_sym(hasher), TaintKind.CREDENTIAL) for hasher in _HASHERS),
43
+ )
@@ -0,0 +1,9 @@
1
+ name = "credential-models"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["model.credential-sources", "model.credential-sinks"]
6
+
7
+ [entrypoint]
8
+ module = "credential_models"
9
+ class = "CredentialModels"
@@ -0,0 +1,123 @@
1
+ """Django security models: typed requests, views, decorators, ORM and HTML sinks.
2
+
3
+ Django views are undecorated, so HTTP input comes from three generic mechanisms: a
4
+ parameter annotated with a request class, a method of a class-based view, or a view
5
+ decorated by one of Django's or Django REST framework's view decorators. A view with a
6
+ bare ``request`` parameter and none of these is not recognised; URL configurations are
7
+ not read.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import ClassVar
13
+
14
+ from coretrace_python.plugins import ModelPlugin
15
+ from coretrace_python.semantic.symbols import SymbolId
16
+ from coretrace_python.taint import (
17
+ AuthorizationGuard,
18
+ EntryPoint,
19
+ Model,
20
+ RouteRegistrar,
21
+ Sanitizer,
22
+ Sink,
23
+ SuffixSink,
24
+ TaintKind,
25
+ TypedParameter,
26
+ )
27
+
28
+ _REQUEST_CLASSES = (
29
+ "django.http.HttpRequest",
30
+ "django.http.request.HttpRequest",
31
+ "django.core.handlers.wsgi.WSGIRequest",
32
+ "django.core.handlers.asgi.ASGIRequest",
33
+ "rest_framework.request.Request",
34
+ )
35
+
36
+ _VIEW_BASES = (
37
+ "django.views.View",
38
+ "django.views.generic.View",
39
+ "django.views.generic.base.View",
40
+ "django.views.generic.TemplateView",
41
+ "django.views.generic.RedirectView",
42
+ "django.views.generic.ListView",
43
+ "django.views.generic.DetailView",
44
+ "django.views.generic.FormView",
45
+ "django.views.generic.CreateView",
46
+ "django.views.generic.UpdateView",
47
+ "django.views.generic.DeleteView",
48
+ "rest_framework.views.APIView",
49
+ "rest_framework.generics.GenericAPIView",
50
+ "rest_framework.viewsets.ViewSet",
51
+ "rest_framework.viewsets.GenericViewSet",
52
+ "rest_framework.viewsets.ModelViewSet",
53
+ "rest_framework.viewsets.ReadOnlyModelViewSet",
54
+ )
55
+
56
+ _VIEW_DECORATORS = (
57
+ "django.views.decorators.csrf.csrf_exempt",
58
+ "django.views.decorators.csrf.csrf_protect",
59
+ "django.views.decorators.http.require_http_methods",
60
+ "django.views.decorators.http.require_GET",
61
+ "django.views.decorators.http.require_POST",
62
+ "django.views.decorators.http.require_safe",
63
+ "django.views.decorators.cache.never_cache",
64
+ "django.views.decorators.cache.cache_page",
65
+ "django.contrib.auth.decorators.login_required",
66
+ "django.contrib.auth.decorators.permission_required",
67
+ "django.contrib.auth.decorators.user_passes_test",
68
+ "rest_framework.decorators.api_view",
69
+ "rest_framework.decorators.action",
70
+ )
71
+
72
+ _ROUTE_REGISTRARS = (
73
+ "django.urls.path",
74
+ "django.urls.re_path",
75
+ "django.conf.urls.url",
76
+ "rest_framework.routers.DefaultRouter.register",
77
+ "rest_framework.routers.SimpleRouter.register",
78
+ )
79
+
80
+ _AUTHORIZATION_DECORATORS = (
81
+ ("django.contrib.auth.decorators.login_required", "login"),
82
+ ("django.contrib.auth.decorators.permission_required", "permission"),
83
+ ("django.contrib.auth.decorators.user_passes_test", "test"),
84
+ ("django.contrib.admin.views.decorators.staff_member_required", "staff"),
85
+ ("rest_framework.decorators.permission_classes", "permission"),
86
+ )
87
+
88
+
89
+ def _sym(path: str) -> SymbolId:
90
+ return SymbolId(f"python.{path}")
91
+
92
+
93
+ class DjangoModels(ModelPlugin):
94
+ name: ClassVar[str] = "django-models"
95
+ models: ClassVar[tuple[Model, ...]] = (
96
+ *(TypedParameter(_sym(cls), "http") for cls in _REQUEST_CLASSES),
97
+ *(EntryPoint(_sym(base), "http") for base in _VIEW_BASES),
98
+ *(EntryPoint(_sym(decorator), "http") for decorator in _VIEW_DECORATORS),
99
+ Sink(_sym("django.db.connection.cursor.execute"), TaintKind.SQL | TaintKind.CREDENTIAL, ((TaintKind.SQL, (0,)),)),
100
+ Sink(_sym("django.db.connection.cursor.executemany"), TaintKind.SQL | TaintKind.CREDENTIAL, ((TaintKind.SQL, (0,)),)),
101
+ Sink(_sym("django.db.models.expressions.RawSQL"), TaintKind.SQL | TaintKind.CREDENTIAL),
102
+ Sink(_sym("django.db.models.Manager.raw"), TaintKind.SQL | TaintKind.CREDENTIAL),
103
+ Sink(_sym("django.db.models.QuerySet.raw"), TaintKind.SQL | TaintKind.CREDENTIAL),
104
+ Sink(_sym("django.db.models.QuerySet.extra"), TaintKind.SQL | TaintKind.CREDENTIAL),
105
+ Sink(_sym("django.utils.safestring.mark_safe"), TaintKind.HTML),
106
+ Sink(_sym("django.http.HttpResponse"), TaintKind.HTML),
107
+ Sink(_sym("django.http.response.HttpResponse"), TaintKind.HTML),
108
+ Sink(_sym("django.template.Template"), TaintKind.HTML),
109
+ Sink(_sym("django.http.FileResponse"), TaintKind.PATH),
110
+ Sink(_sym("django.shortcuts.redirect"), TaintKind.REDIRECT),
111
+ Sink(_sym("django.http.HttpResponseRedirect"), TaintKind.REDIRECT),
112
+ Sink(_sym("django.http.HttpResponsePermanentRedirect"), TaintKind.REDIRECT),
113
+ Sanitizer(_sym("django.utils.html.escape"), TaintKind.HTML),
114
+ Sanitizer(_sym("django.utils.html.conditional_escape"), TaintKind.HTML),
115
+ *(AuthorizationGuard(_sym(decorator), label) for decorator, label in _AUTHORIZATION_DECORATORS),
116
+ # ``urlpatterns = [path('login/', views.log_in)]``: the referenced view is an
117
+ # entry point wherever it is defined; routers register viewsets.
118
+ *(RouteRegistrar(_sym(registrar), 1, "http") for registrar in _ROUTE_REGISTRARS),
119
+ # ``Model.objects.raw(sql)`` for any model class: the statement is the first
120
+ # argument, its parameters are not.
121
+ SuffixSink("objects.raw", TaintKind.SQL, ((TaintKind.SQL, (0,)),)),
122
+ SuffixSink("objects.extra", TaintKind.SQL),
123
+ )
@@ -0,0 +1,9 @@
1
+ name = "django-models"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["model.http-sources", "model.django-views", "model.sql-sinks"]
6
+
7
+ [entrypoint]
8
+ module = "django_models"
9
+ class = "DjangoModels"
@@ -0,0 +1,29 @@
1
+ """FastAPI security models: route handlers and response sinks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.plugins import ModelPlugin
8
+ from coretrace_python.semantic.symbols import SymbolId
9
+ from coretrace_python.taint import EntryPoint, Model, RouteRegistrar, Sink, TaintKind
10
+
11
+ _METHODS = ("get", "post", "put", "delete", "patch", "options", "head", "api_route", "websocket")
12
+
13
+
14
+ def _sym(path: str) -> SymbolId:
15
+ return SymbolId(f"python.{path}")
16
+
17
+
18
+ class FastApiModels(ModelPlugin):
19
+ name: ClassVar[str] = "fastapi-models"
20
+ models: ClassVar[tuple[Model, ...]] = (
21
+ *(EntryPoint(_sym(f"fastapi.FastAPI.{method}"), "http") for method in _METHODS),
22
+ *(EntryPoint(_sym(f"fastapi.APIRouter.{method}"), "http") for method in _METHODS),
23
+ RouteRegistrar(_sym("fastapi.FastAPI.add_api_route"), 1, "http"),
24
+ RouteRegistrar(_sym("fastapi.APIRouter.add_api_route"), 1, "http"),
25
+ Sink(_sym("fastapi.responses.HTMLResponse"), TaintKind.HTML),
26
+ Sink(_sym("fastapi.responses.FileResponse"), TaintKind.PATH),
27
+ Sink(_sym("fastapi.responses.RedirectResponse"), TaintKind.REDIRECT),
28
+ Sink(_sym("starlette.responses.RedirectResponse"), TaintKind.REDIRECT),
29
+ )
@@ -0,0 +1,9 @@
1
+ name = "fastapi-models"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["model.http-sources", "model.fastapi-routes"]
6
+
7
+ [entrypoint]
8
+ module = "fastapi_models"
9
+ class = "FastApiModels"
@@ -0,0 +1,53 @@
1
+ """Flask security models: HTTP sources, route handlers, HTML sinks and escaping."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.plugins import ModelPlugin
8
+ from coretrace_python.semantic.symbols import SymbolId
9
+ from coretrace_python.taint import (
10
+ AuthorizationGuard,
11
+ EntryPoint,
12
+ Model,
13
+ RouteRegistrar,
14
+ Sanitizer,
15
+ Sink,
16
+ Source,
17
+ TaintKind,
18
+ )
19
+
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
+ )
24
+
25
+
26
+ def _sym(path: str) -> SymbolId:
27
+ return SymbolId(f"python.{path}")
28
+
29
+
30
+ class FlaskModels(ModelPlugin):
31
+ name: ClassVar[str] = "flask-models"
32
+ models: ClassVar[tuple[Model, ...]] = (
33
+ *(Source(_sym(f"flask.request.{attribute}"), "http") for attribute in _REQUEST_ATTRIBUTES),
34
+ EntryPoint(_sym("flask.Flask.route"), "http"),
35
+ EntryPoint(_sym("flask.Blueprint.route"), "http"),
36
+ RouteRegistrar(_sym("flask.Flask.add_url_rule"), 2, "http", keyword="view_func"),
37
+ RouteRegistrar(_sym("flask.Blueprint.add_url_rule"), 2, "http", keyword="view_func"),
38
+ Sink(_sym("flask.render_template_string"), TaintKind.HTML),
39
+ Sink(_sym("flask.make_response"), TaintKind.HTML),
40
+ Sink(_sym("flask.Response"), TaintKind.HTML),
41
+ Sink(_sym("flask.Markup"), TaintKind.HTML),
42
+ Sink(_sym("flask.send_file"), TaintKind.PATH),
43
+ Sink(_sym("flask.redirect"), TaintKind.REDIRECT),
44
+ Sink(_sym("werkzeug.utils.redirect"), TaintKind.REDIRECT),
45
+ Sink(_sym("flask.request.files.save"), TaintKind.PATH),
46
+ Sanitizer(_sym("werkzeug.utils.secure_filename"), TaintKind.PATH),
47
+ Sanitizer(_sym("flask.escape"), TaintKind.HTML),
48
+ Sanitizer(_sym("markupsafe.escape"), TaintKind.HTML),
49
+ AuthorizationGuard(_sym("flask_login.login_required"), "login"),
50
+ AuthorizationGuard(_sym("flask_login.fresh_login_required"), "login"),
51
+ AuthorizationGuard(_sym("flask_login.current_user.is_authenticated"), "login"),
52
+ AuthorizationGuard(_sym("flask.session"), "session"),
53
+ )
@@ -0,0 +1,9 @@
1
+ name = "flask-models"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["model.http-sources", "model.flask-routes"]
6
+
7
+ [entrypoint]
8
+ module = "flask_models"
9
+ class = "FlaskModels"
@@ -0,0 +1,43 @@
1
+ """Requests and httpx models: every request function is a SSRF sink, and what it returns
2
+ is an ``http-response`` source, so data fetched from a remote server is untrusted."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import ClassVar
7
+
8
+ from coretrace_python.plugins import ModelPlugin
9
+ from coretrace_python.semantic.symbols import SymbolId
10
+ from coretrace_python.taint import Model, Sink, Source, TaintKind
11
+
12
+ _METHODS = ("get", "post", "put", "patch", "delete", "head", "options", "request")
13
+
14
+ _CALLERS = (
15
+ "requests",
16
+ "requests.Session",
17
+ "requests.api",
18
+ "httpx",
19
+ "httpx.Client",
20
+ "httpx.AsyncClient",
21
+ )
22
+
23
+ _REQUEST_FUNCTIONS = (
24
+ *(f"{caller}.{method}" for caller in _CALLERS for method in _METHODS),
25
+ "requests.Session.send",
26
+ "httpx.stream",
27
+ "httpx.Client.stream",
28
+ "httpx.AsyncClient.stream",
29
+ "httpx.Client.send",
30
+ "httpx.AsyncClient.send",
31
+ )
32
+
33
+
34
+ def _sym(path: str) -> SymbolId:
35
+ return SymbolId(f"python.{path}")
36
+
37
+
38
+ class HttpClientModels(ModelPlugin):
39
+ name: ClassVar[str] = "http-client-models"
40
+ models: ClassVar[tuple[Model, ...]] = (
41
+ *(Sink(_sym(function), TaintKind.SSRF) for function in _REQUEST_FUNCTIONS),
42
+ *(Source(_sym(function), "http-response") for function in _REQUEST_FUNCTIONS),
43
+ )
@@ -0,0 +1,9 @@
1
+ name = "http-client-models"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["model.ssrf-sinks", "model.http-response-sources"]
6
+
7
+ [entrypoint]
8
+ module = "http_client_models"
9
+ class = "HttpClientModels"
@@ -0,0 +1,9 @@
1
+ name = "python-stdlib-models"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["model.python-stdlib"]
6
+
7
+ [entrypoint]
8
+ module = "python_stdlib"
9
+ class = "PythonStdlibModels"