X-Gate-One-Assertion: base64url(payload).base64url(HMAC-SHA256)Plugins
Gate One extensions are out-of-process services. The gateway talks to them over HTTP/JSON; it never loads a third-party authentication provider and never calls a plugin on the authentication path.
Plugin families
| Family | Runs in | Transport | Decides auth? | Licence |
|---|---|---|---|---|
Auth adapter | IAM | HTTP/JSON | yes, in the IAM | Gate One licence |
Injection / userscript / UI | Gate One | route | no | extension licence |
Event consumer | Gate One | HTTP/JSON, signed manifest | no | extension licence |
The injection bundle and the event consumer are extensions; auth adapters stay in the IAM and remain under the Gate One licence.
A Backend-For-Frontend is not a plugin: it is a proxied backend:
route, post-auth by definition (it receives the identity DTO /
X-Gate-One-Subject), and can be written in any language. Gate One
never loads it. Response transforms are the gateway’s native injector
chain (inject, rewriteBody, responseHeaders, rewriteRedirects,
the guard); bespoke logic belongs in the backend or the edge proxy, not
in a plugin.
Invariants
A plugin never decides whether a request is authenticated.
no pre-auth hook; the plugin is called after
checkAuth/checkMCPAuth;no self-registered route; it attaches to a compiled route;
immutable identity DTO, never the mutable
*http.Request;reserved headers (
Authorization,Cookie,X-User-,X-Gate-One-,X-Forwarded-*) are stripped before any outbound call;auth,securityHeaders,license,rateLimitandquotaare out of scope;internal network only, signed assertion on every call, separate UID, no secrets in the environment, egress allow-list.
Specific tests enforce that an anonymous request, an invalid token or a missing scope never reaches the plugin.
Event consumer contract
The plugin exposes two endpoints:
| Endpoint | Purpose |
|---|---|
| readiness; a failing health check stops new batches |
| batch |
Every call carries:
The payload binds the audience (plugin name), an expiry and the body size. The plugin must verify it before processing the batch.
The event schema is published at
usage-event.schema.json and
documented in the reference. The user is
an opaque subjectId; no token, cookie, email or username is present.
Manifest
{
"schemaVersion": 1,
"kind": "extension",
"name": "com.cladone.audit",
"version": "1.0.0",
"apiVersion": "v1",
"hostAPI": ">=1.0 <2.0",
"keyId": "example-2026",
"artifact": {
"type": "binary",
"digest": "sha256:…",
"size": 84,
"platform": "linux/amd64"
},
"capabilities": ["events"],
"events": {
"types": ["auth", "error"],
"filters": { "statusClasses": ["4xx", "5xx"] },
"maxLagBytes": 5000000,
"onMaxLag": "block"
},
"signature": {
"alg": "ed25519",
"keyId": "example-2026",
"value": "base64url…"
}
}The signature covers the canonical JSON with signature zeroed, plus the
artefact digest. For an extension the manifest also carries the event
subscription and the optional licenseFeature.
See the manifest reference for every field.
Trust store
The trust store is the local allow-list. It is provisioned out-of-band (signed lock file, package manager, embedded key) and never fetched from the plugin.
{
"keys": { "example-2026": "base64url-ed25519-public-key" },
"allowedNames": ["com.cladone.*"],
"allowedDependencies": {},
"revokedDigests": [],
"allowAuth": false,
"coreVersion": "1.5.0"
}The loader verifies the signature, artefact digest and size, dependencies, revocation, host API, kind and event subscription. For a local sidecar it verifies before starting; for a remote service it verifies the manifest and the image/attestation, and authenticates the channel.
The publisher can also publish a signed publisher feed listing
compatible components and revocations; the operator adopts it with
publisherFeed. See
Publish and trust a plugin.
Configuration
{
"usage": {
"dir": "./usage-wal",
"nodeId": "gw-1",
"subjectSecretEnv": "GATE_ONE_SUBJECT_SECRET",
"pluginSecretEnv": "GATE_ONE_PLUGIN_SECRET",
"trustStore": "trust-store.json",
"types": {
"auth": { "shareBytes": 10485760, "fsync": "always" },
"error": { "shareBytes": 10485760, "fsync": "window_ms", "windowMs": 250 }
},
"plugins": [{
"name": "com.cladone.audit",
"url": "http://127.0.0.1:9100",
"manifest": "audit.manifest.json",
"artifact": "audit-echo.placeholder"
}]
}
}usage.trustStore is required when a plugin uses a signed manifest.
Relative paths (manifest, artifact, trustStore, dir) are
resolved from the configuration file. After editing, SIGHUP reloads
the configuration and recreates the gateway, including its plugins; an
invalid file is refused.
A plugin can also be installed as a descriptor file instead of an
inline entry: set usage.pluginsDir (conventional name plugins.d) and
every *.json file in it is one plugin entry, merged with
usage.plugins (inline first). Relative manifest/artifact paths are
resolved from the descriptor, so a plugin directory is self-contained.
Duplicate names are refused, and usage.requireSigned applies to
discovered plugins exactly as to inline ones. The directory is rescanned
on SIGHUP.
Reference plugin
cmd/plugin-audit-echo is the reference consumer. It verifies the
assertion, decodes the batch and logs the events; it has no dependency
on Gate One internals.
export GATE_ONE_PLUGIN_SECRET=...
go run ./cmd/plugin-audit-echo -addr :9100 -name com.cladone.auditThe complete signed example is
routes.json, audit.manifest.json, trust-store.json.
The publisher/operator walkthrough — develop unsigned, sign, install and revoke — is in Publish and trust a plugin.
What is not a plugin
an authentication provider inside Gate One (
type: "plugin"is rejected);a mutable request or identity hook before authentication;
a self-registered route;
a replacement for
securityHeaders,license,rateLimitorquota;a compile-time Go module distributed as a third-party plugin.
Licence
The licence terms (core, plugin exception, PLUGIN-LICENSING.md) are
summarised under Source and licence.