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

@phala/aci-verifier

v0.8.0

Published

TypeScript ACI verifier and verified fetch client for browsers, Node.js, and Bun.

Readme

@phala/aci-verifier

A TypeScript verifier for Attested Confidential Inference (ACI), for the browser, Node 20.18+, and Bun 1.4+. verifyService(url) fetches a service's report with a fresh nonce and returns a full §9.1 transcript, including the hardware quote, verified with @phala/dcap-qvl against the Phala PCCS. Every other check uses Web Crypto: Ed25519, X25519, HKDF, AES-GCM, SHA-256. A prebuilt ESM bundle (npm run build:bundle) drops into a <script type="module">.

The npm package is ESM-only. Browser-aware bundlers select the browser condition automatically; native browser imports can use @phala/aci-verifier/browser. Runtime-aware applications import connectAci() from @phala/aci-verifier/runtime; package conditions select the tested Node or Bun transport. Explicit /node and /bun entries are also available. Use the browser entry for document verification and the Node or Bun entries for a pinned transport.

ACI documents verify over their JCS form (spec Appendix A), so this library canonicalizes whatever it parsed and hashes foreign bytes (HTTP bodies, evidence) exactly as observed.

What it verifies

  • A whole service (§9.1): verifyService(url) fetches the report with a fresh nonce and runs the transcript: the quote to the Intel vendor root (check 1, via @phala/dcap-qvl), the binding chain (checks 2–3), and the compose measurement (check 4) when the service publishes app_compose. Returns { verdict, lines, verification, composeHash }. verifyQuote and verifyComposeMeasurement are the individual checks.
  • Reviewed release allowlist (§1.3): pass acceptedComposeHashes to accept only reviewed sha256(app_compose) values measured into RTMR3. Without an allowlist the measurement is verified and reported, but the verifier does not claim that the release was reviewed. See Release acceptance.
  • Report binding (§9.1 checks 2–3): verifyReportBinding(report, nonce) recomputes the keyset digest over the served workload_keyset object's JCS form, rebuilds the attestation statement for the nonce you supplied, checks it hashes to report_data, and checks the keyset is not expired. The result carries the established keyset (digest, parsed form) for every later check.
  • Receipts (§9.3): verifyReceipt(document, keyset, establishedDigest) verifies the Ed25519 signature over the JCS form of the document minus its signature member, under the keyset entry key_id names, and that the document binds to the established keyset digest. checkRequestBodyHash / checkResponseBodyHash cover checks 3–4.
  • Sessions (§8, §9.2): computeSessionId hashes the JCS form of the parsed session document for comparison against the id a signed receipt committed to; checkSessionEvidence checks evidence.data hashes to evidence.digest.
  • Aggregator checks (§9.3(5)-(6)): receiptTranscript(..., upstream) adds upstream-1 (the serving upstream was verified and cites a session) and upstream-2 (the cited session hashes to its id, covers served_at, carries matching evidence, and is one you pinned with provider.aci_session_ids). Pass { session, pinnedSessions, requiresVerified }; anything absent is a skip with its reason, never a pass.
  • E2EE key verification, not an E2EE request builder. Report verification establishes the quote-bound e2ee_public_keys, including the suites in the E2EE v2 compatibility protocol. This package does not encrypt or decrypt content fields. Callers can implement that field-level wire contract or use a v2-capable client.

Current limits

  • No dstack boot-measurement reconstruction. Quote verification authenticates the quote's RTMR fields, and this package replays RTMR3 only.
  • No custody check. §9.1 check 5 (the dstack KMS chain) is not implemented in this package; it reports a skip.
  • No TLS observation in a browser. A browser cannot see the server certificate, so id-6 needs the SPKI your own TLS stack observed (the channel option), or the pap CLI / pap serve proxy, which can; with neither, a live run fails id-6 (§1.1).
  • No deep audit of upstream evidence (§9.2(4)). checkSessionEvidence proves the cited session's evidence.data hashes to evidence.digest and stops there; neither in-tree verifier appraises the evidence itself (conformance gaps item 17).
  • Ed25519 receipts only. A receipt keyed to any other algorithm is reported as a failed signature check, not verified.

Verification failures return { ok: false, checks }; malformed input throws.

Release status: Public releases are available from npm. The repository builds an ESM package with declarations, validates it with publint and Are The Types Wrong, and publishes it with npm provenance from a clients-v<version> GitHub Release.

Clients do not check whether the gateway runs a production or development dstack OS image. A development image permits operator access to the gateway. Users who need assurance about the OS image should verify the quote, event log, and VM configuration with an independent dstack verifier. The deprecated requireProductionOs option remains accepted for compatibility and is ignored.

Usage

One call runs the ACI checks:

import { verifyService } from '@phala/aci-verifier';

const { verdict, lines } = await verifyService('https://tee.redpill.ai');
console.log(verdict.line); // VERIFIED / PARTIAL / NOT VERIFIED
for (const l of lines) console.log(l.status, l.id, l.title);

Runtime SDK and agent frameworks

Node and Bun applications establish the same instance-scoped, SPKI-pinned connection and inject its ordinary fetch into any HTTP-based OpenAI-compatible SDK. The connection rejects HTTP, cross-origin requests, expired identities, and TLS peers whose SPKI is not in the verified workload keyset.

import OpenAI from 'openai';
import { connectAci } from '@phala/aci-verifier/runtime';

