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

@zipbul/ashward

v0.2.0

Published

Test-support library with built-in rules that verify a running server against international standards and security requirements, from inside your test runner.

Readme

ashward

English | 한국어

npm CI

Test-support library with built-in rules that verify a running server against international standards and security requirements — from inside your test runner. You don't write assertions; you point it at a URL and it judges conformance over real bytes.

The rule is the atomic unit. ashward ships the rules and runs them against a live origin — which rules to run is your choice. It probes over one hop and never sends a Cookie; credential rules are judged from response self-contradiction, the way a browser's CORS check reasons.

📦 Installation

bun add -d @zipbul/ashward

🚀 Quick Start

Point it at your running server inside any test runner. assertOk throws — with per-clause detail — on any blocking result, so a thrown error is the one universal failure signal.

import { test } from 'bun:test';
import { ashward, assertOk } from '@zipbul/ashward';

test('my server conforms to the built-in standards + security rules', async () => {
  const report = await ashward('http://localhost:3000/api'); // runs every shipped rule
  assertOk(report);
});

Prefer to inspect instead of throw:

const report = await ashward('http://localhost:3000/api');

report.ok(); // boolean under the default policy
for (const clause of report.results) {
  console.log(clause.ruleId, clause.verdict, clause.reason);
}

The target is a URL, so the server under test can be written in any language.

🎯 Selecting rules

The package ships rules, never an opinion about which ones your app must satisfy. ashward(url) runs every rule by default; pass your own selection to scope it.

import { ashward, rules, ALL_RULES, Rule } from '@zipbul/ashward';

// hand-pick specific rules
await ashward(url, [rules.accessControlAllowOriginGrammar, rules.originReflection]);

// or filter the full registry
await ashward(
  url,
  ALL_RULES.filter(r => r.id !== Rule.PrivateNetworkAccessIdNameFormat),
);

🔬 What it checks

Every rule has a stable, domain-free kebab id (e.g. access-control-allow-origin-wildcard-with-credentials), cites its normative source, and — for security rules — carries a CWE.

HTTP/1.1 framing (RFC 9112) — the parser-discrepancy class behind request smuggling:

| Rule id | Requirement | | -------------------------- | ---------------------------------------------------------------------------------- | | duplicate-content-length | §6.3 — reject two divergent Content-Length headers (MUST → fail) | | cl-te-conflict | §6.1 — Content-Length + Transfer-Encoding ought to be an error (SHOULD → warn) |

WHATWG Fetch CORS — origin-server response conformance across the Fetch/URL/RFC/PNA clauses:

  • Grammar & single-generation (§1–§2) — Access-Control-Allow-Origin serialized-origin grammar, Allow-Credentials byte-exact true, token-list headers, Max-Age delta-seconds, single generation, *-with-credentials contradictions.
  • Preflight (§3) — ok status, method byte-case, *-with-credentials, credentialed-grant consistency.
  • Actual & redirect (§4–§5) — Expose-Headers placement, Location without userinfo.
  • Private Network Access (§6, WICG draft) — Allow-Private-Network literal true, ID/Name format.
  • Caching (§7) — Vary: Origin when the answer varies by origin, and not when it's static.
  • Security heuristics — credentialed origin reflection and null-origin grants (CWE-346 / CWE-942).

Response compression (RFC 9110 / 9111 / 9530, RFC 1950 / 1952 / 8878 / 9659) — content-coding conformance on the wire:

  • Headers — no identity token in Content-Encoding, no coding on a bodiless status, Vary: Accept-Encoding on a negotiated resource, a strong ETag weakened or distinguished across the coded and identity representations.
  • Byte formats — a well-formed gzip header, zlib-wrapped deflate (never raw), and zstd within the 8 MiB HTTP window cap (RFC 9659) with reserved bits zero — checked over the transfer-decoded body.

x-www-form-urlencoded query parsing (WHATWG URL / Encoding, RFC 3986) — judged against a route you opt in that echoes its parsed query as an ordered pair list:

  • Robustness heuristics — malformed percent-escapes, invalid UTF-8, NUL bytes, and prototype-pollution vectors must not crash the origin (CWE-20 / CWE-1321), control-guarded so a flaky endpoint can't be blamed.
  • Parse correctness&-only separation, first-= split, +→space (form) vs. literal (uri-generic), U+FFFD on invalid UTF-8, a preserved malformed %, skipped empty sequences, and a preserved empty name.

Conditional requests (RFC 9110 §13) — precondition evaluation over 304 / 412 / 200, from the resource's own discovered validators:

  • EvaluationIf-None-Match / If-Match (weak vs. strong comparison, §8.8.3.2), If-Modified-Since / If-Unmodified-Since, all three HTTP-date formats, and the §13.2.2 precedence order.
  • Response shape — the required 304 headers (§15.4.5), no content on a 304, and the ignore rules for non-selecting methods and error statuses.

The full roster is exported as ALL_RULES (and each rule by name under rules).

The query-parser reflection rules need a route that echoes its parsed query; pass it opt-in and declare the parse mode (every other rule keeps probing the URL's own path):

await ashward('http://localhost:3000/', ALL_RULES, {
  reflect: { path: '/echo', mode: 'form' }, // or 'uri-generic'
});

🧭 How it judges

A rule sends its crafted probe(s) and classifies the wire response:

  • pass — the server did the conformant thing
  • fail — a MUST / MUST NOT violation (blocks by default)
  • warn — a SHOULD-level or security concern (non-blocking by default)
  • skip — the clause did not apply (the header it judges was absent), with a typed reason
  • inconclusive — couldn't tell (timeout, malformed response), with a typed reason

report.ok(policy) is a view over the results, not a stored flag — you decide what blocks (failOn, how to treat inconclusive). It is fail-closed on connectivity: if ashward could not reach the server (dead host, wrong URL, refused, timeout) the report is never ok, whatever the policy. A reached-but-undecidable inconclusive is non-blocking by default; set inconclusive: 'fail' to block those too.

🚧 Scope

  • HTTP over plaintext only — an https: target throws (no TLS yet); point it at your server over http.
  • One hop, detection only — no CLI, no source scanning, no exploitation. Two-hop proxy⇄backend desync is out of scope.
  • Not every clause is blackbox-testable — clauses that need server intent (e.g. "the server means to share this response") are intent-bound and honestly catalogued as untestable rather than guessed.

📄 License

MIT