AID-H-021.005: AI Coding-Agent Workspace Activation Trust Gate

Version 1.20261015 · Actionable sub-technique

Treat a newly opened, cloned, moved, aliased, or security-relevantly changed repository as an untrusted workspace before an AI coding agent activates repository-controlled behavior. Inventory lifecycle hooks, command-based MCP servers, folder-open tasks, dev-container or bootstrap commands, authority-changing agent rules, auto-approval settings, and repository-local skills or plugins without executing or authenticated-prefetching them. Keep passive bounded inspection available in restricted mode, but require an attributable user decision or exact organization policy decision bound to the canonical workspace identity and activation-manifest digest before a protected surface starts, registers, changes authority, or executes. Re-evaluate on relevant change and support immediate revocation. Workspace trust authorizes only the reviewed activation set; it never authorizes later tool actions.

Outcome states:

Pillars: app, infra

Lifecycle: building, operation

Scope boundary

Owns restricted-mode admission, complete repository activation-surface inventory, normalized effect preview, explicit per-workspace user decision or organization policy decision, immutable workspace and manifest binding, change-triggered re-evaluation, revocation, and proof that no protected activation occurred before a valid decision. It does not classify authentic repository content as benign, admit skill artifacts, authorize later tool effects, or provide execution isolation.

AID-H-021.001: AID-H-021.001 owns development-time configuration quality and repository policy checks; AID-H-021.005 owns local restricted-before-trust activation when a workspace is opened or changes. A pre-commit check does not prove the local client withheld repository automation.

AID-H-021.002: AID-H-021.002 proves startup or runtime artifacts are signed and approved; AID-H-021.005 decides whether repository-controlled behavior may activate for this exact workspace state. Authenticity is an input, not workspace trust or semantic safety.

AID-H-021.003: AID-H-021.003 governs which context and configuration sources are eligible for a trusted release; AID-H-021.005 owns developer-client restricted mode and activation admission. An eligible source is not automatically active in every checkout.

AID-H-030.005: AID-H-030.005 admits an exact agentic skill artifact for installation or continued use; AID-H-021.005 covers the complete workspace activation surface, including non-skill hooks, tasks, servers, and settings. An admitted skill can remain inactive until workspace trust, and workspace trust cannot admit an unreviewed skill.

AID-H-018.003: AID-H-018.003 independently validates and approves an exact high-impact runtime action; AID-H-021.005 authorizes only the reviewed repository activation set. Workspace trust must never satisfy mandatory approval for a later tool call.

AID-I-001: AID-I-001 contains code after execution is permitted; AID-H-021.005 decides whether repository-controlled execution or registration may begin. Restricted-before-trust admission and runtime containment are independently required where both apply.

AID-H-018.007: AID-H-021.005 decides whether the exact repository-controlled activation manifest may leave restricted mode; AID-H-018.007 enforces a skill's declared file, network, shell, tool, and protected-resource permissions after installation and activation. Workspace trust neither grants runtime skill permissions nor proves that later resource accesses stayed within the skill manifest.

Warning

Medium on first-open and security-relevant workspace changes; High if adapter coverage is incomplete

Repeated prompts can create consent fatigue, while an incomplete adapter set creates a false sense of safety. Prefer exact managed organization decisions for stable enterprise repositories and reserve interactive prompts for new or changed activation manifests. Keep restricted mode useful for passive bounded inspection, make the preview non-spoofable, and fail closed only for protected activation. Workspace trust is not content approval, skill admission, sandboxing, filesystem authority, or authorization for later tool actions.

Implementation guidance

AID-H-021.005-G001

Build a canonical activation manifest and trusted safe preview from a complete set of non-executing, client-owned workspace adapters before evaluating trust.
Concept

Inventory repository-controlled activation through client-owned adapters that can read a bounded workspace snapshot but cannot start a process, connect to a server, install a dependency, expand a secret, or perform authenticated prefetch. The manifest builder must reject unknown or incomplete adapter coverage. Repository strings are data: render the returned preview with trusted labels and text nodes, never as repository-supplied HTML or Markdown.

Step 1: Define a closed manifest builder
# File: workspace_activation/manifest.py
from __future__ import annotations

import hashlib
import json
import unicodedata
from dataclasses import asdict, dataclass
from pathlib import PurePosixPath
from typing import Protocol


