🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@moneolabs/guard

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@moneolabs/guard

Spending policy for AI agents, evaluated before anything is signed.

Source
npmnpm
Version
0.3.2
Version published
Weekly downloads
171
-57.25%
Maintainers
1
Weekly downloads
 
Created
Source

@moneolabs/guard

Spending policy for AI agents, evaluated before anything is signed.

npm install @moneolabs/guard

The guard sits between what an agent intends and what actually gets signed. It answers with a verdict, a reason, and the version of the policy that decided, then writes all three to a ledger. A blocked payment is never broadcast, so it costs nothing.

This package has no service behind it and needs none. It is pure evaluation over a ledger you can keep wherever you already keep financial records.

Use

import { createGuard } from "@moneolabs/guard";

const guard = createGuard({
  perTransaction: { max: "$250" },
  rolling24h: { max: "$2,000" },
  velocity: { max: 20, per: "1m" },
  counterparties: "allowlist-only",
  allow: ["x402:*", "0x9f3c...a71b", "stripe:acct_1Nz"],
  deny: ["mixer:*"],
  escalate: { above: "$500" },
});

const decision = await guard.check({
  action: "transfer",
  to: "0x9f3c...a71b",
  amount: "$8,200",
  agent: "ops-03",
});

decision.verdict; // "block"
decision.reason; // "$8,200.00 exceeds the rolling 24h budget: $2,000.00 cap, $1,880.00 used, $120.00 left"
decision.rule; // "budget.24h"

Reserve, then settle

check() does not just answer, it reserves. An allowed decision counts against the budget from the moment it is made, because a payment in flight is money you no longer have. Tell it what happened:

const decision = await guard.check({ action: "pay", to: "vendor:acme", amount: "$100" });
if (decision.verdict === "allow") {
  try {
    const receipt = await payVendor();
    await decision.settle(receipt.amount); // pass the real amount if it differs
  } catch {
    await decision.release(); // it never happened, give the budget back
  }
}

Forgetting to settle is safe. Forgetting to release is not, which is why wrap() exists.

Wrap something you already have

const payVendor = guard.wrap(
  async (vendorId: string, usd: number) => internalPayments.send(vendorId, usd),
  {
    action: "pay",
    amount: (_vendor, usd) => `$${usd}`,
    to: (vendor) => vendor,
  },
);

await payVendor("acme", 40); // settles on success, releases on throw
await payVendor("acme", 9000); // throws PolicyDeniedError, never calls through

Escalation

Above a threshold, the decision holds instead of blocking and waits for a person.

const decision = await guard.check({ action: "pay", to: "vendor:acme", amount: "$900" });

if (decision.verdict === "hold") {
  const outcome = await decision.wait({ timeout: "4h" });
  if (outcome.granted) await wallet.pay(/* ... */);
}

// Answered from wherever your approvals live:
guard.resolve(decision.approvalId!, { granted: true, by: "finance@example.com" });

Approvers are pluggable: manualApprover, autoApprover, loggingApprover, webhookApprover, or your own. A held movement does not consume budget until it settles.

Rules

Evaluated in this order. The first block wins, so a banned counterparty is never reported as merely over budget.

RuleBlocks when
denyThe counterparty matches a denylist pattern.
assetsThe asset is denied, or is outside an allowlist.
actionsThe action kind is denied, or is outside an allowlist.
counterparties: "allowlist-only"The counterparty is not on the allowlist, or is missing entirely.
perTransactionThe USD value of one movement is over the cap.
velocityToo many movements inside the window.
budgets / rolling24hThe rolling window would go over its cap.
escalateNothing above blocked, but it needs a human. Produces hold.

Patterns use * as a wildcard and match case insensitively, so x402:* covers every x402 endpoint and 0xABCD matches 0xabcd.

An intent with no counterparty is blocked under allowlist-only. Omitting a field must never be a way around a rule.

Non-dollar assets

Limits are in USD, agents move whatever they hold. Pass a PriceSource and the guard values each movement before applying a limit.

import { fixedPrices } from "@moneolabs/core";

const guard = createGuard(
  { perTransaction: { max: "$100" } },
  { prices: fixedPrices({ AAPL: 309.92 }) },
);

await guard.check({ action: "trade", amount: "2 AAPL" });
// blocked: "$619.84 exceeds the $100.00 per-transaction limit"

Dollar-pegged assets skip the lookup. Anything else without a price is refused rather than guessed.

Simulate before you ship

import { simulate, eventsFromLedger } from "@moneolabs/guard";

const report = await simulate(proposedPolicy, eventsFromLedger(lastMonth));

report.counts; // { allow: 412, hold: 6, block: 19 }
report.blockedUsd; // what the new policy would have stopped
report.results[0]; // per movement: verdict, rule, reason

Nothing real is touched. This is how you find out that a budget would have blocked a third of last month before it blocks a third of next month.

Reading the ledger

await guard.usage(); // what is left of each budget
await guard.history({ verdict: "block" }); // every refusal, with its reason

Blocked attempts are kept, not discarded. They are the most useful thing you own when tuning a policy: they are the record of what your agents actually tried to do.

Bring your own store by implementing DecisionLedger. The default is in memory.

Testing

Pass a manualClock and rolling windows become instant.

import { manualClock } from "@moneolabs/core";

const clock = manualClock(0);
const guard = createGuard({ rolling24h: { max: "$100" } }, { clock });

await (await guard.check({ action: "pay", amount: "$100" })).settle();
await clock.advance("25h"); // the window rolls, the budget is free again

License

MIT

Keywords

ai-agents

FAQs

Package last updated on 05 Aug 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts