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

@vulsight/guard

v0.6.0

Published

Puts VulSight Guard in front of every x402 payment an agent signs.

Readme

@vulsight/guard

Puts VulSight Guard in front of every x402 payment an agent signs. Two hooks on your x402Client, one wrapper around the fetch the agent reads with, and one call to report the settlement. The guard decides allow, deny, or review before the payment is signed, and every decision explains itself in plain sentences.

Install

bun add @vulsight/guard @x402/fetch @x402/evm viem

The SDK accepts any 2.x release of @x402/core from 2.24 on, so the install line above carries one copy of it beside @x402/fetch and @x402/evm.

Use

import { guard } from "@vulsight/guard";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const client = new x402Client().register("eip155:*", new ExactEvmScheme(account));
const vulsight = guard(client, {
  apiKey: process.env.VULSIGHT_API_KEY ?? "",
  baseUrl: "https://vulsight-guard.vercel.app",
});

const read = vulsight.fetch(fetch);
const pay = wrapFetchWithPayment(read, client);

await read("https://merchant.example/");
const paid = await pay("https://merchant.example/dataset");
console.log(await paid.json(), await vulsight.reportSettlement(paid));

EVM_PRIVATE_KEY is the hex private key of a throwaway wallet made for this. Create one with cast wallet new or viem's generatePrivateKey, paste its address into the Circle faucet (USDC, Base Sepolia), and never point the snippet at a wallet that holds real funds.

Since @x402/core 2.23.0 the client refuses any payment over one dollar by default, before the guard's hook runs, and such a refusal never reaches the guard's log. Keep that cap, or call .setSpendControls(false) on the client so the guard's policy is the only limit.

guard(client, options) registers onBeforePaymentCreation and onAfterPaymentCreation on the client. Before a payment is signed it posts the seller's 402 (payee, amount, asset, network) and the URL the agent asked for plus the session's last page to POST /api/v1/decisions. Each attempt carries its own Idempotency-Key, and a post the guard failed to answer (an outage, a cut connection) is sent once more under the same key, so the payment is decided once; when you call the API yourself, vulsight.api.decide(request, signal, idempotencyKey) takes the key as its third argument. The guard judges the URL the agent asked for; if the seller's terms name another resource, the guard corrects the terms it hands the client. The signature covers the payee, the amount, and the nonce, never the resource, and the client retries the URL it asked for, so settlement is unaffected; without the wrapped fetch the terms reach the hook as the seller sent them. A 402 that arrives through a redirect is refused: fetch follows redirects with the payment header attached, so a retry of the URL the agent asked for could pay for either resource, and the guard asks for the resource by its final URL; a signed retry is sent with redirects disabled, so a seller that redirects the paid request fails it instead of moving the signature to another URL. On allowed the payment goes ahead and the decision id rides in the payload extensions as vulsight.info.decisionId (older SDKs wrote vulsight.decisionId; the demo seller reads both). On denied the payment aborts and wrapFetchWithPayment throws with the reasons. On review_pending the hook long-polls GET /api/v1/decisions/:id until a person answers or waitForReviewSeconds runs out.

vulsight.fetch(fetch) returns a fetch for reads. Every response from an origin other than the guard, a seller's 402 included, is hashed and posted to POST /api/v1/context once per hash from each origin (and again on every read of a page the guard flagged, so the flagged result applies from the last read), so the session carries the pages the agent read by the time a payment shows up. A 402 is posted with the text the proxy files for it, the description in its PAYMENT-REQUIRED terms and then its body, so the SDK and the proxy check the same text for the same purchase. That includes a 402 answering the paid retry: the next payment waits for its filing like any page's. The response is returned untouched. Before a payment is signed the hook waits for every outstanding post, inside its 105 s budget for pending reads, filing, one retry, and the decision, and retries a failed one once. A body still arriving 5 s after its headers, or one that errors, is a page the guard could not check. If the guard still has not taken a page, an enforce agent aborts with "VulSight Guard could not check the page the agent read, so the payment was blocked." followed by the cause, and when the read itself failed the sentence also names the page's origin and what cut the read short and asks you to read the page again; an observe agent warns and proceeds. A post the guard itself refuses, a 401 for a key it does not know for example, is not an unchecked page, so the reason says what the guard said. A rate limit or a 5xx stays an unchecked page. When the shared verification budget expires, the error names the timeout and asks you to reread the page and retry. Human review has its own wait below.