ALLOWED_EFFECTS = {
    "agent_rule_authority",
    "auto_approval_change",
    "bootstrap_command",
    "command_mcp_server",
    "devcontainer_command",
    "folder_open_task",
    "lifecycle_hook",
    "local_plugin",
    "local_skill",
    "remote_tool_server",
}
PREVIEW_LABELS = {
    "agent_rule_authority": "Agent rule changes instruction authority",
    "auto_approval_change": "Approval behavior changes",
    "bootstrap_command": "Project bootstrap command",
    "command_mcp_server": "Local MCP server command",
    "devcontainer_command": "Development-container lifecycle command",
    "folder_open_task": "Folder-open task",
    "lifecycle_hook": "Agent lifecycle hook",
    "local_plugin": "Repository-local plug-in",
    "local_skill": "Repository-local skill",
    "remote_tool_server": "Remote tool-server connection",
}


@dataclass(frozen=True)
class WorkspaceIdentity:
    workspace_id: str
    repository_id: str
    worktree_id: str
    canonical_root: str
    client_id: str
    client_version: str


@dataclass(frozen=True)
class ActivationSurface:
    adapter_id: str
    effect: str
    source_path: str
    source_sha256: str
    source_entry: str
    details: dict


class WorkspaceSnapshot(Protocol):
    def read_regular(
        self, relative_path: str, maximum_bytes: int
    ) -> tuple[bytes, str] | None:
        """Read one regular file from a client-owned immutable snapshot.

        Return exact opened-object bytes and their lowercase SHA-256 digest.
        Reject links, aliases, replacement races, and objects outside the root.
        """
        raise RuntimeError("protocol-only method")


class InventoryAdapter(Protocol):
    adapter_id: str

    def inspect(
        self, snapshot: WorkspaceSnapshot
    ) -> tuple[ActivationSurface, ...]:
        """Parse metadata only; never execute, connect, install, or prefetch."""
        raise RuntimeError("protocol-only method")


def _canonical_json(value: object) -> bytes:
    return json.dumps(
        value,
        sort_keys=True,
        separators=(",", ":"),
        ensure_ascii=False,
        allow_nan=False,
    ).encode("utf-8")


def _strict_json(raw: bytes, maximum_bytes: int) -> object:
    if len(raw) > maximum_bytes:
        raise ValueError("configuration exceeds adapter byte cap")

    def unique(pairs: list[tuple[str, object]]) -> dict:
        result = {}
        for key, value in pairs:
            if key in result:
                raise ValueError(f"duplicate JSON key: {key}")
            result[key] = value
        return result

    return json.loads(raw.decode("utf-8"), object_pairs_hook=unique)


def _require_text(value: object, field: str, maximum: int = 4096) -> str:
    if (
        not isinstance(value, str)
        or not value
        or len(value) > maximum
        or unicodedata.normalize("NFC", value) != value
        or "\x00" in value
    ):
        raise ValueError(f"{field} must be bounded canonical text")
    return value


def _require_relative_path(value: object) -> str:
    text = _require_text(value, "source_path", 1024)
    if "\\" in text:
        raise ValueError("source_path must use canonical forward slashes")
    path = PurePosixPath(text)
    if (
        path.is_absolute()
        or str(path) != text
        or any(part in {"", ".", ".."} for part in path.parts)
    ):
        raise ValueError("source_path is not canonical and relative")
    return text


def _validate_details(value: object, depth: int = 0) -> None:
    if depth > 5:
        raise ValueError("activation details exceed depth cap")
    if value is None or isinstance(value, bool):
        return
    if isinstance(value, str):
        _require_text(value, "activation detail")
        return
    if type(value) is int:
        if abs(value) > 1_000_000_000:
            raise ValueError("activation numeric detail exceeds bound")
        return
    if isinstance(value, list):
        if len(value) > 128:
            raise ValueError("activation detail list exceeds bound")
        for item in value:
            _validate_details(item, depth + 1)
        return
    if isinstance(value, dict):
        if len(value) > 64:
            raise ValueError("activation detail object exceeds bound")
        for key, item in value.items():
            _require_text(key, "activation detail key", 128)
            _validate_details(item, depth + 1)
        return
    raise ValueError(f"unsupported activation detail type: {type(value).__name__}")


class CommandMcpAdapter:
    """Example adapter for strict-JSON project MCP command declarations."""

    adapter_id = "project_mcp_command_v1"
    source_path = ".mcp.json"
    maximum_bytes = 256 * 1024

    def inspect(
        self, snapshot: WorkspaceSnapshot
    ) -> tuple[ActivationSurface, ...]:
        opened = snapshot.read_regular(self.source_path, self.maximum_bytes)
        if opened is None:
            return ()
        raw, opened_sha256 = opened
        if hashlib.sha256(raw).hexdigest() != opened_sha256:
            raise RuntimeError("snapshot digest does not match opened bytes")
        value = _strict_json(raw, self.maximum_bytes)
        if not isinstance(value, dict) or set(value) != {"mcpServers"}:
            raise ValueError("unsupported project MCP document schema")
        servers = value["mcpServers"]
        if not isinstance(servers, dict) or len(servers) > 64:
            raise ValueError("mcpServers must be a bounded object")

        surfaces = []
        for server_name, server in sorted(servers.items()):
            name = _require_text(server_name, "MCP server name", 128)
            if not isinstance(server, dict) or set(server) - {
                "command", "args", "env"
            }:
                raise ValueError(f"unsupported MCP server shape: {name}")
            command = _require_text(server.get("command"), "MCP command", 1024)
            args = server.get("args", [])
            env = server.get("env", {})
            if (
                not isinstance(args, list)
                or len(args) > 64
                or any(
                    not isinstance(item, str) or not item or len(item) > 4096
                    for item in args
                )
                or not isinstance(env, dict)
                or len(env) > 64
                or any(
                    not isinstance(key, str) or not key
                    or not isinstance(item, str)
                    for key, item in env.items()
                )
            ):
                raise ValueError(f"invalid MCP command fields: {name}")
            surfaces.append(ActivationSurface(
                adapter_id=self.adapter_id,
                effect="command_mcp_server",
                source_path=self.source_path,
                source_sha256=opened_sha256,
                source_entry=name,
                details={
                    "executable": command,
                    "arguments": args,
                    "environment_names": sorted(env),
                },
            ))
        return tuple(surfaces)


def build_manifest(
    *,
    identity: WorkspaceIdentity,
    policy_revision: str,
    required_adapter_ids: tuple[str, ...],
    adapters: tuple[InventoryAdapter, ...],
    snapshot: WorkspaceSnapshot,
    maximum_surfaces: int = 512,
) -> tuple[bytes, str]:
    identity_values = asdict(identity)
    for field, value in identity_values.items():
        _require_text(value, field, 2048)
    _require_text(policy_revision, "policy_revision", 128)
    if (
        not required_adapter_ids
        or len(required_adapter_ids) != len(set(required_adapter_ids))
        or maximum_surfaces <= 0
    ):
        raise ValueError("invalid activation inventory policy")
    by_id = {adapter.adapter_id: adapter for adapter in adapters}
    if set(by_id) != set(required_adapter_ids) or len(by_id) != len(adapters):
        raise RuntimeError("required activation adapter population is incomplete")

    normalized = []
    surface_ids = set()
    for adapter_id in sorted(required_adapter_ids):
        _require_text(adapter_id, "adapter_id", 128)
        for surface in by_id[adapter_id].inspect(snapshot):
            if surface.adapter_id != adapter_id or surface.effect not in ALLOWED_EFFECTS:
                raise RuntimeError("adapter emitted an invalid identity or effect")
            _require_relative_path(surface.source_path)
            _require_text(surface.source_entry, "source_entry", 256)
            if (
                len(surface.source_sha256) != 64
                or set(surface.source_sha256) - set("0123456789abcdef")
            ):
                raise RuntimeError("adapter emitted an invalid source digest")
            _validate_details(surface.details)
            core = asdict(surface)
            surface_id = hashlib.sha256(_canonical_json(core)).hexdigest()
            if surface_id in surface_ids:
                raise RuntimeError("duplicate activation surface")
            surface_ids.add(surface_id)
            normalized.append({"surface_id": surface_id, **core})
            if len(normalized) > maximum_surfaces:
                raise RuntimeError("activation surface population exceeds policy")

    manifest = {
        "schema_version": "aidefend.workspace-activation.v1",
        "workspace": identity_values,
        "policy_revision": policy_revision,
        "complete_adapter_ids": sorted(required_adapter_ids),
        "surfaces": sorted(normalized, key=lambda item: item["surface_id"]),
    }
    canonical = _canonical_json(manifest)
    return canonical, hashlib.sha256(canonical).hexdigest()


