Skip to content

fix: add security regression tests for CVE fixes#3347

Open
deacon-mp wants to merge 1 commit intomasterfrom
fix/add-security-regression-tests
Open

fix: add security regression tests for CVE fixes#3347
deacon-mp wants to merge 1 commit intomasterfrom
fix/add-security-regression-tests

Conversation

@deacon-mp
Copy link
Copy Markdown
Contributor

Summary

Add automated security regression tests that prevent reintroduction of fixed CVEs.

Tests (16 total)

TestDependencyVersions (8 tests)

  • Parametrized check of 7 dependencies against minimum safe versions
  • Dedicated pyasn1 CVE-2026-30922 regression test
  • Fails if requirements.txt pins below known-safe versions

TestRequestsTimeout (4 tests)

  • Regression tests for steganography.py, ragdoll.py, elasticat.py
  • Broad scan of all plugin code for missing timeouts (xfail/informational)

TestNoVerifyFalse (2 tests)

  • Regression for steganography.py verify=True
  • Broad plugin scan for verify=False (xfail/informational)

TestNoShellTrue (2 tests)

  • Core code and plugin scan for shell=True
  • Allowlist for intentional agent payloads (ragdoll.py, manx.py)

Test plan

  • Run pytest tests/test_security_regression.py -v
  • Verify all targeted tests pass on fixed branches
  • Verify targeted tests fail on vulnerable code (pre-fix)

Add test_security_regression.py with 16 tests that guard against
reintroduction of security issues:

- TestDependencyVersions: checks requirements.txt pins against
  minimum safe versions for 7 dependencies (incl. CVE-2026-30922)
- TestRequestsTimeout: verifies all requests calls include timeout
- TestNoVerifyFalse: ensures no verify=False in plugin code
- TestNoShellTrue: audits shell=True usage against allowlist

These tests will fail if a future change pins a vulnerable dependency
version or introduces insecure request/subprocess patterns.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new pytest-based “security regression” test suite intended to prevent reintroduction of known dependency/CVE and code-level security anti-patterns by scanning requirements.txt and walking the repository’s Python sources.

Changes:

  • Introduces dependency minimum-version checks for a set of security-sensitive packages.
  • Adds AST-based scans for requests calls missing timeout, verify=False, and shell=True usage (with an allowlist).
  • Scans both core (app/) and plugin (plugins/) trees to surface findings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +14 to +16
from pathlib import Path
from importlib.metadata import version as pkg_version
from packaging.version import Version
Comment on lines +24 to +33
# Minimum safe versions for dependencies with known CVEs
MINIMUM_SAFE_VERSIONS = {
"pyasn1": "0.6.3", # CVE-2026-30922
"cryptography": "44.0.0",
"jinja2": "3.1.4",
"pyyaml": "6.0.1",
"aiohttp": "3.10.11",
"lxml": "5.3.0",
"setuptools": "75.0.0",
}
# ~= means compatible release; the pinned version itself must be >= minimum
assert pinned >= minimum, (
f"{pkg}~={ver_str} allows versions below minimum safe {min_ver}"
)
Comment on lines +177 to +182
pinned = Version(ver_str)
assert pinned >= Version("0.6.3"), (
f"pyasn1 {op}{ver_str} is vulnerable (CVE-2026-30922). Upgrade to >=0.6.3"
)


Comment on lines +40 to +42
"sandcat.go",
}

Comment on lines +92 to +100
# Match requests.get(...), requests.post(...), etc.
if isinstance(func, ast.Attribute) and func.attr in (
"get", "post", "put", "patch", "delete", "head", "request"
):
# Check if the value is 'requests' or an alias
if isinstance(func.value, ast.Name) and func.value.id == "requests":
kwarg_names = [kw.arg for kw in node.keywords]
if "timeout" not in kwarg_names:
issues.append((node.lineno, f"requests.{func.attr}() missing timeout"))
Comment on lines +104 to +119
def _find_verify_false(filepath):
"""Find requests calls with verify=False."""
issues = []
try:
source = filepath.read_text(encoding="utf-8", errors="ignore")
tree = ast.parse(source, filename=str(filepath))
except SyntaxError:
return issues

for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
for kw in node.keywords:
if kw.arg == "verify" and isinstance(kw.value, ast.Constant) and kw.value.value is False:
issues.append((node.lineno, "verify=False disables SSL certificate verification"))
return issues
Comment on lines +131 to +137
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
for kw in node.keywords:
if kw.arg == "shell" and isinstance(kw.value, ast.Constant) and kw.value.value is True:
if filepath.name not in SHELL_TRUE_ALLOWLIST:
issues.append((node.lineno, "subprocess call with shell=True"))
Comment on lines +222 to +227
if all_issues:
# Report as warning rather than hard fail since some may be intentional
pytest.xfail(
f"Found {len(all_issues)} requests call(s) without timeout:\n"
+ "\n".join(all_issues[:20])
)
Comment on lines +213 to +221
def test_all_plugin_requests_have_timeout(self):
"""Scan all plugin Python files for requests calls without timeout."""
all_issues = []
for filepath in _iter_python_files(PLUGIN_DIR):
issues = _find_requests_calls_without_timeout(filepath)
if issues:
for lineno, msg in issues:
all_issues.append(f"{filepath.relative_to(ROOT_DIR)}:{lineno} - {msg}")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants