Skip to content

Brian-Mwangi-developer/agentchain

Repository files navigation

agents-chain

v0.0.3 — A zero-dependency security layer that wraps any app, service object, or AI SDK with Ed25519 identity, JWT-gated capability enforcement, constraint validation, and an encrypted audit trail.

Drop it in front of your billing service, a third-party API client, or an OpenAI/Anthropic SDK — every call is signed, verified, scoped, and logged without touching your existing code.


What it does

  • Host + Agent identity — Ed25519 keypairs with JWK thumbprints as stable IDs. Hosts sign agent registration JWTs; agents sign scoped capability tokens.
  • 9-step JWT verification — Every capability call mints a fresh 60-second single-use token. Sub, iss, aud, signature, expiry, and JTI replay are all verified before the call proceeds.
  • Capability registry + app wrapper — Register named capabilities on any service object. A JavaScript Proxy intercepts method calls by name and gates them through the full auth pipeline.
  • Constraint enforcement — Grants can carry max, min, in, not_in, or exact-equality constraints on call arguments, enforced before execution.
  • Encrypted audit log — AES-256-GCM in-memory log of every call (success, denied, error). Drain to any HTTP endpoint or custom exporter on a schedule or at shutdown.
  • Adapter interfaces — Plug in your own Redis client for JTI replay protection across restarts. Plug in your own grant resolver to read active grants from your DB. The package ships no Redis client.
  • Well-known discovery — Serve GET /.well-known/agent-configuration with one call. Agents discover your capabilities, endpoints, and supported algorithms automatically.
  • Zero mandatory dependencies — In-memory defaults for everything. Redis, databases, and HTTP clients are injected by you.

Package overview

Package overview — all modules and how they connect

The package has six layers. HostIdentity and AgentIdentity hold Ed25519 keypairs. TokenBuilder mints scoped JWTs; TokenVerifier runs the 9-step verification pipeline. CapabilityRegistry maps method names to capability definitions; wrapApp() turns any object into a secured Proxy using that registry. AuditLog records every call into an AES-256-GCM encrypted store and can drain to any AuditExporter. All state lives in EncryptedStore — there is no network I/O by default.


Installation

npm install agents-chain
# or
pnpm add agents-chain

Requires Node.js 18+


Wrapping any app — AppChain

AppChain is the main entry point for wrapping your own services or third-party clients.

Integration flow

Integration flow — AppChain.create() through chain.wrap() to a secured Proxy

import { AppChain, HttpAuditExporter } from 'agents-chain';

const chain = await AppChain.create({
  providerName: 'billing-service',
  issuer: 'https://billing.mycompany.com',
  capabilities: [
    {
      name: 'createInvoice',
      description: 'Create a new invoice for a customer',
      inputSchema: {
        type: 'object',
        required: ['customerId', 'amount'],
        properties: {
          customerId: { type: 'string' },
          amount: { type: 'number' },
        },
      },
      outputSchema: { type: 'object' },
      execute: async (args, ctx) => {
        // ctx carries agentId, hostId, and active permissions
        return billingDb.createInvoice(args.customerId, args.amount);
      },
    },
  ],

  // Optional: resolve grants from your DB instead of in-memory
  grantResolver: async (agentId, capability) => myDb.getGrant(agentId, capability),

  // Optional: drain audit log to a hosted endpoint
  auditExporter: new HttpAuditExporter({
    endpoint: 'https://audit.yourservice.com/ingest',
    apiKey: process.env.AUDIT_API_KEY,
  }),
});

// Serve capability discovery
app.get('/.well-known/agent-configuration', (req, res) =>
  res.json(chain.getWellKnownConfig())
);

// Wrap any object — every registered method is now capability-gated
const secured = chain.wrap(billingService, agentGrants);
const invoice = await secured.createInvoice({ customerId: 'c1', amount: 500 });

// Flush audit log on shutdown
process.on('SIGTERM', () => chain.drain());

Per-call security pipeline

Every intercepted call goes through this verification pipeline before your code runs.

Per-call security flow — 9-step JWT verification pipeline

Step Check Error on failure
1–2 Decode JWT header + payload, confirm typ = "agent+jwt" token_invalid
3 sub matches registered agentId agent_not_found
4 iss matches registered public key thumbprint token_invalid
5 aud matches the requested capability name capability_denied
6 Ed25519 signature is valid token_invalid
7 exp/iat temporal check + 30s clock skew tolerance token_expired / token_invalid
8 JTI not seen in 90-second replay window token_replayed
9 Agent holds an active grant for the capability capability_denied
9b Call arguments satisfy all grant constraints constraint_violated

All failures throw ChainAuthError and are recorded in the audit log as result: "denied".


Wrapping AI SDKs — AgentsChain

For OpenAI and Anthropic clients, use AgentsChain instead. It maps SDK method paths to capability strings automatically.

OpenAI

import { AgentsChain } from 'agents-chain';
import OpenAI from 'openai';

const chain = await AgentsChain.create({
  agentName: 'summarizer',
  hostname: 'my-app',
  capabilities: ['chat.completion'],
});

const ai = chain.openai(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));

const response = await ai.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Summarize the water cycle.' }],
});

Anthropic

import { AgentsChain } from 'agents-chain';
import Anthropic from '@anthropic-ai/sdk';