def safe_preview(canonical_manifest: bytes) -> list[dict]:
    manifest = _strict_json(canonical_manifest, 4 * 1024 * 1024)
    if not isinstance(manifest, dict):
        raise ValueError("activation manifest must be an object")
    if _canonical_json(manifest) != canonical_manifest:
        raise ValueError("activation manifest is not canonical")
    preview = []
    for surface in manifest.get("surfaces", []):
        effect = surface.get("effect") if isinstance(surface, dict) else None
        if effect not in PREVIEW_LABELS:
            raise ValueError("unknown activation effect in manifest")
        preview.append({
            "label": PREVIEW_LABELS[effect],
            "source_path": surface["source_path"],
            "source_entry": surface["source_entry"],
            "details": surface["details"],
            "surface_id": surface["surface_id"],
        })
    return preview
Step 2: Register every supported client adapter

The MCP adapter is one concrete example, not a completeness claim. Add client-owned adapters for every activation surface the deployed coding-agent product supports: lifecycle hooks, folder-open tasks, dev-container lifecycle commands, authority-changing rule files, automatic approval settings, local skills, and plug-ins. Put the exact required adapter IDs in a versioned organization or client policy. An unknown surface, parser ambiguity, unsupported JSONC or YAML feature, adapter error, or missing required adapter keeps the workspace restricted and produces ERROR or INSUFFICIENT_DATA; it never silently disappears from the manifest.

Step 3: Render a non-spoofable preview

Render safe_preview() from a trusted client component using text nodes or an equivalent escaped widget. Show command, executable, arguments, environment-variable names, destination, requested privilege, authority-changing setting, source path, and source digest where applicable. Do not render repository HTML, terminal escapes, control characters, icons, or approval wording. Never run command discovery, shell expansion, package resolution, server connection, secret expansion, or authenticated prefetch while constructing the preview.

Verification

Test duplicate keys, malformed UTF-8, unsupported fields, oversized files and arrays, missing adapters, adapter exceptions, path aliases, links, replacement races, Unicode confusables, UI-control characters, command substitution text, environment values containing secrets, and a workspace with zero surfaces. Independently compare the manifest's complete adapter list with the client feature registry and re-hash every opened source object. A zero-surface manifest can be valid only after all required adapters report complete inspection.

AID-H-021.005-G002

Enforce restricted mode through one revocable activation broker that consumes an exact user or organization decision and issues activation-only authority for the reviewed workspace manifest.
Concept

The activation broker is the only component allowed to call protected workspace adapters. A decision may come from an interactive user flow or a managed organization policy; both must be verified outside repository control and bound to the same workspace identity, manifest digest, policy, client, principal, and validity state. The resulting token is scoped only to workspace activation and cannot satisfy a later tool-action approval.

Implement exact decision consumption
# File: workspace_activation/broker.py
from __future__ import annotations

import hashlib
import json
import time
from dataclasses import asdict, dataclass
from typing import Protocol


@dataclass(frozen=True)
class Decision:
    decision_id: str
    decision: str
    source: str
    workspace_id: str
    repository_id: str
    worktree_id: str
    manifest_sha256: str
    policy_revision: str
    client_id: str
    principal_id: str
    issued_at_epoch: int
    expires_at_epoch: int | None


@dataclass(frozen=True)
class DecisionLease:
    lease_id: str
    decision: Decision
    lease_expires_at_epoch: int


class VerifiedDecisionAuthority(Protocol):
    def acquire_current_lease(
        self, decision_id: str, expected_binding: dict
    ) -> DecisionLease:
        """Verify authenticity and atomically check current/revoked state."""
        raise RuntimeError("protocol-only method")

    def confirm_current(self, lease_id: str) -> None:
        """Fail if the lease or underlying decision was revoked or replaced."""
        raise RuntimeError("protocol-only method")


class ActivationAdapter(Protocol):
    adapter_id: str

    def activate(self, surface: dict, activation_token: bytes) -> dict:
        """Accept only calls authenticated as the broker workload identity."""
        raise RuntimeError("protocol-only method")

    def teardown(self, effect_id: str, activation_token: bytes) -> dict:
        """Stop the exact started effect and return independently readable state."""
        raise RuntimeError("protocol-only method")


class EvidenceStore(Protocol):
    def append_and_readback(self, event_id: str, canonical: bytes) -> bytes:
        raise RuntimeError("protocol-only method")


def _canonical_json(value: object) -> bytes:
    return json.dumps(
        value, sort_keys=True, separators=(",", ":"),
        ensure_ascii=False, allow_nan=False
    ).encode("utf-8")


def _strict_canonical_manifest(raw: bytes) -> dict:
    def unique(pairs: list[tuple[str, object]]) -> dict:
        result = {}
        for key, value in pairs:
            if key in result:
                raise ValueError(f"duplicate manifest key: {key}")
            result[key] = value
        return result

    value = json.loads(raw.decode("utf-8"), object_pairs_hook=unique)
    if not isinstance(value, dict) or _canonical_json(value) != raw:
        raise ValueError("manifest must be canonical JSON")
    if value.get("schema_version") != "aidefend.workspace-activation.v1":
        raise ValueError("unsupported activation manifest schema")
    return value


