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

289 lines
9.8 KiB
Python

"""
SQL Injection Prevention Tests
Tests that verify SQL injection attacks are properly prevented
through SQLAlchemy ORM usage and proper input sanitization.
"""
import pytest
from datetime import datetime, UTC
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from tests.test_utils import add_src_to_path
add_src_to_path()
from local_deep_research.database.models import ( # noqa: E402
Base,
ResearchHistory,
)
class TestSQLInjectionPrevention:
"""Test SQL injection prevention in database queries."""
@pytest.fixture
def test_db_session(self):
"""Create a test database session."""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
engine.dispose()
def test_research_query_with_malicious_id(self, test_db_session):
"""Test that SQLAlchemy ORM prevents SQL injection via filter_by."""
from local_deep_research.storage.database import (
DatabaseReportStorage,
)
storage = DatabaseReportStorage(test_db_session)
# Attempt SQL injection through research_id parameter
malicious_ids = [
"1' OR '1'='1",
"1; DROP TABLE research_history;--",
"1' UNION SELECT * FROM users--",
"' OR 1=1--",
"admin'--",
"1' AND 1=1 UNION SELECT NULL, version()--",
]
for malicious_id in malicious_ids:
# This should safely return None, not execute SQL injection
result = storage.get_report(malicious_id)
assert result is None, (
f"Malicious ID not properly handled: {malicious_id}"
)
def test_query_filter_sql_injection(self, test_db_session):
"""Test that query filtering prevents SQL injection."""
# Malicious queries that could attempt SQL injection
malicious_queries = [
"admin' OR '1'='1",
"query'; DROP TABLE research_history;--",
"' OR 1=1--",
"admin'/*",
"query' UNION SELECT * FROM research_history--",
]
for query in malicious_queries:
# Query should handle malicious input safely
result = (
test_db_session.query(ResearchHistory)
.filter_by(query=query)
.first()
)
# Should return None (no match) rather than executing injection
assert result is None
def test_search_text_sanitization(self, test_db_session):
"""Test that search/filter text inputs are properly sanitized."""
# Create test research entry
research = ResearchHistory(
id="test-id-123",
query="Test query",
mode="auto",
status="pending",
created_at=datetime.now(UTC).isoformat(),
report_content="Test content",
)
test_db_session.add(research)
test_db_session.commit()
# Malicious search patterns
malicious_patterns = [
"%' OR '1'='1",
"test'; DELETE FROM research_history WHERE '1'='1",
"' UNION SELECT * FROM users--",
]
for pattern in malicious_patterns:
# Search using SQLAlchemy ORM (should be safe)
results = (
test_db_session.query(ResearchHistory)
.filter(ResearchHistory.query.like(f"%{pattern}%"))
.all()
)
# Should not execute SQL injection, just search literally
assert isinstance(results, list)
def test_raw_sql_uses_parameterized_queries(self, test_db_session):
"""
Test that if raw SQL is used anywhere, it uses parameterized queries.
This is a defensive test to catch potential future vulnerabilities.
"""
# Example of UNSAFE raw SQL (what we should never do):
# engine.execute(f"SELECT * FROM users WHERE username = '{username}'")
# Example of SAFE raw SQL (what we should do if raw SQL is needed):
safe_param = "admin' OR '1'='1"
result = test_db_session.execute(
text("SELECT :param as test"), {"param": safe_param}
)
row = result.fetchone()
# The parameter should be treated as literal string, not SQL
assert row[0] == safe_param
# Should not return anything else (no injection occurred)
def test_metadata_json_injection(self, test_db_session):
"""Test that JSON metadata fields prevent injection attacks."""
from local_deep_research.storage.database import (
DatabaseReportStorage,
)
storage = DatabaseReportStorage(test_db_session)
# Create research entry
research = ResearchHistory(
id="test-metadata-injection",
query="Test query",
mode="auto",
status="pending",
created_at=datetime.now(UTC).isoformat(),
)
test_db_session.add(research)
test_db_session.commit()
# Attempt injection through JSON metadata
malicious_metadata = {
"key": "'; DROP TABLE research_history;--",
"nested": {"sql": "1' OR '1'='1"},
"array": ["admin'--", "user'; DELETE FROM users;"],
}
# Save should handle malicious JSON safely
result = storage.save_report(
"test-metadata-injection",
"Test content",
metadata=malicious_metadata,
)
assert result is True
# Retrieve and verify data is stored literally, not executed
retrieved = storage.get_report_with_metadata("test-metadata-injection")
assert retrieved is not None
assert (
retrieved["metadata"]["key"] == "'; DROP TABLE research_history;--"
)
def test_special_characters_in_inputs(self, test_db_session):
"""Test that special SQL characters are properly escaped."""
special_chars_inputs = [
"test'query",
'test"query',
"test`query",
"test\\query",
"test%query",
"test_query",
"test;query",
"test--query",
"test/*comment*/query",
]
for input_str in special_chars_inputs:
# Create research with special characters
research = ResearchHistory(
id=f"test-{hash(input_str)}",
query=input_str,
mode="auto",
status="pending",
created_at=datetime.now(UTC).isoformat(),
)
test_db_session.add(research)
test_db_session.commit()
# All entries should be saved and retrievable
count = test_db_session.query(ResearchHistory).count()
assert count == len(special_chars_inputs)
class TestDatabaseStorageSecurityIntegration:
"""Integration tests for database storage security."""
@pytest.fixture
def test_db_session(self):
"""Create a test database session."""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
engine.dispose()
def test_end_to_end_sql_injection_prevention(self, test_db_session):
"""
End-to-end test ensuring SQL injection cannot occur through
the normal application flow.
"""
from local_deep_research.storage.database import (
DatabaseReportStorage,
)
storage = DatabaseReportStorage(test_db_session)
# Simulate attacker attempting SQL injection through multiple vectors
attack_vectors = {
"research_id": "1' OR '1'='1",
"content": "'; DROP TABLE research_history;--",
}
# Create research with attack vectors
research = ResearchHistory(
id=attack_vectors["research_id"],
query="Normal query",
mode="auto",
status="pending",
created_at=datetime.now(UTC).isoformat(),
)
test_db_session.add(research)
test_db_session.commit()
# Save report with malicious content
result = storage.save_report(
attack_vectors["research_id"],
attack_vectors["content"],
)
# All operations should complete safely
assert result is True
# Retrieve and verify all malicious inputs are stored as literals
retrieved = storage.get_report(attack_vectors["research_id"])
assert retrieved == attack_vectors["content"]
# Database should still be intact (no tables dropped)
tables = test_db_session.execute(
text("SELECT name FROM sqlite_master WHERE type='table'")
).fetchall()
assert len(tables) > 0 # Tables still exist
@pytest.mark.skip(reason="documentation/placeholder test - not implemented")
def test_orm_provides_sql_injection_protection():
"""
Verify that SQLAlchemy ORM is being used correctly to prevent SQL injection.
This is a documentation test that verifies our security assumptions.
"""
# SQLAlchemy ORM automatically uses parameterized queries
# This test documents that we rely on ORM for SQL injection prevention
# Verify that we're using ORM methods, not raw SQL:
# - query().filter_by() - ✓ Safe (parameterized)
# - query().filter() - ✓ Safe (parameterized)
# - session.add() - ✓ Safe (parameterized)
# - session.query() - ✓ Safe (parameterized)
# NOT using (these would be unsafe):
# - session.execute(f"SELECT * FROM table WHERE id = '{user_input}'") - ✗ Unsafe
# - raw SQL string concatenation - ✗ Unsafe
# This test passes to document our security model
assert True