const chain = await AgentsChain.create({
  agentName: 'classifier',
  hostname: 'my-app',
  capabilities: ['message'],
});

const ai = chain.anthropic(new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }));

const response = await ai.messages.create({
  model: 'claude-sonnet-4-6',
  max_tokens: 256,
  messages: [{ role: 'user', content: 'Classify: "I love it!"' }],
});

Capability names

OpenAI

SDK method Capability string
ai.chat.completions.create() "chat.completion"
ai.embeddings.create() "embedding"
ai.images.generate() "image.generation"
ai.audio.transcriptions.create() "audio.transcription"
ai.audio.speech.create() "audio.speech"
ai.moderations.create() "moderation"
ai.responses.create() "response"

Anthropic

SDK method Capability string
ai.messages.create() "message"
ai.messages.stream() "message.stream"
ai.messages.countTokens() "message.count_tokens"
ai.completions.create() "completion"
ai.beta.messages.create() "message.beta"

Any method not in these tables passes through without interception.


Grant constraints

Constrain what an agent is allowed to pass in call arguments:

const grants = [
  {
    capability: 'createInvoice',
    status: 'active',
    constraints: {
      amount: { max: 1000 },              // amount <= 1000
      currency: { in: ['USD', 'EUR'] },   // currency must be one of these
    },
    expiresAt: Date.now() + 86_400_000,   // expires in 24h
  },
];

const secured = chain.wrap(billingService, grants);

// This passes
await secured.createInvoice({ customerId: 'c1', amount: 500, currency: 'USD' });

// This throws ChainAuthError("constraint_violated")
await secured.createInvoice({ customerId: 'c1', amount: 2000, currency: 'USD' });

Supported operators: max, min, in, not_in, exact primitive equality.


Persistent JTI replay protection

By default, JTI replay protection is in-memory and resets on restart. For shared deployments, plug in your own Redis client:

import { AppChain } from 'agents-chain';

const redisAdapter = {
  has: (key) => redis.exists(key).then(Boolean),
  set: (key, ttlMs) => redis.set(key, '1', 'PX', ttlMs).then(() => {}),
};

const chain = await AppChain.create({
  providerName: 'my-service',
  issuer: 'https://myservice.com',
  capabilities: [...],
  jtiAdapter: redisAdapter,
});

Audit log

Every call is recorded in an AES-256-GCM encrypted in-memory log.

// Get all entries (decrypted)
const entries = chain.getAuditLog();

// Summary counts
const stats = chain.getStats();
// { agentId, agentName, hostname, totalCalls, successfulCalls, deniedCalls, errorCalls, registeredAt }

// Export and clear — call periodically or on shutdown
await chain.drain();                               // uses auditExporter from config
await chain.drain(new ConsoleAuditExporter());     // override exporter

Each AuditEntry contains:

Field Type Description
id string Unique entry ID
agentId string Agent that made the call
agentName string Human-readable agent name
capability string Capability requested
args object Sanitized call arguments (secrets redacted)
result "success" | "denied" | "error" Outcome
denialReason string? Set when result === "denied"
jti string JWT ID used
timestamp number Unix ms
durationMs number Execution time in ms

Argument keys matching key, secret, token, password, auth, credential, or bearer are automatically replaced with "[REDACTED]" before logging.


Host identity

HostIdentity is the user's anchor for signing agent registration JWTs against an agent-auth server.

// chain.host is a HostIdentity instance
const hostJwt = await chain.host.signJwt();
const registrationJwt = await chain.host.signAgentRegistrationJwt(agentPublicKeyJwk);

// Stable identity across restarts — export and reload the private key
const privateKeyJwk = await chain.host.exportPrivateKeyJwk();
// persist privateKeyJwk securely

// On next startup:
const host = await HostIdentity.fromKeyPair(savedPrivateKeyJwk, savedPublicKeyJwk, config);

Well-known discovery

// Returns AgentConfiguration — serve at GET /.well-known/agent-configuration
chain.getWellKnownConfig();

// {
//   version: "1.0-draft",
//   provider_name: "billing-service",
//   issuer: "https://billing.mycompany.com",
//   algorithms: ["Ed25519"],
//   modes: ["delegated", "autonomous"],
//   approval_methods: ["device_authorization"],
//   endpoints: {
//     register: "/agent/register",
//     capabilities: "/capability/list",
//     execute: "/capability/execute",
//     status: "/agent/status",
//     revoke: "/agent/revoke",
//     ...
//   },
//   default_capabilities: ["createInvoice", ...]
// }

Crypto utilities

Low-level Ed25519 utilities are exported if you need them directly:

import {
  generateKeyPair,
  exportPublicKeyJwk,
  exportPrivateKeyJwk,
  importPublicKeyJwk,
  computeJwkThumbprint,
  signJwt,
  verifyJwtSignature,
  decodeJwtUnsafe,
  generateId,
  generateAgentId,
  base64UrlEncode,
  base64UrlDecode,
} from 'agents-chain';

Full architecture

See ARCHITECTURE.md for Mermaid diagrams covering the package internals, integration flow, per-call security pipeline, Host JWT flow, well-known discovery, and persistence adapter swap-in points.


License

MIT — brianmwangidev

About

Lightweight identity, authentication, and audit layer for AI agent SDKs.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors