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

actionfence

v0.3.0

Published

Server-side policy middleware for MCP servers and Node APIs with spend limits, approvals, schema drift checks, and signed receipts

Downloads

63

Readme

ActionFence

AI Action Firewall for MCP servers and APIs.

One line of code. Signed receipts. Simulation mode.

CI License: MIT Node.js

Why ActionFence?

AI agents can now book flights, send emails, and delete records in your system — often without you knowing until the invoice arrives. ActionFence lets you define exactly what they're allowed to do, and proves every decision with a signed receipt.

It sits in front of your MCP tools or HTTP routes and decides whether an incoming agent action is allowed before your real handler runs.

It gives you:

  • Policy enforcement from guard-policy.json
  • Identity tiers: anonymous, token, verified
  • Built-in verified JWT support via JWKS
  • Capability scope checks from JWT capabilities
  • Per-action spend caps plus session/day spend limits
  • Request and transaction rate limiting
  • Signed, hash-chained receipts in SQLite
  • Simulation mode for dry-run previews

Install

npm install actionfence

Quick Start

MCP Server

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { withGuard } from 'actionfence';

const server = new McpServer({ name: 'my-server', version: '1.0.0' });

withGuard(server, {
  policy: './guard-policy.json',
  identityReaderOptions: {
    jwksUri: 'https://issuer.example/.well-known/jwks.json',
    issuer: 'https://issuer.example',
    audience: 'bookflight-mcp',
  },
});

server.registerTool('search_flights', {}, async () => {
  return { content: [{ type: 'text', text: 'results...' }] };
});

Express / Fastify

import express from 'express';
import { guard } from 'actionfence';

const app = express();
app.use(express.json());

app.use(
  guard({
    policy: './guard-policy.json',
    identityReaderOptions: {
      jwksUri: 'https://issuer.example/.well-known/jwks.json',
      issuer: 'https://issuer.example',
      audience: 'bookflight-api',
    },
    actionResolver: (toolName) => {
      if (/^GET \/bookings\/[^/]+$/.test(toolName)) {
        return 'GET /bookings/:id';
      }
      return toolName;
    },
    spendExtractor: (params) => {
      const body = (params as { body?: { amount?: number } })?.body;
      return body?.amount ?? null;
    },
  }),
);

CLI

npx actionfence init
npx actionfence generate "node server.js" --output guard-policy.json --pin-schemas
npx actionfence validate guard-policy.json
npx actionfence validate guard-policy.json "node server.js"
npx actionfence simulate guard-policy.json --action book_flight --identity verified --spend 250

Using an AI Coding Assistant?

Copy this prompt into Claude, Cursor, Copilot, or any LLM and let it handle the setup:

Install and integrate the "actionfence" npm package into my current project. Read the full integration guide at https://raw.githubusercontent.com/saifeldeen911/actionfence/main/llms-full.txt then: install the package, create a guard-policy.json for my use case, and wire up the middleware.

Trust Model

ActionFence runs on your server as middleware. The agent communicates with your server via MCP protocol or HTTP — it never has direct access to the policy file or the enforcement engine.

This is server-side enforcement, not a client-side honor system.

  • The agent cannot read guard-policy.json — it's a file on your server
  • Tool calls routed through ActionFence are evaluated before they reach your real handlers
  • The agent cannot tamper with receipts — they're signed with your secret key

ActionFence only enforces actions that pass through the protected MCP or HTTP boundary. It does not replace branch protection, deploy approvals, scoped cloud tokens, scoped GitHub App permissions, or other infrastructure-level controls.

Tip: Keep guard-policy.json outside any tool-accessible directories. If your MCP server has a read_file tool, make sure it can't access the directory where your policy file lives.

Storage

ActionFence stores signed receipts in SQLite by default (zero-config). For serverless or horizontally-scaled deployments, use PostgreSQL:

PostgreSQL Setup

  1. Install the pg driver:

    npm install pg
  2. Configure storage in your guard setup:

    withGuard(server, {
      policy: './guard-policy.json',
      storage: {
        adapter: 'postgres',
        connectionString: process.env.DATABASE_URL,
      },
    });

