"""M16 credential and authentication adversarial tests. Every question here is the one an attacker asks: what happens with no credential, a malformed one, a revoked one, a rotated one, one scoped to a different capability, one belonging to a different project, a node credential on an operator route, a capability credential on a lifecycle route. The expected answer is always the same shape — refused, with the same error code, revealing nothing about why. """ from __future__ import annotations import hashlib import uuid from datetime import UTC, datetime, timedelta import pytest from pydantic import SecretStr from sqlalchemy import create_engine, select from sqlalchemy.orm import Session from sqlalchemy.pool import StaticPool from modelforge_api.persistence.models import ( Base, Capability, CapabilityContract, Project, ProjectBinding, ServiceClient, ServiceCredential, ) from modelforge_api.services.manifest_registry import ManifestRegistry from modelforge_api.services.serving import ServingError, ServingService from modelforge_api.services.transient_payloads import MemoryPayloadStore from modelforge_api.settings import Settings @pytest.fixture def session() -> Session: engine = create_engine( "sqlite+pysqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) Base.metadata.create_all(engine) with Session(engine) as value: yield value def settings() -> Settings: return Settings(_env_file=None, operator_api_key=SecretStr("m16-operator-key")) def service(session: Session) -> ServingService: return ServingService(session, settings(), ManifestRegistry(), MemoryPayloadStore()) def _now() -> datetime: return datetime.now(UTC) def make_contract(session: Session, key: str, version: int = 1) -> CapabilityContract: capability = session.scalar(select(Capability).where(Capability.key == key)) if capability is None: capability = Capability(key=key, description=key) session.add(capability) session.flush() contract = CapabilityContract( capability_id=capability.id, version=version, input_schema={}, output_schema={}, contract={}, upgrade_class="behavioral", ) session.add(contract) session.flush() return contract def make_client( session: Session, *, name: str, capabilities: list[str], binding: ProjectBinding | None = None, status: str = "active", ) -> tuple[ServiceClient, str]: client = ServiceClient( name=name, status=status, allowed_capabilities=capabilities, requests_per_minute=600, max_concurrent_requests=32, workload_priority="production", integration_environment="LAB", purpose="M16 adversarial credential test", project_binding_id=binding.id if binding else None, ) session.add(client) session.flush() secret = f"mfsvc_{uuid.uuid4().hex}{uuid.uuid4().hex}" session.add( ServiceCredential( service_client_id=client.id, secret_hash=hashlib.sha256(secret.encode()).hexdigest(), secret_prefix=secret[:12], ) ) session.commit() return client, secret def make_binding(session: Session, contract: CapabilityContract, project_key: str) -> ProjectBinding: project = Project(key=project_key, name=project_key, description=project_key) session.add(project) session.flush() binding = ProjectBinding( project_id=project.id, capability_contract_id=contract.id, channel="stable", priority="production", ) session.add(binding) session.commit() return binding # --------------------------------------------------------------------- missing and malformed @pytest.mark.parametrize( "authorization", [ None, "", " ", "Bearer", "Bearer ", "Bearer ", "Basic dXNlcjpwYXNz", "bearer lowercase-scheme", "mfsvc_no_scheme_at_all", "Bearer \x00nullbyte", "Bearer " + "a" * 10000, "Bearer ../../etc/passwd", "Bearer ' OR '1'='1", "Bearer ", "Bearer \n\rinjected: header", ], ) def test_a_missing_or_malformed_credential_is_refused( session: Session, authorization: str | None ) -> None: make_contract(session, "rag.embedding") with pytest.raises(ServingError) as error: service(session).authenticate(authorization, "rag.embedding@1") assert error.value.status_code == 401 assert error.value.code == "CAPABILITY_NOT_AUTHORIZED" def test_every_invalid_credential_returns_the_same_code_and_status(session: Session) -> None: """A different message for 'unknown' and 'revoked' is a token-enumeration oracle.""" make_contract(session, "rag.embedding") _client, secret = make_client( session, name="probe", capabilities=["rag.embedding@1"] ) revoked_client, revoked_secret = make_client( session, name="revoked", capabilities=["rag.embedding@1"] ) credential = session.scalar( select(ServiceCredential).where(ServiceCredential.service_client_id == revoked_client.id) ) assert credential is not None credential.revoked_at = _now() session.commit() outcomes = set() for authorization in ( "Bearer unknown-credential-value", f"Bearer {revoked_secret}", f"Bearer {secret[:-1]}x", "Bearer ", ): with pytest.raises(ServingError) as error: service(session).authenticate(authorization, "rag.embedding@1") outcomes.add((error.value.status_code, error.value.code)) assert outcomes == {(401, "CAPABILITY_NOT_AUTHORIZED")} def test_a_valid_credential_still_works_after_a_burst_of_invalid_ones(session: Session) -> None: make_contract(session, "rag.embedding") _client, secret = make_client(session, name="survivor", capabilities=["rag.embedding@1"]) for index in range(50): with pytest.raises(ServingError): service(session).authenticate(f"Bearer invalid-{index}", "rag.embedding@1") assert service(session).authenticate(f"Bearer {secret}", "rag.embedding@1").name == "survivor" # --------------------------------------------------------------------- scope isolation def test_a_vision_client_cannot_invoke_speech_transcription(session: Session) -> None: """The ExampleVision Vision client must not reach ASR because both happen to be LAB.""" make_contract(session, "vision.embedding") make_contract(session, "speech.transcription") _client, secret = make_client( session, name="examplevision-vision", capabilities=["vision.embedding@1"] ) assert service(session).authenticate(f"Bearer {secret}", "vision.embedding@1") with pytest.raises(ServingError) as error: service(session).authenticate(f"Bearer {secret}", "speech.transcription@1") assert error.value.status_code == 403 assert error.value.code == "CAPABILITY_NOT_AUTHORIZED" def test_a_speech_client_cannot_invoke_vision(session: Session) -> None: make_contract(session, "vision.embedding") make_contract(session, "speech.transcription") _client, secret = make_client( session, name="asr-consumer", capabilities=["speech.transcription@1"] ) assert service(session).authenticate(f"Bearer {secret}", "speech.transcription@1") with pytest.raises(ServingError) as error: service(session).authenticate(f"Bearer {secret}", "vision.embedding@1") assert error.value.status_code == 403 def test_a_client_cannot_reach_a_different_contract_version(session: Session) -> None: make_contract(session, "rag.embedding", version=1) make_contract(session, "rag.embedding", version=2) _client, secret = make_client(session, name="v1-only", capabilities=["rag.embedding@1"]) assert service(session).authenticate(f"Bearer {secret}", "rag.embedding@1") with pytest.raises(ServingError) as error: service(session).authenticate(f"Bearer {secret}", "rag.embedding@2") assert error.value.status_code == 403 @pytest.mark.parametrize( "capability", [ "admin.recovery", "admin.lifecycle", "node.publish", "node.enroll", "operator", "rag.embedding@1 rag.reranking@1", "RAG.EMBEDDING@1", "rag.embedding@1\u200b", ], ) def test_a_client_cannot_escalate_by_naming_another_scope( session: Session, capability: str ) -> None: """Scope matching is exact; case, whitespace and zero-width tricks do not widen it.""" make_contract(session, "rag.embedding") _client, secret = make_client(session, name="scoped", capabilities=["rag.embedding@1"]) with pytest.raises(ServingError) as error: service(session).authenticate(f"Bearer {secret}", capability) assert error.value.status_code == 403 # --------------------------------------------------------------------- cross project def test_one_project_credential_cannot_serve_another_projects_binding(session: Session) -> None: contract_a = make_contract(session, "rag.embedding") contract_b = make_contract(session, "vision.embedding") binding_a = make_binding(session, contract_a, "examplerag") make_binding(session, contract_b, "examplevision") _client, secret = make_client( session, name="examplerag-client", capabilities=["rag.embedding@1", "vision.embedding@1"], binding=binding_a, ) assert service(session).authenticate(f"Bearer {secret}", "rag.embedding@1") with pytest.raises(ServingError) as error: service(session).authenticate(f"Bearer {secret}", "vision.embedding@1") assert error.value.status_code == 403 assert error.value.code == "PROJECT_CAPABILITY_NOT_BOUND" def test_a_deprecated_project_binding_stops_serving(session: Session) -> None: contract = make_contract(session, "rag.embedding") binding = make_binding(session, contract, "examplerag") _client, secret = make_client( session, name="bound", capabilities=["rag.embedding@1"], binding=binding ) assert service(session).authenticate(f"Bearer {secret}", "rag.embedding@1") binding.deprecated_at = _now() session.commit() with pytest.raises(ServingError) as error: service(session).authenticate(f"Bearer {secret}", "rag.embedding@1") assert error.value.status_code == 403 assert error.value.code == "PROJECT_CAPABILITY_NOT_BOUND" # --------------------------------------------------------------------- revocation and rotation def test_a_revoked_credential_is_refused_immediately_and_permanently(session: Session) -> None: make_contract(session, "rag.embedding") client, secret = make_client(session, name="revoked", capabilities=["rag.embedding@1"]) assert service(session).authenticate(f"Bearer {secret}", "rag.embedding@1") credential = session.scalar( select(ServiceCredential).where(ServiceCredential.service_client_id == client.id) ) assert credential is not None credential.revoked_at = _now() session.commit() for _ in range(5): with pytest.raises(ServingError) as error: service(session).authenticate(f"Bearer {secret}", "rag.embedding@1") assert error.value.status_code == 401 def test_an_expired_credential_is_refused(session: Session) -> None: make_contract(session, "rag.embedding") client, secret = make_client(session, name="expiring", capabilities=["rag.embedding@1"]) credential = session.scalar( select(ServiceCredential).where(ServiceCredential.service_client_id == client.id) ) assert credential is not None credential.expires_at = _now() - timedelta(seconds=1) session.commit() with pytest.raises(ServingError) as error: service(session).authenticate(f"Bearer {secret}", "rag.embedding@1") assert error.value.status_code == 401 def test_rotation_leaves_exactly_one_usable_secret(session: Session) -> None: """Two simultaneously valid secrets is an unbounded window, not a graceful cutover.""" make_contract(session, "rag.embedding") client, old_secret = make_client(session, name="rotating", capabilities=["rag.embedding@1"]) assert service(session).authenticate(f"Bearer {old_secret}", "rag.embedding@1") old = session.scalar( select(ServiceCredential).where(ServiceCredential.service_client_id == client.id) ) assert old is not None old.revoked_at = _now() new_secret = f"mfsvc_{uuid.uuid4().hex}{uuid.uuid4().hex}" session.add( ServiceCredential( service_client_id=client.id, secret_hash=hashlib.sha256(new_secret.encode()).hexdigest(), secret_prefix=new_secret[:12], ) ) session.commit() with pytest.raises(ServingError): service(session).authenticate(f"Bearer {old_secret}", "rag.embedding@1") assert service(session).authenticate(f"Bearer {new_secret}", "rag.embedding@1").id == client.id usable = session.scalars( select(ServiceCredential).where( ServiceCredential.service_client_id == client.id, ServiceCredential.revoked_at.is_(None), ) ).all() assert len(usable) == 1 def test_a_disabled_client_cannot_serve_even_with_a_valid_secret(session: Session) -> None: make_contract(session, "rag.embedding") client, secret = make_client(session, name="disabled", capabilities=["rag.embedding@1"]) client.status = "disabled" client.disabled_at = _now() session.commit() with pytest.raises(ServingError) as error: service(session).authenticate(f"Bearer {secret}", "rag.embedding@1") assert error.value.status_code == 403 # --------------------------------------------------------------------- storage boundary def test_only_a_hash_of_a_credential_is_ever_stored(session: Session) -> None: make_contract(session, "rag.embedding") client, secret = make_client(session, name="hashed", capabilities=["rag.embedding@1"]) credential = session.scalar( select(ServiceCredential).where(ServiceCredential.service_client_id == client.id) ) assert credential is not None assert credential.secret_hash == hashlib.sha256(secret.encode()).hexdigest() assert secret not in credential.secret_hash assert len(credential.secret_prefix) <= 16 assert secret[16:] not in credential.secret_prefix columns = {column.name for column in ServiceCredential.__table__.columns} assert "secret" not in columns assert "plaintext" not in columns assert "password" not in columns