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

@asqav/sdk

v0.10.14

Published

AI agent governance - audit trails, policy enforcement, ML-DSA cryptographic signatures

Readme

asqav

TypeScript SDK for asqav.com, the evidence layer for AI agents.

Every agent action gets a signed, hash-chained compliance receipt: ML-DSA-65 (FIPS 204, post-quantum), timestamped against independent witnesses, and verifiable by anyone - auditor, counterparty, regulator - without an Asqav account and without trusting us.

Zero native dependencies. Cryptography runs server-side.

Install

npm install @asqav/sdk

Quick start

npm install -g @asqav/sdk
asqav login        # validates your key, saves it to ~/.asqav/credentials
import { govern } from "@asqav/sdk";

// govern() = init() + Agent.create() in one call
const agent = await govern({ apiKey: process.env.ASQAV_API_KEY, agentName: "my-agent" });

const sig = await agent.sign({ actionType: "api:openai:chat", context: { model: "gpt-4o" } });

console.log(sig.actionRef);            // "sha256:..." over the JCS-canonical action
console.log(sig.previousReceiptHash);  // 64 hex; "0".repeat(64) on this agent's first receipt
console.log(sig.verificationUrl);      // anyone can open this

One install, one govern, one sign. init({ apiKey }) + Agent.create({ name }) remain available when you want control over algorithm, capabilities and other agent options.

Each Agent keeps the API key and base URL selected when its creation or retrieval starts. Its signing mode and a copy of the supplied salt stay with it too. A later init or govern configures future Agents; it does not retarget an existing Agent, even during an awaited operation. To rotate a key, call init with the new configuration and obtain a new Agent with Agent.get(id). Hooks and detector registrations continue to apply to existing Agents when they sign.

Module helpers such as request and verifySignature use the current default configuration. Integrations that create an Agent lazily through the static API also use the default at that time; storing configuration in another object does not give that object an isolated SDK connection.

Use AsqavClient when each caller needs its own connection before creating an Agent:

import { AsqavClient } from "@asqav/sdk";

const first = new AsqavClient({ apiKey: process.env.FIRST_ASQAV_API_KEY });
const second = new AsqavClient({ apiKey: process.env.SECOND_ASQAV_API_KEY });
// Neither constructor sends an API request.
const firstAgent = await first.createAgent({ name: "first-agent" });
const secondAgent = await second.getAgent("agt_existing");
await firstAgent.sign({ actionType: "api:call", context: { task: "first" } });

Clients keep their construction-time configuration and never change the module default. An omitted baseUrl uses ASQAV_API_URL at construction, or the SDK cloud URL when unset; it does not inherit another caller's init. Mode resolution follows the same rules as init. The client owns a copy of the supplied salt. Create a new client and Agent to rotate credentials. Hooks and detectors still apply across clients. The returned objects are ordinary Agents.

Verify it without an account

This is the point of the whole thing - the receipt stands on its own:

Run this right now, with no key and no signup:

import { verify } from "@asqav/sdk";

const result = await verify("sig_example_regulator_cold_verify_2026");
console.log(result.verified);    // false -- and that is the point, see below
console.log(result.chainHash);   // recomputed on your machine from the canonical bytes

That id is a shape example: its signature bytes are placeholders and its kid resolves to no key, so the verifier returns verified: false instead of waving it through. Swap in a signature_id of your own for a receipt that passes. A verifier that says no when the evidence is absent is the only kind worth having.

From the shell (npm install -g @asqav/sdk provides the asqav command):

asqav verify <your_signature_id>

Offline or air-gapped, snapshot the keys once and verify with no network at all. This re-derives the signature itself from the RFC 8785 canonical bytes and reports a verdict per axis, so it never asks Asqav to vouch for anything:

import { fetchJwks, verifyReceiptOffline } from "@asqav/sdk";
import { readFileSync, writeFileSync } from "node:fs";

const jwks = await fetchJwks();                        // online, once
writeFileSync("jwks.json", JSON.stringify(jwks));

// Offline from here. Pass the raw text, not JSON.parse output (see below).
const result = verifyReceiptOffline(readFileSync("receipt.json", "utf8"), readFileSync("jwks.json", "utf8"));
if (result.verdict === "unverified") throw new Error(JSON.stringify(result.axes));

verdict is "verified" | "verified_keyed" | "unverified". verified_keyed is a pass over a keyed digest: internally consistent, but not third-party re-derivable. Pass the predecessor receipt as the third argument to check the hash-chain link too.

Raw text gets the same verdict as the Python verifiers and the conformance runner, because the parser keeps each number as it was written. The signed bytes can hold 500.0 or -0.0, and the receipt profile accepts only integers in the safe range, so those receipts come back unverified / unverifiable everywhere. An object from JSON.parse cannot carry that spelling. On that path a -0 in a signed part is refused as unverifiable, because it could have been -0 or -0.0. A 500.0 reads back as 500: if the signer wrote the float 500.0, the signature check fails with unverified / invalid, but if the signer wrote the integer 500 and the file respells it as 500.0 or 5e2 (or 0 as 0.0), the object verifies while raw text and the Python verifiers refuse it. Pass raw text when you need the strict profile. An integer past 2^53 anywhere in the receipt is refused as unverifiable on raw text and by every Python verifier, a parsed dict included. JSON.parse rounds it, so one outside the signed bytes goes unseen and that object can still verify. Fractions such as 1e-07 and 0.5 verify the same way on both paths. verifyCounterpartyBinding and computeEnvelopeHash also take the origin as raw text, and then agree with Python's verify_counterparty_binding. A JSON.parse origin loses the spelling here too. If it holds -0, verifyCounterpartyBinding reports unresolved and computeEnvelopeHash and computeCounterpartyBinding throw counterparty_origin_unavailable. If the origin signed the integer 500 and the file respells it as 500.0, the binding reports matches while Python reports mismatch. If the origin signed a float such as 500.0 or -1.0, a JSON.parse origin reads it back as an integer and the binding reports mismatch while Python reports matches. An integer past 2^53 anywhere in the origin makes the binding unresolved on raw text and in Python, while a JSON.parse origin that holds one outside the signed bytes reports matches. Pass raw text for Python's answer. ML-DSA-65 verifies in-process via @noble/post-quantum; Ed25519 and ES256 via node:crypto. Guide: offline and air-gapped verification.