def activate_surface(
    *,
    canonical_manifest: bytes,
    surface_id: str,
    decision_id: str,
    principal_id: str,
    expected_policy_revision: str,
    expected_client_id: str,
    authority: VerifiedDecisionAuthority,
    adapter: ActivationAdapter,
    evidence: EvidenceStore,
    now_epoch: int | None = None,
) -> dict:
    now = int(time.time()) if now_epoch is None else now_epoch
    if type(now) is not int or now <= 0:
        raise ValueError("invalid broker clock")
    manifest = _strict_canonical_manifest(canonical_manifest)
    manifest_sha256 = hashlib.sha256(canonical_manifest).hexdigest()
    workspace = manifest.get("workspace")
    surfaces = manifest.get("surfaces")
    if not isinstance(workspace, dict) or not isinstance(surfaces, list):
        raise ValueError("manifest lacks workspace or surface population")
    matches = [
        item for item in surfaces
        if isinstance(item, dict) and item.get("surface_id") == surface_id
    ]
    if len(matches) != 1:
        raise PermissionError("surface is absent or duplicated")
    surface = matches[0]
    adapter_id = surface.get("adapter_id")
    complete_adapter_ids = manifest.get("complete_adapter_ids")
    if (
        not isinstance(adapter_id, str)
        or not adapter_id
        or not isinstance(complete_adapter_ids, list)
        or adapter_id not in complete_adapter_ids
        or adapter.adapter_id != adapter_id
    ):
        raise PermissionError("surface-to-adapter binding mismatch")
    expected_binding = {
        "workspace_id": workspace.get("workspace_id"),
        "repository_id": workspace.get("repository_id"),
        "worktree_id": workspace.get("worktree_id"),
        "manifest_sha256": manifest_sha256,
        "policy_revision": expected_policy_revision,
        "client_id": expected_client_id,
        "principal_id": principal_id,
    }
    if (
        manifest.get("policy_revision") != expected_policy_revision
        or workspace.get("client_id") != expected_client_id
        or any(not isinstance(value, str) or not value
               for value in expected_binding.values())
    ):
        raise PermissionError("effective workspace binding mismatch")

    lease = authority.acquire_current_lease(decision_id, expected_binding)
    decision = lease.decision
    actual_binding = {
        key: getattr(decision, key) for key in expected_binding
    }
    if actual_binding != expected_binding:
        raise PermissionError("verified decision binding mismatch")
    if (
        decision.decision != "ALLOW"
        or decision.source not in {"user", "organization"}
        or decision.issued_at_epoch > now
        or (
            decision.expires_at_epoch is not None
            and now >= decision.expires_at_epoch
        )
        or now >= lease.lease_expires_at_epoch
    ):
        raise PermissionError("workspace decision is not currently usable")

    token_core = {
        "schema_version": "aidefend.workspace-activation-token.v1",
        "scope": "workspace_activation",
        "decision_id": decision.decision_id,
        "decision_source": decision.source,
        "lease_id": lease.lease_id,
        "workspace_id": decision.workspace_id,
        "manifest_sha256": manifest_sha256,
        "surface_id": surface_id,
        "adapter_id": adapter_id,
        "surface_sha256": hashlib.sha256(
            _canonical_json(surface)
        ).hexdigest(),
        "principal_id": principal_id,
        "policy_revision": expected_policy_revision,
        "client_id": expected_client_id,
        "issued_at_epoch": now,
        "expires_at_epoch": min(
            value for value in (
                lease.lease_expires_at_epoch,
                decision.expires_at_epoch,
            ) if value is not None
        ),
    }
    token = _canonical_json(token_core)
    authority.confirm_current(lease.lease_id)
    effect = adapter.activate(surface, token)
    if (
        not isinstance(effect, dict)
        or effect.get("token_sha256") != hashlib.sha256(token).hexdigest()
        or effect.get("surface_id") != surface_id
        or effect.get("activated") is not True
        or not isinstance(effect.get("effect_id"), str)
        or not effect["effect_id"]
    ):
        raise RuntimeError("activation adapter returned invalid readback")

    event = {
        **token_core,
        "event_id": hashlib.sha256(
            _canonical_json({
                "decision_id": decision.decision_id,
                "surface_id": surface_id,
                "effect_id": effect["effect_id"],
            })
        ).hexdigest(),
        "effect": effect,
    }
    serialized = _canonical_json(event)
    try:
        if evidence.append_and_readback(event["event_id"], serialized) != serialized:
            raise RuntimeError("activation evidence readback mismatch")
    except Exception as evidence_error:
        teardown = adapter.teardown(effect["effect_id"], token)
        if (
            not isinstance(teardown, dict)
            or teardown.get("effect_id") != effect["effect_id"]
            or teardown.get("token_sha256") != hashlib.sha256(token).hexdigest()
            or teardown.get("deactivated") is not True
        ):
            raise RuntimeError(
                "activation evidence failed and teardown was not proven"
            ) from evidence_error
        raise RuntimeError(
            "activation evidence failed; the started surface was torn down"
        ) from evidence_error
    return event