const apiKey = process.env.ACI_API_KEY;
if (!apiKey) throw new Error('ACI_API_KEY is required');

const aci = await connectAci({
  baseURL: 'https://tee.redpill.ai/v1',
  serving: {
    // Every successful JSON POST must carry a receipt and cite verified serving.
    requireVerified: true,
    requireReceipt: true,
  },
});
if (!aci.identity.transcript.verdict.verified) {
  throw new Error(aci.identity.transcript.verdict.line);
}

const openai = new OpenAI({
  baseURL: aci.baseURL,
  apiKey,
  fetch: aci.fetch,
});

const response = await openai.chat.completions.create({
  model: 'z-ai/glm-5.2',
  messages: [{ role: 'user', content: 'Hello' }],
});

// The transport hashes the exact request/response wire bodies while streaming
// and reuses this request's authorization only to fetch its private receipt.
// Verification is on demand, so normal inference latency does not include a
// receipt/session fetch.
const audit = await aci.verifyReceipt();
if (!audit.transcript.verdict.verified) {
  throw new Error(audit.transcript.verdict.line);
}

await aci.refresh(); // Verify a fresh report and rotate the scoped dispatcher.
await aci.close();

Install the two ESM packages with npm install openai @phala/aci-verifier. Run the same source under Node 20.18.1+ or Bun 1.4.0+; the /runtime export selects the matching pinned transport. Set ACI_API_KEY to the RedPill key. The SDK adds it to the inference request; aci.fetch retains that request's authorization in its bounded exchange history and reuses it only for the matching private receipt lookup.

For release-level pinning, add the reviewed deployment's compose hash under policy.acceptedComposeHashes. Take the hash from authenticated release metadata, never from the endpoint you are verifying: a hash copied from the service only trusts it on first use.

The public API is identical in Node and Bun. Internally, Node passes a scoped undici dispatcher to fetch, while Bun passes its documented tls callback and proxy options. Both adapters use the same hostname/SPKI check and the same quote, policy, receipt, session, rotation, and lifecycle implementation. Use /node or /bun only when a bundler cannot select runtime export conditions correctly.

aci.receipts() lists the bounded in-memory exchange history. A receipt can only receive a complete transport audit while its exchange is retained; an unknown id fails instead of returning a misleading verdict with skipped body hashes.

OpenAI Agents SDK

Use a runner-scoped OpenAIProvider so one agent stack owns one verified connection. setDefaultOpenAIClient() also accepts this client, but changes a process-wide default and is a worse fit when several gateways coexist.

import { Agent, OpenAIProvider, Runner } from '@openai/agents';

const modelProvider = new OpenAIProvider({
  openAIClient: openai,
  useResponses: false,
});
const runner = new Runner({ modelProvider });
const agent = new Agent({
  name: 'Private agent',
  instructions: 'Be concise.',
  model: 'your-model',
});

const result = await runner.run(agent, 'Summarize this document.');
console.log(result.finalOutput);

await modelProvider.close();
await aci.close();

aci.fetch protects the HTTP model calls only. Agent tools, MCP calls and tracing use their own transports and remain separate trust boundaries.

Vercel AI SDK

import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

const gateway = createOpenAICompatible({
  name: 'aci',
  baseURL: aci.baseURL,
  apiKey,
  fetch: aci.fetch,
});
const result = await generateText({
  model: gateway('your-model'),
  prompt: 'Summarize this document.',
});

LangChain JS

import { ChatOpenAI } from '@langchain/openai';

const model = new ChatOpenAI({
  model: 'your-model',
  apiKey,
  configuration: {
    baseURL: aci.baseURL,
    fetch: aci.fetch,
  },
});
const result = await model.invoke('Summarize this document.');

These integrations use documented transport hooks in OpenAI Node, OpenAI Agents JS, Vercel AI SDK, and LangChain JS.

This transport does not cover WebSocket model calls. For browser clients, WebSocket-only frameworks, or software that cannot inject a custom fetch, run pap serve and point the framework at its local OpenAI-compatible endpoint instead.

Or drive the individual checks:

import {
  verifyReportBinding,
  verifyReceipt,
  checkResponseBodyHash,
} from '@phala/aci-verifier';

// Establish the workload identity for a fresh nonce (§9.1 checks 2–3).
const nonce = Array.from(crypto.getRandomValues(new Uint8Array(32)), (b) => b.toString(16).padStart(2, '0')).join('');
const report = await (await fetch(`${base}/v1/aci/attestation?nonce=${nonce}`)).json();
const v = await verifyReportBinding(report, nonce);
if (!v.ok) throw new Error('report failed: ' + JSON.stringify(v.checks));

// Check the response against its receipt (§9.3). The receipt document
// comes from GET /v1/aci/receipts/{id}, {id} from the X-Receipt-Id header.
const result = await verifyReceipt(receipt, v.keyset!, v.workloadKeysetDigest!);
if (!result.ok) throw new Error('receipt failed: ' + JSON.stringify(result.checks));

// Checks 3–4: the bytes you sent and received match what the receipt commits to.
if (!(await checkResponseBodyHash(result.payload!, responseBytes))) {
  throw new Error('response bytes do not match the receipt');
}

Development

npm install
npm test      # tsc + node:test; test/vectors.test.ts pins every
              # construction against the ACI and E2EE v2 vector documents
npm run test:bun # the same pinned-transport contract on Bun 1.4+
npm run build # emit dist/ (ESM + .d.ts)