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

@suluk/harden

v0.3.1

Published

Schema HARDENING as a derived contract facet: audit a v4 'Suluk' document's input schemas for the validations that keep weird/oversized input from breaking the system — every string a maxLength + a pattern, every number a maximum, every array a maxItems,

Readme

@suluk/harden

Schema hardening as a derived, scored contract facet — grade a v4 document's input schemas A–F, fail CI below a floor, and fill the gaps with one transform.

CANDIDATE tooling — not official OpenAPI. Suluk is a single-contributor candidate for OpenAPI Specification v4.0 ("Moonwalk"), unaffiliated with the OpenAPI Initiative and unable to ratify anything on the SIG's behalf.

Install

bun add @suluk/harden

What it does

It audits the input surface of an OpenAPI v4 "Suluk" document — every request body and typed parameter slot (path / query / header / cookie / body) — for the validations that keep malformed or oversized input from breaking the system:

  • no any/unknown — every input has a determinable type (or is bounded by enum/const);
  • every string a maxLength and a pattern (a character allowlist) — unless bounded by format/enum/const;
  • every number a maximum (and ideally a minimum);
  • every array a maxItems (a DoS guard);
  • every object closed (additionalProperties: false) and typed (defined properties).

It scores each operation and the whole document (0–100 → letter grade), emits concrete Findings with a fix string, and gives you a CI gate (assertGrade) so a regression below the floor throws. The inverse half — hardenSchema / hardenDocument — is the transform that fills those gaps with sensible baseline bounds, turning an F/D contract into a B with one call (authors then tighten per field).

$ref'd models are walked once and deduped across operations, so a shared schema is audited (and reported) a single time.