Make the broker non-bypassable

Give only the broker workload identity permission to start lifecycle hooks, task runners, local MCP processes, development-container lifecycle commands, plug-in loaders, skill registries, and authority-changing configuration adapters. The UI, model, repository process, extension host, and ordinary agent runtime submit requests but cannot invoke an adapter directly. The decision authority verifies user or organization decisions, applies revocation atomically, and issues short leases; it must not accept a repository- or model-supplied trusted=true field.

Re-evaluate and revoke

Rebuild the manifest whenever a source object, canonical repository/worktree identity, trust policy, client version with activation semantics, or required adapter set changes. A digest mismatch returns the workspace to restricted mode. Revocation stops not-yet-started activation immediately and invokes adapter-specific teardown for already activated local servers, tasks, hooks, or plug-ins. Keep passive bounded reading available unless a separate content or filesystem control denies it.

Verification

Test missing, malformed, stale, expired, revoked, wrong-principal, wrong-client, wrong-repository, wrong-worktree, moved-folder, aliased-path, changed-manifest, rolled-back-manifest, duplicate-surface, lease-expiry, revocation-race, authority outage, adapter outage, evidence-readback failure, and direct-adapter calls. For evidence-write or readback failure after activation, require an exact adapter teardown readback and prove that the started effect is no longer active; an unproven teardown is ERROR. Include a valid non-ASCII workspace and surface manifest and require its bytes and SHA-256 to remain identical through inventory, broker, and verifier canonicalization. Also prove that an organization decision works without repeated human prompts when bound to the exact state, and that an activation token is rejected by every later high-impact tool-action gate.

AID-H-021.005-G003

Apply exact managed policy in enterprise or headless clients and independently reconcile every protected activation attempt with broker decisions and a complete adapter-specific bypass-test matrix.
Concept

Headless operation does not imply automatic trust. CI, remote development, and managed fleets may replace repeated interactive consent with a pre-issued organization decision, but the decision must bind the same exact workspace and activation manifest used by the client. Assess the control from independently collected broker, adapter, process, network, and client telemetry rather than from the coding agent's self-report.

Evaluate the complete activation population
# File: workspace_activation/verify_coverage.py
from __future__ import annotations

import hashlib
import json
import sys
from pathlib import Path


REQUIRED_CASES = {
    "direct_adapter",
    "expired_decision",
    "first_open",
    "headless_without_decision",
    "manifest_mutation",
    "manifest_rollback",
    "moved_worktree",
    "path_alias",
    "restricted_read_no_activation",
    "revoked_decision",
    "ui_confusion",
    "wrong_adapter",
    "wrong_principal",
}
OUTCOMES = {
    "PASS", "FAIL", "INSUFFICIENT_DATA", "NOT_APPLICABLE", "ERROR"
}


def _load_object(path: str) -> dict:
    value = json.loads(Path(path).read_text(encoding="utf-8"))
    if not isinstance(value, dict):
        raise ValueError(f"{path} must contain one JSON object")
    return value


def _canonical_json(value: object) -> bytes:
    return json.dumps(
        value, sort_keys=True, separators=(",", ":"),
        ensure_ascii=False, allow_nan=False
    ).encode("utf-8")