ActionFence auto-creates the actionfence_receipts table on first use.

By default, receipt persistence failures are logged and the guard preserves backward-compatible allow mode, returning receipt: null if execution is still allowed by policy. To fail closed when receipt storage is unavailable, opt in:

withGuard(server, {
  policy: './guard-policy.json',
  receiptFailureMode: 'block',
});

Policy File

guard-policy.json defines what agents can do in your system.

{
  "$schema": "https://raw.githubusercontent.com/saifeldeen911/actionfence/main/schemas/guard-policy.schema.json",
  "service": "BookFlight.com",
  "version": "1.0",
  "default_rule": "deny",
  "actions": {
    "search_flights": {
      "allowed": true,
      "identity": "any"
    },
    "book_flight": {
      "allowed": true,
      "identity": "verified",
      "max_spend": 500,
      "currency": "USD",
      "requires_human_approval": true
    },
    "bulk_booking": {
      "allowed": false
    }
  },
  "rate_limits": {
    "requests_per_minute": 30,
    "transactions_per_day": 5
  },
  "spend_limits": {
    "session_max": 1000,
    "daily_max": 2500,
    "window": {
      "max_amount": 500,
      "duration_minutes": 60
    },
    "currency": "USD"
  },
  "circuit_breaker": {
    "global_max_spend": 10000,
    "action": "block_all",
    "currency": "USD"
  },
  "regulations": ["EU_AI_Act_Art50"]
}

Policy Reference

| Field | Type | Required | Notes | | -------------- | ------------------- | -------- | ----------------------------------------------- | | service | string | Yes | Service name | | version | string | Yes | Policy version | | default_rule | "allow" \| "deny" | No | Defaults to "deny" | | actions | object | Yes | Action rules keyed by action name | | rate_limits | object | No | Request and transaction limits | | spend_limits | object | No | Session, daily, and rolling-window spend limits | | circuit_breaker| object | No | Global maximum spend kill-switch | | schema_enforcement | object | No | Tool schema drift detection and enforcement | | regulations | string[] | No | Parsed as policy metadata but not enforced |

Action Rule Fields

| Field | Type | Default | Notes | | ------------------------- | -------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | allowed | boolean | - | Required | | identity | "any" \| "token" \| "verified" | "any" | Minimum identity tier | | max_spend | number | - | Per-invocation cap in major units | | currency | string | - | ISO 4217 currency code | | requires_human_approval | boolean | false | When true, pauses evaluation only if onApprovalRequired is configured; otherwise the decision records the requirement and proceeds. | | schema_hash | string | - | Pinned SHA-256 hash of the tool's input schema. Set via actionfence pin-schemas. |

Identity Tiers

| Tier | Meaning | | ----------- | ----------------------------------------------- | | anonymous | No credentials presented | | token | Bearer token present but not signature-verified | | verified | JWT passed JWKS verification |

Verified identity is built in when you configure identityReaderOptions.jwksUri and JWKS lookup succeeds from cache or the remote endpoint. When JWKS verification is configured, verification failures, wrong issuers or audiences, unknown kids, and JWKS retrieval errors return anonymous identity. The unverified token tier is used only when no JWKS verifier is configured.

Scope Enforcement

If a decoded or verified token includes a capabilities claim, ActionFence treats it as an exact allowlist of policy action names. A request that passes policy checks but is not listed in capabilities is blocked.

Tool Schema Drift Detection

ActionFence can detect when an MCP server's tool schemas change after you've pinned them. This catches silent breaking changes that could cause agent failures or enable payload injection.

Generating a Starter Policy

actionfence generate "node server.js" --output guard-policy.json --pin-schemas
actionfence validate guard-policy.json "node server.js"

generate connects to the MCP server, discovers tools, and writes a reviewable starter policy. The generated policy defaults to default_rule: "deny" and every discovered action is written with allowed: false; it is a starting point for review, not a completed security policy. Pass --pin-schemas to include current tool schema hashes during generation.

Pinning Schemas

actionfence pin-schemas guard-policy.json "node server.js"

This command connects to the MCP server, fetches all tools, computes SHA-256 hashes of each tool's inputSchema, and writes the hashes into the policy file under each action's schema_hash field.

