<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Skills on Markus Hupfauer</title>
    <link>https://hupfauer.one/tags/skills/</link>
    <description>Recent content in Skills on Markus Hupfauer</description>
    <image>
      <title>Markus Hupfauer</title>
      <url>https://hupfauer.one/og-default.jpg</url>
      <link>https://hupfauer.one/og-default.jpg</link>
    </image>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 25 May 2026 12:00:00 +0200</lastBuildDate>
    <atom:link href="https://hupfauer.one/tags/skills/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>The registry is the control plane</title>
      <link>https://hupfauer.one/posts/the-registry-is-the-control-plane/</link>
      <pubDate>Mon, 25 May 2026 12:00:00 +0200</pubDate>
      <guid>https://hupfauer.one/posts/the-registry-is-the-control-plane/</guid>
      <description>Skills became a distribution format. That makes the registry a natural attachment point for the controls that already exist for every other package format — signing, manifests, allowlists — and the place where capabilities acquire identities operators can govern.</description>
      <content:encoded><![CDATA[<p>The previous three posts on this site argued that probabilistic recognizers — input-side prompt-injection detection, defensive prompt injection, the auto-mode classifier — are sensors, not controls, and that identity scoping, sandboxing, and human-in-the-loop on irreversible actions are the actual controls. This post is the same argument one layer up. The capability surface itself just became distributable, which changes where the controls have a place to bind.</p>
<h2 id="the-npx-moment">The npx moment</h2>
<p>In the last few months, &ldquo;skills&rdquo; went from a Claude Code convention to a plausible distribution format. Anthropic&rsquo;s SKILL.md — a folder with a markdown file, optional scripts, progressive disclosure on activation — got pulled out into an open spec at <a href="https://agentskills.io/specification">agentskills.io</a> with early support across roughly 30 runtimes at varying fidelity, from native handling in Claude Code, Cursor, and the Gemini CLI down to community adapters. Vercel Labs published <a href="https://github.com/vercel-labs/skills"><code>npx skills</code></a>, a package-manager-shaped CLI that reads GitHub repos as a registry and writes SKILL.md files into the agent&rsquo;s config directory. JFrog <a href="https://jfrog.com/ai-catalog/mcp-registry/">shipped MCP Registry</a> inside its AI Catalog. The shape is suddenly familiar: agent capabilities are named, installable, versioned artifacts.</p>
<p>A note on taxonomy before going further, because the next few sections need it. I will use &ldquo;skill&rdquo; loosely to mean any installable agent capability — a SKILL.md package, an MCP server, a runtime-specific plugin, an OS-registered agent action. They are not the same thing. SKILL.md is mostly instruction plus optional scripts; MCP servers expose callable tools over a protocol; Windows On-Device Registry holds agent action manifests. The supply-chain argument applies to all of them precisely because they have collapsed into the same operational category: named, installable, redistributable capabilities. Where the differences matter — and they will — I will say which one.</p>
<p>When a capability becomes a published artifact, a large part of agent governance stops looking novel and starts looking like software supply chain. Not all of it. Model behavior still determines when a skill fires and how its parameters are formed, which is the entire reason ordering and runtime policy matter later in this post. But the <em>control surface</em> becomes much more familiar, and every muscle the industry built for npm, Maven, PyPI, and container registries becomes applicable — along with every failure mode.</p>
<h2 id="skills-are-dependencies-treat-them-like-dependencies">Skills are dependencies. Treat them like dependencies.</h2>
<p>If a skill is a dependency, the first question a security-literate org asks is: where does it come from, who else can change it, and what does the artifact actually claim to do. The current answer is mostly: a GitHub repo, pinned to a commit SHA if you are lucky, auto-updating from <code>main</code> if you are not. Anthropic&rsquo;s own documentation is admirably blunt — plugins and marketplaces are &ldquo;highly trusted components that can execute arbitrary code on your machine with your user privileges&rdquo; and the platform &ldquo;cannot verify that they work as intended.&rdquo; Translated: trust is a property of the source, not the artifact. That is the posture mainstream package ecosystems sat in before lockfiles, before signed provenance, before anyone took attestation seriously. Skills are running through that same arc compressed into a year.</p>
<p>The unglamorous fix is well understood. Orgs run a private registry that proxies the public one, mirrors only what passes policy, attaches a signature, and locks down which sources users can add. JFrog is early in treating MCP servers and adjacent artifacts as governable rather than as URLs; other registry vendors have the same buyer problem in front of them and will likely test the same market. Claude Code&rsquo;s managed-settings layer already exposes the consumer-side primitives: <code>extraKnownMarketplaces</code> lets admins push a private marketplace, <code>enabledPlugins</code> constrains what can be active, and <code>disableSkillShellExecution</code> blocks the dynamic-context-injection feature that lets a skill execute shell at activation. The plumbing exists. Cryptographic signing of the artifact does not, on any mainstream skill runtime. Until it does, the private registry is a name resolver, not a trust boundary.</p>
<h2 id="skill-bom-is-not-ai-bom">Skill-BOM is not AI-BOM</h2>
<p>The instinct here is to reach for an existing standard. The relevant ones — CycloneDX <a href="https://cyclonedx.org/capabilities/mlbom/">ML-BOM</a> and the SPDX 3.0 AI profile — describe the model artifact and its training lineage. They have fields for energy consumption, hyperparameters, training data, evaluation, fairness. They do not have first-class fields for what a runtime agent capability is actually allowed to do. Existing AI-BOM work, including the CISA tiger team output and the OWASP AI BOM Guide, centers on models, datasets, software components, and system documentation; it does not give operators a standard, enforceable manifest of agent capability permissions. EU AI Act Annex IV will increase the <em>documentation</em> pressure on high-risk systems substantially, but it does not mandate a machine-readable runtime permission manifest, and pretending it does by year-end weakens the actual argument.</p>
<p>The actual argument is that a useful capability manifest answers a different question than SBOM or AI-BOM. SBOM answers what is inside. AI-BOM answers what it was trained on. The agent layer needs to answer: <em>what is this capability allowed to do at runtime, under whose identity, with what reversibility, and what does it bring along transitively</em>. The minimum surface is concrete enough to write down:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">skill</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">vendor.salesforce-export</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">1.4.2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">digest</span><span class="p">:</span><span class="w"> </span><span class="l">sha256:7b4f...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">signedBy</span><span class="p">:</span><span class="w"> </span><span class="l">cosign:acme-security</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">testedAgainst</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">anthropic/claude-sonnet-4.6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">runtime</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">claude-code</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">minVersion</span><span class="p">:</span><span class="w"> </span><span class="m">1.2.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tools</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">salesforce.query</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">paramsSchema</span><span class="p">:</span><span class="w"> </span><span class="l">schemas/sf-query.json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">reads</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">salesforce.account/*]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">writes</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">egress</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">csv.write</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">writes</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">workspace:/exports/*.csv]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">identity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="l">delegated-oauth</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">provider</span><span class="p">:</span><span class="w"> </span><span class="l">salesforce</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">scopes</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">read:accounts]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hitl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">requiredFor</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">external_egress, destructive_write]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ordering</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">deny</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">web.fetch]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">afterReads</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">classification:confidential]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">activation</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">triggers</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;export accounts&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;salesforce csv&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">composition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">skills</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">mcpServers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">salesforce-mcp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">0.9.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">digest</span><span class="p">:</span><span class="w"> </span><span class="l">sha256:c1a3...</span><span class="w">
</span></span></span></code></pre></div><p>None of those fields are exotic. Several runtimes already carry pieces — SKILL.md frontmatter has <code>allowed-tools</code>, MCP server manifests declare prompts/resources/tools, Anthropic&rsquo;s managed settings express plugin allowlists. The point is that they are scattered, none is enforceable across hosts, and none of them captures the transitive composition graph that matters most. Skills do not generally call other skills directly, but they regularly invoke MCP servers, which call other tools, which call APIs that the host has authority over. Public specs rarely model that second-hop egress, which is exactly where the interesting failures happen.</p>
<p>A pragmatic implementation does not have to be a new standard. A CycloneDX profile or SPDX extension for agent capabilities would do most of the job. The schema is what matters: runtime permissions, identity delegation, the tool graph, egress, reversibility, ordering constraints, transitive servers, and the host runtime the skill was validated against. The other thing that matters is what reads the manifest. A registry manifest is not a control by itself. If the host runtime can silently ignore the declared tool set, identity mode, or egress list, the manifest is a nicer README. The control exists when registry metadata is bound to runtime enforcement and, where possible, to an OS-recognized identity. Which brings us to the runtime layer.</p>
<h2 id="tiered-execution-is-the-missing-control">Tiered execution is the missing control</h2>
<p>Static allow/deny lists are the admission layer. They are necessary. They are also insufficient, because the interesting policies for agents are about how values <em>flow</em> through a session, not whether a tool is permitted in the abstract. Current MCP gateways — Portkey, MCP Manager, LiteLLM, the various security vendors — do tool-level RBAC, audit, payload scanning, and rate limiting well. Those are table stakes. They are not stateful in the way the next class of policy needs.</p>
<p>Concrete shape. An assistant has a web-search tool that reaches the public internet and a pull-internal-data tool that retrieves classified material from an internal store. The policy is straightforward to state: a tool that can leak data must not run after a tool that has put sensitive data into context. That is the actual control. The symmetric case is worth naming — untrusted public content reaching the model before a privileged internal query is an indirect prompt-injection path, and the same machinery handles it. Both rules are ordering rules expressed over data-sensitivity classes rather than over tool names. Value provenance is the mechanism that makes the ordering enforceable across a session, not a replacement for the ordering frame.</p>
<p>The minimal mechanism is taint tracking at the tool-result level. Every tool response carries a label set — <code>{source: internal, class: confidential, egress: blocked}</code> — and downstream tool invocations are checked against the accumulated context state. The policy is not &ldquo;deny web-search.&rdquo; It is &ldquo;deny any network egress tool after the context contains values labeled <code>class: confidential</code>, unless a human approves a redaction step.&rdquo; Symmetrically: &ldquo;values derived from untrusted web content cannot drive privileged internal queries without mediation.&rdquo; Labels come from the Skill-BOM declarations, from gateway-side DLP classifiers, or from explicit declassification points an operator defines. The state is per session, with the option to scope tighter for short-lived plans.</p>
<p>This is Bell-LaPadula-adjacent, not Bell-LaPadula verbatim. It is closer to information-flow control with explicit declassification, which has decades of formal work behind it. DeepMind&rsquo;s <a href="https://arxiv.org/pdf/2503.18813">CaMeL</a> is the closest recent published work for LLMs specifically: a privileged planner emits restricted Python, a quarantined reader handles untrusted data, and capability metadata is attached to every value flowing through the system. In the authors&rsquo; evaluation on the AgentDojo benchmark, it reduces successful prompt-injection attacks substantially at roughly two to three times the token overhead, with results varying by workload. The numbers matter less than the architecture: separate privilege domains, tainted values, capability metadata enforced by the runtime. That is the shape of the next product. RBAC and rate limits at the tool boundary are not the differentiator. Whoever ships stateful value-provenance policy across a session, hooked to the manifests above, is selling something different.</p>
<h2 id="the-os-finally-cares">The OS finally cares</h2>
<p>The piece that genuinely changes the equation is that the operating system started cooperating. Microsoft&rsquo;s <a href="https://learn.microsoft.com/en-us/windows/ai/agent-launchers/">Agent Launchers framework</a> on Windows 11 — currently preview, surfaced via the Windows Experimental Agentic Features toggle — sits on top of App Actions and an <a href="https://learn.microsoft.com/en-us/windows/ai/agent-launchers/agents-get-started">On-Device Registry</a>. The <code>odr.exe</code> CLI manages registrations; the manifest declares the agent&rsquo;s name, action ID, package identity, and icon. Dynamic registration via <code>agent-info add</code> is gated on MSIX package identity — unpackaged tools cannot register directly, which is a constraint, not a virtue, but it does mean any registered agent on a Win11 machine has an OS-recognized identity to attach policy to.</p>
<p>Above that sits the contained agent session — Microsoft&rsquo;s developer docs call it that; the user-facing name, per the <a href="https://support.microsoft.com/en-us/windows/experimental-agentic-features-a25ede8a-e4c2-4841-85a8-44839191dfb3">Experimental agentic features</a> support page, is Agent Workspace. It is not a Hyper-V VM; Microsoft positions the isolation as comparable to Windows Sandbox but lighter-weight, implemented as a separate Windows session under a dedicated low-privilege agent account with its own files and its own registry hive. The guarantees are the boring kind: separate token, separate ACLs, no default access to the user&rsquo;s profile, settings, credentials, or running app windows; network egress open by default; file access scoped by default to the six known folders — Documents, Downloads, Desktop, Music, Pictures, Videos — and only when the host app&rsquo;s MSIX declares the capability and the user consents at prompt time. Per-agent permission levels (Allow Always / Ask every time / Never allow) landed in build 26100.7344. The feature is off by default, admin-only to enable, and applies device-wide once on. Intune sits on top, with admin-controls documentation still partial in preview.</p>
<p>The consequence worth pulling out explicitly is that OS-level authorization finally detaches from application-level authorization. On a developer laptop today, Claude Code, Cursor, or any agent host runs inside the user&rsquo;s interactive session under the user&rsquo;s token. When the agent triggers anything that needs elevation, the UAC prompt the user dismisses is against the host application — which is also the trust boundary the user crossed when they first launched it. Application-side guardrails (&ldquo;ask before running shell,&rdquo; &ldquo;confirm destructive actions&rdquo;) and OS-side authorization collapse into the same plane. Agent Workspace breaks that by making the agent its own non-admin principal: any elevation request is arbitrated by Windows against the agent&rsquo;s standard token, not the host&rsquo;s, and the host cannot self-elevate the agent past UAC. The application keeps its own approval prompts for its own reasons, but they stop being the only thing standing between a prompt-injected agent and the user&rsquo;s admin authority. The OS sees the agent as someone else, which is the entire point.</p>
<p>What is new here is not OS identity in general. Windows has had app identity, package identity, app containers, and brokered capabilities for years. What is new is that Microsoft is surfacing those primitives explicitly for local AI agents, wiring them into admin tooling, and giving orgs a per-agent principal — separate from the user — that managed policy can target. That is the missing primitive that lets a private registry&rsquo;s signed manifest and a runtime gateway&rsquo;s value-provenance policy actually attach to something on the laptop. It is not equivalent to an AD service principal, and the analogy will mislead if pushed too hard. It is similar in the one respect that matters: policy can bind to a named principal instead of a pile of files.</p>
<p>There are two footguns worth naming. The first is that file-access consent on Win11 is granted <em>per host app</em>, not <em>per skill or per server</em>. Once you grant File access to host X, every server X uses inherits it. Mitigation: run one host per trust domain on managed endpoints, and treat the host as the policy boundary, not the skill. The second is the &ldquo;Reduce protections for agent connectors&rdquo; toggle, which exists explicitly to allow unpackaged servers to bypass containment. Lock it via Intune on managed devices, and refuse unpackaged servers in the registry. Both are places where a real boundary degrades into ambient authority, which is the failure pattern the earlier posts kept hitting: when identity and isolation disappear, you are left hoping a recognizer notices the bad thing in time.</p>
<h2 id="what-to-build-what-to-demand">What to build, what to demand</h2>
<p>The pattern is short, and I will resist the urge to dress it up.</p>
<p>If you are an org, the sequence is: centralize acquisition through a private registry, even if it starts as a thin proxy in front of GitHub; pin by digest, not by branch; block auto-update from mutable refs; require signatures for internal artifacts now and for external ones the moment the ecosystem supports verification; bind registry metadata to runtime allowlists rather than treating the manifest as documentation; enforce per-agent OS identity where the platform supports it; require human approval for irreversible writes and external sends; and disable unpackaged or bypass modes on managed endpoints. Fail closed on unsigned artifacts in CI, not only at install time. Break-glass paths require human approval plus session recording. A concrete starting point on the CLI side: I opened <a href="https://github.com/vercel-labs/skills/pull/1254">vercel-labs/skills#1254</a>, an RFC plus implementation that defaults <code>.well-known</code> resolution to deny, adds an admin-managed <code>skills-policy.json</code> with per-provider rules and an internal mirror-only mode, and writes a machine-readable inventory manifest at a stable path for fleet tooling to read.</p>
<p>If you are building governance products, the open ground is stateful value-provenance policy across a session. Tool-level RBAC, rate limits, audit, and payload scanning are commodities. Track value labels across calls. Support &ldquo;no egress after sensitive read&rdquo; and &ldquo;untrusted content cannot drive privileged query&rdquo; as first-class rules. Make declassification explicit and auditable. Hook the policy to the manifest, so the registry and the runtime are arguing about the same artifact.</p>
<p>If you publish skills, sign your releases, publish digests, declare your tools and scripts and egress and writable destinations and identity scopes, state the runtimes and models you have tested against, list the transitive MCP servers you call, and avoid requiring ambient user authority where a delegated identity will do. Some of this is annoying. Some of it will expose that the skill is doing too much. Good.</p>
<p>The trilogy on this site argued that identity is the control plane for agents. That argument has not changed. The registry is not a competing answer; it is where capabilities acquire identities operators can govern. It is the natural attachment point for signatures, manifests, ordering policy, runtime allowlists, and OS principals. It only becomes a control plane when those bindings are real — when signing is enforced, when runtimes honor the manifest, when the OS recognizes the principal, when gateways track provenance. Right now the industry has maybe half of that stack. The only choice is whether to assemble the other half before the incidents arrive or after.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