def assess(bundle: dict) -> dict:
    if set(bundle) != {
        "architecture",
        "policy_revision",
        "required_adapter_ids",
        "manifest",
        "observation",
        "broker_events",
        "activation_attempts",
        "negative_tests",
        "dependency_errors",
    }:
        return {"outcome": "ERROR", "reasons": ["evidence_schema_mismatch"]}
    architecture = bundle["architecture"]
    if architecture == "absent":
        return {
            "outcome": "NOT_APPLICABLE",
            "reasons": ["no_repository_activation_path"],
        }
    if architecture != "present":
        return {"outcome": "ERROR", "reasons": ["architecture_indeterminate"]}
    if not isinstance(bundle["dependency_errors"], list):
        return {"outcome": "ERROR", "reasons": ["invalid_dependency_errors"]}
    if bundle["dependency_errors"]:
        return {
            "outcome": "ERROR",
            "reasons": sorted(set(bundle["dependency_errors"])),
        }

    required = bundle["required_adapter_ids"]
    manifest = bundle["manifest"]
    observation = bundle["observation"]
    if (
        not isinstance(required, list)
        or not required
        or len(required) != len(set(required))
        or not isinstance(manifest, dict)
        or not isinstance(observation, dict)
    ):
        return {"outcome": "ERROR", "reasons": ["invalid_population_metadata"]}
    complete_adapters = manifest.get("complete_adapter_ids")
    workspace = manifest.get("workspace")
    surfaces = manifest.get("surfaces")
    if (
        not isinstance(workspace, dict)
        or not isinstance(workspace.get("workspace_id"), str)
        or not workspace["workspace_id"]
        or not isinstance(workspace.get("client_id"), str)
        or not workspace["client_id"]
        or not isinstance(surfaces, list)
        or any(not isinstance(item, dict) for item in surfaces)
    ):
        return {"outcome": "ERROR", "reasons": ["invalid_manifest_population"]}
    manifest_surface_ids = [item.get("surface_id") for item in surfaces]
    manifest_adapter_ids = [item.get("adapter_id") for item in surfaces]
    if (
        any(not isinstance(item, str) or not item for item in manifest_surface_ids)
        or any(not isinstance(item, str) or not item for item in manifest_adapter_ids)
        or len(manifest_surface_ids) != len(set(manifest_surface_ids))
        or any(item not in required for item in manifest_adapter_ids)
    ):
        return {"outcome": "ERROR", "reasons": ["invalid_manifest_surfaces"]}
    surface_adapter_by_id = {
        surface_id: adapter_id
        for surface_id, adapter_id in zip(
            manifest_surface_ids, manifest_adapter_ids, strict=True
        )
    }
    manifest_sha256 = hashlib.sha256(_canonical_json(manifest)).hexdigest()
    if (
        sorted(required) != sorted(complete_adapters or [])
        or observation.get("population_complete") is not True
        or observation.get("policy_revision") != bundle["policy_revision"]
        or manifest.get("policy_revision") != bundle["policy_revision"]
        or observation.get("workspace_id")
           != manifest.get("workspace", {}).get("workspace_id")
        or observation.get("manifest_sha256")
           != manifest_sha256
    ):
        return {
            "outcome": "INSUFFICIENT_DATA",
            "reasons": ["inventory_or_observation_population_incomplete"],
        }

    broker_events = bundle["broker_events"]
    attempts = bundle["activation_attempts"]
    if not isinstance(broker_events, list) or not isinstance(attempts, list):
        return {"outcome": "ERROR", "reasons": ["invalid_event_population"]}
    by_effect = {}
    for event in broker_events:
        if (
            not isinstance(event, dict)
            or not isinstance(event.get("effect"), dict)
            or not isinstance(event["effect"].get("effect_id"), str)
            or not event["effect"]["effect_id"]
        ):
            return {"outcome": "ERROR", "reasons": ["invalid_broker_event"]}
        effect_id = event["effect"]["effect_id"]
        if effect_id in by_effect:
            return {"outcome": "ERROR", "reasons": ["duplicate_broker_effect"]}
        by_effect[effect_id] = event

    failures = []
    observed_effects = set()
    for attempt in attempts:
        if not isinstance(attempt, dict) or not attempt.get("effect_id"):
            return {"outcome": "ERROR", "reasons": ["invalid_activation_attempt"]}
        effect_id = attempt["effect_id"]
        if effect_id in observed_effects:
            return {"outcome": "ERROR", "reasons": ["duplicate_observed_effect"]}
        observed_effects.add(effect_id)
        event = by_effect.get(effect_id)
        expected = {
            "workspace_id": workspace["workspace_id"],
            "manifest_sha256": manifest_sha256,
            "surface_id": attempt.get("surface_id"),
            "adapter_id": attempt.get("adapter_id"),
            "policy_revision": bundle["policy_revision"],
            "client_id": workspace["client_id"],
        }
        if (
            event is None
            or event.get("scope") != "workspace_activation"
            or any(event.get(key) != value for key, value in expected.items())
            or event["effect"].get("effect_id") != effect_id
            or event["effect"].get("surface_id") != attempt.get("surface_id")
            or event["effect"].get("activated") is not True
            or attempt.get("adapter_id") not in required
            or surface_adapter_by_id.get(attempt.get("surface_id"))
               != attempt.get("adapter_id")
        ):
            failures.append(f"unbrokered_or_misbound:{effect_id}")

    extra_broker_events = set(by_effect) - observed_effects
    if extra_broker_events:
        return {
            "outcome": "INSUFFICIENT_DATA",
            "reasons": ["broker_effect_missing_from_independent_observation"],
        }

    tests = bundle["negative_tests"]
    if not isinstance(tests, list):
        return {"outcome": "ERROR", "reasons": ["invalid_negative_tests"]}
    covered = {adapter_id: set() for adapter_id in required}
    seen_tests = set()
    for item in tests:
        if (
            not isinstance(item, dict)
            or item.get("adapter_id") not in covered
            or item.get("case") not in REQUIRED_CASES
            or item.get("result") not in {"blocked", "safe_restricted_read"}
        ):
            failures.append("invalid_or_failed_negative_test")
            continue
        test_key = (item["adapter_id"], item["case"])
        if test_key in seen_tests:
            return {"outcome": "ERROR", "reasons": ["duplicate_negative_test"]}
        seen_tests.add(test_key)
        if (
            item["case"] == "restricted_read_no_activation"
            and item["result"] != "safe_restricted_read"
        ) or (
            item["case"] != "restricted_read_no_activation"
            and item["result"] != "blocked"
        ):
            failures.append(
                f"bypass_test_failed:{item['adapter_id']}:{item['case']}"
            )
        covered[item["adapter_id"]].add(item["case"])
    missing = {
        adapter_id: sorted(REQUIRED_CASES - cases)
        for adapter_id, cases in covered.items()
        if cases != REQUIRED_CASES
    }
    if failures:
        return {"outcome": "FAIL", "reasons": sorted(set(failures))}
    if missing:
        return {
            "outcome": "INSUFFICIENT_DATA",
            "reasons": ["missing_bypass_cases"],
            "missing": missing,
        }
    return {
        "outcome": "PASS",
        "reasons": [],
        "adapter_count": len(required),
        "activation_attempt_count": len(attempts),
    }


