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

@gudman/warden-guard

v0.1.2

Published

Fetch-based Warden payload scanning client and Express-style guard middleware.

Readme

@gudman/warden-guard

Fetch-based Warden payload scanning for TypeScript and JavaScript, with an Express-style middleware that has no Express runtime dependency.

The default free endpoint is rate-limited, best-effort telemetry. failOpen therefore defaults to true: network, timeout, and HTTP failures return an ALLOW result whose raw.error explains the failure. Set failOpen: false when a Warden outage must stop processing.

latencyMs is reported by the hosted scanner. End-to-end call latency also includes network round-trip time.

Install

npm install @gudman/warden-guard

The emitted runtime is zero-dependency and supports Node 18+.

Build from source

Building and testing this checkout requires Node 20.19+ because the locked Vite/Vitest development toolchain has a higher engine floor.

cd sdk/ts
npm ci
npm test
npm run build

Client

import { WardenClient, WardenBlocked } from "@gudman/warden-guard";

const warden = new WardenClient();
const result = await warden.scan(userPayload, {
  expectedAddresses: ["0x1234..."],
});

if (result.blocked) {
  console.error(result.threatClasses);
}

try {
  const safePayload = await warden.guard(userPayload);
  await runAgent(safePayload);
} catch (error) {
  if (error instanceof WardenBlocked) {
    console.error(error.result.raw);
  }
}

scan() uses the fast-only https://warden.gudman.xyz/api/demo/scan route. Set baseUrl for another Warden host. The default timeout is 8 seconds.

The default client does not construct or settle x402 payments. An explicit caller-owned paymentHandler is the only way to select the paid /scan flow; there is no boolean switch that can silently authorize spending.

guard() returns the original payload for ALLOW, returns the scanner's sanitized text for SANITIZE, and throws WardenBlocked for BLOCK.

Opt-in x402 pay and replay

The SDK never receives a wallet key and never signs autonomously. Inject an encoded PAYMENT-SIGNATURE only after an external wallet has created it for the challenge:

import { WardenClient, type X402PaymentHandler } from "@gudman/warden-guard";

const paymentSignature = process.env.PAYMENT_SIGNATURE;
if (!paymentSignature) throw new Error("PAYMENT_SIGNATURE is required");

const paymentHandler: X402PaymentHandler = (challenge) => {
  // The external wallet created paymentSignature for this exact challenge.
  // The SDK validates the binding before transmitting it.
  void challenge;
  return paymentSignature;
};

const warden = new WardenClient({
  failOpen: false,
  paymentHandler,
});
const result = await warden.scan(untrustedText, { depth: "thorough" });

The handler receives a frozen X402Challenge only after the SDK validates Warden's exact x402 v2 route, recipient, X Layer network, USDT asset, 100000 atomic amount, 300-second timeout, and USD₮0/1 EIP-712 domain. A handler may return a string or Promise<string>.

The SDK binds the returned base64 x402 v2 EIP-3009 header to those terms, serializes the body once, reuses the exact endpoint and body, sets Fetch redirect: "error", and sends PAYMENT-SIGNATURE on exactly one replay. It requires at least six seconds remaining for replay and rejects authorizations beyond the current 300-second challenge window, allowing only five seconds of clock skew. It rejects callback failure, malformed headers, challenge drift, a second 402, redirects, non-200 replay responses, malformed scan results, and missing, false, or wrong-network PAYMENT-RESPONSE receipts. These failures never use failOpen.

Without paymentHandler, the existing free /api/demo/scan behavior is unchanged. The free path remains fast-only; depth: "thorough" is available only to the injected paid flow. The SDK does not generate a wallet, store a private key, create a signature, or make a live payment without explicit caller injection.

Explicit feedback

scan() and guard() never submit feedback. After a person has removed secrets and identifying details from a reproducer, use the separate opt-in method:

const receipt = await warden.submitFeedback({
  outcome: "missed_attack",
  observedVerdict: "ALLOW",
  threatClass: "PROMPT_INJECTION",
  redactedReproducer: "Human-reviewed reproducer with secrets removed.",
  consentToRetain: true,
  redactionConfirmed: true,
});

FeedbackSubmission is a discriminated union: missed_attack accepts only an observed ALLOW, while false_positive and correct_detection accept only SANITIZE or BLOCK. Runtime checks enforce the same relationship, the backend enums, valid Unicode scalar text, and the 4,000-character limit.

Both consent fields have the literal TypeScript type true and are checked again at runtime. Feedback transport, HTTP, and response-validation failures always throw WardenError, even when scan telemetry uses failOpen: true. The response contains only the feedback ID, queue status, and retention deadline.

Express-style middleware

import express from "express";
import { WardenClient, wardenGuard } from "@gudman/warden-guard";

const app = express();
app.use(express.json());
app.use(
  wardenGuard({
    client: new WardenClient({ failOpen: false }),
  }),
);

The middleware skips GET, HEAD, and OPTIONS. By default it scans a string request body or request.body.payload. A BLOCK returns HTTP 400 JSON, ALLOW passes the body through, and SANITIZE replaces the string body or request.body.payload before calling next(). A custom extractor cannot identify where its text belongs in the original body, so SANITIZE blocks; use guard() directly and forward its returned safe text for custom body shapes.

Custom extraction and block detail are supported without importing Express types:

app.use(
  wardenGuard({
    extract: (request) =>
      typeof request.body === "object" &&
      request.body !== null &&
      "message" in request.body &&
      typeof request.body.message === "string"
        ? request.body.message
        : null,
    onBlock: (result) => ({ threats: result.threatClasses }),
  }),
);

Web-standard fetch handlers

Next.js route handlers, Hono, Cloudflare Workers, Deno and Bun do not have (request, response, next) or a body parser — a handler there is (request: Request) => Response. guardFetch wraps that shape:

import { guardFetch } from "@gudman/warden-guard";

export const POST = guardFetch(async (request) => {
  const { payload } = await request.json();
  return Response.json({ echo: payload });
});

The body is read from a clone, so an ALLOW forwards your original Request untouched — its stream and abort signal intact. SANITIZE calls the handler with a rebuilt request carrying the safe text, and BLOCK returns HTTP 400 JSON without calling the handler at all.

By default the scanned string is payload from a JSON body, or the whole body when it is not JSON. Both of those can be rewritten safely. A JSON body with no payload field is scanned whole but cannot be repaired — a sanitized rewrite of the raw document would not reliably still be valid JSON — so SANITIZE blocks rather than forwarding the original. A custom extract blocks on SANITIZE for the same reason the Express middleware does.

Extra handler arguments are forwarded, so Workers (env, ctx) and dynamic routes ({ params }) work unchanged:

export default {
  fetch: guardFetch(async (request, env: Env, ctx: ExecutionContext) => {
    return new Response("ok");
  }),
};

Pass onBlock to answer refusals yourself. Return a Response to take over the reply completely, or any other value to have it placed in the verdict field of Warden's own 400:

export const POST = guardFetch(handler, {
  client: new WardenClient({ failOpen: false }),
  onBlock: () => new Response("rejected", { status: 403 }),
});

With failOpen: false a scan that cannot complete throws out of the guard rather than letting the request reach the handler.