Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

226 lines
7.2 KiB
Python

"""
Tests for the check-safe-requests pre-commit hook.
Ensures the hook correctly detects unsafe requests usage that bypasses SSRF protection.
"""
import ast
import sys
from importlib import import_module
from pathlib import Path
# Add the pre-commit hooks directory to path
HOOKS_DIR = Path(__file__).parent.parent.parent / ".pre-commit-hooks"
sys.path.insert(0, str(HOOKS_DIR))
# Import the checker from the hook (must be after sys.path modification)
hook_module = import_module("check-safe-requests") # noqa: E402
RequestsChecker = hook_module.RequestsChecker
class TestRequestsChecker:
"""Tests for the RequestsChecker AST visitor."""
def _check_code(self, code: str, filename: str = "src/module.py") -> list:
"""Helper to check code and return errors."""
tree = ast.parse(code)
checker = RequestsChecker(filename)
checker.visit(tree)
return checker.errors
def test_detects_requests_get(self):
"""Should detect requests.get() calls."""
code = """
import requests
response = requests.get("http://example.com")
"""
errors = self._check_code(code)
assert len(errors) == 1
assert "requests.get()" in errors[0][1]
assert "safe_get()" in errors[0][1]
def test_detects_requests_post(self):
"""Should detect requests.post() calls."""
code = """
import requests
response = requests.post("http://example.com", json={"key": "value"})
"""
errors = self._check_code(code)
assert len(errors) == 1
assert "requests.post()" in errors[0][1]
assert "safe_post()" in errors[0][1]
def test_detects_requests_session(self):
"""Should detect requests.Session() instantiation."""
code = """
import requests
session = requests.Session()
"""
errors = self._check_code(code)
assert len(errors) == 1
assert "requests.Session()" in errors[0][1]
assert "SafeSession()" in errors[0][1]
def test_detects_requests_put(self):
"""Should detect requests.put() calls."""
code = """
import requests
response = requests.put("http://example.com", data="test")
"""
errors = self._check_code(code)
assert len(errors) == 1
assert "requests.put()" in errors[0][1]
def test_detects_requests_delete(self):
"""Should detect requests.delete() calls."""
code = """
import requests
response = requests.delete("http://example.com/resource/1")
"""
errors = self._check_code(code)
assert len(errors) == 1
assert "requests.delete()" in errors[0][1]
def test_detects_multiple_violations(self):
"""Should detect multiple violations in one file."""
code = """
import requests
session = requests.Session()
response = requests.get("http://example.com")
data = requests.post("http://example.com/api", json={})
"""
errors = self._check_code(code)
assert len(errors) == 3
def test_allows_safe_requests_module(self):
"""Should allow direct requests in safe_requests.py (the wrapper itself)."""
code = """
import requests
response = requests.get("http://example.com")
"""
errors = self._check_code(
code, filename="src/security/safe_requests.py"
)
assert len(errors) == 0
def test_allows_test_files(self):
"""Should allow direct requests in test files."""
code = """
import requests
response = requests.get("http://example.com")
"""
# Test various test file patterns
assert len(self._check_code(code, filename="tests/test_api.py")) == 0
assert (
len(self._check_code(code, filename="tests/security/test_ssrf.py"))
== 0
)
assert len(self._check_code(code, filename="src/module_test.py")) == 0
def test_ignores_safe_get(self):
"""Should not flag safe_get() calls."""
code = """
from security import safe_get
response = safe_get("http://example.com")
"""
errors = self._check_code(code)
assert len(errors) == 0
def test_ignores_safe_post(self):
"""Should not flag safe_post() calls."""
code = """
from security import safe_post
response = safe_post("http://example.com", json={})
"""
errors = self._check_code(code)
assert len(errors) == 0
def test_ignores_safe_session(self):
"""Should not flag SafeSession() calls."""
code = """
from security import SafeSession
session = SafeSession(allow_localhost=True)
"""
errors = self._check_code(code)
assert len(errors) == 0
def test_ignores_other_modules_get(self):
"""Should not flag get() calls on other modules."""
code = """
import os
value = os.environ.get("KEY")
my_dict = {"key": "value"}
result = my_dict.get("key")
"""
errors = self._check_code(code)
assert len(errors) == 0
def test_reports_correct_line_numbers(self):
"""Should report the correct line number for violations."""
code = """
import requests
# Some comment
x = 1
response = requests.get("http://example.com")
"""
errors = self._check_code(code)
assert len(errors) == 1
assert errors[0][0] == 7 # Line 7
class TestHookIntegration:
"""Integration tests for the hook as a whole."""
def test_check_file_returns_true_for_clean_file(self, tmp_path):
"""check_file should return True for files without violations."""
clean_file = tmp_path / "clean.py"
clean_file.write_text("""
from security import safe_get
response = safe_get("http://example.com")
""")
assert hook_module.check_file(str(clean_file)) is True
def test_check_file_returns_false_for_violations(
self, tmp_path, monkeypatch
):
"""check_file should return False for files with violations."""
# Use a filename that doesn't match exclusion patterns
bad_file = tmp_path / "my_module.py"
bad_file.write_text("""
import requests
response = requests.get("http://example.com")
""")
# Monkeypatch the file path to avoid pytest temp dir containing "test_"
def patched_check(filename):
# Replace the actual path with a clean one for pattern matching
import ast
with open(filename, "r", encoding="utf-8") as f:
content = f.read()
tree = ast.parse(content, filename=filename)
# Use a clean filename for pattern matching
checker = RequestsChecker("src/some_module.py")
checker.visit(tree)
return len(checker.errors) == 0
result = patched_check(str(bad_file))
assert result is False
def test_check_file_handles_non_python_files(self, tmp_path):
"""check_file should return True for non-Python files."""
txt_file = tmp_path / "readme.txt"
txt_file.write_text("This is not Python")
assert hook_module.check_file(str(txt_file)) is True
def test_check_file_handles_syntax_errors(self, tmp_path):
"""check_file should handle files with syntax errors gracefully."""
bad_syntax = tmp_path / "syntax_error.py"
bad_syntax.write_text("def broken(:\n pass")
# Should not raise, just return True (let other tools handle syntax)
assert hook_module.check_file(str(bad_syntax)) is True