npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@strixgov/tool-gateway

v0.4.1

Published

Local-first governed tool execution gateway for AI agents. Read/write classification, policy evaluation, signed execution receipts, multi-key rotation with cross-signed chain snapshots, per-capability rate limits, threshold-based escalation, file/webhook

Readme

@strixgov/tool-gateway

Governed tool execution for AI agents — at the action boundary, not before or after.

Most AI governance tools operate upstream of the action (prompt filters, evals, content moderation) or downstream (audit logs, observability). The tool-gateway operates at the action itself: every tool call an agent makes is classified, evaluated against policy, and either allowed, denied, or held for approval before it runs. Same enforcement applies regardless of whether the actor is the agent, a human operator, or an automation script.

Local-first. No account. No cloud. Every decision produces an Ed25519-signed, append-only receipt that anyone can verify offline with @strixgov/verifier.

v0.4.1 — launch stable. Connected-mode wire envelope is now stable (timestamp + nonce + timing-safe HMAC for replay defense). All v0.3 and v0.4 review-track backlog items closed. The 4-package MCP Governance Bundle (this package + @strixgov/mcp-adapter + @strixgov/capabilities-claude-code + @strixgov/capabilities-mcp-common) launches as a coordinated bundle.

Earlier versions: v0.3 added companion packs + experimental connected mode. v0.2 added multi-key KeyRing with rotation, chain snapshots at every key handoff, per-capability rate limits, threshold-based escalation hooks, webhook-based approval, and a signature-verified shared capability registry. v0.1 introduced schema v2 (receipts bind a content-addressable policyVersion + tenantId + environment; v1 receipts continue to verify).

The gateway sits inline between an AI agent (Claude Code, Cursor, OpenClaw, MCP clients, autonomous coding agents) and the actual execution surface — filesystem, shell, MCP server, HTTP API.

Agent → @strixgov/tool-gateway → Tool

The product positioning is narrow on purpose: this is post-compromise execution control. It is not malware prevention, not EDR, not "AI safety," not observability. It is the layer that lets you safely give agents more power.

For governance of any MCP server specifically, see @strixgov/mcp-adapter — the 5-line drop-in that sits on top of this gateway.


Install

npm install @strixgov/tool-gateway
# or
pnpm add @strixgov/tool-gateway

Node 18+ is required (we depend on node:crypto Ed25519 support).

5-minute quickstart

# Generate a signing key + default deny-by-default policy under
# ~/.strix-gateway. Idempotent.
npx strix-gateway init

# See your local public JWK (this is what an external verifier uses).
npx strix-gateway keys jwks
import {
  createGateway,
  loadOrCreateKeyRing,
  JsonlStorage,
} from "@strixgov/tool-gateway";
import { governedFs } from "@strixgov/tool-gateway/adapters/filesystem";

// Both default to ~/.strix-gateway via os.homedir() — pass `root` / `dir`
// to override (use absolute paths; the library does not expand "~").
const gateway = createGateway({
  keyRing: await loadOrCreateKeyRing(),
  storage: new JsonlStorage(),
  policy: {
    rules: {
      "filesystem.read":   "ALLOW",
      "filesystem.write":  "APPROVAL_REQUIRED",
      "filesystem.delete": "DENY",
    },
    default: "DENY",
  },
  capabilities: {},
});

const fs = governedFs(gateway, { actorId: "agent-claude" });

// Reads run immediately.
const text = await fs.readFile("./CLAUDE.md");

// Writes prompt the user in the terminal. Deny-by-default + 60s timeout.
await fs.writeFile("./out.txt", "agent output");

// Deletes are blocked at the policy layer.
await fs.unlink("./out.txt"); // throws: filesystem.delete denied

Every call appends a signed receipt to ~/.strix-gateway/receipts.jsonl.

$ npx strix-gateway receipts list -n 3
2026-05-07T01:14:22Z  ALLOW              LOW      filesystem.read           rcpt_abf7…
2026-05-07T01:14:22Z  APPROVAL_REQUIRED  HIGH     filesystem.write          rcpt_2a09…
2026-05-07T01:14:22Z  DENY               CRITICAL filesystem.delete         rcpt_91d3…

