""" SQL Injection Attack Tests Tests SQL injection vulnerability prevention in AgentGovernanceService. Verifies that parameterized queries are used and malicious inputs are escaped. OWASP Category: A03:2021 - Injection CWE: CWE-89 (SQL Injection) """ import pytest from sqlalchemy.exc import IntegrityError, ProgrammingError from sqlalchemy.orm import Session from core.agent_governance_service import AgentGovernanceService from core.models import AgentRegistry, AgentStatus # ============================================================================ # Agent ID SQL Injection Tests # ============================================================================ @pytest.mark.sql_injection @pytest.mark.parametrize("malicious_id", [ "'; DROP TABLE agents; --", "' OR '1'='1", "1' UNION SELECT * FROM users --", "'; INSERT INTO agents VALUES ('hacked', 'admin'); --", "1'; DELETE FROM episodes WHERE '1'='1' --", "' OR '1'='1' --", "admin'--", "admin'/*", ]) def test_agent_id_sql_injection_blocked(malicious_id, db_session: Session): """ SECURITY: SQL injection via agent_id parameter. ATTACK: Attempt to inject SQL via agent_id string in can_perform_action(). EXPECTED: Input escaped or parameterized, query returns "agent not found", no SQL injection executed. """ service = AgentGovernanceService(db_session) result = service.can_perform_action( agent_id=malicious_id, action_type="stream_chat" ) # Should not execute injected SQL assert result["allowed"] is False assert "not found" in result["reason"].lower() @pytest.mark.sql_injection def test_agent_id_union_query_blocked(db_session: Session): """ SECURITY: UNION SELECT injection via agent_id. ATTACK: Attempt to extract data from other tables using UNION SELECT. EXPECTED: Query fails, "agent not found" error. """ service = AgentGovernanceService(db_session) malicious_id = "1' UNION SELECT * FROM users --" result = service.can_perform_action( agent_id=malicious_id, action_type="stream_chat" ) # Should not execute UNION assert result["allowed"] is False assert "not found" in result["reason"].lower() @pytest.mark.sql_injection def test_agent_id_drop_table_blocked(db_session: Session): """ SECURITY: DROP TABLE injection via agent_id. ATTACK: Attempt to DROP agents table. EXPECTED: DROP TABLE not executed, agent not found error. """ service = AgentGovernanceService(db_session) malicious_id = "'; DROP TABLE agents; --" result = service.can_perform_action( agent_id=malicious_id, action_type="stream_chat" ) # Verify agent_registry table still exists by querying it agents = db_session.query(AgentRegistry).all() assert agents is not None # Table exists # Should not have dropped table assert result["allowed"] is False assert "not found" in result["reason"].lower() @pytest.mark.sql_injection def test_agent_id_insert_blocked(db_session: Session): """ SECURITY: INSERT injection via agent_id. ATTACK: Attempt to INSERT malicious agent record. EXPECTED: INSERT not executed, agent not found error. """ service = AgentGovernanceService(db_session) initial_count = db_session.query(AgentRegistry).count() malicious_id = "'; INSERT INTO agents VALUES ('hacked', 'admin', 'AUTONOMOUS'); --" result = service.can_perform_action( agent_id=malicious_id, action_type="stream_chat" ) # Verify no new agent was inserted final_count = db_session.query(AgentRegistry).count() assert initial_count == final_count assert result["allowed"] is False # ============================================================================ # User Input SQL Injection Tests # ============================================================================ @pytest.mark.sql_injection @pytest.mark.parametrize("malicious_name", [ "'; DROP TABLE users; --", "' OR '1'='1", "", "$(whoami)", "`cat /etc/passwd`", ]) def test_agent_name_sql_injection_blocked(malicious_name, db_session: Session): """ SECURITY: SQL injection via agent name parameter. ATTACK: Attempt to inject SQL via name field in register_or_update_agent(). EXPECTED: Input escaped or parameterized, agent created safely, malicious name stored as string not executed. """ service = AgentGovernanceService(db_session) agent = service.register_or_update_agent( name=malicious_name, category="test", module_path="test.module", class_name="TestAgent" ) # Agent should be created (input escaped) assert agent is not None # Malicious content should be stored as-is (not executed) assert agent.name == malicious_name @pytest.mark.sql_injection def test_description_field_sql_injection_blocked(db_session: Session): """ SECURITY: SQL injection via description field. ATTACK: Attempt to inject SQL via description parameter. EXPECTED: Input sanitized, description stored as string. """ service = AgentGovernanceService(db_session) malicious_desc = "'; DROP TABLE agents; --" agent = service.register_or_update_agent( name="Test Agent", category="test", module_path="test.module", class_name="TestAgent", description=malicious_desc ) assert agent is not None assert agent.description == malicious_desc # Verify agent_registry table still exists agents = db_session.query(AgentRegistry).all() assert agents is not None @pytest.mark.sql_injection def test_module_path_sql_injection_blocked(db_session: Session): """ SECURITY: SQL injection via module_path parameter. ATTACK: Attempt to inject SQL via module_path. EXPECTED: Input validated, module_path stored safely. """ service = AgentGovernanceService(db_session) malicious_module = "'; DROP TABLE agents; --" # May raise validation error or store safely try: agent = service.register_or_update_agent( name="Test Agent", category="test", module_path=malicious_module, class_name="TestAgent" ) assert agent is not None # If created, should be stored as string assert agent.module_path == malicious_module except (ValueError, AttributeError): # Or validation may reject it pass # ============================================================================ # Database Query SQL Injection Tests # ============================================================================ @pytest.mark.sql_injection def test_filter_query_sql_injection_blocked(db_session: Session): """ SECURITY: SQL injection in filter() clause. ATTACK: Attempt to inject SQL in db.query().filter() with malicious agent_id. EXPECTED: SQLAlchemy uses parameterized queries, injection blocked. """ malicious_id = "1' OR '1'='1" # Query with malicious agent_id agent = db_session.query(AgentRegistry).filter( AgentRegistry.id == malicious_id ).first() # Should return None (no such agent) assert agent is None # Verify no SQL error leaked # (SQLAlchemy parameterizes queries, so this is safe) @pytest.mark.sql_injection def test_order_by_sql_injection_blocked(db_session: Session): """ SECURITY: SQL injection in ORDER BY clause. ATTACK: Attempt to inject SQL in ORDER BY. EXPECTED: Injection blocked, query returns empty or error handled. """ from sqlalchemy import text from sqlalchemy.exc import CompileError # Try to inject in ORDER BY malicious_order = "id; DROP TABLE agents; --" try: # SQLAlchemy should reject this (raises CompileError) agents = db_session.query(AgentRegistry).order_by(malicious_order).all() # If it somehow executes, verify table still exists assert agents is not None except (CompileError, ProgrammingError, AttributeError): # SQLAlchemy correctly rejects malicious input (expected behavior) pass # Verify agent_registry table still exists agents = db_session.query(AgentRegistry).all() assert agents is not None @pytest.mark.sql_injection def test_raw_query_sql_injection_blocked(db_session: Session): """ SECURITY: SQL injection in raw SQL text() queries. ATTACK: Attempt to inject SQL using text() with malicious input. EXPECTED: Parameterization used, injection blocked. """ from sqlalchemy import text malicious_id = "'; DROP TABLE agents; --" # Parameterized query (safe) result = db_session.execute( text("SELECT * FROM agent_registry WHERE id = :agent_id"), {"agent_id": malicious_id} ).fetchone() # Should return None (no such agent) assert result is None # Verify agent_registry table still exists agents = db_session.query(AgentRegistry).all() assert agents is not None # ============================================================================ # Verification Tests # ============================================================================ @pytest.mark.sql_injection def test_parameterized_queries_used(db_session: Session): """ SECURITY: Verify SQLAlchemy uses parameterized queries. CHECK: Ensure ORM methods use parameterized queries, not string concatenation. EXPECTED: SQLAlchemy automatically parameterizes, preventing injection. """ service = AgentGovernanceService(db_session) # Create an agent agent = service.register_or_update_agent( name="Test Agent", category="test", module_path="test.module", class_name="TestAgent" ) # Query using ORM (parameterized by default) queried = db_session.query(AgentRegistry).filter( AgentRegistry.id == agent.id ).first() assert queried is not None assert queried.id == agent.id @pytest.mark.sql_injection @pytest.mark.parametrize("special_char", [ "'", '"', ";", "--", "/*", "*/", "\\", "\0", ]) def test_special_characters_escaped(special_char, db_session: Session): """ SECURITY: Special characters properly escaped. CHECK: Quotes, backslashes, semicolons, comments don't break queries. EXPECTED: Special characters escaped or parameterized. """ service = AgentGovernanceService(db_session) malicious_name = f"test{special_char}name" agent = service.register_or_update_agent( name=malicious_name, category="test", module_path="test.module", class_name="TestAgent" ) assert agent is not None assert agent.name == malicious_name @pytest.mark.sql_injection def test_sql_error_not_exposed_to_user(db_session: Session): """ SECURITY: SQL error messages don't leak database schema. CHECK: SQL errors don't reveal table structure, column names, etc. EXPECTED: Generic error message, no schema details. """ service = AgentGovernanceService(db_session) # Try non-existent agent result = service.can_perform_action( agent_id="nonexistent-agent-123", action_type="stream_chat" ) # Should return generic "not found" error assert result["allowed"] is False assert "not found" in result["reason"].lower() # Should NOT contain SQL details error_msg = str(result).lower() assert "syntax error" not in error_msg assert "table" not in error_msg or "not found" in result["reason"].lower() assert "column" not in error_msg assert "database" not in error_msg # ============================================================================ # Batch SQL Injection Tests # ============================================================================ @pytest.mark.sql_injection @pytest.mark.parametrize("malicious_id", [ "'; DROP TABLE agents; --", "' OR '1'='1", "1' UNION SELECT * FROM users --", "'; INSERT INTO agents VALUES ('hacked', 'admin'); --", "1'; DELETE FROM episodes WHERE '1'='1' --", ]) def test_batch_agent_id_sql_injection(malicious_id, db_session: Session): """ SECURITY: Batch test SQL injection via agent_id. Tests all SQL injection payloads from conftest. """ service = AgentGovernanceService(db_session) result = service.can_perform_action( agent_id=malicious_id, action_type="stream_chat" ) assert result["allowed"] is False @pytest.mark.sql_injection @pytest.mark.parametrize("malicious_name", [ "'; DROP TABLE users; --", "' OR '1'='1", "", "$(whoami)", "`cat /etc/passwd`", ]) def test_batch_agent_name_sql_injection(malicious_name, db_session: Session): """ SECURITY: Batch test SQL injection via agent name. Tests all SQL injection payloads from conftest. """ service = AgentGovernanceService(db_session) agent = service.register_or_update_agent( name=malicious_name, category="test", module_path="test.module", class_name="TestAgent" ) assert agent is not None assert agent.name == malicious_name