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

pineward

v0.1.0

Published

Framework-agnostic anti-bot form shield. VDF + behavior + honeypots. No third-party calls.

Readme

Pineward

Stateless anti-bot form shield. No third-party calls. No CAPTCHAs. One import.

Pineward combines a Verifiable Delay Function (VDF), behavioral entropy analysis, rotated honeypots, and browser instrumentation into a single library that protects HTML forms from automated abuse — with zero external dependencies.

Table of Contents

Features

  • Verifiable Delay Function — Wesolowski VDF forces sequential computation (500ms–2s), blocks mass automation and GPU farms
  • Behavioral Entropy — Passive timing analysis distinguishes humans from robotic input patterns
  • Rotated Honeypots — Per-session field names that look real to bots, verified empty server-side
  • Browser Instrumentation — 20 fingerprint operations detect headless browsers and automation tools
  • Adaptive Difficulty — VDF difficulty scales with request rate to counter sustained DDoS
  • Stateless Tokens — HMAC-SHA256 signed tokens with nonce replay protection, no session store required
  • Framework Agnostic — Pure TypeScript core + thin adapters for React, Next.js, Vite, Express, Hono
  • Privacy First — Behavior collector captures timestamps only, never coordinates or key codes
  • Universal Runtime — Works in Node 20+, Bun, Deno, Cloudflare Workers, Vercel Edge, browsers

Quick Start

1. Install

pnpm add pineward

2. Server — Issue Challenges

// app/api/pineward/route.ts (Next.js App Router)
import { createChallengeRoute } from 'pineward/next';

export const GET = createChallengeRoute({
  secret: process.env.PINEWARD_SECRET!,
});

3. Client — Protect a Form

// app/contact/page.tsx
import { Pineward } from 'pineward/react';

export default function ContactPage() {
  return (
    <Pineward challengeUrl="/api/pineward">
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
    </Pineward>
  );
}

4. Server — Validate Submissions

// app/contact/actions.ts
'use server';
import { withPineward } from 'pineward/next';

export const submitContact = withPineward(async (formData: FormData) => {
  const email = formData.get('email') as string;
  const message = formData.get('message') as string;
  // Save to DB, send email, etc.
}, {
  secret: process.env.PINEWARD_SECRET!,
  origin: 'https://yoursite.com',
});

Three files and your form is protected.

How It Works

When a user loads a page with a Pineward-protected form:

  1. Challenge issued — the server generates a signed token with an embedded VDF challenge, honeypot field names, and an instrumentation seed
  2. VDF solved — the browser solves y = x^(2^t) mod N via sequential squaring in a Web Worker (500ms–2s depending on load)
  3. Behavior collected — passive DOM listeners capture timing entropy from mouse, keyboard, and scroll events
  4. Instrumentation run — browser fingerprint operations detect headless/automated environments
  5. Form submitted — all proof data is attached as hidden fields and validated server-side in a fail-fast pipeline

Defense Layers

| Layer | What it stops | Client cost | Server cost | |---|---|---|---| | HMAC token | Forgery, tamper | µs | µs | | Nonce | Replay attacks | — | µs | | Honeypot | Naive crawlers | 0 | µs | | VDF (Wesolowski) | Mass automation, GPU farms | 500ms–2s | 3–5ms | | Instrumentation | Headless browsers | <50ms | µs | | Behavior entropy | Robotic input patterns | 0 (passive) | µs | | Adaptive difficulty | Sustained DDoS | scales | µs |

Framework Support

React

import { Pineward } from 'pineward/react';

<Pineward
  challengeUrl="/api/pineward"
  onReady={() => console.log('Ready!')}
  onProgress={(done, total) => console.log(`${done}/${total}`)}
>
  {/* form fields */}
</Pineward>

React Hook

import { usePineward } from 'pineward/react';

function MyForm() {
  const { ready, solving } = usePineward({
    challengeUrl: '/api/pineward',
    formSelector: '#my-form',
  });

  return (
    <form id="my-form">
      <input name="email" />
      <button disabled={!ready}>
        {solving ? 'Verifying...' : 'Submit'}
      </button>
    </form>
  );
}

Next.js

// API Route — issue challenges
import { createChallengeRoute } from 'pineward/next';
export const GET = createChallengeRoute({ secret: process.env.PINEWARD_SECRET! });

// Server Action — validate submissions
import { withPineward } from 'pineward/next';
export const submit = withPineward(async (formData) => {
  // your logic
}, { secret: process.env.PINEWARD_SECRET!, origin: 'https://yoursite.com' });

Vite

// vite.config.ts
import { pineward } from 'pineward/vite';

export default defineConfig({
  plugins: [pineward()],
});

The Vite plugin serves /__pineward/challenge in dev mode with fast t values for instant iteration.

Vanilla JS

import { Pineward } from 'pineward';

const pw = new Pineward({
  challengeUrl: '/api/pineward',
  formSelector: '#contact-form',
  onReady: () => document.querySelector('button')!.disabled = false,
});

await pw.mount();

Express

import express from 'express';
import { createNodeChallengeHandler, createNodeValidateMiddleware } from 'pineward/server';

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

const opts = { secret: process.env.PINEWARD_SECRET!, origin: 'https://yoursite.com' };

app.get('/api/pineward', createNodeChallengeHandler(opts));
app.post('/api/contact', createNodeValidateMiddleware(opts), (req, res) => {
  res.json({ ok: true });
});

Hono / Cloudflare Workers

import { Hono } from 'hono';
import { createWebChallengeHandler, createWebValidateHandler } from 'pineward/server';