$ npx strix-gateway verify
✓ rcpt_abf7… VERIFIED
✓ rcpt_2a09… VERIFIED
✓ rcpt_91d3… VERIFIED
3/3 receipts VERIFIED

$ npx strix-gateway chain
proof chain: OK

What you can govern in v1

| Adapter | Capabilities | Default risk | |---|---|---| | @strixgov/tool-gateway/adapters/filesystem | filesystem.read/list/write/append/delete | LOW / LOW / HIGH / HIGH / CRITICAL | | @strixgov/tool-gateway/adapters/shell | shell.exec/spawn (+ hard-fail patterns: rm -rf /, curl ⏐ sh, npm publish, fork bomb, etc) | CRITICAL / HIGH | | @strixgov/tool-gateway/adapters/mcp | every tool an MCP server advertises, auto-classified by name (read_* LOW, delete_* CRITICAL, etc) | configurable |

Custom adapters are mechanical — call gateway.execute({ capabilityId, action, args }, executor) and the gateway does the rest. See examples/.

Observability

The Gateway is an EventEmitter. Wire metrics, audit, alerting, or incident response without modifying the gateway core:

gateway.on("decision", ({ evaluation, invocation }) => {
  metrics.increment(`strix.decision.${evaluation.decision}`, {
    capability: invocation.capabilityId,
    risk: evaluation.risk,
  });
});

gateway.on("receipt", (receipt) => {
  audit.append(receipt);                        // your own log shipper
});

gateway.on("denial", ({ receipt, evaluation, approval, hardfail }) => {
  if (hardfail) alert.page("on-call", receipt); // shell adapter hardfail
});

gateway.on("error", ({ receipt, err, invocation }) => {
  sentry.capture(err, { receipt, invocation });
});

Listener errors are caught and ignored — a buggy observer cannot break the gateway's hot path. The receipt is always written before any event fires (or before the executor runs, on ALLOW).

Headless / CI approval — fileApprover

terminalApprove refuses non-TTY by design. For CI, container, and headless agents use fileApprover: the gateway writes a request file, an out-of-band channel (Slack bot, GitHub Action, on-call rotation) writes a response file, the gateway reads it.

import { createGateway, fileApprover, loadOrCreateSigningKey, JsonlStorage }
  from "@strixgov/tool-gateway";

const gateway = createGateway({
  signingKey: await loadOrCreateSigningKey(),
  storage: new JsonlStorage(),
  policy: { rules: { "filesystem.write": "APPROVAL_REQUIRED" }, default: "DENY" },
  capabilities: {},
  approval: {
    timeoutMs: 5 * 60_000,
    prompt: fileApprover({
      requestDir: "./.strix-approvals",
      onRequestWritten: async (path, id) => {
        // optional hook: notify Slack / GitHub Check / pagerduty
      },
    }),
  },
});

The on-disk schema is documented in src/approval.mjs. Default-deny applies: missing file = TIMEOUT = DENY, malformed JSON = PROMPT_FAILED = DENY, anything other than { approved: true } = DENY.

Tenant + environment scoping

If you run multiple agents on the same machine — different projects, clients, or environments — bind them at gateway construction so receipts can never be confused:

const gateway = createGateway({
  tenantId: "fairytale-farms",
  environment: "prod",
  // ...
});

Both fields are part of the canonical signed payload. Tampering with either invalidates the signature.

Policy versioning

Every PolicyEngine produces a content-addressable hash:

gateway.policyVersion          // "sha256:9a3c7e..." (changes on setPolicy)

Each receipt embeds this hash, so an auditor can answer "which policy was in force when this receipt was issued?" with the receipt alone — no out-of-band record needed.

Key rotation + chain snapshots (v0.2)

Open a multi-key KeyRing instead of a single signing key. New receipts are signed by the active kid; rotation mints a doubly-signed snapshot that an external verifier accepts as proof of continuity.

import {
  createGateway,
  loadOrCreateKeyRing,
  JsonlStorage,
} from "@strixgov/tool-gateway";

