Plugins

The out-of-process extension contract: manifest, trust store, event subscription, assertion and licence.

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

FamilyRuns inTransportDecides auth?Licence

Auth adapter

IAM

HTTP/JSON

yes, in the IAM

Gate One licence

Injection / userscript / UI

Gate One

route inject bundle (config)

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, rateLimit and quota are 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:

EndpointPurpose

GET /health

readiness; a failing health check stops new batches

POST /events

batch [{offset, event}]; commit, then acknowledge

Every call carries:

X-Gate-One-Assertion: base64url(payload).base64url(HMAC-SHA256)

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.audit

The 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, rateLimit or quota;

  • 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.