Neutral verifier

A standalone verifier for agent receipts across formats ships as a subpath export:

import { verify, ADAPTERS } from "@asqav/sdk/verifier";

const result = verify(receipt, ADAPTERS, keyProvider);

It checks the issuer signature over the canonical bytes, the chain link and structural presence for the asqav-native, AERF, ACTA, agent-receipts and Authproof formats. No account required. It is the port of the Python asqav.verifier.oracle, held to verdict parity by a shared conformance corpus.

Data handling modes

The SDK picks the safer default for where you point it:

  • Cloud (*.asqav.com): hash-only. A SHA-256 fingerprint is computed locally and only the hash plus actionType, agentId, sessionId, modelName, toolName is sent. Prompts and tool arguments never leave your process.
  • Self-hosted: full payload, so the server can run policy checks and richer audit.
await init({ apiKey: "...", baseUrl: "https://api.asqav.com", mode: "hash-only" });

There is no local offline queue in TypeScript; use the Python SDK's local_sign for that.

High-value actions

For regulated or high-risk actions, pass envelope fields that an auditor will look for:

const sig = await agent.sign({
  actionType: "payment.wire_transfer",
  context: { amountEur: 850000, beneficiaryIban: "DE89370400440532013000" },
  receiptType: "protectmcp:decision",
  riskClass: "high",                 // low | medium | high | unknown
  issuerId: "legal:Acme GmbH",       // LEI, EIN, CIK or W3C DID
  iterationId: "task-2026-Q2-4821",  // logical task, distinct from session
});

nonce is an opaque per-call token (12 random bytes when omitted). The cloud refuses a value a live signature for the same agent in the same organisation already holds, answering HTTP 409, so the field bounds replay while that earlier receipt is inside its validity window. A receipt whose validUntil has passed frees the token; one signed with no expiry keeps it.

CLI

asqav whoami                       # active key source, validated
asqav init                         # print a ready-to-paste snippet
asqav verify <signature_id>        # verify a receipt (no key needed)
asqav sign --agent-id ID --action-type T --action-json action.json
asqav agents list | create | revoke
asqav replay-verify <agent_id> <session_id> [--strict]
asqav audit-pack export --start ISO --end ISO --output-file bundle.json
asqav payloads erase <signature_id>          # right-to-erasure

Full command reference: asqav.com/docs/cli.

Framework integrations

Native adapters under @asqav/sdk/extras/*:

  • Vercel AI SDK: createAsqavExporter, passed to experimental_telemetry. Every span becomes a signed action.
  • LangChain.js: AsqavCallbackHandler, passed to callbacks: [handler].
  • Mastra: AsqavMastraHook, attached to a Mastra agent.
  • OpenAI Agents JS: AsqavOpenAIAgentsAdapter, wrapping a tool.

Each needs its framework as a peer dependency. CrewAI, LiteLLM, LlamaIndex and Haystack follow the same Hooks API; the Python SDK has the richer ecosystem. Parity status: integrations.

What a receipt does not prove

Stated plainly, because an auditor will ask:

  • Not that the action ran, or ran once. A receipt records a decision and the bytes that passed through, never the effect. A retry produces a second receipt.
  • Not that the policy was correct. policy_digest proves which policy artefact existed, not that it was the right one.
  • Not that the environment was intact. No field here carries a remote attestation result.
  • Tamper-evident, not tamper-proof. Modification is detectable, not prevented.

The full list is in the IETF profile under "What a Compliance Receipt Does Not Prove".

Reference

| Topic | Docs | |---|---| | Threat-framework mappings (MITRE, OWASP, NIST AI RMF, ISO 42001, EU AI Act) | threat-framework-mapping | | NSA CSI U/OO/6030316-26 receipt fields | nsa-mcp-csi-alignment | | Build provenance (executable_hash, sbom_digest, SLSA) | executable-hash-and-sbom-provenance | | Witness policy and multi-witness anchoring | multi-witness-anchoring | | Binding tool output (result_digest) | result-digest | | Configuration-change receipts | configuration-change-receipts | | Code-authorship receipts | code-authorship-receipts | | Structured receipts (optional schema) | structured-receipts | | Bring-your-own DLP / policy detectors | scanning | | Audit Pack export | compliance | | Independent verification protocol | independent-verification |

Requirements

Node 20.19 through 20.x, or Node 22.12 and later. Uses the built-in fetch. Zero native dependencies.

Standards

Profiled in the IETF Internet-Draft draft-marques-asqav-compliance-receipts, an Independent Submission profiling draft-farley-acta-signed-receipts. Aligns with NIST FIPS 204 (ML-DSA), RFC 8785 (JCS) and NSA CSI U/OO/6030316-26.

Links

Docs · SDK guide · Repository · Discovery descriptor

License

Elastic License 2.0. Get an API key at asqav.com.