// Defaults are derived from os.homedir(); pass absolute paths to override.
const ring = await loadOrCreateKeyRing();
const storage = new JsonlStorage();
const gateway = createGateway({
  policy, capabilities, storage,
  keyRing: ring,
});

// ... receipts accumulate, signed by ring.active.kid ...

const snapshot = await gateway.rotateKey({ kid: "local-2026-06" });
// snapshot.signaturePrevious + snapshot.signatureNew = cross-signed boundary.
// New receipts after this line are signed by the new active kid.

CLI equivalents: strix-gateway keys list|jwks|rotate, strix-gateway snapshots list. JWKS served by the ring covers every historical kid, so retired-key receipts stay verifiable.

Per-capability rate limits (v0.2)

Add rateLimits to your policy ruleset. Buckets can be shared or partitioned per-actor. Wildcard prefixes match longest-first.

const policy = {
  rules: { "fs.write": "ALLOW", "fs.delete": "DENY" },
  default: "DENY",
  rateLimits: {
    "fs.write":   { windowMs: 60_000, max: 100 },
    "shell.exec": { windowMs: 60_000, max: 10, perActor: true },
    "ai.*":       { windowMs: 1_000,  max: 5 },
  },
};

Rate-limited calls produce a normal signed DENY receipt with denialReason: "RATE_LIMITED". Limits do not mutate the policy version hash — they're operationally tunable without invalidating receipt-comparison lineage.

Threshold-based escalation (v0.2)

Fire an escalation event (and call onEscalate) when a sliding window of receipts crosses a threshold. Useful for paging on burst denials, tripping a circuit breaker, or quarantining an actor.

const gateway = createGateway({
  // ...
  escalation: {
    threshold: 5,
    windowMs: 60_000,
    decisions: ["DENY"],
    onEscalate: (e) => pageOnCall(e),
  },
});

gateway.on("escalation", (e) => log.warn("burst", e.count, e.receipts));

Webhook-based approval (v0.2)

Same shape as fileApprover, but over HTTP with HMAC-SHA256 signing. Zero new dependencies, no inbound server bound by this package — you provide a pollResponse(requestId) against whatever store your webhook receiver writes to.

import { webhookApprover, verifyWebhookSignature } from "@strixgov/tool-gateway";

const approver = webhookApprover({
  notifyUrl: "https://hooks.example.com/strix-approvals",
  secret: process.env.STRIX_WEBHOOK_SECRET,
  pollResponse: async (requestId) => approvalsStore.get(requestId),
  timeoutMs: 5 * 60_000,
});

// In your inbound webhook handler:
verifyWebhookSignature({ body, signatureHeader, secret });

Shared capability registry (v0.2)

Persist a signed manifest at ~/.strix-gateway/capabilities.json so multiple agent processes on the same host see the same classifications.

import {
  saveCapabilityRegistry,
  loadCapabilityRegistry,
  watchCapabilityRegistry,
} from "@strixgov/tool-gateway";

await saveCapabilityRegistry({
  signingKey: ring.active,
  capabilities: [/* ... */],
});

const manifest = await loadCapabilityRegistry({
  resolvePublicKey: (kid) => ring.publicKeyForKid(kid),
});

Tampering breaks the signature; the loader fails closed. watchCapabilityRegistry reloads on file change.

Companion packs (v0.3)

Skip hand-classifying capabilities for common surfaces:

npm install @strixgov/capabilities-claude-code @strixgov/capabilities-mcp-common
import { createGateway } from "@strixgov/tool-gateway";
import { claudeCodeCapabilities } from "@strixgov/capabilities-claude-code";
import { allMcpCapabilities }     from "@strixgov/capabilities-mcp-common";

const capabilities = Object.fromEntries(
  [...claudeCodeCapabilities, ...allMcpCapabilities].map((c) => [c.id, c]),
);

Both packs ship suggestedPolicy() for sane starter rulesets — strict defaults (writes/EXECUTE → APPROVAL_REQUIRED, default DENY). Override classifications per-environment as needed.

Connected mode (v0.3, experimental)

