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

eve-policy

v0.1.0

Published

Declarative governance and policy enforcement for Vercel Eve agents — deny, escalate, audit, and allow tool calls with composable rules, OWASP Agentic Top 10 coverage, and tamper-evident audit trails.

Readme

eve-policy

Declarative governance and policy enforcement for Vercel Eve agents.

Define rules once. Attach them to any Eve tool. Get deny, escalate (human-in-the-loop), audit, and allow semantics — automatically wired into Eve's needsApproval and execute lifecycle — with zero boilerplate and no runtime overhead on the evaluation hot path.

npm license OWASP


Why eve-policy?

AI agents that call real tools — banking APIs, data stores, messaging systems — need governance that goes beyond "trust the model." You need to be able to say: this tool cannot be called by a subagent, this amount requires human approval, these fields must be redacted in audit logs, this call pattern is a known OWASP Agentic risk. Writing that logic inside individual tools creates drift; writing it inside the agent loop couples your business rules to the framework.

eve-policy puts governance in its own layer: a composable, testable, auditable policy that sits between Eve's lifecycle events and your tool's implementation — the same way a firewall sits between a network and your servers.


Features

  • Four-tier governancedeny (block + throw) → escalate (human-in-the-loop via needsApproval) → audit (log + execute) → allow
  • Fail-closed by default — no rule match defaults to deny, not allow
  • Eve-native lifecycle — integrates with needsApproval (sync, limited context) and execute (async, full session) exactly as the Eve spec requires
  • Two-phase evaluation — input-based controls fire at approval time; session-aware controls (subagent detection, principal checks) fire at execution time
  • Composablecompose() merges any number of policies; strictest effect wins across all of them
  • OWASP Agentic Top 10 (2026) — built-in reference policy covering all 10 ASI risks out of the box
  • Financial services baseline — pre-built rules for CTR thresholds (USD/NGN/KES), OFAC/FATF sanctions screening, card data protection, KYC/AML hooks
  • Rich matcherstoolNameIs, amountExceeds, isSubagentCall, fieldMatches, and, or, not, and more
  • SIEM-compatible audit trail — structured NDJSON, auto-sanitized inputs, pluggable backends
  • Coverage reportinguncoveredOwaspRisks() tells you exactly which ASI risks your policy does not address
  • TypeScript-first — full generics, strict mode, ESM-only

Installation

pnpm add eve-policy
# or
npm install eve-policy

Requirements: Node.js >= 24, Eve >= 0.10.0 (peer dependency)


Quick start

import { definePolicy, withNamedPolicy } from "eve-policy";
import { deny, escalate, audit } from "eve-policy/rules";
import { toolNameIs, amountExceeds, isSubagentCall, and } from "eve-policy/rules";
import { FileAuditLogger } from "eve-policy/audit";

// 1. Define your policy
const transferPolicy = definePolicy({
  name: "transfer-controls",
  version: "1.0.0",
  rules: [
    deny("no-self-transfer",
      ctx => ctx.toolInput.source === ctx.toolInput.destination,
      "Source and destination accounts cannot be the same"),

    escalate("ctr-threshold",
      amountExceeds("amount", 10_000),
      "Exceeds $10,000 CTR threshold — compliance review required",
      { riskLevel: "critical", owaspRisks: ["ASI02"] }),

    escalate("subagent-financial-write",
      and(isSubagentCall(), toolNameIs("transfer_funds")),
      "Financial writes from subagents require human approval",
      { riskLevel: "critical", owaspRisks: ["ASI03"] }),

    audit("all-transfers",
      toolNameIs("transfer_funds"),
      "All fund transfers logged for compliance"),
  ],
});

// 2. Wrap your Eve tool — one line, drop-in replacement
const safeTool = withNamedPolicy("transfer_funds", transferTool, transferPolicy, {
  auditLogger: new FileAuditLogger("/var/log/agent-audit.jsonl"),
});

// 3. Use it in your agent — Eve calls needsApproval and execute automatically

How it works

Eve exposes two lifecycle hooks on every tool: needsApproval (synchronous, called before the turn completes to decide whether to pause for human approval) and execute (async, called when the tool actually runs). eve-policy wraps both.

