Zentrix Gateway docs中文

Gateway extension development

Write a WASM gateway extension in Rust or Lua: get a template, build and validate, runtime limits, and publish

Gateway extension development

A gateway extension is a WASM component that runs during gateway request handling. It allows or rejects a request before calling upstream, rewrites the request, or inspects a streaming response. The official SDK supports Rust and Lua. Both languages publish as an extension package that contains only manifest.json and plugin.wasm, executed by the same Wasmtime host.

When a security vendor connects detection through a gateway extension, first read the Security vendor integration guide to confirm this path fits the scenario.

1. Runtime limits

Both languages share the same limits. Exceeding them aborts execution:

ItemLimit
Instruction budget (fuel)2,000,000 per execution
Duration20 ms per execution
Memory4 MiB
Controlled HTTP requestsAt most 4 per execution; only logical destinations declared in the manifest and authorized by the install key binding
Extension packageAt most 140 MiB; plugin.wasm at most 128 MiB; manifest.json at most 64 KiB
Lua sourceAt most 2 KiB, UTF-8

An execution error is handled by the installed failure policy. A security-critical extension should be configured fail_closed.

2. Package contract

The current package contract is zenova.gateway-plugin/v4. The host interface version is zenova:[email protected].

  • The manifest must declare supportedServiceKinds, one of model (model calls), mcp (MCP services), http_proxy (API services), or a2a (external agents).
  • capabilities.executionSurface: "model" uses the model-execution stage and may only declare model. A generic request-header extension uses request_header and P2_request_transform and may declare the four service kinds above.
  • The backend validates declarations against bindings. Removing a still-bound service type on upgrade is refused, with a prompt to delete the matching binding first.

An identity-header extension reads host-provided trusted.subject.iam_subject, trusted.subject.user_id, trusted.service.kind, and trusted.service.id. A missing identity stays missing. A write whose name matches a service-native identity header is refused by the backend. Caller request headers cannot forge these trusted keys.

3. Get a development template

A tenant administrator opens Gateway extensions in the admin console, chooses Download SDK, and selects Rust or Lua, the extension ID, and the gateway interface to implement. Page download and command-line zr-plugin init use the same template generator.

Command-line entry (zr-plugin is in product source zstack-router/crates/zr-plugin):

zr-plugin init ./tenant-gate \
  --language lua \
  --world request-gate \
  --plugin-id tenant-gate

For Rust, change --language to rust. Three optional interfaces (worlds):

worldRole
request-gateAllow or reject the request before calling upstream
request-transformChange the request body or headers sent upstream
gateway-response-transformIncrementally inspect a streaming response and declare a text prefix that is safe to send down

4. Write business logic

Rust: the template pins the current SDK and WIT snapshot under .zr/sdk/, already including the manifest, sample logic, and later command notes. Edit only src/lib.rs. Before building, install stable Rust and wasm32-wasip2:

rustup target add --toolchain stable wasm32-wasip2

Lua: no Rust, WIT bindgen, or WASM toolchain is needed. Edit only plugin.lua. The template also includes:

  • .zr/sdk/zr.lua: LuaLS / EmmyLua type annotations;
  • .zr/sdk/zr-mock.lua: local no-side-effect mocks of 21 host functions;
  • .luacheckrc: Lua 5.4 and the zr global;
  • harness.lua: legal inputs and result assertions matching the chosen world.

When Lua 5.4 is installed locally, run lua harness.lua and luacheck plugin.lua first for fast feedback. Security and contract validation follow zr-plugin validate in the next step.

Lua is quicker to start and iterate, but has an extra interpretation layer, so performance is usually below Rust. For latency- and throughput-sensitive extensions, prefer Rust.

5. Build and validate

zr-plugin build . --output ./dist/plugin.zip
zr-plugin validate ./dist/plugin.zip

A Rust build uses stable, --locked --release, and wasm32-wasip2. A Lua build writes source, byte count, SHA-256, and a pinned runner version into an immutable manifest, and packs that world's official Lua runner component. The customer machine does not compile the runner.

validate shares the same package validator as server-side upload, and uses the production host to actually compile, pre-instantiate, and run one synthetic request. A Lua package is also checked for:

  • Source size and SHA-256 must match;
  • Runner version must be the currently only supported version (lua54-0.1.0);
  • plugin.wasm must be byte-for-byte equal to that world's official runner and cannot be replaced;
  • Script syntax, entry function, arguments, and return values must be executable in the production host.

6. Lua runtime contract

The Lua runner is fixed at PUC Lua 5.4.8. Each execution creates an independent Lua state and loads only the table, string, utf8, and math standard libraries. The Lua heap is separately limited to 2 MiB. io, os, package, debug, load, loadfile, dofile, and require are unavailable. An infinite loop is aborted by the host instruction budget or the 20 ms limit.

Lua source in the manifest is the only source of truth for that extension version. Install configuration is maintained separately by the tenant. The platform accepts only Lua source, not bytecode. Expected errors inside the script can be handled with pcall. Uncaught errors follow the bound failure policy.

zr exposes 21 snake_case functions with the same names as WIT. The current host provides: log, clock, counter, fixed-window rate limit, release, heartbeat, token count, controlled HTTP request, request context, and local context. The following functions have an entry in Lua and WIT, but the current host aborts execution explicitly and does not pretend success:

  • state_get, state_set, state_add, state_delete, state_expire;
  • breaker_check, concurrency_acquire, verify_signature.

destination of http_request is a logical destination declared in the manifest and authorized by the install key binding, not an arbitrary URL.

7. Publish

publish has no default target. You must pass --server explicitly or set LP_SERVER. The credential must be a user token with gateway-extension management permission. An agent API key or a model-call API key cannot publish an extension.

zr-plugin --server https://gateway.example.com \
  publish ./dist/plugin.zip

The publish request is POST /api/v1/plugins/packages, multipart field package. The server runs the same package, runtime, digest, WIT world, and production-host checks as locally, then admits the artifact and the immutable version manifest into the extension lifecycle. You can also choose Upload plugin in the Gateway extensions catalog to submit the package.

8. Install and bind

  1. Open the extension under Gateway extensions. On the Permissions tab, check declared network targets, key requirements, and security and PII requirements.
  2. On the Configuration tab, fill in configuration and install. The extension is installed disabled. After status shows In effect, turn Enable on.
  3. On the Bindings tab, first choose a service type the extension declared, then All of this type or a multi-select of named services. All covers current and later-created services of that type in this tenant. Model calls are specified by model group.
  4. Binding configuration overrides install defaults by top-level key. A binding to a named service takes precedence over an All binding of the same install. Each stage of the same request runs once. A traffic-rule reference can only further narrow an already-bound scope.
  5. When the extension must reach an external service, reference the key by logical name in the binding's key binding, and restrict allowed destinations.
  6. Verify with hitting and non-hitting requests separately, then check execution count, intercepts, failures, and duration under Execution stats.

On this page