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

@phosra/gatekeeper

v0.7.0

Published

Platform-side SDK for the OCSS (Open Child Safety Specification). Verifies signed enforcement profiles, runs the local decision engine, sends §8.3.8 confirmation receipts, and reports parent-side rating changes back to the Phosra census.

Readme

@phosra/gatekeeper

Platform-side SDK for the OCSS (Open Child Safety Specification). Verifies signed enforcement profiles, runs the local decision engine, sends §8.3.8 confirmation receipts, and reports parent-side rating changes back to the Phosra census.

Deploying? The full env contract — the six PHOSRA_* vars this SDK's config maps onto, plus how to mint your endpoint and receive the connect_secret — is documented at docs.phosra.com/integration/platform-registration.

Install

npm install @phosra/gatekeeper

Quickstart

import { createGatekeeper } from "@phosra/gatekeeper"
import type { GatekeeperConfig, ReportParentChangeArgs } from "@phosra/gatekeeper"

// 1. Construct the gatekeeper with your platform's signing key + census URL.
//    Obtain a key from the Phosra developer console or via
//    POST /api/v1/developers/orgs/{orgID}/keys (see docs.phosra.com).
const config: GatekeeperConfig = {
  platformDid:          "did:ocss:your-platform",
  platformKeyId:        "did:ocss:your-platform#key-2026",
  gatekeeperSigningKey: { seed: new Uint8Array(32) /* real Ed25519 seed */, keyID: "did:ocss:your-platform#key-2026" },
  censusBaseUrl:        "https://phosra-api-sandbox-production.up.railway.app", // the one canonical sandbox host
  trustRootXB64Url:     process.env.PHOSRA_TRUST_ROOT_X!,   // public root pubkey X, pinned out-of-band
  endpointId:           "endpoint-mia-01",   // the §9.3(b) bound resolver label
  ratingMappings: [
    // myField is the key you supply in the signal arg to gk.check() / gk.isAllowed().
    { ocssCategory: "content_rating", myField: "maturity", vocabulary: "mpaa" },
  ],
}

const gk = createGatekeeper(config)

// 2. Decide whether to allow a piece of content.
//    For rating-mapped categories, pass the native value under the key declared
//    in myField ("maturity" above).  For non-rating categories, signal is optional.
const verdict = gk.check("content_rating", { maturity: "PG-13" })
if (verdict.decision === "block") {
  // block the content
}

// 3. Confirm enforcement outcome (§8.3.8 receipt).
await verdict.confirm("applied")

// 4. Report a parent-side rating change.
const args: ReportParentChangeArgs = {
  ocssCategory: "content_rating",
  nativeValue:  "G",
  changeScope:  "family_wide",
}
const result = await gk.reportParentChange(args)

// 5. Clean up timers when done.
gk.destroy()

The connect receiver — @phosra/gatekeeper/next (one route, no secret)

A platform's entire /api/ocss/connect route is one export. It dispatches the three delivery shapes (signed provision batch / signed §3.6 connect bind / legacy HMAC) to the right verify primitive, and fires your materialize hook once the census-verified binding lands.

// app/api/ocss/connect/route.ts — the whole receiver
import { createConnectReceiver } from "@phosra/gatekeeper/next"
import { store }   from "@/lib/ocss/store"   // a ConnectSessionStore over YOUR child table
import { onBound } from "@/lib/ocss/apply"    // materialize: pull the profile / create the profile

export const { POST } = createConnectReceiver({
  env:  "production",                 // → census URL + PINNED trust root (bundled, no TOFU)
  did:  "did:ocss:your-platform",
  seed: process.env.OCSS_SENDER_SEED_B64URL!,  // your Ed25519 seed (base64url-raw, 32 bytes)
  authorize: ["did:ocss:custo"],      // REQUIRED provider allowlist — revoke = remove a DID
  store,
  onBound,                            // (label, childRef, provisionCtx?) => materialize
  onError: (err, ctx) => log.warn("ocss ingest failed", ctx, err), // optional
})

Signed by default. The provider's sender-DID-signed-to-root signature IS the auth — there is no shared HMAC secret to mint, store, sync, or rotate (OCSS §8.1 clause 6 bans bearer secrets). Audience-binding + created freshness + the authorizedConnectDids gate are inherited from the verified envelope. assertConfig() throws the exact missing field at construction. To migrate an existing HMAC receiver, pass legacyHmacSecret (opt-in; flips the posture to accept both lanes) and drain to signed-only, then delete the secret.

onBound is fired post-2xx, once per bound label, and MUST be idempotent (create-or-adopt keyed by label):

  • connect bindonBound(label, childProfileId) — pull the signed profile + apply caps;
  • provisiononBound(label, label, { age_band, display_hint, state, adult_pin_auto_set }) — create-or-adopt a profile for the child (band → age).

Boot-time config assert — assertGatekeeperConfig (kills the AuthKit-matcher trap)

The #1 integrator footgun is a Next middleware.ts whose config.matcher excludes the OCSS connect routes (the classic /((?!…|api).*) negative-lookahead drops all of /api), so the AuthKit/proxy middleware never runs on the connect route and the ceremony returns 500. This exact gap regressed one integrator three times. Call the assert once, in middleware.ts, and a matcher gap fails loudly at boot instead of silently mid-ceremony:

// middleware.ts
import { assertGatekeeperConfig } from "@phosra/gatekeeper"

