coretrace-python-analyzer 0.23.0__py3-none-any.whl → 0.25.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coretrace_python/__init__.py +1 -1
- coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +12 -2
- coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +8 -2
- coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +8 -2
- coretrace_python/bundled/security/code_injection/code_injection.py +8 -1
- coretrace_python/bundled/security/command_injection/command_injection.py +7 -1
- coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +8 -1
- coretrace_python/bundled/security/nosql_injection/nosql_injection.py +8 -1
- coretrace_python/bundled/security/open_redirect/open_redirect.py +8 -1
- coretrace_python/bundled/security/path_traversal/path_traversal.py +8 -1
- coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +8 -1
- coretrace_python/bundled/security/sql_injection/sql_injection.py +8 -1
- coretrace_python/bundled/security/ssrf/ssrf.py +8 -1
- coretrace_python/bundled/security/xss/xss.py +8 -1
- coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +8 -1
- coretrace_python/bundled/syntax/flask_debug/flask_debug.py +8 -2
- coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +8 -1
- coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +7 -1
- coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +8 -2
- coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +7 -1
- coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +7 -1
- coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +8 -1
- coretrace_python/bundled/syntax/weak_random/weak_random.py +8 -2
- coretrace_python/cache.py +8 -2
- coretrace_python/cli.py +3 -1
- coretrace_python/engine.py +214 -88
- coretrace_python/findings/__init__.py +10 -1
- coretrace_python/findings/model.py +11 -1
- coretrace_python/findings/rules.py +16 -0
- coretrace_python/interprocedural/__init__.py +4 -0
- coretrace_python/interprocedural/modulegraph.py +54 -28
- coretrace_python/interprocedural/summaries.py +51 -15
- coretrace_python/plugins/api.py +5 -1
- coretrace_python/plugins/detectors.py +6 -1
- coretrace_python/plugins/secrets.py +16 -2
- coretrace_python/reporters/report.py +6 -2
- coretrace_python/reporters/sarif.py +57 -14
- coretrace_python/semantic/scopes.py +42 -1
- coretrace_python/source/__init__.py +17 -2
- coretrace_python/source/manager.py +74 -7
- coretrace_python/source/model.py +12 -1
- coretrace_python/source/positions.py +3 -20
- coretrace_python/taint/__init__.py +2 -0
- coretrace_python/taint/templates.py +901 -132
- {coretrace_python_analyzer-0.23.0.dist-info → coretrace_python_analyzer-0.25.0.dist-info}/METADATA +1 -1
- {coretrace_python_analyzer-0.23.0.dist-info → coretrace_python_analyzer-0.25.0.dist-info}/RECORD +50 -49
- {coretrace_python_analyzer-0.23.0.dist-info → coretrace_python_analyzer-0.25.0.dist-info}/WHEEL +1 -1
- {coretrace_python_analyzer-0.23.0.dist-info → coretrace_python_analyzer-0.25.0.dist-info}/entry_points.txt +0 -0
- {coretrace_python_analyzer-0.23.0.dist-info → coretrace_python_analyzer-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {coretrace_python_analyzer-0.23.0.dist-info → coretrace_python_analyzer-0.25.0.dist-info}/licenses/NOTICE +0 -0
coretrace_python/__init__.py
CHANGED
|
@@ -2,17 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Sequence
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
6
|
from typing import ClassVar
|
|
7
7
|
|
|
8
8
|
from coretrace_python.analysis import AnyAnalysis
|
|
9
9
|
from coretrace_python.dependency import DependencyAnalysis
|
|
10
|
-
from coretrace_python.findings import Confidence, Finding, Severity
|
|
10
|
+
from coretrace_python.findings import Confidence, Finding, RuleDescription, Severity
|
|
11
11
|
from coretrace_python.plugins import ProjectContext, ProjectPlugin
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class DependencyPolicyPlugin(ProjectPlugin):
|
|
15
15
|
name: ClassVar[str] = "dependency-policy"
|
|
16
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
17
|
+
"denied-dependency": RuleDescription(
|
|
18
|
+
"Denied dependency",
|
|
19
|
+
"A requirement on a package the policy denies.",
|
|
20
|
+
),
|
|
21
|
+
"unpinned-dependency": RuleDescription(
|
|
22
|
+
"Unpinned dependency",
|
|
23
|
+
"A requirement without an exact pin, when the policy requires pins.",
|
|
24
|
+
),
|
|
25
|
+
}
|
|
16
26
|
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({DependencyAnalysis})
|
|
17
27
|
|
|
18
28
|
def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
|
|
@@ -4,7 +4,7 @@ template applies is a call to the function behind it, at the template's line."""
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from collections.abc import Iterator, Sequence
|
|
7
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
8
8
|
from typing import ClassVar
|
|
9
9
|
|
|
10
10
|
from coretrace_python.analysis import AnyAnalysis
|
|
@@ -17,7 +17,7 @@ from coretrace_python.dependency.correlation import (
|
|
|
17
17
|
evidence,
|
|
18
18
|
suffix_index,
|
|
19
19
|
)
|
|
20
|
-
from coretrace_python.findings import Confidence, Finding
|
|
20
|
+
from coretrace_python.findings import Confidence, Finding, RuleDescription
|
|
21
21
|
from coretrace_python.interprocedural import CallGraphAnalysis, ExternalSymbol
|
|
22
22
|
from coretrace_python.plugins import ProjectContext, ProjectPlugin
|
|
23
23
|
from coretrace_python.semantic.symbols import SymbolId
|
|
@@ -26,6 +26,12 @@ from coretrace_python.source import SourceSpan
|
|
|
26
26
|
|
|
27
27
|
class ReachableVulnerabilityPlugin(ProjectPlugin):
|
|
28
28
|
name: ClassVar[str] = "reachable-vulnerability"
|
|
29
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
30
|
+
"reachable-vulnerability": RuleDescription(
|
|
31
|
+
"Reachable dependency vulnerability",
|
|
32
|
+
"The project calls an API that an advisory of a vulnerable requirement affects.",
|
|
33
|
+
),
|
|
34
|
+
}
|
|
29
35
|
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({DependencyAnalysis, CallGraphAnalysis})
|
|
30
36
|
|
|
31
37
|
def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Sequence
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
6
|
from pathlib import PurePath
|
|
7
7
|
from typing import ClassVar
|
|
8
8
|
|
|
@@ -15,13 +15,19 @@ from coretrace_python.dependency.correlation import (
|
|
|
15
15
|
ruled_out,
|
|
16
16
|
suffix_index,
|
|
17
17
|
)
|
|
18
|
-
from coretrace_python.findings import Confidence, Finding
|
|
18
|
+
from coretrace_python.findings import Confidence, Finding, RuleDescription
|
|
19
19
|
from coretrace_python.interprocedural import CallGraphAnalysis, ExternalSymbol
|
|
20
20
|
from coretrace_python.plugins import ProjectContext, ProjectPlugin
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class VulnerableDependencyPlugin(ProjectPlugin):
|
|
24
24
|
name: ClassVar[str] = "vulnerable-dependency"
|
|
25
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
26
|
+
"vulnerable-dependency": RuleDescription(
|
|
27
|
+
"Vulnerable dependency",
|
|
28
|
+
"A requirement allows a version that an advisory affects.",
|
|
29
|
+
),
|
|
30
|
+
}
|
|
25
31
|
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({DependencyAnalysis, CallGraphAnalysis})
|
|
26
32
|
|
|
27
33
|
def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
|
|
@@ -18,11 +18,12 @@ runs depends on what the object exposes, a hotspot."""
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
+
from collections.abc import Mapping
|
|
21
22
|
from typing import ClassVar
|
|
22
23
|
|
|
23
24
|
from coretrace_python.abstract.strings import ModuleStringsAnalysis, leading_text
|
|
24
25
|
from coretrace_python.analysis import AnyAnalysis
|
|
25
|
-
from coretrace_python.findings import Severity
|
|
26
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
26
27
|
from coretrace_python.findings.refutation import Status, Verdict
|
|
27
28
|
from coretrace_python.hir import nodes
|
|
28
29
|
from coretrace_python.ir.ssa import SSAAnalysis
|
|
@@ -42,6 +43,12 @@ _GETATTR = SymbolId("python.builtins.getattr")
|
|
|
42
43
|
|
|
43
44
|
class CodeInjectionPlugin(TaintDetector):
|
|
44
45
|
name: ClassVar[str] = "code-injection"
|
|
46
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
47
|
+
"code-injection": RuleDescription(
|
|
48
|
+
"Code injection",
|
|
49
|
+
"Attacker-controlled data chooses code that runs: the code eval or exec runs, the module an import loads, the script runpy runs, or the attribute getattr selects and the program calls.",
|
|
50
|
+
),
|
|
51
|
+
}
|
|
45
52
|
rule_id: ClassVar[str] = "code-injection"
|
|
46
53
|
kind: ClassVar[TaintKind] = TaintKind.CODE
|
|
47
54
|
severity: ClassVar[Severity] = Severity.HIGH
|
|
@@ -36,7 +36,7 @@ from typing import ClassVar
|
|
|
36
36
|
|
|
37
37
|
from coretrace_python.abstract.strings import ModuleStringsAnalysis, leading_text
|
|
38
38
|
from coretrace_python.analysis import AnyAnalysis
|
|
39
|
-
from coretrace_python.findings import Severity
|
|
39
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
40
40
|
from coretrace_python.findings.refutation import Status, Verdict
|
|
41
41
|
from coretrace_python.hir import nodes
|
|
42
42
|
from coretrace_python.interprocedural import Arguments
|
|
@@ -398,6 +398,12 @@ class _Operands:
|
|
|
398
398
|
|
|
399
399
|
class CommandInjectionPlugin(TaintDetector):
|
|
400
400
|
name: ClassVar[str] = "command-injection"
|
|
401
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
402
|
+
"command-injection": RuleDescription(
|
|
403
|
+
"Command injection",
|
|
404
|
+
"Attacker-controlled data reaches a shell or process execution (os.system, subprocess, ...) where it can run or choose a command.",
|
|
405
|
+
),
|
|
406
|
+
}
|
|
401
407
|
rule_id: ClassVar[str] = "command-injection"
|
|
402
408
|
kind: ClassVar[TaintKind] = TaintKind.COMMAND
|
|
403
409
|
severity: ClassVar[Severity] = Severity.HIGH
|
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from collections.abc import Mapping
|
|
5
6
|
from typing import ClassVar
|
|
6
7
|
|
|
7
|
-
from coretrace_python.findings import Severity
|
|
8
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
8
9
|
from coretrace_python.plugins import TaintDetector
|
|
9
10
|
from coretrace_python.taint import TaintKind
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class InsecureDeserializationPlugin(TaintDetector):
|
|
13
14
|
name: ClassVar[str] = "insecure-deserialization"
|
|
15
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
16
|
+
"insecure-deserialization": RuleDescription(
|
|
17
|
+
"Insecure deserialization",
|
|
18
|
+
"Attacker-controlled data reaches a deserializer that can build arbitrary objects or run code: pickle, marshal, dill, jsonpickle, shelve, or a YAML loader without a safe loader.",
|
|
19
|
+
),
|
|
20
|
+
}
|
|
14
21
|
rule_id: ClassVar[str] = "insecure-deserialization"
|
|
15
22
|
kind: ClassVar[TaintKind] = TaintKind.DESERIALIZATION
|
|
16
23
|
severity: ClassVar[Severity] = Severity.CRITICAL
|
|
@@ -5,15 +5,22 @@ sink; model plugins declare them."""
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
from collections.abc import Mapping
|
|
8
9
|
from typing import ClassVar
|
|
9
10
|
|
|
10
|
-
from coretrace_python.findings import Severity
|
|
11
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
11
12
|
from coretrace_python.plugins import TaintDetector
|
|
12
13
|
from coretrace_python.taint import TaintKind
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class NoSqlInjectionPlugin(TaintDetector):
|
|
16
17
|
name: ClassVar[str] = "nosql-injection"
|
|
18
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
19
|
+
"nosql-injection": RuleDescription(
|
|
20
|
+
"NoSQL injection",
|
|
21
|
+
"Attacker input that may hold a structure, such as a JSON body, reaches a NoSQL query, where an operator such as {\"$ne\": null} matches any value.",
|
|
22
|
+
),
|
|
23
|
+
}
|
|
17
24
|
rule_id: ClassVar[str] = "nosql-injection"
|
|
18
25
|
kind: ClassVar[TaintKind] = TaintKind.NOSQL
|
|
19
26
|
severity: ClassVar[Severity] = Severity.HIGH
|
|
@@ -10,11 +10,12 @@ unproven, ``"/" + next`` included, stays a vulnerability.
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
from collections.abc import Mapping
|
|
13
14
|
from typing import ClassVar
|
|
14
15
|
|
|
15
16
|
from coretrace_python.abstract.strings import ModuleStringsAnalysis
|
|
16
17
|
from coretrace_python.analysis import AnyAnalysis
|
|
17
|
-
from coretrace_python.findings import Severity
|
|
18
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
18
19
|
from coretrace_python.findings.refutation import Status, Verdict
|
|
19
20
|
from coretrace_python.hir import nodes
|
|
20
21
|
from coretrace_python.ir.ssa import SSAAnalysis
|
|
@@ -25,6 +26,12 @@ from coretrace_python.taint.urls import flow_url
|
|
|
25
26
|
|
|
26
27
|
class OpenRedirectPlugin(TaintDetector):
|
|
27
28
|
name: ClassVar[str] = "open-redirect"
|
|
29
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
30
|
+
"open-redirect": RuleDescription(
|
|
31
|
+
"Open redirect",
|
|
32
|
+
"Attacker-controlled data chooses the target of an HTTP redirect, which can send the user to another site.",
|
|
33
|
+
),
|
|
34
|
+
}
|
|
28
35
|
rule_id: ClassVar[str] = "open-redirect"
|
|
29
36
|
kind: ClassVar[TaintKind] = TaintKind.REDIRECT
|
|
30
37
|
severity: ClassVar[Severity] = Severity.MEDIUM
|
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from collections.abc import Mapping
|
|
5
6
|
from typing import ClassVar
|
|
6
7
|
|
|
7
|
-
from coretrace_python.findings import Severity
|
|
8
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
8
9
|
from coretrace_python.plugins import TaintDetector
|
|
9
10
|
from coretrace_python.taint import TaintKind
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class PathTraversalPlugin(TaintDetector):
|
|
13
14
|
name: ClassVar[str] = "path-traversal"
|
|
15
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
16
|
+
"path-traversal": RuleDescription(
|
|
17
|
+
"Path traversal",
|
|
18
|
+
"Attacker-controlled data reaches a file system path (open, send_file, os.remove, ...).",
|
|
19
|
+
),
|
|
20
|
+
}
|
|
14
21
|
rule_id: ClassVar[str] = "path-traversal"
|
|
15
22
|
kind: ClassVar[TaintKind] = TaintKind.PATH
|
|
16
23
|
severity: ClassVar[Severity] = Severity.HIGH
|
|
@@ -5,15 +5,22 @@ a hint, so medium confidence."""
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
from collections.abc import Mapping
|
|
8
9
|
from typing import ClassVar
|
|
9
10
|
|
|
10
|
-
from coretrace_python.findings import Confidence, Severity
|
|
11
|
+
from coretrace_python.findings import Confidence, RuleDescription, Severity
|
|
11
12
|
from coretrace_python.plugins import TaintDetector
|
|
12
13
|
from coretrace_python.taint import TaintKind
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class PlaintextCredentialsPlugin(TaintDetector):
|
|
16
17
|
name: ClassVar[str] = "plaintext-credentials"
|
|
18
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
19
|
+
"plaintext-credential-storage": RuleDescription(
|
|
20
|
+
"Plaintext credential storage",
|
|
21
|
+
"A parameter named like a password is stored in a database without hashing.",
|
|
22
|
+
),
|
|
23
|
+
}
|
|
17
24
|
rule_id: ClassVar[str] = "plaintext-credential-storage"
|
|
18
25
|
kind: ClassVar[TaintKind] = TaintKind.CREDENTIAL
|
|
19
26
|
severity: ClassVar[Severity] = Severity.HIGH
|
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from collections.abc import Mapping
|
|
5
6
|
from typing import ClassVar
|
|
6
7
|
|
|
7
|
-
from coretrace_python.findings import Severity
|
|
8
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
8
9
|
from coretrace_python.plugins import TaintDetector
|
|
9
10
|
from coretrace_python.taint import TaintKind
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class SqlInjectionPlugin(TaintDetector):
|
|
13
14
|
name: ClassVar[str] = "sql-injection"
|
|
15
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
16
|
+
"sql-injection": RuleDescription(
|
|
17
|
+
"SQL injection",
|
|
18
|
+
"Attacker-controlled data reaches a database statement (cursor.execute, SQLAlchemy text, Django raw, ...) rather than a query parameter.",
|
|
19
|
+
),
|
|
20
|
+
}
|
|
14
21
|
rule_id: ClassVar[str] = "sql-injection"
|
|
15
22
|
kind: ClassVar[TaintKind] = TaintKind.SQL
|
|
16
23
|
severity: ClassVar[Severity] = Severity.HIGH
|
|
@@ -10,11 +10,12 @@ a base of unknown value or a URL built in a helper function, stays a vulnerabili
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
from collections.abc import Mapping
|
|
13
14
|
from typing import ClassVar
|
|
14
15
|
|
|
15
16
|
from coretrace_python.abstract.strings import ModuleStringsAnalysis
|
|
16
17
|
from coretrace_python.analysis import AnyAnalysis
|
|
17
|
-
from coretrace_python.findings import Severity
|
|
18
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
18
19
|
from coretrace_python.findings.refutation import Status, Verdict
|
|
19
20
|
from coretrace_python.hir import nodes
|
|
20
21
|
from coretrace_python.ir.ssa import SSAAnalysis
|
|
@@ -25,6 +26,12 @@ from coretrace_python.taint.urls import flow_url, redirects_disabled
|
|
|
25
26
|
|
|
26
27
|
class SsrfPlugin(TaintDetector):
|
|
27
28
|
name: ClassVar[str] = "ssrf"
|
|
29
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
30
|
+
"ssrf": RuleDescription(
|
|
31
|
+
"Server-side request forgery",
|
|
32
|
+
"Attacker-controlled data reaches the destination of an HTTP client request (Requests, httpx, urllib).",
|
|
33
|
+
),
|
|
34
|
+
}
|
|
28
35
|
rule_id: ClassVar[str] = "ssrf"
|
|
29
36
|
kind: ClassVar[TaintKind] = TaintKind.SSRF
|
|
30
37
|
severity: ClassVar[Severity] = Severity.HIGH
|
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from collections.abc import Mapping
|
|
5
6
|
from typing import ClassVar
|
|
6
7
|
|
|
7
|
-
from coretrace_python.findings import Severity
|
|
8
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
8
9
|
from coretrace_python.plugins import TaintDetector
|
|
9
10
|
from coretrace_python.taint import TaintKind
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class XssPlugin(TaintDetector):
|
|
13
14
|
name: ClassVar[str] = "xss"
|
|
15
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
16
|
+
"xss": RuleDescription(
|
|
17
|
+
"Cross-site scripting",
|
|
18
|
+
"Attacker-controlled data reaches an HTTP response body built without escaping.",
|
|
19
|
+
),
|
|
20
|
+
}
|
|
14
21
|
rule_id: ClassVar[str] = "xss"
|
|
15
22
|
kind: ClassVar[TaintKind] = TaintKind.HTML
|
|
16
23
|
severity: ClassVar[Severity] = Severity.HIGH
|
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from collections.abc import Mapping
|
|
5
6
|
from typing import ClassVar
|
|
6
7
|
|
|
7
|
-
from coretrace_python.findings import Severity
|
|
8
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
8
9
|
from coretrace_python.plugins import SymbolCallDetector
|
|
9
10
|
from coretrace_python.semantic.symbols import SymbolId
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class DangerousEvalPlugin(SymbolCallDetector):
|
|
13
14
|
name: ClassVar[str] = "dangerous-eval"
|
|
15
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
16
|
+
"dangerous-eval": RuleDescription(
|
|
17
|
+
"Dynamic code execution",
|
|
18
|
+
"A call to eval or exec, whatever name the file gives them.",
|
|
19
|
+
),
|
|
20
|
+
}
|
|
14
21
|
rule_id: ClassVar[str] = "dangerous-eval"
|
|
15
22
|
symbols: ClassVar[frozenset[SymbolId]] = frozenset(
|
|
16
23
|
{SymbolId("python.builtins.eval"), SymbolId("python.builtins.exec")}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Iterator, Sequence
|
|
5
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
6
6
|
from typing import ClassVar
|
|
7
7
|
|
|
8
8
|
from coretrace_python.analysis import AnyAnalysis
|
|
9
|
-
from coretrace_python.findings import Confidence, Finding, Severity
|
|
9
|
+
from coretrace_python.findings import Confidence, Finding, RuleDescription, Severity
|
|
10
10
|
from coretrace_python.hir import nodes
|
|
11
11
|
from coretrace_python.hir.visitors import Node, children
|
|
12
12
|
from coretrace_python.plugins import Plugin, PluginContext
|
|
@@ -18,6 +18,12 @@ RUN = SymbolId("python.flask.Flask.run")
|
|
|
18
18
|
|
|
19
19
|
class FlaskDebugPlugin(Plugin):
|
|
20
20
|
name: ClassVar[str] = "flask-debug"
|
|
21
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
22
|
+
"debug-enabled": RuleDescription(
|
|
23
|
+
"Debug mode enabled",
|
|
24
|
+
"A Flask application run with debug=True, which exposes an interactive debugger.",
|
|
25
|
+
),
|
|
26
|
+
}
|
|
21
27
|
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({ScopeAnalysis, SymbolAnalysis})
|
|
22
28
|
|
|
23
29
|
def analyze(self, ctx: PluginContext) -> Sequence[Finding]:
|
|
@@ -3,10 +3,11 @@ create it first. ``mkstemp``, ``NamedTemporaryFile`` and ``TemporaryDirectory``
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
from collections.abc import Mapping
|
|
6
7
|
from typing import ClassVar
|
|
7
8
|
|
|
8
9
|
from coretrace_python.analysis import AnyAnalysis
|
|
9
|
-
from coretrace_python.findings import Severity
|
|
10
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
10
11
|
from coretrace_python.interprocedural import CallGraphAnalysis
|
|
11
12
|
from coretrace_python.ir.ssa import SSAAnalysis
|
|
12
13
|
from coretrace_python.plugins import SymbolCallDetector
|
|
@@ -15,6 +16,12 @@ from coretrace_python.semantic.symbols import SymbolId
|
|
|
15
16
|
|
|
16
17
|
class InsecureTempFilePlugin(SymbolCallDetector):
|
|
17
18
|
name: ClassVar[str] = "insecure-temp-file"
|
|
19
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
20
|
+
"insecure-temp-file": RuleDescription(
|
|
21
|
+
"Insecure temporary file",
|
|
22
|
+
"tempfile.mktemp, which names a file without creating it, so that another process can create it first.",
|
|
23
|
+
),
|
|
24
|
+
}
|
|
18
25
|
rule_id: ClassVar[str] = "insecure-temp-file"
|
|
19
26
|
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({SSAAnalysis, CallGraphAnalysis})
|
|
20
27
|
symbols: ClassVar[frozenset[SymbolId]] = frozenset({SymbolId("python.tempfile.mktemp")})
|
|
@@ -7,7 +7,7 @@ from collections.abc import Mapping
|
|
|
7
7
|
from typing import ClassVar
|
|
8
8
|
|
|
9
9
|
from coretrace_python.analysis import AnyAnalysis
|
|
10
|
-
from coretrace_python.findings import Severity
|
|
10
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
11
11
|
from coretrace_python.interprocedural import CallGraphAnalysis
|
|
12
12
|
from coretrace_python.ir.model import Call, Constant, Instruction, Value
|
|
13
13
|
from coretrace_python.ir.ssa import SSAAnalysis
|
|
@@ -24,6 +24,12 @@ UNVERIFIED = frozenset({SymbolId("python.ssl._create_unverified_context")})
|
|
|
24
24
|
|
|
25
25
|
class InsecureTlsPlugin(SymbolCallDetector):
|
|
26
26
|
name: ClassVar[str] = "insecure-tls"
|
|
27
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
28
|
+
"insecure-tls": RuleDescription(
|
|
29
|
+
"TLS certificate verification disabled",
|
|
30
|
+
"verify=False on a Requests or httpx call, or ssl._create_unverified_context(), which accept any certificate.",
|
|
31
|
+
),
|
|
32
|
+
}
|
|
27
33
|
rule_id: ClassVar[str] = "insecure-tls"
|
|
28
34
|
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({SSAAnalysis, CallGraphAnalysis})
|
|
29
35
|
symbols: ClassVar[frozenset[SymbolId]] = HTTP_CALLS | UNVERIFIED
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Sequence
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
6
|
from typing import ClassVar
|
|
7
7
|
|
|
8
8
|
from coretrace_python.analysis import AnyAnalysis
|
|
9
|
-
from coretrace_python.findings import Confidence, Finding, Severity
|
|
9
|
+
from coretrace_python.findings import Confidence, Finding, RuleDescription, Severity
|
|
10
10
|
from coretrace_python.interprocedural import CallGraphAnalysis
|
|
11
11
|
from coretrace_python.ir.model import Call
|
|
12
12
|
from coretrace_python.ir.ssa import SSAAnalysis
|
|
@@ -20,6 +20,12 @@ HTTP_CALLS = frozenset(SymbolId(f"python.{caller}.{method}") for caller in _CALL
|
|
|
20
20
|
|
|
21
21
|
class MissingTimeoutPlugin(Plugin):
|
|
22
22
|
name: ClassVar[str] = "missing-timeout"
|
|
23
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
24
|
+
"missing-timeout": RuleDescription(
|
|
25
|
+
"HTTP request without a timeout",
|
|
26
|
+
"A Requests or httpx call without a timeout, which can wait forever.",
|
|
27
|
+
),
|
|
28
|
+
}
|
|
23
29
|
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({SSAAnalysis, CallGraphAnalysis})
|
|
24
30
|
|
|
25
31
|
def analyze(self, ctx: PluginContext) -> Sequence[Finding]:
|
|
@@ -7,7 +7,7 @@ from collections.abc import Mapping
|
|
|
7
7
|
from typing import ClassVar
|
|
8
8
|
|
|
9
9
|
from coretrace_python.analysis import AnyAnalysis
|
|
10
|
-
from coretrace_python.findings import Severity
|
|
10
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
11
11
|
from coretrace_python.interprocedural import CallGraphAnalysis
|
|
12
12
|
from coretrace_python.ir.model import Call, Instruction, Value
|
|
13
13
|
from coretrace_python.ir.ssa import SSAAnalysis
|
|
@@ -20,6 +20,12 @@ _EXTRACTORS = tuple(f"{opener}.{method}" for opener in _OPENERS for method in ("
|
|
|
20
20
|
|
|
21
21
|
class UnsafeArchivePlugin(SymbolCallDetector):
|
|
22
22
|
name: ClassVar[str] = "unsafe-archive-extraction"
|
|
23
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
24
|
+
"unsafe-archive-extraction": RuleDescription(
|
|
25
|
+
"Unsafe archive extraction",
|
|
26
|
+
"A tarfile or zipfile extraction, or shutil.unpack_archive, without a member filter: a member can be written outside the destination.",
|
|
27
|
+
),
|
|
28
|
+
}
|
|
23
29
|
rule_id: ClassVar[str] = "unsafe-archive-extraction"
|
|
24
30
|
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({SSAAnalysis, CallGraphAnalysis})
|
|
25
31
|
symbols: ClassVar[frozenset[SymbolId]] = frozenset(
|
|
@@ -19,7 +19,7 @@ from typing import ClassVar
|
|
|
19
19
|
|
|
20
20
|
from coretrace_python.abstract.strings import ModuleStringsAnalysis
|
|
21
21
|
from coretrace_python.analysis import AnyAnalysis
|
|
22
|
-
from coretrace_python.findings import Confidence, Severity
|
|
22
|
+
from coretrace_python.findings import Confidence, RuleDescription, Severity
|
|
23
23
|
from coretrace_python.interprocedural import CallGraphAnalysis
|
|
24
24
|
from coretrace_python.ir.model import Call, Constant, Global, Instruction, Value
|
|
25
25
|
from coretrace_python.ir.ssa import SSAAnalysis
|
|
@@ -64,6 +64,12 @@ def _resolve_entities(call: Call, defs: Mapping[Value, Instruction]) -> object:
|
|
|
64
64
|
|
|
65
65
|
class UnsafeXmlPlugin(SymbolCallDetector):
|
|
66
66
|
name: ClassVar[str] = "unsafe-xml"
|
|
67
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
68
|
+
"unsafe-xml": RuleDescription(
|
|
69
|
+
"Unsafe XML parser",
|
|
70
|
+
"An XML parser of the standard library or of lxml, exposed to the entities a document declares; defusedxml is the safe replacement.",
|
|
71
|
+
),
|
|
72
|
+
}
|
|
67
73
|
rule_id: ClassVar[str] = "unsafe-xml"
|
|
68
74
|
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({SSAAnalysis, CallGraphAnalysis, ModuleStringsAnalysis})
|
|
69
75
|
symbols: ClassVar[frozenset[SymbolId]] = _STANDARD_LIBRARY | _LXML | _LXML_UNTIL_6_1
|
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from collections.abc import Mapping
|
|
5
6
|
from typing import ClassVar
|
|
6
7
|
|
|
7
|
-
from coretrace_python.findings import Severity
|
|
8
|
+
from coretrace_python.findings import RuleDescription, Severity
|
|
8
9
|
from coretrace_python.plugins import SymbolCallDetector
|
|
9
10
|
from coretrace_python.semantic.symbols import SymbolId
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class WeakCryptoPlugin(SymbolCallDetector):
|
|
13
14
|
name: ClassVar[str] = "weak-crypto"
|
|
15
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
16
|
+
"weak-crypto": RuleDescription(
|
|
17
|
+
"Weak cryptographic algorithm",
|
|
18
|
+
"A broken algorithm such as hashlib.md5 or hashlib.sha1.",
|
|
19
|
+
),
|
|
20
|
+
}
|
|
14
21
|
rule_id: ClassVar[str] = "weak-crypto"
|
|
15
22
|
symbols: ClassVar[frozenset[SymbolId]] = frozenset(
|
|
16
23
|
{SymbolId("python.hashlib.md5"), SymbolId("python.hashlib.sha1")}
|
|
@@ -7,11 +7,11 @@ produces one."""
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import re
|
|
10
|
-
from collections.abc import Iterator, Sequence
|
|
10
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
11
11
|
from typing import ClassVar
|
|
12
12
|
|
|
13
13
|
from coretrace_python.analysis import AnyAnalysis
|
|
14
|
-
from coretrace_python.findings import Confidence, Finding, Severity
|
|
14
|
+
from coretrace_python.findings import Confidence, Finding, RuleDescription, Severity
|
|
15
15
|
from coretrace_python.hir import nodes
|
|
16
16
|
from coretrace_python.hir.visitors import Node, children
|
|
17
17
|
from coretrace_python.plugins import Plugin, PluginContext
|
|
@@ -25,6 +25,12 @@ CREDENTIAL = re.compile(r"(?i)(token|secret|password|passwd|api_?key|session|non
|
|
|
25
25
|
|
|
26
26
|
class WeakRandomPlugin(Plugin):
|
|
27
27
|
name: ClassVar[str] = "weak-random"
|
|
28
|
+
rules: ClassVar[Mapping[str, RuleDescription]] = {
|
|
29
|
+
"weak-random": RuleDescription(
|
|
30
|
+
"Weak random value for a credential",
|
|
31
|
+
"A value of the random module used as a credential; random is not cryptographically secure.",
|
|
32
|
+
),
|
|
33
|
+
}
|
|
28
34
|
rule_id: ClassVar[str] = "weak-random"
|
|
29
35
|
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({ScopeAnalysis, SymbolAnalysis})
|
|
30
36
|
|
coretrace_python/cache.py
CHANGED
|
@@ -21,7 +21,7 @@ from pathlib import Path
|
|
|
21
21
|
from types import MappingProxyType
|
|
22
22
|
from typing import Any
|
|
23
23
|
|
|
24
|
-
from coretrace_python.findings import Confidence, Finding, Severity
|
|
24
|
+
from coretrace_python.findings import Confidence, Finding, RelatedLocation, Severity
|
|
25
25
|
from coretrace_python.interprocedural import (
|
|
26
26
|
Arguments,
|
|
27
27
|
CallSite,
|
|
@@ -42,7 +42,11 @@ from coretrace_python.interprocedural import (
|
|
|
42
42
|
from coretrace_python.semantic.symbols import SymbolId
|
|
43
43
|
from coretrace_python.source import FileLocation, Location, SourceId, SourceSpan
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
# Cached summaries from earlier versions preserve unstable external-call
|
|
46
|
+
# insertion order. They must be recomputed with canonical ordering (#261).
|
|
47
|
+
# Findings carry their related locations from format 18 on (#271). A module's key holds
|
|
48
|
+
# the names it is imported by from format 19 on (#264).
|
|
49
|
+
CACHE_FORMAT = 19
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
@dataclass(frozen=True)
|
|
@@ -227,6 +231,7 @@ def _encode_finding(finding: Finding) -> dict[str, Any]:
|
|
|
227
231
|
"span": _encode_location(finding.span),
|
|
228
232
|
"function": finding.function,
|
|
229
233
|
"metadata": dict(finding.metadata),
|
|
234
|
+
"related": [[_encode_span(r.span), r.message] for r in finding.related],
|
|
230
235
|
}
|
|
231
236
|
|
|
232
237
|
|
|
@@ -240,6 +245,7 @@ def _decode_finding(data: Mapping[str, Any]) -> Finding:
|
|
|
240
245
|
_decode_location(data["span"]),
|
|
241
246
|
None if function is None else _string(function),
|
|
242
247
|
{_string(k): _string(v) for k, v in data["metadata"].items()},
|
|
248
|
+
tuple(RelatedLocation(_decode_span(span), _string(message)) for span, message in data["related"]),
|
|
243
249
|
)
|
|
244
250
|
|
|
245
251
|
|
coretrace_python/cli.py
CHANGED
|
@@ -280,6 +280,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
280
280
|
coverage = analysis.coverage
|
|
281
281
|
suppressed = analysis.suppressed
|
|
282
282
|
components = analysis.components
|
|
283
|
+
rules = analysis.rules
|
|
283
284
|
if args.sbom is not None:
|
|
284
285
|
args.sbom.write_text(
|
|
285
286
|
render_sbom(
|
|
@@ -307,6 +308,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
307
308
|
findings, coverage = file_analysis.findings, file_analysis.coverage
|
|
308
309
|
suppressed = file_analysis.suppressed
|
|
309
310
|
components = ()
|
|
311
|
+
rules = file_analysis.rules
|
|
310
312
|
baselined = None
|
|
311
313
|
recorded = None
|
|
312
314
|
if previous is not None:
|
|
@@ -317,7 +319,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
317
319
|
elif args.baseline is not None:
|
|
318
320
|
recorded = Baseline.of(findings, root, key)
|
|
319
321
|
findings, baselined = (), findings
|
|
320
|
-
report = engine.report(findings, coverage, root, suppressed, baselined, components)
|
|
322
|
+
report = engine.report(findings, coverage, root, suppressed, baselined, components, rules)
|
|
321
323
|
text = render(args.format or "text", report)
|
|
322
324
|
# Recorded once the report is rendered, so that a failed check keeps the
|
|
323
325
|
# previous baseline.
|