What gets checked is the first 64 KiB of a text body and, for a binary body (a NUL byte in its first 8 KiB), the readable text inside it: runs of 8 or more characters, so an image usually files nothing. A longer text, or a binary body past 4 MiB, is filed at 64 KiB, which the guard records as unchecked, so the next payment to that seller goes to review.

Wrap in the order the snippet shows, the guard's fetch inside and wrapFetchWithPayment outside. The guard's fetch is then the inner fetch that wrapFetchWithPayment calls for the paid retry, the request that carries PAYMENT-SIGNATURE, so the guard sees which decision paid for which response.

vulsight.reportSettlement(response) takes the paid response the guarded fetch returned, reads its PAYMENT-RESPONSE header, and posts the transaction hash to POST /api/v1/settlements against the decision that paid for it. Each response is reported once. A post the guard failed throws and keeps the response on record, so call reportSettlement again with the same response to retry.

vulsight.api is the GuardClient, a thin typed HTTP client for the API, if you want to call them yourself (for example to check an evm_call before signing a raw transaction).

The x402 hook accepts only exact authorization flows. On EVM, an explicit transfer method must be eip3009. Selected Permit2, upfront or escrow flows are refused before payment creation, even in observe mode or during a Guard outage. Sponsorship advertisements for other payment options do not block a supported authorization. The stock EIP-3009 path does not use them to sign approvals. A purchase verdict does not authorize an additional token approval or permit signature. Locally registered signing schemes and extensions remain trusted integration code.

Direct contract calls

evm_call supports canonical USDC transfer, transferFrom, approve and increaseAllowance calls on Base and Base Sepolia. Guard checks the exact network and native USDC contract, not just the function selector. Unknown functions, arbitrary token contracts, extra calldata, unsupported execution-envelope fields, native value and unsafe approval forms are refused. These support limits apply in observe mode and when an older approval is reused. A payee allowlist entry cannot override them.

Zero approvals still carry the token and spender through policy checks. Finite approvals authorize future spending; their amount limit does not cap an existing allowance or control what the spender does later. Guard does not support arbitrary NFT, swap, batch or Solana program calls. The x402 hook checks payment terms before signing, while a direct GuardClient.decide call returns a verdict for your signer to enforce. Neither intercepts other wallet tools. Sign only the exact action you submitted, without adding other execution permissions.

Read everything the agent reads

vulsight.fetch files the pages the agent fetches. A file, a search result, or another tool's output never reaches the guard unless you file it. vulsight.file(text, origin) posts the text under the session the way a page is filed, once per distinct text under each label and again on every read of a text the guard flagged, and answers pass, restrict (flagged), or unavailable. It never throws. A guard it cannot reach, a post it refused, or a check that did not finish all read as unavailable. The next payment is checked on its own; a rejected API key aborts it and says so. origin is a short label such as the tool's name, default tool, or the URL of the page the text came from: https://seller.example/page files under https://seller.example, which links the text to that seller. A flagged text filed under a site holds only later payments in the session to that seller (by origin or host) for an hour, plus any payment that cites it. One filed under a label that names no site, the default tool or a tool's name, cannot be tied to a seller, so it holds every payment in the session for an hour, as it always has. Text over 64 KiB is clipped to 64 KiB before it is posted, from file and from fetch alike, and the guard files a clipped text as unavailable, since it never read the rest. A NUL byte in text is filed as the replacement character and counts three bytes toward the 64 KiB cap. A leading byte order mark is kept, so a page opening with one has a new hash and is scanned once more. With content enforcement enabled, a completed block denies the payment; a result requiring review holds it for a person. The filed text also joins the session's pages, so the next payment in enforce mode blocks if the guard never took it.

With the Vercel AI SDK, file each tool result as JSON as the step ends:

await generateText({
  model, tools, prompt,
  onStepFinish: async ({ toolResults }) => {
    for (const r of toolResults) await vulsight.file(JSON.stringify(r.output), r.toolName);
  },
});

The user's own turns are not filed: the person is the principal, the check scores untrusted content, and a scan of their instructions would hold their own orders.