if __name__ == "__main__":
    if len(sys.argv) != 2:
        raise SystemExit("usage: verify_coverage.py verified-evidence.json")
    result = assess(_load_object(sys.argv[1]))
    if result["outcome"] not in OUTCOMES:
        raise RuntimeError("verifier emitted an invalid outcome")
    print(json.dumps(result, sort_keys=True))
    raise SystemExit(0 if result["outcome"] in {"PASS", "NOT_APPLICABLE"} else 1)
Bind managed and headless decisions

Provision headless workers with a pre-issued organization decision through a channel the repository cannot write. Bind it to enterprise repository identity, worktree or immutable checkout identity, exact activation-manifest digest, policy revision, client identity/version, principal or workload, and any policy-defined validity bound. A missing UI, service-account identity, signed commit, allowlisted repository, or previously trusted path is not by itself an allow decision. Authentic provenance helps identify the workspace but does not prove its activation behavior is benign.

Collect independent evidence

Build the verifier bundle from signature- or access-control-verified policy, broker, adapter, endpoint process, network, and client telemetry collected under an identity the coding-agent runtime cannot alter. The sample evaluator is deterministic but does not authenticate the bundle itself; perform that verification before invoking it. Resolve retention and freshness from versioned organization policy rather than publishing a universal duration.

Run bypass and lifecycle tests

Run every required case against every supported activation adapter: first open, direct adapter invocation, wrong-adapter substitution, symlink/junction/path alias, moved worktree, manifest mutation and rollback, expiry, revocation, wrong-principal reuse, absent UI, UI-confusion strings, and passive restricted reading. Also run a positive Unicode-manifest fixture and require the inventory, broker, and verifier to compute identical canonical bytes and SHA-256. Exercise adapter, policy, identity, revocation, evidence-store, and observation outages. A path that exists but has no trust boundary is FAIL; an unmeasured or incomplete path is INSUFFICIENT_DATA; a dependency failure during assessment is ERROR. Do not convert any of them to PASS or N/A.

Open-source tools

Commercial / hosted tools

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-H-021.005: AI Coding-Agent Workspace Activation Trust Gate | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version