export const config = {
  matcher: [
    "/((?!_next/static|_next/image|favicon.ico).*)", // your app routes (does NOT exclude api)
    "/api/phosra/:path*",                            // ← the OCSS connect family MUST be covered
  ],
}
assertGatekeeperConfig({ matcher: config.matcher }) // throws GatekeeperConfigError if uncovered

The error names the uncovered route and the one-line fix. Mount elsewhere (e.g. /api/ocss)? Pass routes: gatekeeperConnectRoutes("/api/ocss").

The OCSS protocol layer — send + receive (no bespoke lib/ocss/)

The delivery/enforcement primitives every platform used to hand-roll are exported directly, so your lib/ocss/ shrinks to your own persistence + classifier:

import {
  loadSenderKey, TrustListCache, makePhosraTrustListResolver,
  sendHarmContext, openInboundEnvelope, buildAbuseSignal, boundedExcerpt,
} from "@phosra/gatekeeper"

// Load your Ed25519 identity from env (OCSS_SENDER_SEED_B64URL + OCSS_DID + OCSS_SIGNING_KEY_ID).
const senderKey = loadSenderKey()
const trustList = makePhosraTrustListResolver("sandbox")

// SEND (monitored platform → parental app): seal to the recipient's payload key, sign the
// outer §4.2 envelope + the RFC-9421 request, POST the router-blind harm-context lane. Never throws.
await trustList.ensure()
const res = await sendHarmContext({
  senderKey,
  receiverDid: "did:ocss:household-acme",
  receiverPayloadJwk: trustList.payloadKey("did:ocss:household-acme"), // root-verified
  resource: "endpoint:mia",
  content: new TextEncoder().encode(JSON.stringify(
    buildAbuseSignal({ harmClass: "grooming", severity: "severe", sessionScope: "thread:t1",
      eventRef: "message:m1", emitterId: senderKey.keyID.split("#")[0], appealPath: "https://you/appeal" }),
  )),
  censusUrl: "https://phosra-api-sandbox-production.up.railway.app",
})

// RECEIVE (parental app): verify the outer signature to the Trust-List ROOT + open the inner
// sealed payload — one fail-closed call.
const opened = await openInboundEnvelope({
  envelope,                       // the parsed inbound { outer, inner }
  trustList,                      // resolve + root-verify the signer
  receiverPayloadJwk: myPayloadPrivateJwk, // your private P-256 key
  expectedReceiver: "did:ocss:household-acme",
})
// opened.signerDid is verified-to-root; opened.payload is the decrypted bytes.

Disconnection — the revoked tombstone (§8.3.6 / §3.1)

A connect is only as honest as its reverse. When a parent withdraws the last standing for a child, the census stops serving rules and serves one final router-signed §8.3.6 tombstone with top-level status: "revoked". This is a positive, signed, terminal state — never an absence — so a fail-closed platform cannot mistake a disconnect for an outage and keep enforcing revoked rules from cache forever.

gk.endpointState() is the first-class signal ("active" | "revoked" | "stale" | "absent"):

| endpointState | wire cause | what the platform does | |---------------|------------------------------------|-----------------------------------| | active | 200 + valid in-window profile | enforce the tighten-only overlay | | revoked | 200 + status:"revoked" tombstone | release-to-native, stop polling | | stale | 401/5xx/timeout, or aged/expired | keep last-known-good, fail-closed | | absent | 404 / never fetched / not yours | fail-closed, alert operator |

Hook release-to-native with the onRevoked callback. Once revoked, the state is terminal: the tombstone is never overwritten by a later stale profile, and refreshProfile() short-circuits (polling stops).

const gk = createGatekeeper({
  ...config,
  onRevoked: ({ childRef, endpointLabel }) => {
    // Release-to-native: drop the OCSS overlay, remove the "Managed via Phosra" badge.
    // Release is safe by construction — the overlay only TIGHTENS, so lifting it never
    // loosens anything the platform's own parent set. For OCSS-created artifacts (e.g. a
    // kid profile) convert to natively-managed: keep the data, clear the managed flag.
    releaseOverlay(childRef)
  },
})

// A poller stops the moment the endpoint is revoked (do NOT release on "stale"):
if (gk.endpointState() === "revoked") stopPolling()

A withdrawn standing is never un-withdrawn — re-connect is a fresh ceremony.

Error classes

import { RuleRefRequired, NoRatingMappingError, UnmappedRatingValueError } from "@phosra/gatekeeper"

try {
  await gk.reportParentChange({ ocssCategory: "unknown_cat", nativeValue: "PG", changeScope: "platform_local" })
} catch (e) {
  if (e instanceof NoRatingMappingError) { /* no mapping declared */ }
  if (e instanceof UnmappedRatingValueError) { /* nativeValue not in crosswalk */ }
}

Pricing & keys

This SDK is free and open (MIT). It performs all signing/verification locally and calls the hosted Phosra census over RFC 9421. Metered census usage requires a Phosra API keybilling is enforced server-side, never in this package. Provision a key via the Phosra developer console (docs.phosra.com) or the REST endpoint POST /api/v1/developers/orgs/{orgID}/keys.

The /protocol subpath

import { verifyDocument, verifyReceipt } from "@phosra/gatekeeper/protocol"

@phosra/gatekeeper/protocol is a verbatim re-export of @openchildsafety/ocss — identical object identities. It lets you verify signed documents and receipts without a separate @openchildsafety/ocss install when you already depend on this package.

Stability

This package is 0.1.0 — live-proven but still co-evolving with the OCSS spec. Under 0.x semver, minor bumps (0.1 → 0.2) may include breaking changes with a one-minor deprecation window. Exports marked @experimental are excluded from that window.