Eve lifecycle                    eve-policy evaluation
─────────────────────────────────────────────────────────────

  turn starts
       │
       ▼
  needsApproval(ctx)  ──────►  evaluatePolicy(policy, approvalCtx)
       │                            │  rule 1: match? → effect
       │                            │  rule 2: match? → effect
       │  escalate? return true     │  ...
       │  otherwise:  return false  │  default effect
       │
       │  [human approves if needsApproval=true]
       │
       ▼
  execute(input, ctx) ──────►  evaluatePolicy(policy, executeCtx)
       │                            │  full session available here:
       │                            │  session.id, session.auth,
       │                            │  session.parent (subagent?)
       │
       ├── deny?     → throw PolicyDenialError  (tool never called)
       ├── escalate? → run tool, write audit entry
       ├── audit?    → run tool, write audit entry
       └── allow?    → run tool  (no audit by default)

Why two evaluation passes? Eve's needsApproval is synchronous and does not provide session context — it runs before the turn resolves, so there is no session yet. This means input-based controls (large amounts, sensitive field patterns) can fire at approval time, while session-based controls (subagent detection, principal type) fire again in execute where the full ToolContext.session is available.


Core concepts

The four effects

| Effect | needsApproval | execute | Audit | |--------|----------------|-----------|-------| | deny | false | throws PolicyDenialError | yes (always) | | escalate | true | runs tool if approved, writes audit | yes | | audit | false | runs tool, writes audit | yes | | allow | false | runs tool | opt-in |

Rules are evaluated in declaration order. First match wins. When multiple policies are composed with compose(), the strictest effect across all policies wins: deny > escalate > audit > allow.

Fail-closed semantics

When no rule matches, the defaultEffect applies. The default for defaultEffect is "deny" — a governance layer with no matching rules should not silently permit execution. Set defaultEffect: "allow" explicitly if your policy's stated intent is "allow by default, restrict specific patterns."

Synchronous evaluation

All rule match predicates must be synchronous. This is not a limitation — it is a deliberate constraint. Governance decisions on the hot path should never block on network calls, database lookups, or async I/O. External lookups belong in pre-execution middleware, not inside policy rules. Async work (audit logging) runs fire-and-forget after the decision is made.


Composing policies

compose() is the primary way to build production policies. It flattens the rules from all component policies in order and applies deny-wins semantics for the default effect.

import { compose } from "eve-policy";
import { owaspTop10Policy, financialBaselinePolicy } from "eve-policy/profiles";
import { ndpaPolicy, cbnFintechPolicy } from "@comply54/adapter-eve/profiles";

// Full Nigerian fintech stack: OWASP + financial baseline + NDPA + CBN
const nigeriaFintechPolicy = compose(
  owaspTop10Policy,          // 20 rules — ASI01–ASI10
  financialBaselinePolicy,   // 15 rules — CTR, sanctions, card data, KYC
  ndpaPolicy,                // 10 rules — NDPA 2023 full NDPC profile
  cbnFintechPolicy,          //  8 rules — CBN AML/CFT, NFIU reporting, structuring
);

// compose() semantics:
//   • Rules are evaluated in the order they appear across all component policies
//   • First match wins within a single evaluate() call
//   • defaultEffect is the STRICTEST default across all component policies
//   • If any component is defaultEffect:"deny", the composed policy is defaultEffect:"deny"

Avoiding catch-all rules in composable policies: If you add a rule that matches always() as a fallback in a component policy, it will intercept every tool call before later policies' specific rules can fire. Use defaultEffect for fallthrough behaviour instead.


API reference

definePolicy(config)

Validate and freeze a policy at definition time. Throws PolicyDefinitionError immediately for duplicate rule names, empty rule names, or invalid effect values — fail fast at startup, not at runtime.

const p = definePolicy({
  name: "my-policy",
  version: "1.0.0",
  description: "Optional, shown in audit entries and coverage reports",
  defaultEffect: "deny",  // "deny" | "escalate" | "audit" | "allow" — default: "deny"
  rules: [ /* PolicyRule[] */ ],
});

compose(...policies)

import { compose } from "eve-policy";

const fullPolicy = compose(owaspTop10Policy, myDomainPolicy, myBusinessRules);

withNamedPolicy(toolName, tool, policy, options?)

Wrap an Eve ToolDefinition with governance. Returns a new ToolDefinition with governed needsApproval and execute.

The toolName parameter must match the slug Eve derives from the tool file path (e.g. agent/tools/transfer_funds.ts"transfer_funds"). This is the value matched by toolNameIs() and toolNameIn() matchers.

const safeTool = withNamedPolicy("transfer_funds", originalTool, policy, {
  auditLogger: new ConsoleAuditLogger(),
  logAllowDecisions: false,  // default: only log deny/escalate/audit decisions
});

withPolicy(tool, policy, options?)

Convenience wrapper that uses the policy's name as the tool name. Prefer withNamedPolicy when toolNameIs/toolNameIn matchers must match an exact Eve tool slug.

defineGovernanceHook(policy, options?)

