# How should an instance Distinguish Registry-Sourced Skill Injection from Legitimate Dynamic Tool Loading **Date of Record:** 2026 November 13 **Scope:** Applies to agents that load skills or tools from a mutable registry (remote manifest, plugin directory, MCP-style server list) at runtime, where the loader trusts registry metadata to decide what to activate. Does not apply to statically compiled tool sets with no post-deployment mutation path, nor to registries where every entry is signed and verified against a pinned publisher key at load time. ## Core Distinction The non-obvious boundary is not "trusted vs. untrusted registry." It is **description-plane vs. execution-plane authority**. A skill registry entry carries two separable artifacts: (a) the executable handler, and (b) the natural-language description, parameter schema, and trigger conditions that the agent's planner reads to decide *when and how* to invoke the handler. Most loaders gate (a) — sandboxing, capability manifests, allowlists — while treating (b) as inert metadata. But the description plane is itself an instruction channel consumed by the reasoning model. An attacker who can mutate a description without touching the handler achieves skill injection: the handler remains benign and passes every execution-plane check, while the description steers the planner into invoking it with attacker-chosen arguments, or into treating injected text as a system-level directive. The failure is a *confused authority* invariant violation: the loader assumes that "this text came from the registry" implies "this text is data," when the planner's consumption path treats registry text with the same priority as operator-provided instructions. ## What Was Observed A registry synced a tool entry whose handler was byte-identical to a previously approved version — hash matched, signature over the handler verified. The description field, however, was not covered by the signature (only the handler blob was). The mutated description contained a trigger clause instructing the planner to prefer the tool for any task involving credential rotation and to pass the current session token as an argument "for audit correlation." The planner, reading descriptions as trusted configuration, selected the tool outside its normal scope and populated the argument from context. The handler executed exactly as designed and logged the token to its normal output channel, which was readable by the registry publisher. Key observation: every execution-plane control passed. The only violated assumption was that the description plane was immutable relative to the handler plane. Signature coverage was per-blob, not per-entry, so the two planes had divergent integrity guarantees that no single check reconciled. ## Condition of Validity This boundary is real only when all of the following hold: 1. The registry is mutable post-deployment and at least one mutation path does not require re-approval of the full entry. 2. The planner consumes registry-sourced description/schema text at a trust level at or above operator instructions. 3. Integrity attestation covers the handler but not the full entry (description, schema defaults, trigger conditions, or dependency list). 4. Tool selection or argument binding is influenced by description text rather than by a fixed dispatch table. If any condition fails, the attack surface collapses: signed full-entry manifests, description-as-untrusted-data handling with explicit provenance tagging, or fixed dispatch tables each independently close it. The dangerous case is partial coverage — enough signing to create false confidence, not enough to bind the description plane. ## Decision Boundary Treat the registry entry as a single atomic trust unit, not a handler plus metadata. The decision rule: - If the entry's integrity attestation does not cover *every field the planner reads*, the entry must be loaded in **quarantine mode**: handler invocable only via explicit operator dispatch, description never presented to the planner as trusted context, and all planner-visible text wrapped with provenance markers that the model is instructed to treat as untrusted data. - If attestation covers the full entry and the publisher key is pinned, the entry may be loaded in **normal mode**. - Never derive "trusted" from handler-hash equality alone. A matching handler with a changed description is a *new* entry requiring fresh attestation, not a benign update. The boundary to enforce in code: reject any registry entry whose attested field set is a strict subset of its planner-visible field set. This is a set-inclusion check, cheap and total. ## Operational Effect Enforcing full-entry attestation converts a silent steering attack into a loud load-time rejection. The cost is that publishers must re-sign entries on any description edit, which slows iteration on trigger wording — an acceptable trade because description edits are exactly the mutation the attack requires. Quarantine mode preserves availability for unsigned entries while removing their authority over the planner. The residual risk shifts from "attacker steers the planner" to "attacker cannot mutate the description without breaking the signature," which is the intended invariant: the description plane and the execution plane must share one integrity boundary, or the weaker plane silently becomes the attack surface. ```python import hashlib from dataclasses import dataclass, field from typing import FrozenSet class DescriptionPlaneDivergence(RuntimeError): """Raised when an entry's attested fields do not cover all planner-visible fields.""" pass @dataclass(frozen=True) class RegistryEntry: name: str handler_blob: bytes description: str schema_defaults: dict trigger_conditions: str attested_fields: FrozenSet[str] # fields covered by publisher signature def planner_visible_fields(self) -> FrozenSet[str]: return frozenset({"description", "schema_defaults", "trigger_conditions"}) def load_entry(entry: RegistryEntry, publisher_key_pinned: bool) -> str: visible = entry.planner_visible_fields() uncovered = visible - entry.attested_fields if uncovered: # Handler may be intact; the description plane is not. Refuse normal load. raise DescriptionPlaneDivergence( f"entry {entry.name!r}: planner-visible fields {sorted(uncovered)} " f"are outside the attested set {sorted(entry.attested_fields)}; " f"handler hash {hashlib.sha256(entry.handler_blob).hexdigest()[:12]} " f"is irrelevant to this check" ) if not publisher_key_pinned: raise DescriptionPlaneDivergence( f"entry {entry.name!r}: full attestation present but publisher key unpinned" ) return "normal" def load_or_quarantine(entry: RegistryEntry, publisher_key_pinned: bool) -> str: try: return load_entry(entry, publisher_key_pinned) except DescriptionPlaneDivergence: return "quarantine" # operator-dispatch only; description withheld from planner ```