# Under what conditions must an instance reject a dynamically loaded agent skill whose manifest declares capabilities the host cannot independently verify **Date of Record:** 2026 November 13 **Scope:** Applies to hosts that resolve agent skills or plugins from a manifest at runtime and grant capability (tool access, filesystem scope, network egress, credential handles) based on manifest self-declaration. Does not apply to statically compiled-in skills, or to hosts that perform out-of-band attestation of every declared capability against a signed inventory before load. ## Core Distinction The failure is not "a malicious skill runs arbitrary code." That is a known and largely unsolvable problem once code is loaded. The non-obvious boundary is narrower and more insidious: **a skill whose manifest declares a superset of the capabilities it actually needs, where the host has no mechanism to distinguish a truthful superset from a lying one, and where the runtime grants the declared set verbatim.** The attack surface is the *delta between declared and required capability*, not the code itself. A skill that declares `filesystem:write:/tmp` and only reads `/tmp` is indistinguishable, at manifest-parse time, from a skill that declares the same and writes `/etc/cron.d`. The host cannot verify either claim without either (a) sandboxing enforcement that makes the grant moot, or (b) static analysis of the skill body, which is undecidable in the general case for dynamic languages. When neither exists, the manifest is an *assertion of trust*, not a *description of behavior*, and the host is conflating the two. The invariant that must hold: **every capability granted at load must be independently enforceable at the syscall, RPC, or credential boundary — not merely declared at the manifest boundary.** If a declared capability has no enforcement point, it is a liability, not a permission. ## What Was Observed In a representative incident, a manifest declared: ```json { "name": "summarize-inbox", "capabilities": ["mail:read", "llm:invoke", "filesystem:write:/tmp", "env:read:HOME"], "entrypoint": "skill.py" } ``` The host parsed the manifest, resolved each capability to an ACL entry, and loaded the skill. `env:read:HOME` was granted because the host had a coarse `env:read` handler that, when given a suffix, filtered the environment it exposed to the child process. The filter matched on prefix `HOME` — which also matched `HOME_SSH_KEY_PATH`, `HOMEBREW_GITHUB_API_TOKEN`, and any other variable beginning with `HOME`. The skill did not need `env:read` at all; it was declared defensively by a developer copying a template. The exfiltration path was not in the skill's declared behavior. It was in the host's *interpretation* of the declaration: the manifest said `env:read:HOME`, the host read that as "expose variables matching `HOME*`", and the skill's `llm:invoke` capability let it ship the result to an external endpoint. Two individually reasonable capabilities composed into a credential leak that neither the manifest author nor the host author intended. The second observation: a sibling skill in the same registry declared `filesystem:write:/tmp` and, at runtime, called a subprocess that resolved `/tmp` through `$TMPDIR`, which the host had not constrained. The declared capability was enforced on the literal path `/tmp`; the actual write landed in `/var/folders/.../T/`, outside the ACL. The manifest was truthful about intent and wrong about effect. ## Condition of Validity The load must be rejected — or the capability downgraded to a hard-deny — when all of the following hold: 1. The manifest declares a capability for which the host has no enforcement hook at a boundary below the skill's execution context (no seccomp filter, no namespace, no RPC-side authorization, no credential scoping). 2. The capability's selector is non-canonical (prefix match, glob, environment-variable-indirected path, or symbolic name resolved at runtime by the child). 3. The host cannot enumerate the concrete resources the selector will resolve to *before* the skill executes. If any one is false, the load may proceed: a capability with a real enforcement hook is safe regardless of manifest honesty; a canonical selector (`filesystem:write:/tmp/scratch` with no indirection) is verifiable; a host that can pre-resolve the selector set can diff declared against resolved. This is a *conjunction*. The common mistake is treating (1) alone as sufficient — "we have a sandbox, so we're fine" — when the sandbox enforces a different boundary than the one the manifest names. The sandbox enforces *syscalls*; the manifest names *resources*. The gap between them is the attack surface. ## Decision Boundary The practical test at manifest-parse time: - For each declared capability, resolve the selector to a concrete, finite set of resources using host-controlled logic only. If resolution requires the skill to run, the capability is unverifiable. - For each resolved resource, locate the enforcement point. If the enforcement point is the skill's own code (e.g., the skill is trusted to check its own ACL), the capability is unenforced. - If any capability is both unverifiable and unenforced, reject the load with a specific error naming the capability and the missing enforcement point. Do not silently downgrade — silent downgrade produces skills that pass load and fail at first use, which is worse for operability than a hard reject. The boundary is *not* "reject manifests that declare more than they need." Over-declaration is a code smell, not a security event, and rejecting it produces false positives that train operators to ignore the check. The boundary is "reject capabilities the host cannot hold the skill to." ## Operational Effect Enforcing this invariant converts a class of silent trust-elevation bugs into loud load-time failures. The cost is a registry of capabilities with known enforcement points, maintained alongside the host's sandbox configuration. The benefit is that the manifest becomes a *contract the host can audit*, not a *wish list the host honors*. Skills that previously loaded and leaked now fail fast with a diagnostic naming the exact capability (`env:read:HOME` → "no enforcement point; env exposure is prefix-matched by the child, not scoped by the host") and the operator can either add an enforcement hook or narrow the selector to something canonical. The secondary effect is that skill authors stop declaring defensive capabilities. When over-declaration is a load failure rather than a no-op, the manifest converges toward the actual required set, and the delta — the attack surface — shrinks to zero by construction. ```python class UnverifiableCapabilityError(RuntimeError): """Raised when a manifest declares a capability the host cannot enforce.""" pass # Capabilities the host can enforce at a boundary below the skill. # Anything not in this map has no enforcement hook and is therefore # unverifiable by construction. ENFORCEABLE = { "filesystem:write": "mount-namespace", "mail:read": "rpc-authorizer", "llm:invoke": "egress-proxy", } def _selector_is_canonical(capability: str) -> bool: # Non-canonical selectors: env-var indirection, globs, bare prefixes. selector = capability.split(":", 2)[-1] if capability.count(":") >= 2 else "" if not selector: return False if "$" in selector or "*" in selector or "?" in selector: return False # A bare name like "HOME" is a prefix match, not a path. if ":" not in capability and "/" not in selector: return False return True def validate_manifest(manifest: dict) -> list[str]: granted = [] for cap in manifest.get("capabilities", []): kind = ":".join(cap.split(":")[:2]) if kind not in ENFORCEABLE: raise UnverifiableCapabilityError( f"capability {cap!r} has no enforcement point; " f"host cannot hold skill to declared scope" ) if not _selector_is_canonical(cap): raise UnverifiableCapabilityError( f"capability {cap!r} uses a non-canonical selector; " f"host cannot pre-resolve target set before execution" ) granted.append(cap) return granted def load_skill(manifest: dict) -> dict: caps = validate_manifest(manifest) return {"manifest": manifest, "granted": caps, "enforcement": ENFORCEABLE} ```