Skip to content

Making the gateway the only door

The gateway can only govern what clients fetch through it. Nothing on the server side stops a developer from adding an upstream marketplace directly — that half of the control lives in client configuration, network policy, and CI. This guide collects the recommended settings per client, with pointers to each vendor's own documentation. It is defense in depth: apply what your fleet management supports, and treat none of it as the sole control (see architecture §8).

Vendor-controlled and time-sensitive

Every setting on this page is implemented by the client vendor, not by the gateway, and behavior changes with client releases. Verify current behavior with your vendor account team before relying on any of these as a hard control — in particular, confirm in writing how managed settings behave for the client builds your fleet actually runs (desktop, terminal CLI, and IDE surfaces have differed historically).

Claude Code

Claude Code supports fleet-managed settings distributed via MDM that users cannot override. The relevant keys (managed settings documentation):

Setting Effect
strictKnownMarketplaces Restricts claude plugin marketplace add to an explicit allowlist — set it to the gateway's marketplace URLs and ad-hoc additions are blocked client-side
blockedMarketplaces Denylist with owner wildcards, for blocking known-bad sources even where the strict allowlist is not used
extraKnownMarketplaces + enabledPlugins Pre-registers the gateway's marketplaces and installs the approved set fleet-wide

A minimal managed-settings fragment pointing a fleet at the gateway:

{
  "strictKnownMarketplaces": [
    "https://skills.example.corp/git/catalog"
  ]
}

Pointing the allowlist at the virtual catalog keeps it stable: newly approved marketplaces appear inside the catalog without a fleet configuration change.

Cursor

Cursor's enterprise controls offer an equivalent posture (plugins documentation):

  • Public Marketplace Allowlist — restricts which public-marketplace plugins members may use; an empty allowlist disables the public marketplace for the organization.
  • Team marketplace — the curated internal channel; approved content is distributed through it instead of public sources, with access scoped by Organization Groups.
  • Network allowlists (Enterprise plan) — org-wide egress policy applied to agent sandbox sessions, which can be scoped to the gateway host.

GitHub Copilot CLI

Copilot's enterprise controls cover MCP servers, not skill sources: an MCP server allowlist in the enterprise managed-settings.json and a registry-only MCP policy that applies to Copilot CLI. There is currently no setting restricting which skill repositories or marketplaces a Copilot CLI user can add — for skills distribution, network egress policy carries the load.

Network egress and CI

Client settings only reach managed machines. Two backstops apply regardless of client:

  • Egress policy — block (or at minimum alert on) direct git/HTTPS access from developer machines and CI to known marketplace hosts. Blocked attempts are themselves a signal worth forwarding to the SIEM.
  • CI as a backstop — pipelines resolve skills only through the gateway; builds referencing unapproved sources fail. This catches what laptop-level controls miss before anything ships.

Noticing that something you already hold was withdrawn

Everything above keeps unapproved content from arriving. It does nothing about content that arrived while it was approved and has since been withdrawn: the skills are already on disk, and the gateway cannot reach a client machine to take them back.

So the client asks. POST /status/v1/snapshots takes the marketplace-and-commit pairs a machine holds and answers one state each, and it authenticates with the same PAT a fetch uses — a client that can clone can ask.

curl -sS -u "token:$SKILLS_GATEWAY_PAT" \
  -H 'Content-Type: application/json' \
  -d '{"holdings":[{"marketplace":"platform-skills","sha":"'"$(git -C ~/.claude/marketplaces/platform-skills rev-parse HEAD)"'"}]}' \
  https://skills.corp.example/status/v1/snapshots
{
  "results": [
    {
      "marketplace": "platform-skills",
      "sha": "9f2c1b…",
      "state": "revoked",
      "revokedAt": "2026-09-19T08:14:02Z",
      "approvedOverReversedRevocation": false
    }
  ]
}

unknown is not a pass — and neither is an error

unknown means the gateway makes no statement. It is the answer for a commit it never had, one whose record has aged out, a marketplace your token is not scoped to, and a marketplace that does not exist — deliberately the same answer for all four, so a leaked token cannot be used to map the estate. A client that treats unknown as "still fine" has inverted the safe direction. Treat unknown, a refusal, and an unreachable gateway alike: not approved.

The full contract — states, refusals, and why the withdrawal reason is not in the answer — is in the facade reference.

Where to run it

Placement Catches
A scheduled job on managed machines (MDM, cron, a launch agent) Laptops that cloned and moved on. The common case, and the one nothing else reaches.
A CI step before a build that uses skills Anything a pipeline cached. Fails the build rather than shipping with withdrawn content.
A lifecycle webhook subscriber Withdrawals as they happen — but only for machines that are online and subscribed, which is why it complements the poll rather than replacing it.

Deciding what to do with a revoked answer is yours: fail a build, raise an alert, or re-clone the marketplace's current tip. The gateway deliberately does not reach into client filesystems — the goal is that a client can know.