When to reach for it

  • You build a v4 document (from @suluk/drizzle, @suluk/hono, or by hand) and want input-validation coverage scored and visible — to incentivise hardening rather than hope for it.
  • You want to gate CI: assertGrade(doc, "A") in a test throws if the authored surface regresses below the floor.
  • You want to auto-tighten loose schemas: run hardenDocument over a built document to add baseline bounds doc-wide (including the route generator's otherwise-unbounded path-param strings).

This package only reads and rewrites the schemas inside a v4 document. It does not validate live requests (that's the wire / @suluk/core), and it does not render the grade into a UI — the @suluk/reference hardening panel and the @suluk/editor diagnostics bar do that by calling auditDocument themselves.

Usage

Audit — grade the input surface

import { auditDocument } from "@suluk/harden";

const report = auditDocument(doc); // doc: OpenAPIv4Document
report.grade;        // "A" | "B" | "C" | "D" | "F"
report.score;        // 0–100
report.bySeverity;   // { high, medium, low } — counts across all findings
report.byOperation;  // per-operation audits, weakest first
report.findings;     // Finding[] — { rule, severity, path, message, fix }, deduped by rule@path

for (const f of report.findings) {
  console.log(`${f.severity.toUpperCase()} ${f.path}: ${f.message} → ${f.fix}`);
}

Audit a single operation, or skip surfaces you don't author (e.g. a merged third-party auth surface) so they don't count toward the grade:

import { auditOperation, auditDocument } from "@suluk/harden";

// One operation: auditOperation(doc, uri, operationName, rawRequest)
const op = auditOperation(doc, "/users", "createUser", req);

// Exclude ingested surfaces from the rollup:
const report = auditDocument(doc, {
  ignore: (uri, name) => uri.toLowerCase().includes("auth"),
});

Gate CI — assertGrade

The hard incentive: throw if the document's hardening grade falls below min. Returns the full DocAudit when it passes.

import { test } from "bun:test";
import { assertGrade } from "@suluk/harden";

test("contract input surface stays fully bounded (grade A)", async () => {
  const document = await app.request("/openapi.json").then((r) => r.json());
  const audit = assertGrade(document, "A", {
    ignore: (uri) => uri.toLowerCase().includes("auth"), // auth is third-party
  });
  // throws below A; on pass you get the report:
  expect(audit.bySeverity.high).toBe(0);
});

The thrown message names the worst operations and the high/medium counts, so a failing build tells the author exactly what to add.

Unified contract grade — combine the input-schema grade with the agent grade

A v4 document has two graded dimensions: its input schemas (this package) and its agent composition (@suluk/agents' gradeAgent). combineGrades folds them into one contract grade — on the letter, never the raw score (the two scores are non-comparable: a clean/nodes ratio vs an absolute 100 − Σ penalty). It's a pure combinator, so no package depends on the other — the caller passes the letters:

import { auditDocument, combineGrades, assertCombinedGrade } from "@suluk/harden";
import { gradeAgents } from "@suluk/agents";

const grades = [auditDocument(doc).grade, ...gradeAgents(doc).map((g) => g.grade)];
combineGrades(grades);             // { worst, average, grades } — `worst` is the value to GATE on (a contract is as strong as its weakest dimension)
                                   //   `average` is informational; its ties round toward the higher letter, so it only ever masks optimistically
assertCombinedGrade(grades, "B");  // CI gate on the worst dimension; pass `"average"` to soften. Pass at least the doc grade — an empty set passes vacuously.

Harden — the inverse transform

hardenSchema adds baseline bounds to a single JSON Schema; hardenDocument does it to every input schema in a built v4 document, in place. Both are idempotent and never override an author-set bound — they only fill gaps. Strings bounded by enum/const/format are left alone.

import { hardenSchema, hardenDocument } from "@suluk/harden";

// Per schema — layer it after your own per-field validations so it only fills what's left:
const schema = hardenSchema(myInsertSchema);
// strings → maxLength + a control-char-rejecting pattern; numbers → min/max;
// arrays → maxItems; objects → additionalProperties:false

// Doc-wide, in place — the transform that makes assertGrade pass:
const document = hardenDocument(builtDoc);

Override the floors when the defaults (1024 chars / ±1e12 / 1000 items) are wrong for you, or pass textPattern: null to skip the string pattern entirely:

import { hardenSchema, type HardenOptions } from "@suluk/harden";

const opts: HardenOptions = { maxLength: 80, numberMax: 1_000, maxItems: 50 };
const tightened = hardenSchema(schema, opts);

API

| Export | What it does | | --- | --- | | auditDocument(doc, opts?) | Audit the whole document → DocAudit (per-op grades, deduped rollup, severity breakdown). | | auditOperation(doc, uri, name, req) | Audit one operation's input surface → OpAudit. | | assertGrade(doc, min, opts?) | CI gate: throw if the grade is below min; else return the DocAudit. | | grade(score) | Map a 0–100 score to a letter grade (A ≥ 90, B ≥ 75, C ≥ 60, D ≥ 40, else F). | | combineGrades(grades) / assertCombinedGrade(grades, min, mode?) | UNIFIED contract grade (Stage 1.5): combine this input-schema grade with @suluk/agents' gradeAgent grade on the LETTER → { worst, average, grades }; gate on the worst (safe) or "average". | | hardenSchema(schema, opts?) | Add baseline bounds to one JSON Schema (idempotent; never overrides). | | hardenDocument(doc, opts?) | Harden every input schema in a built v4 document, in place. | | HardenOptions | { maxLength?, textPattern?, numberMax?, numberMin?, maxItems? } — overridable floors for the transform. |

Types Audit, OpAudit, DocAudit, Finding, Severity ("high" \| "medium" \| "low"), and Grade ("A" \| "B" \| "C" \| "D" \| "F") are exported alongside.

Boundary

The audit↔transform pair is the template for "ship both halves": the audit grades the gaps, harden fills them. Hardening is a derived contract facet — like x-suluk-cost/x-suluk-access/x-suluk-source, it is projected from the contract, not authored separately.

This package stays L3 — render/generate, never host. It reads and rewrites the schemas inside a v4 document and returns reports/transformed documents; it does not run a server, validate live wire requests, or store anything. You inject the document (the built v4 doc) and consume the result (a grade, findings, or a hardened copy). Surfacing the grade to a developer (a docs panel, an editor bar) is the consumer's job; enforcing the bounds at runtime is the wire's job.

License

Apache-2.0