Create a cross-cutting audit observer using Eve's event system. Unlike withNamedPolicy, this watches all tool calls without modifying individual tools — suitable for global monitoring or compliance reporting.

import { defineGovernanceHook } from "eve-policy";
import { ConsoleAuditLogger } from "eve-policy/audit";

const hook = defineGovernanceHook(myPolicy, {
  auditLogger: new ConsoleAuditLogger(),
  eventTypes: ["actions.requested", "action.result", "authorization.required"],
});

agent.addHook(hook.eventTypes, hook.handle.bind(hook));

evaluatePolicy(policy, ctx)

Low-level synchronous evaluation. Returns a PolicyDecision without executing anything. Use this in tests, custom integrations, or pre-flight checks.

import { evaluatePolicy } from "eve-policy";
import type { PolicyContext } from "eve-policy";

const ctx: PolicyContext = {
  toolName: "transfer_funds",
  toolInput: { amount: 6_500_000, currency: "NGN" },
  approvedTools: new Set(),
  // session is optional — provide it when testing session-aware rules
  session: { id: "sess-001", turn: { id: "turn-1", sequence: 1 }, auth: { current: null, initiator: null } },
};

const decision = evaluatePolicy(policy, ctx);
// {
//   effect: "escalate",
//   ruleName: "cbn-ngn-ctr",
//   policyName: "cbn-fintech",
//   requiresApproval: true,
//   reason: "Exceeds ₦5M CBN CTR threshold — NFIU reporting required",
//   riskLevel: "critical",
//   owaspRisks: ["ASI02"],
// }

Rule helpers (eve-policy/rules)

Effect factories

import { deny, escalate, audit, allow } from "eve-policy/rules";

deny("rule-name", matchFn, "Reason surfaced in errors and audit logs", {
  riskLevel: "critical",       // "critical" | "high" | "medium" | "low"
  owaspRisks: ["ASI01"],       // OWASP Agentic Top 10 risk tags
})

Pre-built matchers

import {
  // Tool name
  toolNameIs, toolNameIn, toolNameStartsWith, toolNameEndsWith,
  toolNameContains, toolNameMatches,

  // Input fields
  amountExceeds, amountBetween,
  fieldEquals, fieldContains, fieldMatches,
  fieldPresent, fieldAbsent,

  // Session / identity
  isSubagentCall, principalTypeIs, initiatorTypeIs,

  // Approval state
  notYetApproved, alreadyApproved,

  // Logic combinators
  and, or, not, always, never,
} from "eve-policy/rules";

Examples

// Deny all shell-like tool names
deny("no-shell", toolNameIn("bash", "shell", "exec", "run_command"), "Shell execution prohibited")

// Escalate large NGN transfers (CBN CTR threshold)
escalate("ngn-ctr",
  ctx => {
    const i = ctx.toolInput as { amount?: number; currency?: string };
    return i.currency === "NGN" && (i.amount ?? 0) > 5_000_000;
  },
  "Exceeds ₦5M CBN reporting threshold",
  { riskLevel: "critical", owaspRisks: ["ASI02"] })

// Require human approval for subagent financial writes
escalate("subagent-financial-write",
  and(isSubagentCall(), toolNameMatches(/^(transfer|payment|disburse)/)),
  "Subagent financial write — principal separation required",
  { riskLevel: "critical", owaspRisks: ["ASI03"] })

// Deny card PAN patterns in any input field
deny("no-card-pan",
  fieldMatches("*", /\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13})\b/),
  "Card PAN detected in tool input — ASI02 data exfiltration vector",
  { riskLevel: "critical", owaspRisks: ["ASI02"] })

// Escalate cross-border data transfers to non-adequate countries
deny("no-cross-border-inadequate",
  ctx => {
    const dest = (ctx.toolInput as Record<string, unknown>).destination_country as string | undefined;
    const inadequate = new Set(["CN", "RU", "BY", "KP", "IR"]);
    return dest !== undefined && inadequate.has(dest);
  },
  "Cross-border transfer to non-adequate country — NDPA §43 requires adequacy decision",
  { riskLevel: "critical" })

Built-in profiles (eve-policy/profiles)

owaspTop10Policy

Full coverage of the OWASP Top 10 for Agentic Applications 2026:

| Risk | Coverage | |------|----------| | ASI01 — Goal Hijack | Deny shell tools, prompt injection patterns, goal-modification language | | ASI02 — Tool Misuse | Deny unapproved file writes, card PAN in input; escalate network calls | | ASI03 — Identity Abuse | Escalate privileged tools invoked from subagents | | ASI04 — Supply Chain | Escalate runtime package installs and dynamic tool registration | | ASI05 — Code Execution | Deny eval()/exec() patterns, escalate code runner tools | | ASI06 — Context Poisoning | Escalate memory writes; audit memory reads | | ASI07 — Inter-Agent Comms | Escalate cross-agent and cross-session invocations | | ASI08 — Cascading Failures | Escalate bulk operations exceeding 100 items | | ASI09 — Trust Exploitation | Deny impersonation language; escalate approval-bypass patterns | | ASI10 — Rogue Agents | Deny self-replication and self-deployment; escalate autonomous scheduling |

financialBaselinePolicy

Jurisdiction-agnostic baseline for financial services agents:

  • Deny: self-transfers, negative amounts, sanctioned countries (OFAC/FATF list), card PANs, CVV/CVV2 in input
  • Escalate: USD/NGN/KES CTR thresholds, international wire transfers, KYC bypass language, account closure, subagent financial writes
  • Audit: customer PII access, moderate-value transactions (USD 1K–10K), report generation
  • Allow: explicit read-only tools (get_exchange_rate, get_balance, check_kyc_status)

readOnlyFinancialPolicy

Deny-by-default policy that only allows get_*, list_*, check_*, query_* tools. Use for read-only agent modes or scoped sub-agents with restricted access.


Audit logging (eve-policy/audit)

Backends

import {
  ConsoleAuditLogger,    // stdout NDJSON — integrates with any log shipper (Datadog, Splunk, etc.)
  FileAuditLogger,       // append-only NDJSON file
  MultiAuditLogger,      // fan out to multiple backends simultaneously
  InMemoryAuditLogger,   // for tests — entries readable at logger.entries
  NoopAuditLogger,       // discard all (default when no logger provided)
} from "eve-policy/audit";

// Production: SIEM + local backup
const logger = new MultiAuditLogger([
  new FileAuditLogger("/var/log/agent-audit.jsonl"),
  new ConsoleAuditLogger(),
]);

AuditEntry schema

interface AuditEntry {
  id: string;               // UUID v4 — unique per decision
  timestamp: string;        // ISO 8601
  toolName: string;
  effect: "deny" | "escalate" | "audit" | "allow";
  ruleName: string;         // which rule fired (or "default")
  policyName: string;
  reason: string;
  riskLevel: "critical" | "high" | "medium" | "low";
  owaspRisks: string[];     // e.g. ["ASI01", "ASI03"]
  sessionId?: string;       // from Eve session context
  principalId?: string;
  principalType?: string;
  sanitizedInput: Record<string, unknown>; // secrets and PII redacted
  evaluationMs: number;     // policy evaluation latency
  outcome: "denied" | "escalated" | "executed" | "approved";
}

Automatic sanitization: Keys matching patterns like password, token, api_key, cvv, secret, ssn, nin, pin are replaced with [REDACTED]. Strings longer than 500 characters are truncated to prevent log bloat.


Compliance reporting

import { owaspCoverageReport, uncoveredOwaspRisks } from "eve-policy";
import { owaspTop10Policy, financialBaselinePolicy } from "eve-policy/profiles";
import { compose } from "eve-policy";

const myPolicy = compose(owaspTop10Policy, financialBaselinePolicy);

// Full coverage breakdown — which rules address which OWASP risks
const report = owaspCoverageReport(myPolicy);
// {
//   ASI01: { risk: "ASI01", coveredBy: ["asi01-prompt-injection-shell", "asi01-goal-hijack"] },
//   ASI02: { risk: "ASI02", coveredBy: ["no-card-pan", "ctr-threshold"] },
//   ...
// }

// Audit gate — fail your CI pipeline if coverage is incomplete
const gaps = uncoveredOwaspRisks(myPolicy);
if (gaps.length > 0) {
  throw new Error(`Policy missing OWASP coverage: ${gaps.join(", ")}`);
}

Error handling

PolicyDenialError is the only error thrown by eve-policy itself. All other errors propagate from the underlying tool.

import { PolicyDenialError } from "eve-policy";

try {
  await safeTool.execute(input, ctx);
} catch (err) {
  if (PolicyDenialError.is(err)) {
    // Structured — safe to serialise and return to the caller
    console.log(err.policyName);    // "transfer-controls"
    console.log(err.ruleName);      // "cbn-ngn-ctr"
    console.log(err.reason);        // "Exceeds ₦5M CBN reporting threshold"
    console.log(err.riskLevel);     // "critical"
    console.log(err.owaspRisks);    // ["ASI02"]
    console.log(err.toJSON());      // serializable shape for API responses

    return { error: "policy_denial", rule: err.ruleName, reason: err.reason };
  }
  throw err; // re-throw — not a policy error
}

