Files
aios/tests/v2r1/test_secret_scope.py
T

81 lines
3.5 KiB
Python

"""Executable completeness contract for scoped secret references."""
from __future__ import annotations
from dataclasses import fields
import pytest
from aios_v2r1.secret_scope import SecretRef
from aios_v2r1.tenant_context import ScopeError, ValidationError
def secret(**changes: object) -> SecretRef:
values: dict[str, object] = {
"secret_id": "channel-key", "tenant_id": "tenant-a", "cell_id": "cell-a",
"kind": "channel", "version": "v1",
"broker_path": "secret://tenant-a/cell-a/channel-key/v1"}
values.update(changes)
return SecretRef(**values) # type: ignore[arg-type]
@pytest.mark.parametrize("path", [
"secret://tenant-a/cell-a/channel-key/v1",
"secret://models/provider/key-v2",
"secret://runtime/cell-a/signing_key"])
def test_valid_broker_paths_are_references(path: str) -> None:
assert secret(broker_path=path).broker_path == path
@pytest.mark.parametrize("path", [
"", "vault://tenant-a/key", "secret://", "secret:///key",
"secret://tenant-a/../tenant-b/key", "secret://tenant-a//key",
"secret://tenant-a/key?version=1", "secret://tenant-a/key#fragment"])
def test_broker_path_traversal_and_malformed_paths_are_rejected(path: str) -> None:
with pytest.raises(ValidationError, match="broker path"):
secret(broker_path=path)
@pytest.mark.parametrize(("tenant_id", "cell_id", "allowed"), [
("tenant-a", "cell-a", True), ("tenant-b", "cell-a", False),
("tenant-a", "cell-b", False)])
def test_secret_scope_is_exact(tenant_id: str, cell_id: str, allowed: bool) -> None:
assert secret().can_be_used_by(tenant_id=tenant_id, cell_id=cell_id) is allowed
@pytest.mark.parametrize("kind", ["channel", "model", "tool", "runtime"])
def test_secret_kind_allowlist(kind: str) -> None:
assert secret(kind=kind).kind == kind
def test_invalid_secret_kind_is_rejected() -> None:
with pytest.raises(ValidationError, match="kind"):
secret(kind="admin")
@pytest.mark.parametrize("changes", [
{"secret_id": "../key"}, {"secret_id": " key "},
{"version": "../v2"}, {"version": ""}, {"tenant_id": "Tenant-A"},
{"cell_id": "cell/a"}])
def test_secret_identifiers_and_version_are_validated(changes: dict[str, str]) -> None:
with pytest.raises(ValidationError):
secret(**changes)
def test_reference_never_contains_a_secret_value_field() -> None:
assert "value" not in {field.name for field in fields(SecretRef)}
assert "super-secret-value" not in repr(secret())
def test_broker_resolution_uses_only_the_validated_reference() -> None:
seen: list[SecretRef] = []
class Broker:
def read_secret(self, reference: SecretRef) -> str:
seen.append(reference)
return "resolved-value"
reference = secret()
assert reference.resolve(Broker()) == "resolved-value"
assert seen == [reference]
@pytest.mark.parametrize("broker", [None, object()])
def test_missing_or_invalid_broker_fails_closed(broker: object | None) -> None:
with pytest.raises(ScopeError, match="secret broker unavailable"):
secret().resolve(broker)
def test_broker_error_is_sanitized_without_secret_leakage() -> None:
class BrokenBroker:
def read_secret(self, reference: SecretRef) -> str:
raise RuntimeError("provider leaked super-secret-value")
with pytest.raises(ScopeError, match="secret broker unavailable") as raised:
secret().resolve(BrokenBroker())
assert "super-secret-value" not in str(raised.value)
assert raised.value.__cause__ is None
assert raised.value.__context__ is None