#!/usr/bin/env python3 """Tests for scripts/security_linter.py — Issue #158: 9.4 Security Linter.""" import sys import tempfile from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent / "scripts")) from security_linter import ( scan_file, scan_directory, generate_json_report, generate_markdown_report, SEVERITY_CRITICAL, SEVERITY_HIGH, SEVERITY_MEDIUM, SEVERITY_LOW, ) def test_scan_file_detects_eval(): with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: f.write("result = eval(user_input)\n") f.flush() findings = scan_file(Path(f.name)) assert len(findings) >= 1 assert findings[0].severity == SEVERITY_CRITICAL assert "eval" in findings[0].issue.lower() def test_scan_file_detects_hardcoded_password(): with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: f.write("password = 'supersecret123'\n") f.flush() findings = scan_file(Path(f.name)) assert any(f.severity == SEVERITY_HIGH for f in findings) def test_scan_file_detects_subprocess_shell_true(): with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: f.write("subprocess.run(cmd, shell=True)\n") f.flush() findings = scan_file(Path(f.name)) assert any(f.severity == SEVERITY_HIGH and "shell" in f.issue.lower() for f in findings) def test_scan_file_detects_pickle(): with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: f.write("data = pickle.loads(raw)\n") f.flush() findings = scan_file(Path(f.name)) assert any(f.severity == SEVERITY_HIGH and "pickle" in f.issue.lower() for f in findings) def test_scan_file_detects_yaml_load(): with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: f.write("config = yaml.load(stream)\n") f.flush() findings = scan_file(Path(f.name)) assert any("yaml.load" in f.issue.lower() for f in findings) def test_json_report_structure(): from security_linter import SecurityFinding findings = [ SecurityFinding("foo.py", 1, "eval() used", SEVERITY_CRITICAL, "CWE-95", "Use ast.literal_eval"), SecurityFinding("bar.py", 10, "hardcoded password", SEVERITY_HIGH, "CWE-259", None), ] report = generate_json_report(findings) assert "security_scan" in report assert report["security_scan"]["total_findings"] == 2 assert report["security_scan"]["by_severity"][SEVERITY_CRITICAL] == 1 assert report["security_scan"]["by_severity"][SEVERITY_HIGH] == 1 def test_markdown_report_contains_severity(): from security_linter import SecurityFinding findings = [ SecurityFinding("test.py", 1, "eval() used", SEVERITY_CRITICAL, "CWE-95", "Use ast.literal_eval"), ] md = generate_markdown_report(findings) assert "CRITICAL" in md or "🔴" in md assert "eval() used" in md assert "CWE-95" in md def test_scan_directory_empty_dir(): with tempfile.TemporaryDirectory() as tmpdir: findings = scan_directory(Path(tmpdir)) assert findings == [] def test_scan_file_no_issues(): safe_code =