Skip to content

Repository files navigation

openmax-agentgate

Agent identity and per-request Ed25519 signing for zylos agents.

Manages Ed25519 key pairs (keygen, registration, rotation) and signs outgoing HTTP requests with RFC-9421-shaped signatures that are byte-identical to the Go agentsig verifier in platform/internal/agentsig.

Install

Zero runtime dependencies, pure ESM, no build step. Install from GitHub, pinned to a release:

npm install github:openmaxai/openmax-agentgate#v0.2.0

This provides both the agentgate CLI (verify with npx agentgate --version) and the library import (import { createSigner } from 'openmax-agentgate/client'). Check for updates any time: npx agentgate upgrade --check. Requires Node.js >= 20.

CLI

agentgate --version                    # installed version
agentgate upgrade --check [--json]     # check for upgrades (GitHub Releases)
agentgate keygen --kid <id> [--out <path>]
agentgate register --kid <id> --tenant <t> --agent <a> --cp-url <url>
agentgate keys list --tenant <t> --agent <a> --cp-url <url>
agentgate keys revoke --kid <id> --tenant <t> --agent <a> --cp-url <url>

Security: the ops token is read from the AGENTGATE_OPS_TOKEN environment variable, never from CLI arguments (avoids shell history / ps leaks). Private keys are never printed, logged, or uploaded.

Exit codes (upgrade --check): 0 = up to date, 10 = upgrade available, 20 = check failed.

Architecture

Each agent instance is bound to a single (tenant_id, agent_id, kid). The private key is generated locally, stored with 0600 permissions, and never leaves the agent runtime.

openmax-agentgate/
├── src/cli.js           # CLI entry point (agentgate bin)
├── src/lib/signing.js   # Signing base + Ed25519 signing (byte-identical to Go)
├── src/lib/keygen.js    # Ed25519 key generation, PEM load/save
├── src/lib/register.js  # Control plane key registration API
├── src/lib/client.js    # AgentSigner class (high-level signing client)
├── src/lib/version.js   # Version + upgrade check (GitHub Releases)
├── src/lib/config.js    # Config loader with hot-reload
└── src/index.js         # PM2 service entry point

Usage

import { createSigner } from 'openmax-agentgate/client';

const signer = createSigner({
  private_key_path: '/path/to/key.pem',
  tenant_id: 'my-tenant',
  agent_id: 'my-agent',
  key_id: 'my-agent-2026a',
  path_prefix: '/ajj/agent',  // prefix the reverse proxy strips
});

const headers = signer.sign('POST', 'https://gateway.example/ajj/agent/api/v1/query', body);
// Signature covers /api/v1/query (internal path), not the full public URL.
// headers: Content-Digest, X-Openmax-Tenant, X-Openmax-Agent,
//          Signature-Input, Signature

Signing Path Prefix

When a reverse proxy (e.g. Caddy) strips a path prefix before forwarding to the control plane, the signature must cover the internal path (after stripping), not the public URL. Set path_prefix (config) or pathPrefix (constructor) to the prefix being stripped (e.g. "/ajj/agent"). The client signs the stripped path automatically while still sending requests to the full public URL.

Without this, every request silently gets 401 with no hint — the signed path does not match what the control plane sees.

Testing

npm test

Runs 135 tests: golden vectors (signing), keygen, register (mock fetch), and pathPrefix verification.

Path Encoding

Node's new URL().pathname and Go's url.URL.EscapedPath() diverge on two characters: | (→ %7C) and ^ (→ %5E). The encodePath() function supplements Node's pathname by encoding only those two.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages