Skip to content

Compatibility and allowlists

What the gateway accepts, what it rejects, what it does not support yet, and what its own API promises in return. Everything on this page is enforced in code or in CI, not merely recommended — where a rule is not mechanically enforced, it says so.

URL schemes

Applied to every operator-supplied outbound URL. The scheme is lower-cased and matched against skills-gateway.allowed-url-schemes.

Scheme Default Notes
https Allowed The intended production scheme.
http Allowed Present for local development and internal forges. Consider narrowing to https alone in production.
ssh, git, git+ssh Rejected Not in the default allowlist. The gateway clones over HTTP(S) with JGit.
file Rejected Would let a registration read the gateway's own filesystem.
ext, and any other scheme Rejected Not in the allowlist.
(no scheme, or unparseable) Rejected The check fails closed — an unparseable URL is a rejection, not a pass-through.

Rejection is HTTP 400 with a ProblemDetail naming the allowed schemes.

Widening the allowlist widens a trust boundary

allowed-url-schemes governs what a registration can make the gateway connect to. Adding file or ext would let an operator-supplied string reach local resources. Treat changes to it as a security decision.

An empty list is not configurable — it falls back to the default.

Refs

Input Behaviour
ref absent Accepted. The default branch is ingested.
ref: main Accepted — identical to omitting it.
Any other ref 400.

Which ref is ingested is the gateway's decision, not the registrant's. This is a deliberate trust-boundary constraint rather than a missing feature: multi-ref support will arrive as promotion per (upstream, ref), with each ref advancing independently through the same approval gate, not by relaxing this check.

Consequently marketplace add <url>#release-1.x against the facade will not find a ref — only main exists on the published repository.

Names

Marketplace names must match ^[a-z0-9][a-z0-9_-]*$: lowercase letters, digits, hyphen and underscore, not starting with a hyphen or underscore. A violation is 422.

The name is also a path segment on the facade (/git/{name}), which is why the character set is constrained.

Plugin sources inside a marketplace

The current scope accepts local sources only.

Source type Status
Relative path inside the marketplace repository Accepted
github, url, git-subdir, npm, archive, or any external source Rejected fail-closed at ingestion, with the reason recorded as the snapshot's violation

This removes transitive resolution and source rewriting from the current scope entirely: relative sources resolve inside the served snapshot by themselves, so every URL a client dereferences already resolves inside the gateway.

Supporting external sources means mirroring the closure and rewriting the manifest — a designed future capability, not a configuration flag.

Clients

The facade is plain read-only git smart-HTTP, so anything that clones works.

Client Support
Claude Code (claude plugin marketplace add) The primary target.
Copilot / Cursor (open Agent Skills SKILL.md repositories) Works — the facade serves a git repository.
CI pipelines, bare git clone Works with a PAT.
Anything attempting git push to /git/** Rejected by construction. (A hosted marketplace is published to on /publish/**, a separate endpoint.)

Enforcement mechanisms

Making the gateway the only path is outside the gateway itself:

Mechanism Availability
Claude Code strictKnownMarketplaces (managed settings) Available today — a hard client-side allowlist.
Claude Code extraKnownMarketplaces, enabledPlugins Available today — pre-register and force-install.
Copilot / Cursor equivalent None. Egress policy carries the load.
Network egress blocking of upstream marketplace hosts Your network, not the gateway.

The API contract

Everything above is about what the gateway accepts. This is about what its own HTTP surface promises.

Within a major, /api/** only grows. Endpoints, fields and enum values may be added; nothing a deployed client could depend on is removed, narrowed or renamed. A breaking change is allowed — it is not free. It moves the path prefix and ships as a major release, so a client that pinned neither is never surprised.

Change Additive?
A new endpoint, a new optional field, a new enum value Yes. Ships as a minor.
/api/v2/... added while /api/v1/... remains Yes — nothing was taken away. Deprecate first, remove in a later major.
Removing or renaming an endpoint or field, making an optional field required, narrowing a type No. New prefix, and a major.

The prefix does not exist yet

Endpoints are unversioned today (/api/marketplaces, not /api/v1/marketplaces). The version segment arrives with the change that introduces API versioning; until then the promise binds, and the check below enforces it, against the unversioned paths.

How it is enforced

Three mechanisms, and it is worth being clear about which is which.

Rule Enforced by
The published contract is the contract the gateway serves A build test. src/main/frontend/openapi.json must equal the document the running application serves, or ./mvnw verify fails naming the command that regenerates it.
A breaking change is detected The API contract workflow diffs every pull request's contract against the one it forked from with oasdiff, and fails on a breaking classification.
A breaking change is declared The same workflow requires the PR title — which becomes the squash commit subject, and so the release version — to carry ! or BREAKING CHANGE.
The prefix was moved Nobody. No check can tell that a break should have been versioned instead; that is review's job.

Deliberately breaking the contract takes two visible acts: the ⚠️ BREAKING CONTRACT label on the pull request, and the break declared in its title. Both stay in the record. Keeping the old surface alongside the new one needs neither — that path is additive, and the gate passes on its own.

The document served at /v3/api-docs declares the release it describes, derived from the build. The copy published in the repository carries a placeholder there instead: it is regenerated by hand, and a version that changes on every commit would make it differ from the build on every commit.

Platform

Component Requirement
Runtime Java 25 (Temurin); GraalVM CE 25 for native builds
Database PostgreSQL
Identity An OIDC provider supporting authorization-code flow
Container Distroless image built from the native binary
Kubernetes helm/skills-gateway; bring your own PostgreSQL and OIDC provider