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,68 @@
1
+ """Security models for the Python standard library (architecture §16)."""
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 Model, Sanitizer, Sink, Source, TaintKind, Validator
10
+
11
+
12
+ def _sym(path: str) -> SymbolId:
13
+ return SymbolId(f"python.{path}")
14
+
15
+
16
+ class PythonStdlibModels(ModelPlugin):
17
+ name: ClassVar[str] = "python-stdlib-models"
18
+ models: ClassVar[tuple[Model, ...]] = (
19
+ Source(_sym("builtins.input"), "stdin"),
20
+ Source(_sym("sys.stdin"), "stdin"),
21
+ # A command-line tool is expected to open the paths it is given.
22
+ Source(_sym("sys.argv"), "argv", TaintKind.ALL & ~TaintKind.PATH),
23
+ Source(_sym("os.environ"), "environment"),
24
+ Source(_sym("subprocess.run.stdout"), "process-output"),
25
+ Source(_sym("subprocess.check_output"), "process-output"),
26
+ Source(_sym("subprocess.getoutput"), "process-output"),
27
+ Source(_sym("subprocess.Popen.communicate"), "process-output"),
28
+ Sink(_sym("os.system"), TaintKind.COMMAND),
29
+ Sink(_sym("os.popen"), TaintKind.COMMAND),
30
+ Sink(_sym("subprocess.run"), TaintKind.COMMAND),
31
+ Sink(_sym("subprocess.call"), TaintKind.COMMAND),
32
+ Sink(_sym("subprocess.check_call"), TaintKind.COMMAND),
33
+ Sink(_sym("subprocess.check_output"), TaintKind.COMMAND),
34
+ Sink(_sym("subprocess.Popen"), TaintKind.COMMAND),
35
+ Sink(_sym("builtins.eval"), TaintKind.CODE),
36
+ Sink(_sym("builtins.exec"), TaintKind.CODE),
37
+ Sink(_sym("builtins.open"), TaintKind.PATH),
38
+ Sink(_sym("os.remove"), TaintKind.PATH),
39
+ Sink(_sym("os.unlink"), TaintKind.PATH),
40
+ Sink(_sym("os.rmdir"), TaintKind.PATH),
41
+ Sink(_sym("shutil.rmtree"), TaintKind.PATH),
42
+ Sink(_sym("urllib.request.urlopen"), TaintKind.SSRF),
43
+ *(
44
+ Sink(
45
+ _sym(f"sqlite3.connect{cursor}.{method}"),
46
+ TaintKind.SQL | TaintKind.CREDENTIAL,
47
+ ((TaintKind.SQL, (0,)),),
48
+ )
49
+ for cursor in ("", ".cursor")
50
+ for method in ("execute", "executemany", "executescript")
51
+ ),
52
+ Sanitizer(_sym("shlex.quote"), TaintKind.COMMAND),
53
+ Sanitizer(_sym("html.escape"), TaintKind.HTML),
54
+ Sink(_sym("pickle.loads"), TaintKind.DESERIALIZATION),
55
+ Sink(_sym("pickle.load"), TaintKind.DESERIALIZATION),
56
+ Sink(_sym("pickle.Unpickler"), TaintKind.DESERIALIZATION),
57
+ Sink(_sym("marshal.loads"), TaintKind.DESERIALIZATION),
58
+ Sink(_sym("marshal.load"), TaintKind.DESERIALIZATION),
59
+ Sink(_sym("shelve.open"), TaintKind.DESERIALIZATION),
60
+ Sink(_sym("dill.loads"), TaintKind.DESERIALIZATION),
61
+ Sink(_sym("jsonpickle.decode"), TaintKind.DESERIALIZATION),
62
+ Sink(_sym("yaml.load"), TaintKind.DESERIALIZATION),
63
+ Sink(_sym("yaml.unsafe_load"), TaintKind.DESERIALIZATION),
64
+ Sink(_sym("yaml.full_load"), TaintKind.DESERIALIZATION),
65
+ Sanitizer(_sym("os.path.basename"), TaintKind.PATH),
66
+ Validator(_sym("re.fullmatch"), argument=1),
67
+ Validator(_sym("re.compile.fullmatch")),
68
+ )
@@ -0,0 +1,9 @@
1
+ name = "sqlalchemy-models"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["model.sql-sinks"]
6
+
7
+ [entrypoint]
8
+ module = "sqlalchemy_models"
9
+ class = "SqlAlchemyModels"
@@ -0,0 +1,47 @@
1
+ """SQLAlchemy security models: raw SQL execution sinks.
2
+
3
+ Symbols follow the engine's call-chain rule: the result of calling a symbol denotes
4
+ that symbol, so ``create_engine(url).connect().execute`` is
5
+ ``python.sqlalchemy.create_engine.connect.execute``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import ClassVar
11
+
12
+ from coretrace_python.plugins import ModelPlugin
13
+ from coretrace_python.semantic.symbols import SymbolId
14
+ from coretrace_python.taint import Model, Sanitizer, Sink, TaintKind
15
+
16
+ _EXECUTORS = (
17
+ "sqlalchemy.create_engine",
18
+ "sqlalchemy.create_engine.connect",
19
+ "sqlalchemy.create_engine.begin",
20
+ "sqlalchemy.engine.create_engine",
21
+ "sqlalchemy.engine.create_engine.connect",
22
+ "sqlalchemy.orm.Session",
23
+ "sqlalchemy.orm.sessionmaker",
24
+ "sqlalchemy.orm.scoped_session",
25
+ )
26
+
27
+
28
+ _TEXT_MODULES = ("sqlalchemy", "sqlalchemy.sql", "sqlalchemy.sql.expression")
29
+
30
+
31
+ def _sym(path: str) -> SymbolId:
32
+ return SymbolId(f"python.{path}")
33
+
34
+
35
+ class SqlAlchemyModels(ModelPlugin):
36
+ name: ClassVar[str] = "sqlalchemy-models"
37
+ models: ClassVar[tuple[Model, ...]] = (
38
+ *(
39
+ Sink(_sym(f"{executor}.{method}"), TaintKind.SQL | TaintKind.CREDENTIAL, ((TaintKind.SQL, (0,)),))
40
+ for executor in _EXECUTORS
41
+ for method in ("execute", "exec_driver_sql", "scalar", "scalars")
42
+ ),
43
+ # ``text()`` is where SQL text is committed: a sink, and its result carries no
44
+ # further SQL taint so the ``execute`` that follows does not report it twice.
45
+ *(Sink(_sym(f"{module}.text"), TaintKind.SQL) for module in _TEXT_MODULES),
46
+ *(Sanitizer(_sym(f"{module}.text"), TaintKind.SQL) for module in _TEXT_MODULES),
47
+ )
@@ -0,0 +1,38 @@
1
+ """Hardcoded secrets in configuration files: ``.env``, YAML, TOML, JSON, INI, properties.
2
+
3
+ Judged with the same rules as the Python literal detector, once per project.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from collections.abc import Sequence
9
+ from typing import ClassVar
10
+
11
+ from coretrace_python.findings import Finding
12
+ from coretrace_python.plugins import PluginContext, ProjectContext, ProjectPlugin
13
+ from coretrace_python.plugins.secrets import (
14
+ DEFAULT_CREDENTIAL_NAMES,
15
+ DEFAULT_PATTERNS,
16
+ SecretDetector,
17
+ SecretPattern,
18
+ config_literals,
19
+ )
20
+
21
+
22
+ class ConfigSecrets(SecretDetector, ProjectPlugin):
23
+ name: ClassVar[str] = "config-secrets"
24
+ patterns: ClassVar[tuple[SecretPattern, ...]] = DEFAULT_PATTERNS
25
+ credential_names: ClassVar[tuple[str, ...]] = DEFAULT_CREDENTIAL_NAMES
26
+
27
+ def analyze(self, ctx: PluginContext) -> Sequence[Finding]:
28
+ return ()
29
+
30
+ def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
31
+ if ctx.root is None:
32
+ return ()
33
+ findings: list[Finding] = []
34
+ for value, name, span, function in config_literals(ctx.root):
35
+ finding = self.judge(value, name, span, function)
36
+ if finding is not None:
37
+ findings.append(finding)
38
+ return findings
@@ -0,0 +1,9 @@
1
+ name = "config-secrets"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["secret.config-files"]
6
+
7
+ [entrypoint]
8
+ module = "config_secrets"
9
+ class = "ConfigSecrets"
@@ -0,0 +1,19 @@
1
+ """Hardcoded secrets in Python literals: provider formats, credential-like names,
2
+ high-entropy tokens."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import ClassVar
7
+
8
+ from coretrace_python.plugins.secrets import (
9
+ DEFAULT_CREDENTIAL_NAMES,
10
+ DEFAULT_PATTERNS,
11
+ SecretDetector,
12
+ SecretPattern,
13
+ )
14
+
15
+
16
+ class HardcodedSecrets(SecretDetector):
17
+ name: ClassVar[str] = "hardcoded-secrets"
18
+ patterns: ClassVar[tuple[SecretPattern, ...]] = DEFAULT_PATTERNS
19
+ credential_names: ClassVar[tuple[str, ...]] = DEFAULT_CREDENTIAL_NAMES
@@ -0,0 +1,9 @@
1
+ name = "hardcoded-secrets"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = []
5
+ provides = ["secret.provider-patterns", "secret.credentials", "secret.entropy"]
6
+
7
+ [entrypoint]
8
+ module = "hardcoded_secrets"
9
+ class = "HardcodedSecrets"
@@ -0,0 +1,17 @@
1
+ """Command injection: attacker-controlled input reaching a COMMAND sink."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.findings import Severity
8
+ from coretrace_python.plugins import TaintDetector
9
+ from coretrace_python.taint import TaintKind
10
+
11
+
12
+ class CommandInjectionPlugin(TaintDetector):
13
+ name: ClassVar[str] = "command-injection"
14
+ rule_id: ClassVar[str] = "command-injection"
15
+ kind: ClassVar[TaintKind] = TaintKind.COMMAND
16
+ severity: ClassVar[Severity] = Severity.HIGH
17
+ title: ClassVar[str] = "Command injection"
@@ -0,0 +1,9 @@
1
+ name = "command-injection"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["taint.flows", "findings.refutation"]
5
+ provides = ["vulnerability.command-injection"]
6
+
7
+ [entrypoint]
8
+ module = "command_injection"
9
+ class = "CommandInjectionPlugin"
@@ -0,0 +1,17 @@
1
+ """Insecure deserialization: attacker-controlled input reaching a insecure deserialization sink."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.findings import Severity
8
+ from coretrace_python.plugins import TaintDetector
9
+ from coretrace_python.taint import TaintKind
10
+
11
+
12
+ class InsecureDeserializationPlugin(TaintDetector):
13
+ name: ClassVar[str] = "insecure-deserialization"
14
+ rule_id: ClassVar[str] = "insecure-deserialization"
15
+ kind: ClassVar[TaintKind] = TaintKind.DESERIALIZATION
16
+ severity: ClassVar[Severity] = Severity.CRITICAL
17
+ title: ClassVar[str] = "Insecure deserialization"
@@ -0,0 +1,9 @@
1
+ name = "insecure-deserialization"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["taint.flows", "findings.refutation"]
5
+ provides = ["vulnerability.insecure-deserialization"]
6
+
7
+ [entrypoint]
8
+ module = "insecure_deserialization"
9
+ class = "InsecureDeserializationPlugin"
@@ -0,0 +1,17 @@
1
+ """Open redirect: attacker-controlled input reaching a open redirect sink."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.findings import Severity
8
+ from coretrace_python.plugins import TaintDetector
9
+ from coretrace_python.taint import TaintKind
10
+
11
+
12
+ class OpenRedirectPlugin(TaintDetector):
13
+ name: ClassVar[str] = "open-redirect"
14
+ rule_id: ClassVar[str] = "open-redirect"
15
+ kind: ClassVar[TaintKind] = TaintKind.REDIRECT
16
+ severity: ClassVar[Severity] = Severity.MEDIUM
17
+ title: ClassVar[str] = "Open redirect"
@@ -0,0 +1,9 @@
1
+ name = "open-redirect"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["taint.flows", "findings.refutation"]
5
+ provides = ["vulnerability.open-redirect"]
6
+
7
+ [entrypoint]
8
+ module = "open_redirect"
9
+ class = "OpenRedirectPlugin"
@@ -0,0 +1,17 @@
1
+ """Path traversal: attacker-controlled input reaching a PATH sink."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.findings import Severity
8
+ from coretrace_python.plugins import TaintDetector
9
+ from coretrace_python.taint import TaintKind
10
+
11
+
12
+ class PathTraversalPlugin(TaintDetector):
13
+ name: ClassVar[str] = "path-traversal"
14
+ rule_id: ClassVar[str] = "path-traversal"
15
+ kind: ClassVar[TaintKind] = TaintKind.PATH
16
+ severity: ClassVar[Severity] = Severity.HIGH
17
+ title: ClassVar[str] = "Path traversal"
@@ -0,0 +1,9 @@
1
+ name = "path-traversal"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["taint.flows", "findings.refutation"]
5
+ provides = ["vulnerability.path-traversal"]
6
+
7
+ [entrypoint]
8
+ module = "path_traversal"
9
+ class = "PathTraversalPlugin"
@@ -0,0 +1,21 @@
1
+ """Plaintext credentials in database queries: a password-named parameter reaching a
2
+ database statement without passing through a hashing function, whether the statement
3
+ stores it or compares it, since comparing in clear implies storing in clear. A name is
4
+ a hint, so medium confidence."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import ClassVar
9
+
10
+ from coretrace_python.findings import Confidence, Severity
11
+ from coretrace_python.plugins import TaintDetector
12
+ from coretrace_python.taint import TaintKind
13
+
14
+
15
+ class PlaintextCredentialsPlugin(TaintDetector):
16
+ name: ClassVar[str] = "plaintext-credentials"
17
+ rule_id: ClassVar[str] = "plaintext-credential-storage"
18
+ kind: ClassVar[TaintKind] = TaintKind.CREDENTIAL
19
+ severity: ClassVar[Severity] = Severity.HIGH
20
+ confidence: ClassVar[Confidence] = Confidence.MEDIUM
21
+ title: ClassVar[str] = "Plaintext credential in a database query"
@@ -0,0 +1,9 @@
1
+ name = "plaintext-credentials"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["taint.flows", "findings.refutation"]
5
+ provides = ["vulnerability.plaintext-credential-storage"]
6
+
7
+ [entrypoint]
8
+ module = "plaintext_credentials"
9
+ class = "PlaintextCredentialsPlugin"
@@ -0,0 +1,9 @@
1
+ name = "sql-injection"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["taint.flows", "findings.refutation"]
5
+ provides = ["vulnerability.sql-injection"]
6
+
7
+ [entrypoint]
8
+ module = "sql_injection"
9
+ class = "SqlInjectionPlugin"
@@ -0,0 +1,17 @@
1
+ """SQL injection: attacker-controlled input reaching a SQL sink."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.findings import Severity
8
+ from coretrace_python.plugins import TaintDetector
9
+ from coretrace_python.taint import TaintKind
10
+
11
+
12
+ class SqlInjectionPlugin(TaintDetector):
13
+ name: ClassVar[str] = "sql-injection"
14
+ rule_id: ClassVar[str] = "sql-injection"
15
+ kind: ClassVar[TaintKind] = TaintKind.SQL
16
+ severity: ClassVar[Severity] = Severity.HIGH
17
+ title: ClassVar[str] = "SQL injection"
@@ -0,0 +1,9 @@
1
+ name = "ssrf"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["taint.flows", "findings.refutation"]
5
+ provides = ["vulnerability.ssrf"]
6
+
7
+ [entrypoint]
8
+ module = "ssrf"
9
+ class = "SsrfPlugin"
@@ -0,0 +1,17 @@
1
+ """Server-side request forgery: attacker-controlled input reaching a SSRF sink."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.findings import Severity
8
+ from coretrace_python.plugins import TaintDetector
9
+ from coretrace_python.taint import TaintKind
10
+
11
+
12
+ class SsrfPlugin(TaintDetector):
13
+ name: ClassVar[str] = "ssrf"
14
+ rule_id: ClassVar[str] = "ssrf"
15
+ kind: ClassVar[TaintKind] = TaintKind.SSRF
16
+ severity: ClassVar[Severity] = Severity.HIGH
17
+ title: ClassVar[str] = "Server-side request forgery"
@@ -0,0 +1,9 @@
1
+ name = "xss"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["taint.flows", "findings.refutation"]
5
+ provides = ["vulnerability.xss"]
6
+
7
+ [entrypoint]
8
+ module = "xss"
9
+ class = "XssPlugin"
@@ -0,0 +1,17 @@
1
+ """Cross-site scripting: attacker-controlled input reaching a HTML sink."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.findings import Severity
8
+ from coretrace_python.plugins import TaintDetector
9
+ from coretrace_python.taint import TaintKind
10
+
11
+
12
+ class XssPlugin(TaintDetector):
13
+ name: ClassVar[str] = "xss"
14
+ rule_id: ClassVar[str] = "xss"
15
+ kind: ClassVar[TaintKind] = TaintKind.HTML
16
+ severity: ClassVar[Severity] = Severity.HIGH
17
+ title: ClassVar[str] = "Cross-site scripting"
@@ -0,0 +1,19 @@
1
+ """Report calls to the dynamic-code builtins, whatever name the file gives them."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.findings import Severity
8
+ from coretrace_python.plugins import SymbolCallDetector
9
+ from coretrace_python.semantic.symbols import SymbolId
10
+
11
+
12
+ class DangerousEvalPlugin(SymbolCallDetector):
13
+ name: ClassVar[str] = "dangerous-eval"
14
+ rule_id: ClassVar[str] = "dangerous-eval"
15
+ symbols: ClassVar[frozenset[SymbolId]] = frozenset(
16
+ {SymbolId("python.builtins.eval"), SymbolId("python.builtins.exec")}
17
+ )
18
+ severity: ClassVar[Severity] = Severity.HIGH
19
+ message_template: ClassVar[str] = "call to {symbol} executes dynamically built code"
@@ -0,0 +1,9 @@
1
+ name = "dangerous-eval"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["ir.ssa"]
5
+ provides = ["vulnerability.dangerous-eval"]
6
+
7
+ [entrypoint]
8
+ module = "dangerous_eval"
9
+ class = "DangerousEvalPlugin"
@@ -0,0 +1,63 @@
1
+ """``app.run(debug=True)``: the Werkzeug debugger executes code on any exception."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator, Sequence
6
+ from typing import ClassVar
7
+
8
+ from coretrace_python.analysis import AnyAnalysis
9
+ from coretrace_python.findings import Confidence, Finding, Severity
10
+ from coretrace_python.hir import nodes
11
+ from coretrace_python.hir.visitors import Node, children
12
+ from coretrace_python.plugins import Plugin, PluginContext
13
+ from coretrace_python.semantic.scopes import ScopeAnalysis, ScopeId, ScopeTable
14
+ from coretrace_python.semantic.symbols import SymbolAnalysis, SymbolId
15
+
16
+ RUN = SymbolId("python.flask.Flask.run")
17
+
18
+
19
+ class FlaskDebugPlugin(Plugin):
20
+ name: ClassVar[str] = "flask-debug"
21
+ requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({ScopeAnalysis, SymbolAnalysis})
22
+
23
+ def analyze(self, ctx: PluginContext) -> Sequence[Finding]:
24
+ scopes = ctx.get(ScopeAnalysis)
25
+ symbols = ctx.get(SymbolAnalysis)
26
+ findings: list[Finding] = []
27
+ for call, scope, function in _calls(ctx.module.body, scopes, scopes.module_scope.id, None):
28
+ if symbols.resolve_expression(scope, call.callee) != RUN:
29
+ continue
30
+ if not any(
31
+ k.name == "debug" and isinstance(k.value, nodes.Constant) and k.value.value is True
32
+ for k in call.keywords
33
+ ):
34
+ continue
35
+ findings.append(
36
+ Finding(
37
+ "debug-enabled",
38
+ "Flask debugger enabled: app.run(debug=True) exposes an interactive "
39
+ "debugger able to execute Python on any exception",
40
+ Severity.HIGH,
41
+ Confidence.HIGH,
42
+ call.span,
43
+ function,
44
+ {"symbol": str(RUN)},
45
+ )
46
+ )
47
+ return findings
48
+
49
+
50
+ def _calls(
51
+ body: Sequence[Node], scopes: ScopeTable, scope: ScopeId, function: str | None
52
+ ) -> Iterator[tuple[nodes.Call, ScopeId, str | None]]:
53
+ for node in body:
54
+ if isinstance(node, nodes.Function):
55
+ yield from _calls(node.body, scopes, scopes.scope_for(node).id, node.name)
56
+ elif isinstance(node, nodes.Class):
57
+ yield from _calls(node.body, scopes, scopes.scope_for(node).id, None)
58
+ elif isinstance(node, nodes.Lambda | nodes.Comprehension):
59
+ continue
60
+ else:
61
+ if isinstance(node, nodes.Call):
62
+ yield node, scope, function
63
+ yield from _calls(list(children(node)), scopes, scope, function)
@@ -0,0 +1,9 @@
1
+ name = "flask-debug"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["semantic.scopes", "semantic.symbols"]
5
+ provides = ["syntax.flask-debug"]
6
+
7
+ [entrypoint]
8
+ module = "flask_debug"
9
+ class = "FlaskDebugPlugin"
@@ -0,0 +1,51 @@
1
+ """HTTP client calls without a ``timeout``: a slow or silent peer blocks the caller."""
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.findings import Confidence, Finding, Severity
10
+ from coretrace_python.interprocedural import CallGraphAnalysis
11
+ from coretrace_python.ir.model import Call
12
+ from coretrace_python.ir.ssa import SSAAnalysis
13
+ from coretrace_python.plugins import Plugin, PluginContext
14
+ from coretrace_python.semantic.symbols import SymbolId
15
+
16
+ _METHODS = ("get", "post", "put", "patch", "delete", "head", "options", "request")
17
+ _CALLERS = ("requests", "requests.Session", "requests.api", "httpx", "httpx.Client", "httpx.AsyncClient")
18
+ HTTP_CALLS = frozenset(SymbolId(f"python.{caller}.{method}") for caller in _CALLERS for method in _METHODS)
19
+
20
+
21
+ class MissingTimeoutPlugin(Plugin):
22
+ name: ClassVar[str] = "missing-timeout"
23
+ requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({SSAAnalysis, CallGraphAnalysis})
24
+
25
+ def analyze(self, ctx: PluginContext) -> Sequence[Finding]:
26
+ graph = ctx.get(CallGraphAnalysis)
27
+ findings: list[Finding] = []
28
+ for function in ctx.functions():
29
+ symbols = graph.symbols(graph.name_of(function))
30
+ for block in ctx.get(SSAAnalysis, function).blocks:
31
+ for instruction in block.instructions:
32
+ if not isinstance(instruction, Call):
33
+ continue
34
+ symbol = symbols.get(instruction.callee)
35
+ if symbol is None or symbol not in HTTP_CALLS:
36
+ continue
37
+ if any(name == "timeout" for name, _ in instruction.keywords):
38
+ continue
39
+ findings.append(
40
+ Finding(
41
+ "missing-timeout",
42
+ f"{symbol} is called without a timeout; a slow or silent peer "
43
+ "blocks this code indefinitely",
44
+ Severity.LOW,
45
+ Confidence.HIGH,
46
+ instruction.location,
47
+ function.name,
48
+ {"symbol": str(symbol)},
49
+ )
50
+ )
51
+ return findings
@@ -0,0 +1,9 @@
1
+ name = "missing-timeout"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["ir.ssa", "interprocedural.callgraph"]
5
+ provides = ["syntax.missing-timeout"]
6
+
7
+ [entrypoint]
8
+ module = "missing_timeout"
9
+ class = "MissingTimeoutPlugin"
@@ -0,0 +1,9 @@
1
+ name = "weak-crypto"
2
+ version = "1.0.0"
3
+ plugin_api = ">=1,<2"
4
+ requires = ["ir.ssa"]
5
+ provides = ["vulnerability.weak-crypto"]
6
+
7
+ [entrypoint]
8
+ module = "weak_crypto"
9
+ class = "WeakCryptoPlugin"
@@ -0,0 +1,19 @@
1
+ """Report uses of broken hash algorithms."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from coretrace_python.findings import Severity
8
+ from coretrace_python.plugins import SymbolCallDetector
9
+ from coretrace_python.semantic.symbols import SymbolId
10
+
11
+
12
+ class WeakCryptoPlugin(SymbolCallDetector):
13
+ name: ClassVar[str] = "weak-crypto"
14
+ rule_id: ClassVar[str] = "weak-crypto"
15
+ symbols: ClassVar[frozenset[SymbolId]] = frozenset(
16
+ {SymbolId("python.hashlib.md5"), SymbolId("python.hashlib.sha1")}
17
+ )
18
+ severity: ClassVar[Severity] = Severity.MEDIUM
19
+ message_template: ClassVar[str] = "call to {symbol} uses a broken hash algorithm"