Validating for Drift

actionfence validate guard-policy.json "node server.js"

Compares live tool schemas against pinned hashes and reports any mismatches.

Runtime Enforcement

Configure schema_enforcement in your policy to control drift behavior at runtime:

{
  "schema_enforcement": {
    "on_mismatch": "warn"
  }
}

| on_mismatch | Behavior | | ------------- | ------------------------------------------------------------------------ | | "warn" | Logs a warning on drift but allows the action (default if not configured) | | "block" | Blocks the action if the schema has drifted from the pinned hash |

When schema_enforcement is omitted, pinned schema hashes are still checked with warning behavior. Set on_mismatch to "block" to deny drifted tools at runtime.

Payload Processing

ActionFence canonicalizes tool params before hashing receipts. That keeps the receipt chain deterministic, but it also means tool payloads must stay JSON-friendly.

Payload Redaction

By default, full tool params are stored in receipts. To strip sensitive fields (passwords, API keys, PII) before persistence, use payloadRedactor:

withGuard(server, {
  policy: './guard-policy.json',
  payloadRedactor: (params) => {
    const safe = { ...(params as Record<string, unknown>) };
    delete safe.password;
    delete safe.apiKey;
    return safe;
  },
  maxPayloadBytes: 32_768, // optional: truncate stored payloads above 32 KB
});
  • The receipt hash is computed from the original params (integrity is preserved).
  • Only the stored payload_json is redacted/truncated (privacy).
  • payloadRedactor must return a sanitized copy — do not mutate the input.
  • maxPayloadBytes defaults to 65 536 (64 KB). Payloads exceeding this are replaced with a truncation marker that includes the original hash.

Payload Requirements

  • params must be JSON-serializable.
  • Unsupported values such as BigInt, Symbols, and circular references will fail before receipt creation.
  • undefined values are omitted by canonicalization, and NaN or Infinity become null.
  • Tool authors should validate their params are JSON-friendly before calling ActionFence-protected endpoints.

Simulation Mode

Simulation mode runs the full policy pipeline without executing the handler or storing a receipt.

MCP

withGuard(server, {
  policy: './guard-policy.json',
  simulate: true,
});

Express

app.use(
  guard({
    policy: './guard-policy.json',
    simulate: true,
  }),
);

CLI output

actionfence simulate guard-policy.json --action book_flight --identity verified --spend 250

SIMULATION - actionfence

  Action:          book_flight
  Tool:            book_flight
  Identity:        verified
  Status:          PASS
  Spend:           250.00
  Session total:   250.00
  Daily total:     250.00
  Human approval:  required
  Rate limit:      29/30 remaining

Action Receipts

In normal operation, every enforced decision stores a signed receipt in SQLite.

Stored payloads may be redacted or truncated before persistence. The receipt binds the original request hash and the stored payload-view hash so chain verification can still detect tampering.

receipt_id:     a1b2c3d4-...
timestamp:      2026-05-07T14:02:11Z
agent_id:       agt_7x9f2k...
action:         book_flight
status:         PASSED
payload_hash:   0x8f3a...
prev_hash:      0x7e2d...
receipt_sig:    0x4f9b...

Receipts are:

  • Hash-chained
  • HMAC-SHA256 signed
  • Append-only
  • Verifiable with ReceiptStore.verifyChain()

Note: Receipts are stored in a local SQLite file (.actionfence/receipts.db) by default. This works perfectly for single-instance deployments. If you run multiple server instances, use the postgres storage adapter to maintain a single global receipt chain.

Receipt persistence defaults to allow mode for backward compatibility. Set receiptFailureMode: 'block' to make ActionFence return ACTIONFENCE_RECEIPT_PERSISTENCE_FAILED with HTTP 503/MCP error output and prevent handler execution when receipt storage is unavailable. Simulation mode never stores receipts.

Signing key resolution order:

  1. options.secret
  2. ACTIONFENCE_SECRET
  3. .actionfence/key

API Reference

withGuard(server, options)