With LangChain, a callback files each tool's output:

const callbacks = [{
  handleToolEnd: async (output: unknown) => {
    await vulsight.file(JSON.stringify(output));
  },
}];
await agent.invoke({ input }, { callbacks });

Again only tool output, never the user's messages, for the same reason. An agent that files faster than the account's rate limit gets a 429 the SDK reads as unavailable and moves past. Filing never blocks the agent.

The proxy, for any language and no code

An agent that should not change code points at the guard instead of the seller. The seller URL becomes https://vulsight-guard.vercel.app/p/<proxy token>/merchant.example/dataset, with the proxy token from the dashboard. The proxy token is a secret separate from the API key. Keep the URL out of browser history, shared logs, and screenshots, and rotate the key on the Keys page if the token leaks. The first request passes through and the seller's 402 comes back untouched. The retry that carries PAYMENT-SIGNATURE is decided under the agent's policy before it reaches the seller. Allow forwards it unchanged, deny answers 403 with the reasons and the signature never leaves the proxy, and an EVM review holds the request for up to 35 seconds. It answers 402 with Retry-After and Guard-Status: review_pending when the hold ends or the signed authorization would expire with the review still open, so the client signs again and the same review answers, and 403 payment_denied when the review timeout runs out first. Settlements are recorded from PAYMENT-RESPONSE on the way back. Only traffic through that URL is enforced. A request sent straight to the seller is neither seen nor decided. Each payment is decided under the policy as it stands, and a revoked proxy token stops working within ten seconds. The proxy speaks x402 v2 only, reaches sellers over https, and refuses private and loopback addresses. Leave the seller's own scheme out, https is implied. Writing https://merchant.example/dataset still works, but the server folds its // into one slash with a 308 first, so a client that follows redirects pays an extra hop and one that does not (httpx by default) stops there.

Solana

The SDK supports USDC on Solana Devnet (solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1) and Solana Mainnet (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp). Register the @x402/svm client scheme with your own Solana signer, then attach guard(client, options) before paying. Choose the same network in the guard policy. Mainnet uses real USDC; the default policy and demo stay on Base Sepolia.

Keep wallet and mint addresses case-sensitive. reportSettlement accepts the base58 transaction signature in the receipt and the website links to the right Solana Explorer cluster. The proxy also supports sponsored exact USDC transfers on these Solana networks. SDK and MCP check before signing; the proxy inspects the signed message before forwarding. Unsupported instruction formats fail closed.

Through the proxy, a Solana review returns 402 immediately with the decision id and retry-after: 2. Send the same request again after retry-after seconds until the review is answered, or read the decision at GET /api/v1/decisions/{id} with your API key, and after approval sign with a fresh blockhash. An expired blockhash answers with guard-status: resign_required and guard-next-step: fresh_quote, so fetch a new unsigned quote before signing again; any PAYMENT-REQUIRED header in that response is not a fresh quote. The proxy records a Solana settlement only after confirming that the approved transaction executed on chain. Do not retry a submitted payment with an uncertain receipt.

Options

| Option | Default | What it does | | --- | --- | --- | | apiKey | required | Your vs_test_ key. | | baseUrl | required | Where VulSight Guard runs. | | session | one id per process | Groups the pages and tool results an agent read with the payments it makes. A flagged page affects later payments to its seller in this session for an hour. With content enforcement enabled, a completed block denies them; a result requiring review holds them for a person. | | waitForReviewSeconds | 120 | How long a payment waits for a human answer before it aborts, two minutes by default to match the default review timeout, so a hold notification reaches the person while the agent still waits. The review stays open on the guard. When the hold was the first payment to that payee, Approve and always allow this payee lets a rerun skip that review. | | mode | "enforce" | What happens when the guard cannot answer within 105 seconds. enforce aborts the payment and says why (the guard did not answer, or a page it read was not checked in time). observe lets it through and logs the gap: an unreachable guard lets a payment through unchecked, including on mainnet, so use enforce for real money (the default for new keys). In both modes the payment aborts on a wrong API key, on a network the guard does not know (it knows Base Sepolia, Base, Solana Devnet, and Solana Mainnet, and the SDK refuses an unknown one without asking), and on a deny: observe covers only what the guard could not answer. |

Check it

bun test packages/sdk