Agent Plugins 1.0 makes a useful package portable across compatible coding-agent clients. It does not make that package safe to run. Admit a plugin only after you pin its bytes, review every executable surface, apply MCP policy independently, and canary the exact package in every client you plan to support.
Portability is not runtime authority
The published Agent Plugins 1.0 specification gives teams a common package shape. A root plugin.json identifies the package. Skills live under skills/. MCP server configuration lives in mcp.json. A reverse-domain namespace can carry client-specific manifest data and files.
That is a meaningful interoperability improvement. GitHub's August 12 release says one package can now be used across VS Code, Copilot CLI, the Copilot SDK, and the Copilot app. It also documents enterprise settings for enabled plugins and approved marketplaces. (GitHub release, managed settings reference)
The security boundary is narrower than the package boundary. The specification requires package-supplied paths to remain inside the plugin root, then explicitly states that this does not sandbox a subprocess or restrict paths supplied at runtime. A stdio MCP server can launch a bare executable found through the platform's search rules. Its arguments and environment can point at runtime resources. A remote MCP server can connect the agent to an external authority. Client extension behavior remains defined by each client. (Agent Plugins 1.0 specification)
Treat the standard as a distribution contract. Build the trust decision around the authority the installed package receives.

Pin the package before reviewing it
Do not approve a mutable branch, marketplace label, or package name. Resolve the install source to immutable bytes and retain the evidence beside the decision.
The minimum admission manifest is small:
plugin:
source_repository: "<owner/repository>"
source_commit: "<full-commit-sha>"
package_digest: "sha256:<digest>"
plugin_schema: "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"
declared_version: "<plugin-version-or-null>"
review:
reviewer: "<team-or-role>"
skill_digests: "<ordered-manifest-digest>"
mcp_config_digest: "<digest-or-null>"
extension_digests: "<namespace-to-digest-map>"
runtime:
client_builds: ["<client-and-build>"]
permission_profile: "<policy-digest>"
network_profile: "<policy-digest>"
credential_profile: "<broker-policy-digest>"
evidence_bundle: "<artifact-uri>"
rollback_digest: "sha256:<last-known-good>"The digest closes a common review gap. A team may approve version 1.2.0, then install different bytes after a force-push, tag move, marketplace refresh, or repack. The human-readable version is useful for change history. The digest identifies what actually passed.
Generate an inventory before opening files manually. This Node script uses only built-in modules. It hashes every regular file, lists skills and client-extension directories, and extracts the MCP surfaces that deserve authority review.
// scripts/inspect-agent-plugin.mjs
import { createHash } from "node:crypto";
import { readFile, readdir, stat } from "node:fs/promises";
import { basename, join, relative, resolve } from "node:path";
const root = resolve(process.argv[2] ?? ".");
async function walk(dir) {
const out = [];
for (const entry of await readdir(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isSymbolicLink()) throw new Error(`Review symlink manually: ${relative(root, path)}`);
if (entry.isDirectory()) out.push(...await walk(path));
if (entry.isFile()) out.push(path);
}
return out.sort();
}
const files = await walk(root);
const digest = createHash("sha256");
for (const file of files) {
const rel = relative(root, file);
const bytes = await readFile(file);
digest.update(rel).update("\0").update(bytes).update("\0");
}
const plugin = JSON.parse(await readFile(join(root, "plugin.json"), "utf8"));
let mcp = null;
try { mcp = JSON.parse(await readFile(join(root, "mcp.json"), "utf8")); } catch {}
const top = await readdir(root, { withFileTypes: true });
const extensions = top
.filter((entry) => entry.isDirectory() && /^[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(entry.name))
.map((entry) => entry.name);
const skills = files
.filter((file) => basename(file) === "SKILL.md" && relative(root, file).split("/").length === 3)
.map((file) => relative(root, file));
const servers = Object.entries(mcp?.mcpServers ?? {}).map(([name, cfg]) => ({
name,
type: cfg.type,
command: cfg.command,
url: cfg.url,
cwd: cfg.cwd,
env_keys: Object.keys(cfg.env ?? {}),
header_keys: Object.keys(cfg.headers ?? {})
}));
console.log(JSON.stringify({
package_digest: `sha256:${digest.digest("hex")}`,
manifest: { name: plugin.name, version: plugin.version ?? null, schema: plugin.$schema },
file_count: files.length,
skills,
extensions,
mcp_servers: servers
}, null, 2));Run it against the unpacked package and store its JSON output as an admission artifact:
node scripts/inspect-agent-plugin.mjs ./candidate-plugin > plugin-inventory.jsonThis is an inventory, not a conformance validator or malware scanner. It intentionally stops on symlinks so a reviewer must resolve them. Follow it with schema validation, source review, dependency review, and execution tests.
Review every authority surface together
A plugin review that opens only SKILL.md misses the surfaces with the most direct authority. Review the package as one release unit.
For each skill, inspect the instructions, scripts, references, and assets it can cause the agent to load or execute. Map any tool invocation to the client permission that controls it. Look for instructions that widen scope, retrieve remote material, write outside the working tree, invoke package managers, or pass data to a network service.
For each stdio MCP server, record the resolved executable, arguments, working directory, environment keys, inherited environment, filesystem access, network access, and credential source. The specification permits bare executable names, so command: "npx" is valid package syntax. It is also a supply-chain and network decision that deserves an explicit policy.
For each remote MCP server, record the exact origin, redirect policy, authentication flow, tool catalog, tenant boundary, and data classes that may cross the connection. Agent Plugins 1.0 requires HTTPS for non-loopback endpoints and prohibits secrets in configured headers. It does not define portable OAuth or credential-reference fields. Authentication and credential storage remain client-managed.
Finally, inventory every client-extension namespace and file. The portable specification assigns no common discovery, validation, loading, or failure semantics to extensions. A hook or agent profile under one client namespace therefore needs that client's documentation and a separate review decision.
Keep plugin policy and MCP policy separate
An approved marketplace answers where a package may come from. An MCP policy answers which tool servers may run. Do not collapse those into one checkbox.
GitHub's managed settings can restrict marketplaces and enable or disable named plugins. GitHub also documents a separate MCP registry and allowlist. Its current allowlist reference warns that enforcement is based only on server name or ID matching and can be bypassed by editing configuration files. GitHub says strict enforcement that prevents installation of non-registry servers is not yet available. (MCP allowlist enforcement)
That limitation should change the control design. Use the registry and managed settings for distribution policy, then enforce runtime authority through controls the plugin cannot rewrite:
- endpoint and process allow rules owned outside the package;
- egress policy at the host or workload boundary;
- short-lived credentials issued for one tool and one tenant;
- filesystem isolation for local servers;
- approval for destructive or irreversible tools;
- audit receipts that include subject, tool, arguments, decision, and result.
The same principle applies across clients. A client-side allowlist is useful, but it is not the only enforcement layer for a process that can start commands or reach the network. For a full MCP boundary design, see Secure MCP for Coding Agents Across Laptops and CI.
Canary the exact package in every client
One portable package can produce different effective runtimes. Clients may expose skills differently, support different MCP transports, apply different permission prompts, inherit different environment variables, or implement distinct extension namespaces.
Build a small matrix for the clients you intend to support:
Run positive tasks that exercise the capability the plugin is meant to provide. Then run negative probes that attempt the authority it must not receive: read an unrelated secret, write outside the workspace, contact an unapproved origin, invoke an unapproved MCP server, or bypass a destructive-action approval.
A denial passes only when an external control blocks the action and produces a receipt. A model declining the request is useful behavior, but it is not enforcement. This mirrors the broader distinction in A Repository Readiness Score Is Not a Coding-Agent Release Gate: inventory finds what exists, while executable evidence qualifies one pinned runtime.
Bind upgrades to the same evidence
Package portability makes distribution easier. It also makes a weak upgrade decision propagate faster.
Treat any change to the package digest, dependency lockfiles, skill instructions, MCP configuration, extension data, client build, permission profile, network policy, or credential broker as a new release tuple. Re-run the static inventory, affected source review, per-client canaries, and denial probes before broad rollout.
Keep the last-known-good package and its runtime policy together. Rollback means restoring both. Reinstalling yesterday's plugin bytes while leaving today's broader MCP allowlist or credential scope in place is not a complete rollback.
The admission decision can now be concise: this exact digest, on these client builds, under these external controls, passed these tasks and denials. Anything less is package recognition, not runtime qualification.
Does Agent Plugins 1.0 sandbox plugin code?
No. The specification contains package-path containment rules, but explicitly says those rules do not sandbox a plugin subprocess or restrict runtime paths. Isolation must come from the client, operating system, workload boundary, network policy, and credential design.
Is an approved plugin marketplace enough for enterprise rollout?
No. Marketplace policy controls distribution. MCP server policy, process authority, network egress, credentials, approvals, and audit evidence need separate controls.
Why test the same plugin in every coding-agent client?
The portable package is shared, but client support, permission behavior, environment handling, transport support, and extension semantics may differ. The effective runtime must be qualified per client and build.
What should trigger plugin requalification?
Any change to package bytes, skills, MCP configuration, extensions, dependencies, client build, permissions, network policy, credentials, or target environment should trigger the affected admission checks again.
Build an MCP Server
Design a bounded MCP server with explicit tools, identity, approvals, audit evidence, and a production rollout plan.