const guardInstance = withGuard(server, {
  policy: './guard-policy.json',
  simulate: false,
  silent: false,
  secret: process.env.ACTIONFENCE_SECRET,
  identityReaderOptions: {
    jwksUri: 'https://issuer.example/.well-known/jwks.json',
    issuer: 'https://issuer.example',
    audience: 'bookflight-mcp',
  },
  actionResolver: (toolName, params) => toolName,
  spendExtractor: (params) => null,
  onDecision: (decision) => {},
  watchPolicy: true,
});

const status = guardInstance.getAgentStatus('agt_7x9f2k');
console.log(`Allowed actions:`, status.allowedActions);

guardInstance.dispose();

guard(options)

const middleware = guard({
  policy: './guard-policy.json',
  identityReaderOptions: {
    jwksUri: 'https://issuer.example/.well-known/jwks.json',
  },
});

GuardOptions

| Option | Type | Default | Notes | | ----------------------- | ----------------------------------------- | ------- | ---------------------------------------- | | policy | string \| GuardPolicy | - | Required | | simulate | boolean | false | Dry-run mode | | silent | boolean | false | Suppress console output | | secret | string | - | HMAC secret override | | identityReaderOptions | IdentityReaderOptions | - | Built-in JWKS verification config | | identityReader | IdentityReaderLike | - | Full custom identity resolution override | | actionResolver | (toolName, params) => string | - | Map tool names to policy actions | | spendExtractor | (params) => number \| null | - | Extract spend in major units | | transactionResolver | (toolName, params, decision) => boolean | - | Override transaction classification | | onDecision | (decision) => void | - | Metrics, logging, hooks | | onApprovalRequired | (decision) => Promise<boolean> | - | Webhook callback to pause and await human approval | | approvalTimeoutMs | number | 30000 | Timeout for approval in milliseconds | | watchPolicy | boolean | false | Hot-reload file-backed policies | | storage | StorageConfig | - | Storage backend (SQLite/PostgreSQL) | | receiptFailureMode | 'allow' \| 'block' | 'allow' | Use 'block' to fail closed when receipt persistence fails | | payloadRedactor | (params: unknown) => unknown | - | Strip sensitive fields before receipt storage | | maxPayloadBytes | number | 65536 | Max stored payload_json size; larger payloads are truncated |

IdentityReaderOptions

| Field | Type | Notes | | ---------- | -------------------- | ----------------------- | | jwksUri | string | Remote JWKS endpoint | | issuer | string \| string[] | Optional issuer check | | audience | string \| string[] | Optional audience check |

Current Limitations

  • Capability checks are exact string matches only
  • No APoP / LAS-WG adapters yet
  • No path-policy DSL
  • requires_human_approval pauses execution only when onApprovalRequired is configured; there is no built-in approval UI or queue
  • Receipt persistence fail-closed behavior is opt-in via receiptFailureMode: 'block'; default allow mode can still return receipt: null
  • Money is major-unit only; mixed-currency accounting is out of scope for one policy
  • SQLite storage is single-instance only (use the postgres adapter for multi-instance deployments)

CLI Reference

actionfence init

actionfence init
actionfence init --service MyAPI
actionfence init --output ./policies/guard-policy.json

actionfence generate <server-command>

actionfence generate "node server.js"
actionfence generate "node server.js" --default-rule deny --output guard-policy.json --service MyAPI
actionfence generate "node server.js" --pin-schemas

Discovers tools from an MCP server and writes a reviewable guard-policy.json. The command refuses to overwrite an existing output file, writes discovered actions as blocked (allowed: false), and only adds schema_hash values when --pin-schemas is passed.

actionfence validate <path>

actionfence validate guard-policy.json
actionfence validate guard-policy.json "node server.js"  # check schema drift

actionfence pin-schemas <path> <server-command>

actionfence pin-schemas guard-policy.json "node server.js"

Connects to the MCP server, hashes each tool's input schema, and pins the hashes in the policy file.

actionfence simulate <path>

actionfence simulate guard-policy.json --action search_flights
actionfence simulate guard-policy.json --action book_flight --identity verified --spend 250

Examples

Development

git clone https://github.com/saifeldeen911/actionfence.git
cd actionfence
npm install
npm run typecheck
npm run lint
npm test
npm run build

License

MIT (c) Saifeldeen