910 lines
31 KiB
Python
910 lines
31 KiB
Python
"""RC audit-chain integrity and compatibility regressions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import inspect as pyinspect
|
|
import uuid
|
|
from collections.abc import Iterator
|
|
from contextlib import contextmanager
|
|
from datetime import UTC, datetime, timedelta
|
|
from types import SimpleNamespace
|
|
from typing import Any, cast
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
from sqlalchemy import create_engine, delete, event, func, insert, select, text, update
|
|
from sqlalchemy.dialects import postgresql
|
|
from sqlalchemy.engine import Connection, Engine
|
|
from sqlalchemy.orm import Session
|
|
|
|
import modelforge_api.domain.audit as audit_domain
|
|
import modelforge_api.persistence.models as persistence_models
|
|
from modelforge_api.db import build_engine
|
|
from modelforge_api.domain.audit import AUDIT_EMPTY_LEGACY_PREFIX_SEAL
|
|
from modelforge_api.persistence.models import (
|
|
AuditChainHead,
|
|
AuditEvent,
|
|
Base,
|
|
_protected_audit_dml_targets,
|
|
_textual_audit_dml_targets,
|
|
)
|
|
from modelforge_api.persistence.repositories import AuditRepository
|
|
from modelforge_api.services.audit import (
|
|
AUDIT_CHAIN_POSTGRES_LOCK_KEY,
|
|
AUDIT_CURRENT_HASH_FORMAT,
|
|
AUDIT_HASH_FORMAT_V1,
|
|
AuditContext,
|
|
AuditWriter,
|
|
_acquire_audit_write_lock,
|
|
audit_chain_violations,
|
|
canonical_audit_payload_and_hash,
|
|
legacy_audit_prefix_seal,
|
|
)
|
|
from modelforge_api.services.invariants import InvariantStatus, check_invariants
|
|
|
|
|
|
@pytest.fixture
|
|
def session() -> Session:
|
|
engine = create_engine("sqlite+pysqlite:///:memory:")
|
|
Base.metadata.create_all(engine)
|
|
with Session(engine) as value:
|
|
yield value
|
|
|
|
|
|
def _seed_chain(session: Session, count: int = 3) -> list[AuditEvent]:
|
|
writer = AuditWriter(session, "operator", "alice")
|
|
events = [
|
|
writer.write(
|
|
action=f"ACTION_{index}",
|
|
resource_type="model",
|
|
resource_id=f"model-{index}",
|
|
details={"index": index, "evidence": {"approved": True}},
|
|
)
|
|
for index in range(1, count + 1)
|
|
]
|
|
session.commit()
|
|
return events
|
|
|
|
|
|
def _audit_invariant(session: Session) -> Any:
|
|
return next(
|
|
result
|
|
for result in check_invariants(session).results
|
|
if result.key == "audit_chain_intact"
|
|
)
|
|
|
|
|
|
def _checkpoint(session: Session) -> AuditChainHead:
|
|
checkpoint = session.get(AuditChainHead, 1)
|
|
assert checkpoint is not None
|
|
return checkpoint
|
|
|
|
|
|
@contextmanager
|
|
def _privileged_database_connection(session: Session) -> Iterator[Connection]:
|
|
"""Use a direct Engine connection to model a database administrator/compromise."""
|
|
|
|
session.commit()
|
|
engine = session.get_bind()
|
|
assert isinstance(engine, Engine)
|
|
with engine.begin() as connection:
|
|
yield connection
|
|
session.expire_all()
|
|
|
|
|
|
def _privileged_tamper(session: Session, statement: Any) -> None:
|
|
with _privileged_database_connection(session) as connection:
|
|
connection.execute(statement)
|
|
|
|
|
|
def _application_engine(tmp_path: Any) -> Engine:
|
|
database = tmp_path / "application-audit-boundary.sqlite3"
|
|
url = f"sqlite+pysqlite:///{database.as_posix()}"
|
|
setup = create_engine(url)
|
|
Base.metadata.create_all(setup)
|
|
setup.dispose()
|
|
return build_engine(url)
|
|
|
|
|
|
def test_fresh_metadata_database_has_a_valid_empty_checkpoint(session: Session) -> None:
|
|
checkpoint = _checkpoint(session)
|
|
assert checkpoint.event_count == 0
|
|
assert checkpoint.last_sequence == 0
|
|
assert checkpoint.last_event_hash is None
|
|
assert checkpoint.legacy_prefix_seal == AUDIT_EMPTY_LEGACY_PREFIX_SEAL
|
|
assert audit_chain_violations([], checkpoint) == []
|
|
|
|
AuditWriter(session, "operator", "first-legitimate-writer").write(
|
|
"FIRST_EVENT", "model", "model-1", {}
|
|
)
|
|
session.commit()
|
|
events = list(session.scalars(select(AuditEvent)))
|
|
assert audit_chain_violations(events, _checkpoint(session)) == []
|
|
|
|
|
|
def test_a_canonical_chain_is_contiguous_linked_and_content_verified(session: Session) -> None:
|
|
_seed_chain(session)
|
|
events = list(session.scalars(select(AuditEvent).order_by(AuditEvent.sequence)))
|
|
|
|
assert [event.sequence for event in events] == [1, 2, 3]
|
|
assert events[0].previous_event_hash is None
|
|
assert events[1].previous_event_hash == events[0].event_hash
|
|
assert events[2].previous_event_hash == events[1].event_hash
|
|
assert audit_chain_violations(events, _checkpoint(session)) == []
|
|
assert _audit_invariant(session).status is InvariantStatus.HOLDS
|
|
|
|
|
|
def test_writer_stores_the_exact_detached_payload_that_it_hashes(session: Session) -> None:
|
|
details = {"evidence": {"approved": True}}
|
|
event = AuditWriter(session, "operator", "alice").write(
|
|
"APPROVE", "revision", "revision-1", details
|
|
)
|
|
details["evidence"]["approved"] = False
|
|
session.commit()
|
|
|
|
assert event.details == {"evidence": {"approved": True}}
|
|
assert audit_chain_violations([event], _checkpoint(session)) == []
|
|
|
|
|
|
def test_audit_context_preserves_request_correlation_and_principal(session: Session) -> None:
|
|
context = AuditContext(
|
|
actor_type="operator",
|
|
actor_id="principal-42",
|
|
correlation_id="request-correlation-42",
|
|
)
|
|
writer = AuditWriter(session, context=context)
|
|
writer.write("START", "operation", "op-1", {})
|
|
writer.write("COMPLETE", "operation", "op-1", {})
|
|
session.commit()
|
|
|
|
events = list(session.scalars(select(AuditEvent).order_by(AuditEvent.sequence)))
|
|
assert {event.actor_id for event in events} == {"principal-42"}
|
|
assert {event.correlation_id for event in events} == {"request-correlation-42"}
|
|
assert audit_chain_violations(events, _checkpoint(session)) == []
|
|
|
|
|
|
def test_legacy_audit_repository_uses_the_canonical_writer(session: Session) -> None:
|
|
event = AuditRepository(session).append(
|
|
correlation_id="repository-correlation",
|
|
actor_type="system",
|
|
actor_id="repository-test",
|
|
action="SYNC",
|
|
resource_type="project",
|
|
resource_id="project-1",
|
|
outcome="success",
|
|
details={"count": 2},
|
|
)
|
|
session.commit()
|
|
|
|
assert event.sequence == 1
|
|
assert audit_chain_violations([event], _checkpoint(session)) == []
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("column", "tampered_value"),
|
|
[
|
|
("correlation_id", "tampered-correlation"),
|
|
("actor_type", "tampered-actor-type"),
|
|
("actor_id", "mallory"),
|
|
("action", "TAMPERED_ACTION"),
|
|
("resource_type", "tampered-resource"),
|
|
("resource_id", "tampered-resource-id"),
|
|
("outcome", "failure"),
|
|
("details", {"tampered": True}),
|
|
("previous_event_hash", "b" * 64),
|
|
("event_hash", "c" * 64),
|
|
],
|
|
)
|
|
def test_every_hashed_field_and_both_hash_columns_are_tamper_evident(
|
|
session: Session,
|
|
column: str,
|
|
tampered_value: object,
|
|
) -> None:
|
|
_seed_chain(session)
|
|
_privileged_tamper(
|
|
session,
|
|
update(AuditEvent)
|
|
.where(AuditEvent.sequence == 2)
|
|
.values({column: tampered_value}),
|
|
)
|
|
session.commit()
|
|
|
|
result = _audit_invariant(session)
|
|
assert result.status is InvariantStatus.VIOLATED
|
|
assert result.violations
|
|
|
|
|
|
def test_a_non_null_first_link_is_detected(session: Session) -> None:
|
|
_seed_chain(session)
|
|
_privileged_tamper(
|
|
session,
|
|
update(AuditEvent)
|
|
.where(AuditEvent.sequence == 1)
|
|
.values(previous_event_hash="d" * 64),
|
|
)
|
|
session.commit()
|
|
|
|
result = _audit_invariant(session)
|
|
assert result.status is InvariantStatus.VIOLATED
|
|
assert any("first event" in violation for violation in result.violations)
|
|
|
|
|
|
def test_a_sequence_gap_is_detected_even_when_remaining_numbers_are_unique(
|
|
session: Session,
|
|
) -> None:
|
|
_seed_chain(session)
|
|
_privileged_tamper(
|
|
session, delete(AuditEvent).where(AuditEvent.sequence == 2)
|
|
)
|
|
session.commit()
|
|
|
|
result = _audit_invariant(session)
|
|
assert result.status is InvariantStatus.VIOLATED
|
|
assert any("expected 2" in violation for violation in result.violations)
|
|
|
|
|
|
@pytest.mark.parametrize("field", ["id", "occurred_at"])
|
|
def test_v2_event_id_and_timestamp_are_tamper_evident(
|
|
session: Session, field: str
|
|
) -> None:
|
|
_seed_chain(session)
|
|
tampered = (
|
|
uuid.uuid4()
|
|
if field == "id"
|
|
else datetime.now(UTC) + timedelta(days=1)
|
|
)
|
|
_privileged_tamper(
|
|
session,
|
|
update(AuditEvent)
|
|
.where(AuditEvent.sequence == 2)
|
|
.values({field: tampered}),
|
|
)
|
|
session.commit()
|
|
assert _audit_invariant(session).status is InvariantStatus.VIOLATED
|
|
|
|
|
|
@pytest.mark.parametrize("retained", [2, 0])
|
|
def test_checkpoint_detects_tail_and_complete_chain_deletion(
|
|
session: Session, retained: int
|
|
) -> None:
|
|
_seed_chain(session)
|
|
_privileged_tamper(
|
|
session, delete(AuditEvent).where(AuditEvent.sequence > retained)
|
|
)
|
|
session.commit()
|
|
|
|
result = _audit_invariant(session)
|
|
assert result.status is InvariantStatus.VIOLATED
|
|
assert any("checkpoint" in violation for violation in result.violations)
|
|
|
|
|
|
def test_privileged_middle_tamper_is_caught_by_the_explicit_strict_gate(
|
|
session: Session,
|
|
) -> None:
|
|
_seed_chain(session)
|
|
_privileged_tamper(
|
|
session,
|
|
update(AuditEvent)
|
|
.where(AuditEvent.sequence == 2)
|
|
.values(details={"attacker": "changed-without-moving-head"}),
|
|
)
|
|
session.commit()
|
|
|
|
# The O(1) append gate intentionally does not rescan a sealed middle prefix. A privileged
|
|
# database edit remains visible to the explicit full-chain invariant/recovery verification.
|
|
AuditWriter(session, "operator", "legitimate-writer").write(
|
|
"LEGITIMATE_APPEND", "model", "4", {}
|
|
)
|
|
session.commit()
|
|
assert len(list(session.scalars(select(AuditEvent)))) == 4
|
|
assert _audit_invariant(session).status is InvariantStatus.VIOLATED
|
|
|
|
|
|
def test_privileged_tail_tamper_is_refused_by_the_constant_time_append_gate(
|
|
session: Session,
|
|
) -> None:
|
|
_seed_chain(session)
|
|
_privileged_tamper(
|
|
session,
|
|
update(AuditEvent)
|
|
.where(AuditEvent.sequence == 3)
|
|
.values(details={"attacker": "changed-tail"}),
|
|
)
|
|
session.commit()
|
|
|
|
with pytest.raises(RuntimeError, match="checkpoint/tail failed"):
|
|
AuditWriter(session, "operator", "legitimate-writer").write(
|
|
"REFUSED_AFTER_TAIL_TAMPER", "model", "4", {}
|
|
)
|
|
assert len(list(session.scalars(select(AuditEvent)))) == 3
|
|
|
|
|
|
def test_mixed_migrated_v1_prefix_and_v2_suffix_remain_verifiable(session: Session) -> None:
|
|
occurred_at = datetime(2026, 8, 29, 12, 0, tzinfo=UTC)
|
|
previous_hash: str | None = None
|
|
legacy: list[AuditEvent] = []
|
|
for sequence in (1, 2):
|
|
event_id = uuid.uuid4()
|
|
payload, event_hash = canonical_audit_payload_and_hash(
|
|
correlation_id=f"legacy-{sequence}",
|
|
actor_type="operator",
|
|
actor_id="legacy",
|
|
action=f"LEGACY_{sequence}",
|
|
resource_type="model",
|
|
resource_id=str(sequence),
|
|
outcome="success",
|
|
details={"sequence": sequence},
|
|
previous_event_hash=previous_hash,
|
|
hash_format=AUDIT_HASH_FORMAT_V1,
|
|
)
|
|
row = AuditEvent(
|
|
id=event_id,
|
|
sequence=sequence,
|
|
occurred_at=occurred_at + timedelta(seconds=sequence),
|
|
hash_format=AUDIT_HASH_FORMAT_V1,
|
|
event_hash=event_hash,
|
|
**payload,
|
|
)
|
|
legacy.append(row)
|
|
previous_hash = event_hash
|
|
with _privileged_database_connection(session) as connection:
|
|
connection.execute(
|
|
insert(AuditEvent),
|
|
[
|
|
{
|
|
"id": row.id,
|
|
"sequence": row.sequence,
|
|
"occurred_at": row.occurred_at,
|
|
"hash_format": row.hash_format,
|
|
"event_hash": row.event_hash,
|
|
"correlation_id": row.correlation_id,
|
|
"actor_type": row.actor_type,
|
|
"actor_id": row.actor_id,
|
|
"action": row.action,
|
|
"resource_type": row.resource_type,
|
|
"resource_id": row.resource_id,
|
|
"outcome": row.outcome,
|
|
"details": row.details,
|
|
"previous_event_hash": row.previous_event_hash,
|
|
}
|
|
for row in legacy
|
|
],
|
|
)
|
|
connection.execute(
|
|
update(AuditChainHead)
|
|
.where(AuditChainHead.singleton_id == 1)
|
|
.values(
|
|
event_count=2,
|
|
last_sequence=2,
|
|
last_event_hash=previous_hash,
|
|
hash_format=AUDIT_CURRENT_HASH_FORMAT,
|
|
v2_start_sequence=3,
|
|
legacy_prefix_count=2,
|
|
legacy_prefix_seal=legacy_audit_prefix_seal(legacy),
|
|
updated_at=datetime.now(UTC),
|
|
)
|
|
)
|
|
session.commit()
|
|
|
|
AuditWriter(session, "operator", "new-writer").write("V2", "model", "3", {})
|
|
session.commit()
|
|
events = list(session.scalars(select(AuditEvent).order_by(AuditEvent.sequence)))
|
|
assert [row.hash_format for row in events] == ["v1", "v1", "v2"]
|
|
assert audit_chain_violations(events, _checkpoint(session)) == []
|
|
|
|
_privileged_tamper(
|
|
session,
|
|
update(AuditEvent)
|
|
.where(AuditEvent.sequence == 1)
|
|
.values(occurred_at=datetime.now(UTC)),
|
|
)
|
|
session.commit()
|
|
assert any(
|
|
"prefix seal" in violation
|
|
for violation in audit_chain_violations(events, _checkpoint(session))
|
|
)
|
|
AuditWriter(session, "operator", "new-writer").write(
|
|
"APPEND_AFTER_PRIVILEGED_PREFIX_TAMPER", "model", "4", {}
|
|
)
|
|
session.commit()
|
|
assert _audit_invariant(session).status is InvariantStatus.VIOLATED
|
|
|
|
|
|
def test_ordinary_orm_code_cannot_update_the_checkpoint_separately(session: Session) -> None:
|
|
_seed_chain(session, count=1)
|
|
checkpoint = _checkpoint(session)
|
|
checkpoint.event_count = 0
|
|
|
|
with pytest.raises(ValueError, match="canonical audit writer"):
|
|
session.commit()
|
|
session.rollback()
|
|
|
|
with pytest.raises(ValueError, match="canonical audit writer"):
|
|
session.execute(
|
|
update(AuditChainHead)
|
|
.where(AuditChainHead.singleton_id == 1)
|
|
.values(event_count=0)
|
|
)
|
|
session.rollback()
|
|
|
|
|
|
def test_exact_bulk_delete_and_head_rewrite_poc_is_refused_but_writer_succeeds(
|
|
session: Session,
|
|
) -> None:
|
|
_seed_chain(session, count=3)
|
|
before = (
|
|
_checkpoint(session).event_count,
|
|
_checkpoint(session).last_sequence,
|
|
_checkpoint(session).last_event_hash,
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="canonical audit writer"):
|
|
session.execute(delete(AuditEvent))
|
|
session.execute(
|
|
update(AuditChainHead)
|
|
.where(AuditChainHead.singleton_id == 1)
|
|
.values(event_count=0, last_sequence=0, last_event_hash=None)
|
|
)
|
|
session.rollback()
|
|
|
|
assert len(list(session.scalars(select(AuditEvent)))) == 3
|
|
head = _checkpoint(session)
|
|
assert (head.event_count, head.last_sequence, head.last_event_hash) == before
|
|
|
|
appended = AuditWriter(session, "operator", "legitimate-writer").write(
|
|
"LEGITIMATE_APPEND", "model", "model-4", {}
|
|
)
|
|
session.commit()
|
|
assert appended.sequence == 4
|
|
assert audit_chain_violations(
|
|
list(session.scalars(select(AuditEvent).order_by(AuditEvent.sequence))),
|
|
_checkpoint(session),
|
|
) == []
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"statement",
|
|
[
|
|
delete(AuditChainHead),
|
|
insert(AuditChainHead).values(
|
|
singleton_id=1,
|
|
event_count=0,
|
|
last_sequence=0,
|
|
last_event_hash=None,
|
|
hash_format="v2",
|
|
v2_start_sequence=1,
|
|
legacy_prefix_count=0,
|
|
legacy_prefix_seal=AUDIT_EMPTY_LEGACY_PREFIX_SEAL,
|
|
),
|
|
update(AuditEvent).values(action="BULK_TAMPER"),
|
|
insert(AuditEvent).values(
|
|
id=uuid.uuid4(),
|
|
sequence=99,
|
|
occurred_at=datetime.now(UTC),
|
|
correlation_id="bulk",
|
|
actor_type="attacker",
|
|
actor_id="attacker",
|
|
action="BULK_INSERT",
|
|
resource_type="audit",
|
|
resource_id=None,
|
|
outcome="success",
|
|
details={},
|
|
previous_event_hash=None,
|
|
event_hash="a" * 64,
|
|
hash_format="v2",
|
|
),
|
|
text("delete from audit_events"),
|
|
],
|
|
)
|
|
def test_ordinary_session_dml_has_no_audit_bypass(session: Session, statement: Any) -> None:
|
|
with pytest.raises(ValueError, match="canonical audit writer"):
|
|
session.execute(statement)
|
|
session.rollback()
|
|
|
|
|
|
def test_ordinary_orm_and_legacy_bulk_inserts_cannot_create_audit_events(
|
|
session: Session,
|
|
) -> None:
|
|
payload = {
|
|
"id": uuid.uuid4(),
|
|
"sequence": 1,
|
|
"occurred_at": datetime.now(UTC),
|
|
"correlation_id": "ordinary",
|
|
"actor_type": "attacker",
|
|
"actor_id": "attacker",
|
|
"action": "INSERT",
|
|
"resource_type": "audit",
|
|
"resource_id": None,
|
|
"outcome": "success",
|
|
"details": {},
|
|
"previous_event_hash": None,
|
|
"event_hash": "a" * 64,
|
|
"hash_format": "v2",
|
|
}
|
|
session.add(AuditEvent(**payload))
|
|
with pytest.raises(ValueError, match="canonical audit writer"):
|
|
session.flush()
|
|
session.rollback()
|
|
|
|
with pytest.raises(ValueError, match="application session connection"):
|
|
session.bulk_insert_mappings(AuditEvent, [payload])
|
|
session.rollback()
|
|
|
|
|
|
def test_aliased_and_annotated_core_dml_resolve_the_protected_base_table(
|
|
session: Session,
|
|
) -> None:
|
|
event_alias = AuditEvent.__table__.alias("erased_events")
|
|
head_alias = AuditChainHead.__table__.alias("forged_head")
|
|
statements = [
|
|
delete(event_alias),
|
|
update(head_alias).values(
|
|
event_count=0,
|
|
last_sequence=0,
|
|
last_event_hash=None,
|
|
),
|
|
delete(AuditEvent.__table__._annotate({"reviewer": "alias-control"})),
|
|
update(
|
|
AuditChainHead.__table__._annotate({"reviewer": "alias-control"})
|
|
).values(event_count=0, last_sequence=0, last_event_hash=None),
|
|
]
|
|
|
|
for statement in statements:
|
|
assert _protected_audit_dml_targets(statement)
|
|
compiled = str(statement.compile(dialect=postgresql.dialect()))
|
|
assert _textual_audit_dml_targets(compiled)
|
|
with pytest.raises(ValueError, match="canonical audit writer"):
|
|
session.execute(statement)
|
|
session.rollback()
|
|
|
|
|
|
def test_exact_aliased_delete_and_head_rewrite_poc_is_refused(
|
|
session: Session,
|
|
) -> None:
|
|
_seed_chain(session, count=3)
|
|
event_alias = AuditEvent.__table__.alias("erased_events")
|
|
head_alias = AuditChainHead.__table__.alias("forged_head")
|
|
|
|
with pytest.raises(ValueError, match="canonical audit writer"):
|
|
session.execute(delete(event_alias))
|
|
session.rollback()
|
|
with pytest.raises(ValueError, match="canonical audit writer"):
|
|
session.execute(
|
|
update(head_alias).values(
|
|
event_count=0,
|
|
last_sequence=0,
|
|
last_event_hash=None,
|
|
)
|
|
)
|
|
session.rollback()
|
|
|
|
appended = AuditWriter(session, "operator", "legitimate-writer").write(
|
|
"APPEND_AFTER_ALIASED_POC", "model", "model-4", {}
|
|
)
|
|
session.commit()
|
|
assert appended.sequence == 4
|
|
assert audit_chain_violations(
|
|
list(session.scalars(select(AuditEvent).order_by(AuditEvent.sequence))),
|
|
_checkpoint(session),
|
|
) == []
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"statement",
|
|
[
|
|
"/* leading /* nested */ comment */ DELETE\nFROM [main].[audit_events]",
|
|
"-- leading decoy\nUPDATE `main`.`audit_chain_heads` "
|
|
"SET event_count=0, last_sequence=0, last_event_hash=NULL",
|
|
'INSERT /* gap */ INTO "public"."audit_events" (id) VALUES (NULL)',
|
|
"WITH doomed AS (SELECT 1) DELETE FROM audit_events",
|
|
"TRUNCATE TABLE harmless_table, audit_events",
|
|
"MERGE INTO public.audit_chain_heads AS head USING incoming ON false "
|
|
"WHEN MATCHED THEN DELETE",
|
|
"COPY audit_events FROM STDIN",
|
|
"DROP TABLE harmless_table, audit_chain_heads",
|
|
"DO $$ BEGIN DELETE FROM audit_events; END $$",
|
|
"CALL rewrite_audit_chain()",
|
|
],
|
|
)
|
|
def test_session_connection_exec_driver_sql_blocks_obfuscated_audit_dml(
|
|
session: Session, statement: str
|
|
) -> None:
|
|
connection = session.connection()
|
|
with pytest.raises(ValueError, match="raw audit DML"):
|
|
connection.exec_driver_sql(statement)
|
|
session.rollback()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"statement",
|
|
[
|
|
"SELECT 'delete from audit_events' AS harmless",
|
|
"/* DELETE FROM audit_events */ SELECT 1",
|
|
"-- UPDATE audit_chain_heads SET event_count=0\nSELECT 1",
|
|
],
|
|
)
|
|
def test_raw_sql_lexer_ignores_non_executable_comments_and_strings(
|
|
session: Session, statement: str
|
|
) -> None:
|
|
assert _textual_audit_dml_targets(statement) == frozenset()
|
|
assert session.connection().exec_driver_sql(statement).scalar_one() in {
|
|
"delete from audit_events",
|
|
1,
|
|
}
|
|
session.rollback()
|
|
|
|
|
|
def test_raw_sql_lexer_ignores_postgresql_dollar_quoted_decoys() -> None:
|
|
assert (
|
|
_textual_audit_dml_targets(
|
|
"SELECT $audit$DELETE FROM audit_events$audit$, "
|
|
"$$UPDATE audit_chain_heads SET event_count=0$$"
|
|
)
|
|
== frozenset()
|
|
)
|
|
|
|
|
|
def test_raw_sql_lexer_classifies_postgresql_unicode_quoted_identifiers() -> None:
|
|
assert _textual_audit_dml_targets('DELETE FROM U&"audit_events"') == frozenset(
|
|
{"audit_events"}
|
|
)
|
|
|
|
|
|
def test_direct_engine_raw_tamper_is_outside_hook_boundary_but_strictly_detected(
|
|
session: Session,
|
|
) -> None:
|
|
_seed_chain(session, count=2)
|
|
with _privileged_database_connection(session) as connection:
|
|
connection.exec_driver_sql(
|
|
"UPDATE audit_events SET action='PRIVILEGED_RAW_TAMPER' WHERE sequence=1"
|
|
)
|
|
|
|
events = list(session.scalars(select(AuditEvent).order_by(AuditEvent.sequence)))
|
|
assert any(
|
|
"content hash" in violation
|
|
for violation in audit_chain_violations(events, _checkpoint(session))
|
|
)
|
|
|
|
|
|
def test_every_connection_from_the_application_engine_blocks_audit_dml(
|
|
tmp_path: Any,
|
|
) -> None:
|
|
engine = _application_engine(tmp_path)
|
|
try:
|
|
with Session(engine) as session:
|
|
AuditWriter(session, "operator", "legitimate").write(
|
|
"LEGITIMATE", "model", "model-1", {}
|
|
)
|
|
session.commit()
|
|
bind = session.bind
|
|
assert isinstance(bind, Engine)
|
|
with pytest.raises(ValueError, match="audit"), bind.begin() as connection:
|
|
connection.exec_driver_sql(
|
|
" /* ordinary connection */ DELETE FROM audit_events"
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="audit"), engine.begin() as connection:
|
|
connection.exec_driver_sql(
|
|
'UPDATE "audit_chain_heads" SET event_count=0, last_sequence=0, '
|
|
"last_event_hash=NULL"
|
|
)
|
|
with Session(engine) as session:
|
|
assert session.scalar(select(func.count()).select_from(AuditEvent)) == 1
|
|
assert _checkpoint(session).event_count == 1
|
|
finally:
|
|
engine.dispose()
|
|
|
|
|
|
def test_late_statement_rewrite_is_rolled_back_before_commit(tmp_path: Any) -> None:
|
|
engine = _application_engine(tmp_path)
|
|
try:
|
|
with Session(engine) as session:
|
|
AuditWriter(session, "operator", "legitimate").write(
|
|
"LEGITIMATE", "model", "model-1", {}
|
|
)
|
|
session.commit()
|
|
|
|
def late_rewrite(
|
|
_connection: Any,
|
|
_cursor: Any,
|
|
statement: str,
|
|
parameters: Any,
|
|
_context: Any,
|
|
_executemany: bool,
|
|
) -> tuple[str, Any]:
|
|
if statement.strip().upper() == "SELECT 1":
|
|
return "DELETE FROM audit_events", parameters
|
|
return statement, parameters
|
|
|
|
event.listen(engine, "before_cursor_execute", late_rewrite, retval=True)
|
|
try:
|
|
with pytest.raises(ValueError, match="rolled back"), engine.begin() as connection:
|
|
connection.exec_driver_sql("SELECT 1")
|
|
finally:
|
|
event.remove(engine, "before_cursor_execute", late_rewrite)
|
|
|
|
with Session(engine) as session:
|
|
events = list(session.scalars(select(AuditEvent)))
|
|
assert len(events) == 1
|
|
assert audit_chain_violations(events, _checkpoint(session)) == []
|
|
finally:
|
|
engine.dispose()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"statement",
|
|
[
|
|
r'ALTER TABLE IF EXISTS U&"audit\005fevents" DISABLE TRIGGER ALL',
|
|
r'''ALTER TABLE IF EXISTS U&"audit!005fchain!005fheads" UESCAPE '!' DISABLE TRIGGER ALL''',
|
|
"CREATE OR REPLACE FUNCTION reset_chain() RETURNS void LANGUAGE SQL AS $$ "
|
|
"DELETE FROM audit_events $$",
|
|
"SELECT public.reset_chain()",
|
|
"CALL reset_chain()",
|
|
"DO $$ BEGIN EXECUTE 'DELETE FROM audit_events'; END $$",
|
|
"DELETE events, heads FROM audit_events AS events JOIN audit_chain_heads AS heads ON 1=1",
|
|
"DELETE FROM audit_events AS events USING audit_chain_heads AS heads",
|
|
],
|
|
)
|
|
def test_runtime_sql_classifier_defaults_procedural_and_obfuscated_mutation_to_deny(
|
|
statement: str,
|
|
) -> None:
|
|
assert _textual_audit_dml_targets(statement)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"statement",
|
|
[
|
|
"SELECT count(*) FROM audit_events",
|
|
"SELECT * FROM audit_chain_heads",
|
|
"SELECT * FROM modelforge_audit.append_event_v2(NULL, NULL, NULL, NULL, NULL, "
|
|
"NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
|
|
],
|
|
)
|
|
def test_runtime_sql_classifier_keeps_read_and_canonical_append_controls(
|
|
statement: str,
|
|
) -> None:
|
|
assert _textual_audit_dml_targets(statement) == frozenset()
|
|
|
|
|
|
def test_old_visible_capabilities_cannot_be_imported_guessed_reused_or_crossed(
|
|
session: Session,
|
|
) -> None:
|
|
retired_names = {
|
|
"_AUDIT_INTERNAL_EXECUTION_TOKEN",
|
|
"_AUDIT_INTERNAL_EXECUTION_OPTION",
|
|
"_AUDIT_INTERNAL_CONNECTION_INFO_KEY",
|
|
"_allow_privileged_audit_connection",
|
|
"_allow_canonical_audit_event_insert",
|
|
"_mark_canonical_audit_head_update",
|
|
}
|
|
assert retired_names.isdisjoint(vars(audit_domain))
|
|
assert retired_names.isdisjoint(vars(persistence_models))
|
|
assert "_install_audit_dml_boundary" not in vars(persistence_models)
|
|
canonical_operation = persistence_models._append_canonical_audit_event
|
|
assert canonical_operation.__closure__ is None
|
|
assert pyinspect.getclosurevars(canonical_operation).nonlocals == {}
|
|
with pytest.raises(TypeError, match="unexpected keyword argument"):
|
|
canonical_operation(session, statement=delete(AuditEvent)) # type: ignore[call-arg]
|
|
|
|
connection = session.connection()
|
|
stolen_or_guessed = object()
|
|
for info in (session.info, connection.info):
|
|
info["_modelforge_audit_internal_execution"] = stolen_or_guessed
|
|
info["_modelforge_audit_privileged_connection"] = stolen_or_guessed
|
|
statement = delete(AuditEvent).execution_options(
|
|
_modelforge_audit_internal_execution=stolen_or_guessed
|
|
)
|
|
with pytest.raises(ValueError, match="canonical audit writer"):
|
|
session.execute(statement)
|
|
session.rollback()
|
|
|
|
engine = session.get_bind()
|
|
assert isinstance(engine, Engine)
|
|
with Session(engine) as other:
|
|
other.info["_modelforge_audit_internal_execution"] = stolen_or_guessed
|
|
other_connection = other.connection()
|
|
other_connection.info["_modelforge_audit_privileged_connection"] = stolen_or_guessed
|
|
with pytest.raises(ValueError, match="canonical audit writer"):
|
|
other.execute(
|
|
update(AuditChainHead)
|
|
.values(event_count=0, last_sequence=0, last_event_hash=None)
|
|
.execution_options(
|
|
_modelforge_audit_internal_execution=stolen_or_guessed
|
|
)
|
|
)
|
|
other.rollback()
|
|
|
|
|
|
def test_canonical_append_leaves_no_visible_or_reusable_connection_capability(
|
|
session: Session,
|
|
) -> None:
|
|
AuditWriter(session, "operator", "legitimate-writer").write(
|
|
"CAPABILITY_CLEANUP_CONTROL", "model", "model-1", {}
|
|
)
|
|
session.commit()
|
|
connection = session.connection()
|
|
forbidden_fragments = ("internal", "permit", "privileged", "token")
|
|
assert not any(
|
|
any(fragment in str(key).lower() for fragment in forbidden_fragments)
|
|
for key in session.info
|
|
)
|
|
assert not any(
|
|
any(fragment in str(key).lower() for fragment in forbidden_fragments)
|
|
for key in connection.info
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="application session connection"):
|
|
connection.execute(delete(AuditEvent))
|
|
session.rollback()
|
|
|
|
|
|
def test_privileged_checkpoint_rewrite_is_refused_by_the_append_gate(
|
|
session: Session,
|
|
) -> None:
|
|
events = _seed_chain(session, count=3)
|
|
_privileged_tamper(
|
|
session,
|
|
update(AuditChainHead)
|
|
.where(AuditChainHead.singleton_id == 1)
|
|
.values(
|
|
event_count=2,
|
|
last_sequence=2,
|
|
last_event_hash=events[1].event_hash,
|
|
),
|
|
)
|
|
session.commit()
|
|
|
|
with pytest.raises(RuntimeError, match="checkpoint/tail failed"):
|
|
AuditWriter(session, "operator", "legitimate-writer").write(
|
|
"REFUSED_AFTER_HEAD_REWRITE", "model", "model-4", {}
|
|
)
|
|
|
|
|
|
def test_failed_event_insert_rolls_back_event_and_checkpoint(session: Session) -> None:
|
|
_seed_chain(session, count=1)
|
|
before = _checkpoint(session)
|
|
expected = (before.event_count, before.last_sequence, before.last_event_hash)
|
|
|
|
engine = session.get_bind()
|
|
assert isinstance(engine, Engine)
|
|
|
|
def fail_insert(
|
|
_connection: Connection,
|
|
_cursor: Any,
|
|
statement: str,
|
|
parameters: Any,
|
|
_context: Any,
|
|
_executemany: bool,
|
|
) -> None:
|
|
if statement.lstrip().lower().startswith("insert into audit_events") and (
|
|
"FAIL_INSERT" in repr(parameters)
|
|
):
|
|
raise RuntimeError("injected audit insert failure")
|
|
|
|
event.listen(engine, "before_cursor_execute", fail_insert)
|
|
try:
|
|
with pytest.raises(RuntimeError, match="injected"):
|
|
AuditWriter(session, "operator", "alice").write(
|
|
"FAIL_INSERT", "model", "2", {}
|
|
)
|
|
finally:
|
|
event.remove(engine, "before_cursor_execute", fail_insert)
|
|
|
|
rows = list(session.scalars(select(AuditEvent)))
|
|
after = _checkpoint(session)
|
|
assert len(rows) == 1
|
|
assert (after.event_count, after.last_sequence, after.last_event_hash) == expected
|
|
|
|
|
|
def test_postgresql_writers_take_the_shared_transaction_scoped_advisory_lock() -> None:
|
|
session = Mock()
|
|
session.get_bind.return_value = SimpleNamespace(
|
|
dialect=SimpleNamespace(name="postgresql")
|
|
)
|
|
|
|
_acquire_audit_write_lock(cast(Session, session))
|
|
|
|
statement, parameters = session.execute.call_args.args
|
|
assert str(statement) == "SELECT pg_advisory_xact_lock(:lock_key)"
|
|
assert parameters == {"lock_key": AUDIT_CHAIN_POSTGRES_LOCK_KEY}
|