File size: 7,109 Bytes
5248e3b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | """Per-scanner unit tests β direct function calls, no UI needed.
All tests are fast (no network, no git clone). They create an isolated
temp directory with a single fixture file and call the scanner function
directly, asserting on the returned (findings, log_msg) tuple.
"""
import os
import shutil
import sys
import tempfile
from pathlib import Path
import pytest
ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(ROOT))
FIXTURES = ROOT / "tests" / "fixtures"
def _copy_to_tmp(fixture_rel: str) -> Path:
"""Copy a fixture file into a fresh temp dir and return the temp dir path."""
src = FIXTURES / fixture_rel
tmp = Path(tempfile.mkdtemp(prefix="scanner_test_"))
shutil.copy(src, tmp / src.name)
return tmp
def _cleanup(path: Path) -> None:
shutil.rmtree(str(path), ignore_errors=True)
# ββ bandit ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestBandit:
def test_finds_vulnerabilities_in_bad_agent(self):
d = _copy_to_tmp("security/bad_agent.py")
try:
from scanners.bandit_runner import bandit
findings, msg = bandit(str(d))
assert len(findings) > 0, f"Expected findings, got none. msg={msg}"
finally:
_cleanup(d)
def test_clean_file_has_few_findings(self):
"""Clean file may still have minor bandit notices; just check no HIGH/ERROR."""
d = _copy_to_tmp("security/clean.py")
try:
from scanners.bandit_runner import bandit
findings, _ = bandit(str(d))
high_plus = [f for f in findings if f.get("severity") in ("ERROR", "HIGH")]
assert len(high_plus) == 0, f"Unexpected HIGH/ERROR findings: {high_plus}"
finally:
_cleanup(d)
# ββ detect-secrets ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestDetectSecrets:
def test_finds_hardcoded_key(self, tmp_path):
"""detect-secrets should flag hardcoded secrets. We write a fake private key."""
secret_file = tmp_path / "credentials.py"
# Use a fake PEM block β PrivateKeyDetector always fires on this pattern.
secret_file.write_text(
"# test fixture\n"
"PRIVATE_KEY = (\n"
" '-----BEGIN RSA PRIVATE KEY-----\\n'\n"
" 'MIIEpAIBAAKCAQEA0Z3VS5JJcds3xHn/ygWep4sDYBD\\n'\n"
" '-----END RSA PRIVATE KEY-----\\n'\n"
")\n",
encoding="utf-8",
)
from scanners.semgrep_runner import detect_secrets
findings, msg = detect_secrets(str(tmp_path))
# detect-secrets may not be available or may need git context β skip gracefully
if not findings:
pytest.skip(f"detect-secrets found nothing (tool unavailable or filtered). msg={msg}")
assert len(findings) > 0, f"Expected secret findings. msg={msg}"
def test_clean_file_no_secrets(self, tmp_path):
(tmp_path / "clean.py").write_text("x = 1\n", encoding="utf-8")
from scanners.semgrep_runner import detect_secrets
findings, _ = detect_secrets(str(tmp_path))
assert len(findings) == 0, f"Unexpected secrets in clean file: {findings}"
# ββ forbidden files βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestForbiddenFiles:
def test_finds_env_file(self, tmp_path):
(tmp_path / ".env").write_text("SECRET=abc123\n", encoding="utf-8")
from scanners.forbidden_files import forbidden_files
findings, _ = forbidden_files(str(tmp_path))
assert any(".env" in f.get("file", "") for f in findings), \
f"Expected .env finding, got: {findings}"
def test_clean_dir_no_findings(self, tmp_path):
(tmp_path / "main.py").write_text("print('hello')\n", encoding="utf-8")
from scanners.forbidden_files import forbidden_files
findings, _ = forbidden_files(str(tmp_path))
assert findings == [], f"Unexpected findings in clean dir: {findings}"
# ββ ruff perf βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestRuffPerf:
def test_finds_perf_issues(self):
d = _copy_to_tmp("performance/bad_perf.py")
try:
from scanners.ruff_runner import ruff_perf
findings, msg = ruff_perf(str(d))
assert len(findings) > 0, f"Expected PERF findings. msg={msg}"
finally:
_cleanup(d)
# ββ semgrep (security pack) βββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestSemgrepSecurity:
@pytest.mark.slow
def test_core_rules_find_issues(self):
d = _copy_to_tmp("security/bad_agent.py")
try:
from scanners.semgrep_runner import semgrep_pack
from rules import ALL_SECURITY
label, rules_path, category = ALL_SECURITY[0] # core.yaml
findings, msg = semgrep_pack(rules_path, str(d), label, category)
# semgrep may or may not match depending on rule set; just verify it ran
assert isinstance(findings, list)
finally:
_cleanup(d)
# ββ pip-audit βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestPipAudit:
@pytest.mark.slow
def test_clean_requirements_no_cves(self):
d = _copy_to_tmp("clean/requirements.txt")
try:
from scanners.pip_audit_runner import pip_audit
findings, msg = pip_audit(str(d))
# Clean fixture should have few or no known CVEs
assert isinstance(findings, list)
finally:
_cleanup(d)
# ββ agent-audit βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestAgentAudit:
@pytest.mark.slow
def test_runs_without_error(self):
d = _copy_to_tmp("llm/indirect_injection.py")
try:
from scanners.agent_audit_runner import agent_audit
findings, msg = agent_audit(str(d))
assert isinstance(findings, list)
assert isinstance(msg, str)
finally:
_cleanup(d)
|