const app = new Hono();
const opts = { secret: 'your-secret', origin: 'https://yoursite.com' };

app.get('/api/pineward', (c) => createWebChallengeHandler(opts)(c.req.raw));
app.post('/api/contact', async (c) => {
  const result = await createWebValidateHandler(opts)(c.req.raw);
  if (!result.ok) return c.json({ error: result.reason }, 403);
  return c.json({ ok: true });
});

Configuration

Environment Variables

| Variable | Required | Description | |---|---|---| | PINEWARD_SECRET | Yes | HMAC secret for signing tokens. Minimum 32 characters. |

Difficulty Tuning

| Parameter | Default | Description | |---|---|---| | baseT | 2^20 | ~500ms on a 2024 phone (normal traffic) | | maxT | 2^23 | ~4s (under sustained attack) | | windowMs | 60000 | Sliding window for rate tracking | | thresholdLow | 30 | Requests/min before scaling starts | | thresholdHigh | 300 | Requests/min for maximum difficulty |

Behavior Scoring

| Parameter | Default | Description | |---|---|---| | minDurationMs | 500 | Reject submissions faster than 500ms | | minTimingEntropy | 2.0 | Reject perfectly regular timing (bits) | | minEventCount | 3 | Minimum total interaction events |

Architecture

pineward (single package, multiple subpath exports)
├── pineward          → client widget + core types
├── pineward/server   → validate(), issueChallenge(), framework handlers
├── pineward/react    → <Pineward />, usePineward()
├── pineward/next     → createChallengeRoute(), withPineward(), middleware
└── pineward/vite     → Vite plugin with dev endpoint

Core is pure TypeScript — no framework imports. Adapters are thin wrappers (<500 LOC each).

Algorithm Details

VDF (Wesolowski)

The VDF forces clients to perform sequential computation that cannot be parallelized:

  • Solve: y = x^(2^t) mod N — sequential squaring, O(t)
  • Prove: π = x^(⌊2^t/l⌋) mod N — Wesolowski proof, O(t)
  • Verify: π^l · x^r ≡ y (mod N) — two modular exponentiations, O(log N)

Uses the RSA-2048 modulus from the RSA Factoring Challenge (no known factorization).

Token Format

base64url(json_payload).base64url(hmac_sha256_signature)

All crypto via Web Crypto API — works in Node 20+, Bun, Deno, browsers, Cloudflare Workers, Vercel Edge.

Project Structure

pineward/
├── src/
│   ├── index.ts                 # main entry: client widget + core types
│   ├── core/                    # framework-agnostic, pure TS
│   │   ├── types.ts             # shared types
│   │   ├── constants.ts         # RSA-2048 modulus, defaults
│   │   ├── vdf.ts               # Wesolowski VDF (solve, verify, createChallenge)
│   │   ├── hash-to-prime.ts     # Miller-Rabin primality, hashToPrime
│   │   ├── token.ts             # HMAC-signed stateless tokens
│   │   ├── honeypot.ts          # rotated honeypot field names
│   │   ├── behavior-score.ts    # entropy scoring (server-side)
│   │   ├── instrumentation.ts   # browser fingerprint challenges
│   │   └── adaptive.ts          # difficulty engine (rate-aware)
│   ├── server/                  # server validator
│   │   ├── validate.ts          # main validate() + issueChallenge()
│   │   ├── handlers.ts          # Express, Hono, Web Standard handlers
│   │   └── storage.ts           # NonceStore abstraction
│   ├── client/                  # vanilla browser widget
│   │   ├── widget.ts            # Pineward class (mount, solve, submit)
│   │   ├── worker.ts            # Web Worker for VDF
│   │   ├── behavior.ts          # DOM event listeners
│   │   └── instrumentation.ts   # runs instrumentation ops
│   ├── react/                   # React adapter
│   │   ├── component.tsx        # <Pineward />
│   │   └── hook.ts              # usePineward()
│   ├── next/                    # Next.js adapter
│   │   ├── route-handler.ts     # createChallengeRoute()
│   │   ├── server-action.ts     # withPineward() HOF
│   │   └── middleware.ts        # Edge middleware
│   └── vite/                    # Vite plugin
│       └── plugin.ts            # auto-inject + dev endpoint
├── test/                        # vitest tests
├── examples/                    # Express, Next.js, Vite+React, Hono+CF
├── spec/                        # wire format spec + test vectors
└── package.json

Scripts

| Command | Description | |---------|-------------| | pnpm build | Build all entry points (ESM + CJS + DTS) | | pnpm test | Run test suite (vitest) | | pnpm test:watch | Run tests in watch mode | | pnpm lint | Lint with Biome | | pnpm lint:fix | Lint and auto-fix | | pnpm typecheck | TypeScript type checking |

Testing

pnpm test

| Test Suite | Tests | What | |-----------|-------|------| | vdf.test.ts | 8 | VDF round-trip, tamper rejection, determinism, progress | | token.test.ts | 15 | Sign/verify, wrong secret, tamper, expiry, base64url | | honeypot.test.ts | 12 | Deterministic generation, uniqueness, checker | | behavior.test.ts | 16 | Scoring, entropy, quantization, thresholds | | adaptive.test.ts | 11 | computeT bounds, log-space interpolation, RateTracker | | validate.test.ts | 14 | Full pipeline round-trip, every failure mode, replay | | client.test.ts | 10 | BehaviorCollector, widget mount, event counting |

87 tests, all passing in under 1 second.

License

MIT