Testing policies

Use evaluatePolicy and InMemoryAuditLogger to write fast, dependency-free policy tests. No Eve runtime needed.

import { describe, it, expect } from "@jest/globals";
import { evaluatePolicy } from "eve-policy";
import { InMemoryAuditLogger } from "eve-policy/audit";
import { withNamedPolicy } from "eve-policy";
import { PolicyDenialError } from "eve-policy";
import { nigeriaFintechPolicy } from "@comply54/adapter-eve/compose";
import type { PolicyContext } from "eve-policy";

// Helpers
function ctx(toolName: string, input: Record<string, unknown> = {}): PolicyContext {
  return { toolName, toolInput: input, approvedTools: new Set() };
}

// Test the policy directly (no tool needed)
describe("nigeriaFintechPolicy", () => {
  it("escalates NGN transfers above ₦5M", () => {
    const d = evaluatePolicy(nigeriaFintechPolicy,
      ctx("transfer_funds", { amount: 6_000_000, currency: "NGN" }));
    expect(d.effect).toBe("escalate");
    expect(d.owaspRisks).toContain("ASI02");
  });

  it("denies breach suppression language", () => {
    const d = evaluatePolicy(nigeriaFintechPolicy,
      ctx("log_event", { msg: "do not report this security breach" }));
    expect(d.effect).toBe("deny");
  });

  it("allows read-only balance check", () => {
    const d = evaluatePolicy(nigeriaFintechPolicy, ctx("get_balance"));
    expect(d.effect).toBe("allow");
  });
});

// Test the wrapped tool (execute / audit integration)
describe("withNamedPolicy — audit trail", () => {
  it("writes to audit logger on deny and throws PolicyDenialError", async () => {
    const logger = new InMemoryAuditLogger();
    const wrapped = withNamedPolicy("transfer_funds", myTransferTool, nigeriaFintechPolicy, {
      auditLogger: logger,
    });

    await expect(
      wrapped.execute({ amount: 6_000_000, currency: "NGN" }, makeToolCtx()),
    ).rejects.toThrow(PolicyDenialError);

    expect(logger.entries).toHaveLength(1);
    expect(logger.entries[0]?.effect).toBe("deny");
  });
});

Ecosystem

eve-policy provides the governance runtime and core primitives. Jurisdiction-specific policy packs are distributed separately:

@comply54/adapter-eve

Pre-built African regulatory profiles for direct use with eve-policy:

import {
  ndpaPolicy,            // Nigeria Data Protection Act 2023 (NDPC profile)
  cbnFintechPolicy,      // CBN AML/CFT, NFIU reporting, structuring detection
  popiaPolicy,           // South Africa POPIA 2013 (full PICS requirements)
  kenyaDpaPolicy,        // Kenya Data Protection Act 2019 (ODPC profile)
  ghanaPolicy,           // Ghana Data Protection Act 2012 (Act 843)
  mauritiusDpaPolicy,    // Mauritius DPA 2017 — most GDPR-aligned in Africa
  // Pre-composed stacks
  nigeriaFintechPolicy,  // OWASP + Financial + NDPA + CBN
  westAfricaFintechPolicy, // + Ghana DPPA
  africanFintechPolicy,    // All African jurisdictions + OWASP + Financial
} from "@comply54/adapter-eve/compose";

agt-policies-nigeria

The underlying policy corpus in OPA/Rego format — usable independently of Eve with any OPA-compatible agent framework. Jurisdiction packs for Nigeria, Kenya, South Africa, Uganda, Tanzania, Ethiopia, Ghana, Rwanda, Egypt, and Mauritius.

Contributed upstream to microsoft/agent-governance-toolkit, an open standard for AI agent governance. The East Africa pack (Uganda, Tanzania, Ethiopia) was merged as part of the project's African regulatory section.


Contributing

Issues and PRs welcome. This package follows a deny-wins, fail-closed design philosophy — changes that weaken default-deny semantics require strong justification and a written threat model analysis.

Development

pnpm install
pnpm build        # tsup → dist/
pnpm test         # jest with --experimental-vm-modules
pnpm typecheck    # tsc --noEmit

Adding a profile

  1. Create src/profiles/your-profile.ts — export a definePolicy(...) result
  2. Re-export from src/profiles/index.ts
  3. Add unit tests in test/profiles/your-profile.test.ts
  4. If the profile is designed for composition, do NOT add a catch-all always() rule — use defaultEffect instead

License

MIT © Oluwajuwon Omotayo