Opt-in upstream sync for fleets of agents that want to roll their proof chains up to a hosted system. Local-first stays the default truth — upstream failures never block local execution.

const gateway = createGateway({
  // ... policy, capabilities, signingKey/keyRing, storage ...
  connectedMode: {
    kernelUrl: process.env.STRIX_KERNEL_URL,    // e.g. https://kernel.example.com
    apiKey:    process.env.STRIX_API_KEY,
    tenantId:  "fairytale-farms",
    syncReceipts:  true,                         // default
    syncSnapshots: true,                         // default
    onSyncError: (e) => log.warn("strix sync", e.reason, e.err.message),
  },
});

gateway.on("sync",       (e) => metrics.inc("strix.sync.ok"));
gateway.on("sync-error", (e) => metrics.inc("strix.sync.err", { reason: e.reason }));

// Periodically drain the queue (e.g. every minute):
setInterval(() => gateway.drainSync(), 60_000);

Wire format is v0.3-experimental — it may rev before v0.4 stable. The Ed25519 signature on each inner receipt is what survives across wire format changes.

Verification

Receipts can be verified anywhere, by anyone, with the same primitives an auditor would use:

# Offline — provide the JWKS exported from your local gateway.
npx strix-gateway keys jwks > ./public-jwks.json
npx @strixgov/verifier receipt path/to/receipt.json --jwks ./public-jwks.json

# Whole chain
npx @strixgov/verifier chain ~/.strix-gateway/receipts.jsonl --jwks ./public-jwks.json

The verifier is a separate package with zero Strix dependencies — just Ed25519 + SHA-256 from node:crypto.

The five invariants

These hold by construction in v1; loosening any of them is a breaking change.

  1. Nothing executes without evaluation. Gateway.execute evaluates policy → (optionally prompts approval) → mints a signed receipt → then invokes the executor. There is no fast path.
  2. Execution does not inherit authority. Every invocation is a fresh evaluation. A previous ALLOW does not authorize a future call.
  3. Admissibility at execution time. Policy is evaluated against the exact (capabilityId, action, args, actor) tuple that the executor will see — not the agent's intent, not a stale cache.
  4. Runtime enforcement. A receipt is not a log entry. The receipt is the gate: no receipt, no execution. (See gateway.mjs line where appendReceipt precedes executor(invocation.args).)
  5. Bounded and revocable. Receipts are append-only; a tampered receipt fails signature verification. Approval prompts time out and default to deny. Storage is a single JSONL file you can tail -f.

What this is NOT

  • not malware prevention
  • not antivirus / EDR / endpoint security
  • not "AI safety" or "AI guardrails"
  • not analytics or observability
  • not a hosted service or SaaS dashboard

The threat model is post-compromise: assume the agent process is already running attacker-controlled instructions. The gateway prevents those instructions from reaching the executor without a signed admission decision.

See docs/threat-model.md for the full list of in- and out-of-scope threats.

Architecture

See docs/architecture.md for the execution flow, proof chain construction, canonical-payload schema, and the wire contract with @strixgov/verifier.

Connected mode

This package is local-only by design. A future @strixgov/connector package will ship receipts to a hosted Strix kernel for cross-team evidence aggregation, capability registries, and policy distribution. That work doesn't affect this contract — connected receipts are byte- for-byte identical to local ones.

Standards alignment

Strix is listed in the Cloud Security Alliance AARM Builders Registry with status Aligned. AARM (Autonomous Action Runtime Management) is the CSA-led specification for runtime governance of autonomous AI actions (donated to CSA by Vanta, paper arXiv:2602.09433).

The tool-gateway is the local-first execution-control half of Strix's AARM coverage: AARM's R1–R3 (runtime authorization, deterministic policy evaluation, fail-closed enforcement) implemented as a local primitive any agent can drop in. The signed receipts it produces are tamper-evident under @strixgov/verifier, which is the public reference implementation of AARM Core R6.

Cryptographic agent identity binding (the largest AARM Extended capability) is planned for a subsequent release; see the strixgov.com AARM mapping for the per-requirement breakdown.

License

MIT.