boti-tripwire 0.1.0__tar.gz
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.
- boti_tripwire-0.1.0/PKG-INFO +66 -0
- boti_tripwire-0.1.0/README.md +49 -0
- boti_tripwire-0.1.0/pyproject.toml +59 -0
- boti_tripwire-0.1.0/setup.cfg +4 -0
- boti_tripwire-0.1.0/src/boti_tripwire.egg-info/PKG-INFO +66 -0
- boti_tripwire-0.1.0/src/boti_tripwire.egg-info/SOURCES.txt +21 -0
- boti_tripwire-0.1.0/src/boti_tripwire.egg-info/dependency_links.txt +1 -0
- boti_tripwire-0.1.0/src/boti_tripwire.egg-info/entry_points.txt +2 -0
- boti_tripwire-0.1.0/src/boti_tripwire.egg-info/requires.txt +1 -0
- boti_tripwire-0.1.0/src/boti_tripwire.egg-info/top_level.txt +1 -0
- boti_tripwire-0.1.0/src/tripwire/__init__.py +12 -0
- boti_tripwire-0.1.0/src/tripwire/ast_helpers.py +163 -0
- boti_tripwire-0.1.0/src/tripwire/checks/__init__.py +124 -0
- boti_tripwire-0.1.0/src/tripwire/checks/ast_checks.py +462 -0
- boti_tripwire-0.1.0/src/tripwire/checks/manifest_checks.py +100 -0
- boti_tripwire-0.1.0/src/tripwire/checks/text_checks.py +903 -0
- boti_tripwire-0.1.0/src/tripwire/cli.py +133 -0
- boti_tripwire-0.1.0/src/tripwire/config.py +61 -0
- boti_tripwire-0.1.0/src/tripwire/models.py +22 -0
- boti_tripwire-0.1.0/src/tripwire/py.typed +0 -0
- boti_tripwire-0.1.0/src/tripwire/reporting.py +159 -0
- boti_tripwire-0.1.0/src/tripwire/scanner.py +34 -0
- boti_tripwire-0.1.0/tests/test_tripwire.py +81 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: boti-tripwire
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Zero-day security scanner for the Boti workspace
|
|
5
|
+
Author-email: Luis Valverde <lvalverdeb@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Security
|
|
14
|
+
Requires-Python: >=3.13
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: boti>=1.0.0
|
|
17
|
+
|
|
18
|
+
# tripwire
|
|
19
|
+
|
|
20
|
+
Zero-day security scanner for the Boti workspace.
|
|
21
|
+
|
|
22
|
+
Maps every finding to a CWE from the 2025 CWE Top 25, OWASP Top 10:2025, and known
|
|
23
|
+
Python zero-day exploit patterns (pickle bypass CVE-2026-56315, PyYAML shadow
|
|
24
|
+
vulnerability CVE-2026-24009, LangChain SSTI CVE-2025-68664, dependency confusion
|
|
25
|
+
CVE-2025-61774, and more — see the CWE reference printed at the end of every
|
|
26
|
+
text-mode report). 52 checks total: most are AST-based (won't false-positive on a
|
|
27
|
+
dangerous call mentioned in a docstring or comment); the rest are line-regex checks
|
|
28
|
+
for surface-syntax patterns that don't need full parsing.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv run tripwire
|
|
34
|
+
uv run tripwire --json > report.json
|
|
35
|
+
uv run tripwire -q
|
|
36
|
+
uv run tripwire --fail-on critical
|
|
37
|
+
uv run tripwire --package my-lib=my-lib/src/my_lib
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Exit codes: `0` (no findings at or above `--fail-on`), `1` (one or more).
|
|
41
|
+
|
|
42
|
+
### Options
|
|
43
|
+
|
|
44
|
+
| Flag | Default | Description |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `--json` | off | Machine-readable output |
|
|
47
|
+
| `--quiet` / `-q` | off | Suppress the banner |
|
|
48
|
+
| `--fail-on` | `high` | Severity threshold (`critical`\|`high`\|`medium`\|`low`\|`info`) for the exit code |
|
|
49
|
+
| `--package NAME=PATH` | — | Add or override a scan target package (repeatable) |
|
|
50
|
+
|
|
51
|
+
By default, tripwire scans `boti`, `boti-data`, `boti-dask`, and itself.
|
|
52
|
+
|
|
53
|
+
## Design
|
|
54
|
+
|
|
55
|
+
- `tripwire.models` — the `Finding` dataclass and severity ordering.
|
|
56
|
+
- `tripwire.config` — workspace-root discovery and the default package registry.
|
|
57
|
+
- `tripwire.ast_helpers` — shared AST/text-matching helpers used across checks.
|
|
58
|
+
- `tripwire.scanner` — file discovery and reading.
|
|
59
|
+
- `tripwire.checks.ast_checks` — checks that walk the parsed AST (won't match
|
|
60
|
+
a dangerous call mentioned in a docstring or string literal).
|
|
61
|
+
- `tripwire.checks.text_checks` — checks that scan raw source lines for
|
|
62
|
+
surface-syntax patterns.
|
|
63
|
+
- `tripwire.checks.manifest_checks` — checks over dependency manifests
|
|
64
|
+
(`pyproject.toml`, `requirements.txt`, etc.), not Python source.
|
|
65
|
+
- `tripwire.reporting` — text/JSON report rendering.
|
|
66
|
+
- `tripwire.cli` — argument parsing and the scan loop.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# tripwire
|
|
2
|
+
|
|
3
|
+
Zero-day security scanner for the Boti workspace.
|
|
4
|
+
|
|
5
|
+
Maps every finding to a CWE from the 2025 CWE Top 25, OWASP Top 10:2025, and known
|
|
6
|
+
Python zero-day exploit patterns (pickle bypass CVE-2026-56315, PyYAML shadow
|
|
7
|
+
vulnerability CVE-2026-24009, LangChain SSTI CVE-2025-68664, dependency confusion
|
|
8
|
+
CVE-2025-61774, and more — see the CWE reference printed at the end of every
|
|
9
|
+
text-mode report). 52 checks total: most are AST-based (won't false-positive on a
|
|
10
|
+
dangerous call mentioned in a docstring or comment); the rest are line-regex checks
|
|
11
|
+
for surface-syntax patterns that don't need full parsing.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv run tripwire
|
|
17
|
+
uv run tripwire --json > report.json
|
|
18
|
+
uv run tripwire -q
|
|
19
|
+
uv run tripwire --fail-on critical
|
|
20
|
+
uv run tripwire --package my-lib=my-lib/src/my_lib
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Exit codes: `0` (no findings at or above `--fail-on`), `1` (one or more).
|
|
24
|
+
|
|
25
|
+
### Options
|
|
26
|
+
|
|
27
|
+
| Flag | Default | Description |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `--json` | off | Machine-readable output |
|
|
30
|
+
| `--quiet` / `-q` | off | Suppress the banner |
|
|
31
|
+
| `--fail-on` | `high` | Severity threshold (`critical`\|`high`\|`medium`\|`low`\|`info`) for the exit code |
|
|
32
|
+
| `--package NAME=PATH` | — | Add or override a scan target package (repeatable) |
|
|
33
|
+
|
|
34
|
+
By default, tripwire scans `boti`, `boti-data`, `boti-dask`, and itself.
|
|
35
|
+
|
|
36
|
+
## Design
|
|
37
|
+
|
|
38
|
+
- `tripwire.models` — the `Finding` dataclass and severity ordering.
|
|
39
|
+
- `tripwire.config` — workspace-root discovery and the default package registry.
|
|
40
|
+
- `tripwire.ast_helpers` — shared AST/text-matching helpers used across checks.
|
|
41
|
+
- `tripwire.scanner` — file discovery and reading.
|
|
42
|
+
- `tripwire.checks.ast_checks` — checks that walk the parsed AST (won't match
|
|
43
|
+
a dangerous call mentioned in a docstring or string literal).
|
|
44
|
+
- `tripwire.checks.text_checks` — checks that scan raw source lines for
|
|
45
|
+
surface-syntax patterns.
|
|
46
|
+
- `tripwire.checks.manifest_checks` — checks over dependency manifests
|
|
47
|
+
(`pyproject.toml`, `requirements.txt`, etc.), not Python source.
|
|
48
|
+
- `tripwire.reporting` — text/JSON report rendering.
|
|
49
|
+
- `tripwire.cli` — argument parsing and the scan loop.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "boti-tripwire"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Zero-day security scanner for the Boti workspace"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.13"
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Luis Valverde", email = "lvalverdeb@gmail.com"},
|
|
10
|
+
]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Topic :: Security",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"boti>=1.0.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
tripwire = "tripwire.cli:main"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["setuptools>=80", "wheel"]
|
|
29
|
+
build-backend = "setuptools.build_meta"
|
|
30
|
+
|
|
31
|
+
[dependency-groups]
|
|
32
|
+
dev = [
|
|
33
|
+
"pytest>=9.0.3",
|
|
34
|
+
"ruff>=0.11.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools]
|
|
38
|
+
include-package-data = true
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.package-dir]
|
|
41
|
+
"" = "src"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
where = ["src"]
|
|
45
|
+
include = ["tripwire*"]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
tripwire = ["py.typed"]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
line-length = 100
|
|
55
|
+
target-version = "py313"
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint]
|
|
58
|
+
select = ["E", "F", "W", "I", "UP"]
|
|
59
|
+
ignore = ["E501"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: boti-tripwire
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Zero-day security scanner for the Boti workspace
|
|
5
|
+
Author-email: Luis Valverde <lvalverdeb@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Security
|
|
14
|
+
Requires-Python: >=3.13
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: boti>=1.0.0
|
|
17
|
+
|
|
18
|
+
# tripwire
|
|
19
|
+
|
|
20
|
+
Zero-day security scanner for the Boti workspace.
|
|
21
|
+
|
|
22
|
+
Maps every finding to a CWE from the 2025 CWE Top 25, OWASP Top 10:2025, and known
|
|
23
|
+
Python zero-day exploit patterns (pickle bypass CVE-2026-56315, PyYAML shadow
|
|
24
|
+
vulnerability CVE-2026-24009, LangChain SSTI CVE-2025-68664, dependency confusion
|
|
25
|
+
CVE-2025-61774, and more — see the CWE reference printed at the end of every
|
|
26
|
+
text-mode report). 52 checks total: most are AST-based (won't false-positive on a
|
|
27
|
+
dangerous call mentioned in a docstring or comment); the rest are line-regex checks
|
|
28
|
+
for surface-syntax patterns that don't need full parsing.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv run tripwire
|
|
34
|
+
uv run tripwire --json > report.json
|
|
35
|
+
uv run tripwire -q
|
|
36
|
+
uv run tripwire --fail-on critical
|
|
37
|
+
uv run tripwire --package my-lib=my-lib/src/my_lib
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Exit codes: `0` (no findings at or above `--fail-on`), `1` (one or more).
|
|
41
|
+
|
|
42
|
+
### Options
|
|
43
|
+
|
|
44
|
+
| Flag | Default | Description |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `--json` | off | Machine-readable output |
|
|
47
|
+
| `--quiet` / `-q` | off | Suppress the banner |
|
|
48
|
+
| `--fail-on` | `high` | Severity threshold (`critical`\|`high`\|`medium`\|`low`\|`info`) for the exit code |
|
|
49
|
+
| `--package NAME=PATH` | — | Add or override a scan target package (repeatable) |
|
|
50
|
+
|
|
51
|
+
By default, tripwire scans `boti`, `boti-data`, `boti-dask`, and itself.
|
|
52
|
+
|
|
53
|
+
## Design
|
|
54
|
+
|
|
55
|
+
- `tripwire.models` — the `Finding` dataclass and severity ordering.
|
|
56
|
+
- `tripwire.config` — workspace-root discovery and the default package registry.
|
|
57
|
+
- `tripwire.ast_helpers` — shared AST/text-matching helpers used across checks.
|
|
58
|
+
- `tripwire.scanner` — file discovery and reading.
|
|
59
|
+
- `tripwire.checks.ast_checks` — checks that walk the parsed AST (won't match
|
|
60
|
+
a dangerous call mentioned in a docstring or string literal).
|
|
61
|
+
- `tripwire.checks.text_checks` — checks that scan raw source lines for
|
|
62
|
+
surface-syntax patterns.
|
|
63
|
+
- `tripwire.checks.manifest_checks` — checks over dependency manifests
|
|
64
|
+
(`pyproject.toml`, `requirements.txt`, etc.), not Python source.
|
|
65
|
+
- `tripwire.reporting` — text/JSON report rendering.
|
|
66
|
+
- `tripwire.cli` — argument parsing and the scan loop.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/boti_tripwire.egg-info/PKG-INFO
|
|
4
|
+
src/boti_tripwire.egg-info/SOURCES.txt
|
|
5
|
+
src/boti_tripwire.egg-info/dependency_links.txt
|
|
6
|
+
src/boti_tripwire.egg-info/entry_points.txt
|
|
7
|
+
src/boti_tripwire.egg-info/requires.txt
|
|
8
|
+
src/boti_tripwire.egg-info/top_level.txt
|
|
9
|
+
src/tripwire/__init__.py
|
|
10
|
+
src/tripwire/ast_helpers.py
|
|
11
|
+
src/tripwire/cli.py
|
|
12
|
+
src/tripwire/config.py
|
|
13
|
+
src/tripwire/models.py
|
|
14
|
+
src/tripwire/py.typed
|
|
15
|
+
src/tripwire/reporting.py
|
|
16
|
+
src/tripwire/scanner.py
|
|
17
|
+
src/tripwire/checks/__init__.py
|
|
18
|
+
src/tripwire/checks/ast_checks.py
|
|
19
|
+
src/tripwire/checks/manifest_checks.py
|
|
20
|
+
src/tripwire/checks/text_checks.py
|
|
21
|
+
tests/test_tripwire.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
boti>=1.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tripwire
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Zero-day security scanner for the Boti workspace.
|
|
2
|
+
|
|
3
|
+
Maps findings to the CWE Top 25 (2025), OWASP Top 10:2025, and known Python
|
|
4
|
+
zero-day exploit patterns. See :mod:`tripwire.cli` for the CLI entry point
|
|
5
|
+
and :mod:`tripwire.checks` for the check registry.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from tripwire.cli import main
|
|
11
|
+
|
|
12
|
+
__all__ = ["main"]
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""AST and text-matching helpers shared by the check functions in
|
|
2
|
+
:mod:`tripwire.checks`.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import ast
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"_LOGGING_METHOD_NAMES",
|
|
11
|
+
"_LOG_METHOD_CALL_RE",
|
|
12
|
+
"_LOG_METHOD_CALL_ANY_RE",
|
|
13
|
+
"_LOG_NAME_TOKENS",
|
|
14
|
+
"_NESTED_SCOPE_TYPES",
|
|
15
|
+
"_call_name",
|
|
16
|
+
"_full_attr",
|
|
17
|
+
"_is_re_compile",
|
|
18
|
+
"_is_sqlalchemy_compile",
|
|
19
|
+
"_has_dynamic_arg",
|
|
20
|
+
"_node_line",
|
|
21
|
+
"_has_log_like_token",
|
|
22
|
+
"_walk_skip_nested_scopes",
|
|
23
|
+
"_handler_has_diagnostic",
|
|
24
|
+
"_skip",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# Logging method names shared by every check that needs to recognize a
|
|
28
|
+
# logging call (CWE-778 diagnostic detection, CWE-117 log injection,
|
|
29
|
+
# CWE-532 log secrets). Defined once so these checks can never independently
|
|
30
|
+
# drift out of sync with each other on which method names count as logging.
|
|
31
|
+
_LOGGING_METHOD_NAMES = frozenset(
|
|
32
|
+
{"debug", "info", "warning", "warn", "error", "critical", "exception", "log"}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Derived from _LOGGING_METHOD_NAMES (not a second hand-typed list) — matches
|
|
36
|
+
# a logging call whose argument is an f-string, e.g. `logger.error(f"...")`.
|
|
37
|
+
_LOG_METHOD_CALL_RE = re.compile(
|
|
38
|
+
r'\.(?:' + "|".join(re.escape(name) for name in _LOGGING_METHOD_NAMES) + r')\s*\(\s*f["\']'
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Same method-name set, without the f-string requirement — for checks (e.g.
|
|
42
|
+
# check_log_secrets) that need to detect any logging call regardless of the
|
|
43
|
+
# argument's literal form.
|
|
44
|
+
_LOG_METHOD_CALL_ANY_RE = re.compile(
|
|
45
|
+
r'\.(?:' + "|".join(re.escape(name) for name in _LOGGING_METHOD_NAMES) + r')\s*\('
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Word-level tokens (not raw substrings) that mark a bare function call as
|
|
49
|
+
# logging-related, e.g. `_log(logger, "debug", ...)` or `log_error(...)`.
|
|
50
|
+
# Matching on whole '.'/'_'-delimited tokens rather than a raw substring test
|
|
51
|
+
# avoids false positives on unrelated names that merely end in "...log" with
|
|
52
|
+
# no delimiter — `catalog.get()`, `dialog.close()`, `backlog.append()`, and
|
|
53
|
+
# `analog_signal.process()` all contain the substring "log" but are not
|
|
54
|
+
# logging calls; none of them tokenize to a standalone "log"-family word.
|
|
55
|
+
_LOG_NAME_TOKENS = frozenset({"log", "logs", "logger", "logging"})
|
|
56
|
+
|
|
57
|
+
# AST node types that introduce a new, separately-executed scope. A statement
|
|
58
|
+
# merely *defined* inside one of these (a nested function/lambda/class body)
|
|
59
|
+
# does not run when the enclosing except block runs, so diagnostics found
|
|
60
|
+
# there don't count as the handler having one.
|
|
61
|
+
_NESTED_SCOPE_TYPES = (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda, ast.ClassDef)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _call_name(node: ast.Call) -> str:
|
|
65
|
+
if isinstance(node.func, ast.Name):
|
|
66
|
+
return node.func.id
|
|
67
|
+
if isinstance(node.func, ast.Attribute):
|
|
68
|
+
return node.func.attr
|
|
69
|
+
return ""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _full_attr(node: ast.Call) -> str:
|
|
73
|
+
parts: list[str] = []
|
|
74
|
+
cur = node.func
|
|
75
|
+
while isinstance(cur, ast.Attribute):
|
|
76
|
+
parts.append(cur.attr)
|
|
77
|
+
cur = cur.value
|
|
78
|
+
if isinstance(cur, ast.Name):
|
|
79
|
+
parts.append(cur.id)
|
|
80
|
+
parts.reverse()
|
|
81
|
+
return ".".join(parts)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _is_re_compile(node: ast.Call) -> bool:
|
|
85
|
+
name = _full_attr(node)
|
|
86
|
+
return name in ("re.compile", "re.compile",) or _call_name(node) == "compile" and isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == "re"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _is_sqlalchemy_compile(node: ast.Call) -> bool:
|
|
90
|
+
name = _full_attr(node)
|
|
91
|
+
return "compile" in name and any(x in name for x in ("statement", "query", "select", "sql"))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _has_dynamic_arg(node: ast.Call) -> bool:
|
|
95
|
+
"""Check if any positional or keyword argument to a call is dynamic (not a constant)."""
|
|
96
|
+
for arg in node.args:
|
|
97
|
+
if not isinstance(arg, (ast.Constant, ast.Str, ast.Num, ast.NameConstant)):
|
|
98
|
+
return True
|
|
99
|
+
for kw in node.keywords:
|
|
100
|
+
if not isinstance(kw.value, (ast.Constant, ast.Str, ast.Num, ast.NameConstant)):
|
|
101
|
+
return True
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _node_line(node: ast.AST) -> int:
|
|
106
|
+
return getattr(node, "lineno", 0)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _has_log_like_token(full_name: str) -> bool:
|
|
110
|
+
"""True if any ``.``/``_``-delimited token in *full_name* is a
|
|
111
|
+
logging-related word, e.g. ``"log"`` or ``"logger"`` — not merely present
|
|
112
|
+
as a substring somewhere inside a longer, unrelated word.
|
|
113
|
+
"""
|
|
114
|
+
tokens = re.split(r"[._]+", full_name.lower())
|
|
115
|
+
return any(tok in _LOG_NAME_TOKENS for tok in tokens if tok)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _walk_skip_nested_scopes(node: ast.AST):
|
|
119
|
+
"""Like ``ast.walk()``, but does not descend into nested function/lambda/
|
|
120
|
+
class bodies — see ``_NESTED_SCOPE_TYPES`` for why.
|
|
121
|
+
|
|
122
|
+
Checks *node itself* (not just its children) against ``_NESTED_SCOPE_TYPES``
|
|
123
|
+
before recursing, so this is correct both when a scope-creating node is
|
|
124
|
+
reached via recursion from a wrapping statement (e.g. a ``Lambda`` inside
|
|
125
|
+
an ``Assign``) and when it *is* the starting node passed in directly (e.g.
|
|
126
|
+
a top-level ``def`` statement in the handler body being walked itself).
|
|
127
|
+
"""
|
|
128
|
+
yield node
|
|
129
|
+
if isinstance(node, _NESTED_SCOPE_TYPES):
|
|
130
|
+
return
|
|
131
|
+
for child in ast.iter_child_nodes(node):
|
|
132
|
+
yield from _walk_skip_nested_scopes(child)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _handler_has_diagnostic(handler: ast.ExceptHandler) -> bool:
|
|
136
|
+
"""True if an ``except`` block logs, re-raises, or otherwise surfaces the
|
|
137
|
+
exception rather than silently swallowing it.
|
|
138
|
+
|
|
139
|
+
Looks for: a call whose attribute name is a logging method (``logger.debug(...)``,
|
|
140
|
+
``self.logger.error(...)``); a bare function call whose name tokenizes to a
|
|
141
|
+
logging-related word (catches helper wrappers like ``_log(logger, "debug", ...)``
|
|
142
|
+
without false-positiving on unrelated names like ``catalog``/``dialog``); ``warnings.warn(...)``;
|
|
143
|
+
or a ``raise`` statement anywhere in the block (including nested ``if``/``try``,
|
|
144
|
+
but not inside a nested function/lambda/class body — code merely defined there
|
|
145
|
+
doesn't run as part of this handler). Walks the whole subtree, not just
|
|
146
|
+
top-level statements, so a diagnostic inside a nested block still counts.
|
|
147
|
+
"""
|
|
148
|
+
for stmt in handler.body:
|
|
149
|
+
for node in _walk_skip_nested_scopes(stmt):
|
|
150
|
+
if isinstance(node, ast.Raise):
|
|
151
|
+
return True
|
|
152
|
+
if not isinstance(node, ast.Call):
|
|
153
|
+
continue
|
|
154
|
+
if isinstance(node.func, ast.Attribute) and node.func.attr in _LOGGING_METHOD_NAMES:
|
|
155
|
+
return True
|
|
156
|
+
full = _full_attr(node)
|
|
157
|
+
if full == "warnings.warn" or _has_log_like_token(full):
|
|
158
|
+
return True
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _skip(line: str) -> bool:
|
|
163
|
+
return line.strip().startswith("#")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Aggregates every check function into a single ordered CHECKS registry.
|
|
2
|
+
|
|
3
|
+
Each entry is ``(display_name, check_fn)``; ``check_fn`` receives
|
|
4
|
+
``(path, lines, tree)`` and returns ``list[Finding]``. See
|
|
5
|
+
:mod:`tripwire.checks.ast_checks`, :mod:`tripwire.checks.text_checks`,
|
|
6
|
+
and :mod:`tripwire.checks.manifest_checks` for the implementations.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from tripwire.checks.ast_checks import (
|
|
11
|
+
check_decode_exec_chains,
|
|
12
|
+
check_eval_exec,
|
|
13
|
+
check_except_pass,
|
|
14
|
+
check_format_string,
|
|
15
|
+
check_insecure_default,
|
|
16
|
+
check_insecure_random,
|
|
17
|
+
check_insufficient_logging,
|
|
18
|
+
check_numpy_load,
|
|
19
|
+
check_pandas_eval,
|
|
20
|
+
check_parquet_arrow_deserialize,
|
|
21
|
+
check_request_timeout,
|
|
22
|
+
check_torch_load,
|
|
23
|
+
)
|
|
24
|
+
from tripwire.checks.manifest_checks import (
|
|
25
|
+
check_dependency_cve,
|
|
26
|
+
check_unbounded_pins,
|
|
27
|
+
)
|
|
28
|
+
from tripwire.checks.text_checks import (
|
|
29
|
+
check_arbitrary_write,
|
|
30
|
+
check_assert_security,
|
|
31
|
+
check_command_injection,
|
|
32
|
+
check_crlf,
|
|
33
|
+
check_debug_mode,
|
|
34
|
+
check_exec_driver_sql,
|
|
35
|
+
check_hardcoded_secrets,
|
|
36
|
+
check_hardcoded_tokens,
|
|
37
|
+
check_ldap,
|
|
38
|
+
check_log_injection,
|
|
39
|
+
check_log_secrets,
|
|
40
|
+
check_model_file_load,
|
|
41
|
+
check_null_byte,
|
|
42
|
+
check_numpy_load_lib,
|
|
43
|
+
check_open_redirect,
|
|
44
|
+
check_pandas_pickle,
|
|
45
|
+
check_pandas_xml_xxe,
|
|
46
|
+
check_path_traversal,
|
|
47
|
+
check_pickle,
|
|
48
|
+
check_pth_startup_hooks,
|
|
49
|
+
check_redos,
|
|
50
|
+
check_resource_limits,
|
|
51
|
+
check_sensitive_exposure,
|
|
52
|
+
check_sql_injection,
|
|
53
|
+
check_ssh_host_key,
|
|
54
|
+
check_ssrf,
|
|
55
|
+
check_ssti,
|
|
56
|
+
check_supply_chain,
|
|
57
|
+
check_symlink,
|
|
58
|
+
check_tempfile,
|
|
59
|
+
check_timing_attack,
|
|
60
|
+
check_tls_verify,
|
|
61
|
+
check_trojan_source,
|
|
62
|
+
check_weak_crypto,
|
|
63
|
+
check_weak_hash,
|
|
64
|
+
check_xxe,
|
|
65
|
+
check_yaml_deserialize,
|
|
66
|
+
check_zipslip,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
__all__ = ["CHECKS"]
|
|
70
|
+
|
|
71
|
+
CHECKS = [
|
|
72
|
+
("Pickle Deserialization (CWE-502)", check_pickle),
|
|
73
|
+
("eval/exec/compile Injection (CWE-94)", check_eval_exec),
|
|
74
|
+
("Command Injection (CWE-78)", check_command_injection),
|
|
75
|
+
("SQL Injection (CWE-89)", check_sql_injection),
|
|
76
|
+
("Path Traversal (CWE-22)", check_path_traversal),
|
|
77
|
+
("Hardcoded Secrets (CWE-798)", check_hardcoded_secrets),
|
|
78
|
+
("YAML Unsafe Deserialization (CWE-502)", check_yaml_deserialize),
|
|
79
|
+
("XXE (CWE-611)", check_xxe),
|
|
80
|
+
("SSRF (CWE-918)", check_ssrf),
|
|
81
|
+
("Insecure Temp File (CWE-377)", check_tempfile),
|
|
82
|
+
("Symlink Following (CWE-61)", check_symlink),
|
|
83
|
+
("ReDoS (CWE-1333)", check_redos),
|
|
84
|
+
("Assert Security (CWE-617/670)", check_assert_security),
|
|
85
|
+
("exec_driver_sql Safety (CWE-89)", check_exec_driver_sql),
|
|
86
|
+
("Supply Chain Dep Confusion (CWE-1104)", check_supply_chain),
|
|
87
|
+
("Active Debug Code (CWE-489)", check_debug_mode),
|
|
88
|
+
("Insufficient Logging (CWE-778)", check_insufficient_logging),
|
|
89
|
+
("Timing Attack (CWE-208)", check_timing_attack),
|
|
90
|
+
("Sensitive Exposure (CWE-200)", check_sensitive_exposure),
|
|
91
|
+
("NUL Byte Injection (CWE-158)", check_null_byte),
|
|
92
|
+
("Resource Limits (CWE-770)", check_resource_limits),
|
|
93
|
+
("Insecure Random (CWE-338)", check_insecure_random),
|
|
94
|
+
("SSTI (CWE-1336)", check_ssti),
|
|
95
|
+
("CRLF Injection (CWE-93)", check_crlf),
|
|
96
|
+
("LDAP Injection (CWE-90)", check_ldap),
|
|
97
|
+
("Weak Hash (CWE-328)", check_weak_hash),
|
|
98
|
+
("Open Redirect (CWE-601)", check_open_redirect),
|
|
99
|
+
("Log Injection (CWE-117)", check_log_injection),
|
|
100
|
+
("Format String (CWE-134)", check_format_string),
|
|
101
|
+
("Arbitrary File Write (CWE-73)", check_arbitrary_write),
|
|
102
|
+
("Insecure Default (CWE-453)", check_insecure_default),
|
|
103
|
+
("Log Secrets (CWE-532)", check_log_secrets),
|
|
104
|
+
("Disabled TLS Verify (CWE-295)", check_tls_verify),
|
|
105
|
+
("ZipSlip / TarSlip (CWE-22)", check_zipslip),
|
|
106
|
+
("Hardcoded API Keys (CWE-798)", check_hardcoded_tokens),
|
|
107
|
+
("Weak Crypto (CWE-327)", check_weak_crypto),
|
|
108
|
+
("Dependency CVE Scan (CWE-1104)", check_dependency_cve),
|
|
109
|
+
("Trojan Source (CWE-1007)", check_trojan_source),
|
|
110
|
+
("HTTP Request Timeout (CWE-1088)", check_request_timeout),
|
|
111
|
+
("torch.load weights_only (CWE-502)", check_torch_load),
|
|
112
|
+
("SSH Host Key Verification (CWE-322)", check_ssh_host_key),
|
|
113
|
+
("except:pass/continue (CWE-391)", check_except_pass),
|
|
114
|
+
("pandas eval/query Code Injection (CWE-94)", check_pandas_eval),
|
|
115
|
+
("numpy.load allow_pickle (CWE-502)", check_numpy_load),
|
|
116
|
+
("pandas.read_pickle (CWE-502)", check_pandas_pickle),
|
|
117
|
+
("numpy.load library injection (CWE-114)", check_numpy_load_lib),
|
|
118
|
+
("pandas.read_xml XXE (CWE-611)", check_pandas_xml_xxe),
|
|
119
|
+
(".pth Startup Hooks (CWE-829 / T1546.018)", check_pth_startup_hooks),
|
|
120
|
+
("Parquet Arrow Deserialization (CWE-502 / CVE-2026-41486)", check_parquet_arrow_deserialize),
|
|
121
|
+
("Unbounded Dependency Pins (CWE-1104)", check_unbounded_pins),
|
|
122
|
+
("ML Model File Loading (OWASP ML06 / CWE-502)", check_model_file_load),
|
|
123
|
+
("Decode-then-Execute Chains (CWE-94)", check_decode_exec_chains),
|
|
124
|
+
]
|