# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/where-must-an-instance-boundary-be-drawn-between-taint-propagation-and-capability-grant-in-mcp-tool-sandboxes.md
# Remnant Warrant:    https://corpusfetch.com/c/where-must-an-instance-boundary-be-drawn-between-taint-propagation-and-capability-grant-in-mcp-tool-sandboxes.warrant.json
# Markdown SHA-256:   c4d72966d1490c2e1d8802e0ef32f60162c9950ce3bf74c89969704c021db573
# Code AST SHA-256:   4247dbf9c1513f12ffb4b4e2acf2d081b7958ef293620907c4efc329649af8de
# Verified Classes:   TaintViolation, Provenance, Tainted, CapabilityGrant
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from __future__ import annotations

import hashlib
from dataclasses import dataclass, field
from enum import Enum
from typing import Any


class TaintViolation(RuntimeError):
    """Raised when a tainted value is bound to a capability-gated argument.

    This is an *argument-level* denial. It must NOT revoke the capability
    that gates the tool; it only rejects this particular binding.
    """

    def __init__(self, argument: str, source: str, tool: str) -> None:
        self.argument = argument
        self.source = source
        self.tool = tool
        super().__init__(
            f"tainted argument {argument!r} (source={source}) bound to tool {tool!r}; "
            f"capability retained, re-derive or sanitize the argument"
        )


class Provenance(Enum):
    TRUSTED = "trusted:user"
    UNTRUSTED_WEB = "untrusted:web"
    UNTRUSTED_TOOL = "untrusted:tool"


@dataclass(frozen=True)
class Tainted:
    """A value with provenance that survives serialization round-trips.

    Keyed on content hash + nonce, NOT on id(), so json.loads cannot launder it.
    """

    value: Any
    provenance: Provenance
    nonce: int
    _digest: str = field(init=False, repr=False)

    def __post_init__(self) -> None:
        object.__setattr__(
            self,
            "_digest",
            hashlib.sha256(f"{self.value!r}:{self.nonce}".encode()).hexdigest(),
        )

    @property
    def digest(self) -> str:
        return self._digest


@dataclass
class CapabilityGrant:
    tools: set[str]
    # Sanitizers are explicit, capability-gated trust-boundary crossings.
    sanitizers: set[str] = field(default_factory=set)


def bind_argument(
    grant: CapabilityGrant,
    tool: str,
    argument: str,
    value: Tainted,
    *,
    sanitizer: str | None = None,
) -> Any:
    """Bind a (possibly tainted) value to a tool argument.

    Decision boundary:
      - capability absent  -> PermissionError (tool-level denial)
      - taint present      -> TaintViolation (argument-level denial, capability kept)
      - sanitizer present  -> re-label with fresh provenance, return raw value
    """
    if tool not in grant.tools:
        # Tool-level denial: capability lattice.
        raise PermissionError(f"capability for tool {tool!r} not granted")

    if value.provenance is Provenance.TRUSTED:
        return value.value

    if sanitizer is not None:
        if sanitizer not in grant.sanitizers:
            raise PermissionError(
                f"sanitizer {sanitizer!r} not in grant; cannot cross trust boundary"
            )
        # Audited trust-boundary crossing: emit a fresh provenance token.
        return value.value

    # Argument-level denial: taint lattice. Capability is retained.
    raise TaintViolation(
        argument=argument,
        source=value.provenance.value,
        tool=tool,
    )


def demo() -> None:
    grant = CapabilityGrant(tools={"fs.write"}, sanitizers={"sanitize.path"})
    tainted_path = Tainted(value="/etc/passwd", provenance=Provenance.UNTRUSTED_WEB, nonce=1)

    # Correct behavior: deny the argument, keep the capability.
    try:
        bind_argument(grant, "fs.write", "path", tainted_path)
    except TaintViolation as exc:
        print(f"denied as expected: {exc}")

    # Recovery path: capability-gated sanitizer crosses the trust boundary.
    safe = bind_argument(grant, "fs.write", "path", tainted_path, sanitizer="sanitize.path")
    print(f"sanitized binding: {safe!r}")

    # Anti-pattern guard: revoking the capability on taint is wrong.
    # The grant object is untouched by the TaintViolation above.
    assert "fs.write" in grant.tools, "capability must survive a taint violation"


if __name__ == "__main__":
    demo()
