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

@clossys/locksmith

v0.2.5

Published

Key custody, distribution, rotation, revocation, credential lifecycle evaluation, and provider-neutral secret resolution through environment, test, and optional Infisical adapters.

Readme

@clossys/locksmith

Locksmith owns keys end to end: custody, distribution, rotation, revocation — and, unchanged from the package it renames, provider-neutral resolution. Resolution is one verb of five; nothing in this catalogue used to rotate a key, so this package's job is to close that gap, not merely to fetch a value.

npm install @clossys/locksmith

This package is published to the public npm registry, https://registry.npmjs.org. Installing it needs no authentication: no npm token, no .npmrc registry override, and no GitHub credential of any kind.

Controlled key rate

Independent consumer evidence shows the position's owned metric meets its setpoint over the declared review cadence. The owned metric is controlled key rate, computed by assessControlledKeyRate(). An empty evaluated set is indeterminate, never a perfect rate of 1. summarizeRotationMetric remains the rotation triple; it is not this rate. This package does not measure consumer evidence and does not close the loop. A green run of this package's tests is not a close.

import { assessControlledKeyRate } from "@clossys/locksmith";

const report = assessControlledKeyRate(input);
locksmith-check assessment.json

The command prints JSON and exits 0 for satisfied, 1 for violated, and 2 for indeterminate, unreadable, or invalid input.

This package declares that command as its first-day assessment surface in its own manifest:

"foundry": { "assessment": { "bin": "locksmith-check", "invocation": "single-json-input" } }

Onboarding discovers that declaration from the installed manifest and never infers a surface. Existing credential and adapter CLIs stay as they are; they are not the assessment surface. Locksmith is not a required first-day role; Advisor remains the only required first-day assessment.

The job

Aim — every live key is owned, current, and revocable.

Metric

Key age at the ninety-fifth percentile, the count of keys with no recorded owner, and the count of owned keys whose rotation could not be observed. summarizeRotationMetric computes exactly this triple from a set of rotation evaluations, so the metric is a function this package ships, not a promise made only in prose. The third count is deliberately its own field, not folded into the owner count: "nobody has checked" and "nobody owns this" are different findings, and collapsing them would let a repeatedly unverifiable key hide inside a count meant for custody gaps.

Loop

  • sense — enumerate declared keys and their last rotation (defineKeyCustody, RotationRecord).
  • judge — current / stale / unowned / unverifiable (evaluateRotation).
  • act — emit the rotation queue (rotationQueue); this package never rotates a key itself and never calls a revocation authority — only the plane's own explicit authority does that.
  • learn — a key that is repeatedly unverifiable is not a custody gap and not a scheduling problem; it is its own finding, and summarizeRotationMetric reports it in unverifiableKeyCount, a field separate from unownedKeyCount so the two are never graded the same way.

The loop closes when every declared key evaluates to current, every key has a recorded owner, and both summarizeRotationMetric's unownedKeyCount and unverifiableKeyCount are zero. A key stuck at unverifiable never counts toward that close — it stays visible in rotationQueue until an observation actually resolves it one way or the other.

Close condition, for a consuming plane's credential inventory: this loop closes on inventory COVERAGE, which is deliberately a weaker claim than rotation health: every live credential is declared in the plane's own inventory (defineKeyCustody's entries) and every entry EVALUATES — to current, stale, or unowned — with unverifiable appearing only under an explicit, recorded opt-out. stale and unowned do not block this close; they are the gate's real findings, reported as violations for the plane to act on. Coverage closing means the loop can SEE everything; rotation health is the separate, stricter condition the gate then judges, and conflating the two would let "we know about it" read as "it is fine". unverifiable standing in for "nobody checked" is not a close; that is the same distinction evaluateRotation's ternary exists to keep visible rather than let collapse into a quiet pass. The loop reopens the moment a credential is observed in live use — in a workflow, a runtime environment, a provider console — that the declared inventory never named: an undeclared credential is a bigger gap than a stale one, because nothing in this package can report on a key it was never told about. Issue #326 records the fuller lifecycle this package is still growing into — naming, inventory, exposure, rotation, revocation, and expiry distribution all failing closed together — and this close condition is scoped to the custody-and-rotation slice shipped today, not a claim that #326 is already done.

Usage

Resolution (unchanged)

import {
  createEnvSecretsAdapter,
  createSecretsClient,
  defineSecretCatalog,
} from "@clossys/locksmith";

const secrets = createSecretsClient(createEnvSecretsAdapter());
const signingKey = await secrets.require("APP_SIGNING_KEY");

const catalog = defineSecretCatalog([
  { key: "APP_SIGNING_KEY", required: true, group: "runtime" },
  { key: "OPTIONAL_WEBHOOK_KEY", required: false, group: "integrations" },
]);

createEnvSecretsAdapter() reads process.env at call time, so tests and startup code can change the environment after importing the package. It does not copy the environment or read any key during module evaluation.

For dependency-injected tests:

import {
  createSecretsClient,
  createTestSecretsAdapter,
} from "@clossys/locksmith";

const adapter = createTestSecretsAdapter({ APP_SIGNING_KEY: "example-value" });
const secrets = createSecretsClient(adapter);

adapter.set("ANOTHER_KEY", "another-value");
adapter.delete("APP_SIGNING_KEY");

The test adapter's keys() method returns names only. It has no method that dumps all values.

Custody

import { custodyOf, defineKeyCustody, unownedKeys } from "@clossys/locksmith";

const custody = defineKeyCustody([
  { key: "APP_SIGNING_KEY", owner: "team-platform", store: "infisical" },
  { key: "LEGACY_WEBHOOK_KEY", owner: null, store: "environment" },
]);

custodyOf(custody, "APP_SIGNING_KEY"); // { key, owner: "team-platform", store: "infisical" }
unownedKeys(custody); // ["LEGACY_WEBHOOK_KEY"]

An owner-less key is null, never an empty string, so unownedKeys never has to guess at what counts as unowned.

Rotation

import { evaluateRotation, rotationQueue, summarizeRotationMetric } from "@clossys/locksmith";

const evaluation = evaluateRotation(
  { key: "APP_SIGNING_KEY", lastRotatedAt: "2026-05-01T00:00:00.000Z" },
  { key: "APP_SIGNING_KEY", maxAgeDays: 90 },
  custody,
);
// evaluation.state is one of "current" | "stale" | "unowned" | "unverifiable"

rotationQueue([evaluation]); // every key that is not "current"
summarizeRotationMetric([evaluation]); // { p95AgeDays, unownedKeyCount, unverifiableKeyCount }

RotationState is exactly four literal states, enforced in the type itself: a caller cannot express "we think it's probably fine" or collapse "could not check" into "current". Custody is judged before age: an unowned key reports unowned regardless of how recent its last rotation looks, because ownership is a precondition for trusting any age at all. sameDigest compares two caller-supplied, opaque digest strings for equality — this package never derives a digest from a value, computes a hash, or stores one; it only echoes back whatever opaque fingerprint the caller already produced elsewhere.

Credential lifecycle

evaluateCredential keeps provider-created, per-job credentials separate from credentials an owner must rotate manually. It accepts metadata only and returns a value-free satisfied / violated / indeterminate result with exit code 0 / 1 / 2:

evaluateCredential({
  key: "GITHUB_TOKEN",
  credentialClass: "ephemeral-job",
  provider: "github-actions",
  scope: ["packages:read"],
  jobStartedAt: "2026-08-18T00:00:00.000Z",
  jobEndedAt: "2026-08-18T00:01:00.000Z",
  expiresAtJobEnd: true,
  scopedUseObserved: true,
}); // { verdict: "satisfied", exitCode: 0, ... }

The ephemeral path requires the github-actions provider, a dense, sorted, unique scope drawn from the closed contents:read, id-token:write, packages:read, and packages:write vocabulary, a canonical UTC job lifetime whose end is strictly after its start, explicit expiry-at-job-end semantics, and observed scoped use; it does not invent a rotation timestamp. The manually rotatable path requires the explicit github provider and the same closed scope except id-token:write, plus owner-controlled token provenance, while keeping provider metadata (including an optional repository-secret updatedAt) separate. updatedAt alone therefore remains indeterminate, never proof of a token rotation. Unknown fields are rejected without being returned, so credential values cannot travel through the evidence or evaluation objects. defineCredentialEvidence freezes complete, already-satisfied evidence for callers that want an authored record.

clossys-locksmith-credential CLI

The installed executable clossys-locksmith-credential is the command-line entry point for evaluateCredential. It reads one caller-assembled JSON evidence document, judges it, and reports the verdict unchanged -- it mints, fetches, and rotates nothing, and talks to no provider; a caller's own collection step is responsible for assembling the evidence beforehand.

clossys-locksmith-credential --evidence ./credential-evidence.json
clossys-locksmith-credential --evidence ./credential-evidence.json --format json
clossys-locksmith-credential --help

Exit codes mirror evaluateCredential exactly: 0 for satisfied, 1 for violated (the evidence itself proves a problem), and 2 for indeterminate, including evidence that could not be read at all. There is no flag that turns a 1 or 2 into a 0; whether either blocks a merge is left to the caller.

Revocation

import { defineRevocationPath, isRevoked, recordRevocation } from "@clossys/locksmith";

const path = defineRevocationPath({
  key: "APP_SIGNING_KEY",
  authority: "infisical-project:prod",
  procedure: "runbook: rotate-signing-key",
});

const revocation = recordRevocation({
  key: "APP_SIGNING_KEY",
  revokedAt: "2026-08-01T00:00:00.000Z",
  revokedBy: "team-platform",
  reason: "suspected exposure",
});

isRevoked([revocation], "APP_SIGNING_KEY"); // true

defineRevocationPath records where revocation authority lives; it never calls it. recordRevocation records that a revocation happened; it never performs one. Credential issuance, revocation, and rotation policy remain the consumer's identity/access-control layer, exactly as they did before this rename — this package adds the record-keeping half, not the authority.

Distribution manifest

import { defineDistributionManifest, mayResolve, principalsFor } from "@clossys/locksmith";

const distribution = defineDistributionManifest([
  { key: "APP_SIGNING_KEY", principals: ["service-api", "service-worker"] },
]);

mayResolve(distribution, "service-api", "APP_SIGNING_KEY"); // true
principalsFor(distribution, "APP_SIGNING_KEY"); // ["service-api", "service-worker"]

The manifest states policy only. It grants nothing by itself and performs no access check against a live resolver — a consumer's own adapter/provider boundary is what actually enforces access; this manifest is the declaration that boundary is checked against.

Synchronous compatibility

getSync and requireSync support existing synchronous call sites when the injected adapter is synchronous. They throw AsyncSecretAdapterError if the adapter returns a promise. New provider-backed code should prefer the async methods.

const value = secrets.requireSync("APP_SIGNING_KEY");

Error safety

MissingSecretError, SecretAccessError, and AsyncSecretAdapterError contain a stable code and the requested key. Adapter exceptions are replaced, not chained, because a provider error or response body may contain sensitive material. Applications should log the error code and key, never a resolved value. SecretError is the shared base class; SecretErrorCode names the stable, safe error-code union.

Infisical subpath and CLI

The root entry remains provider-neutral. Install this package and import the optional Infisical integration from its explicit subpath when a consumer has chosen Infisical:

import {
  createAccessTokenProvider,
  createInfisicalClient,
} from "@clossys/locksmith/infisical";

const infisical = createInfisicalClient({
  baseUrl,
  projectId,
  environment,
  secretPath: "/application",
  accessTokenProvider: createAccessTokenProvider(() => accessToken),
});

const value = await infisical.get("APP_SIGNING_KEY");

All provider configuration and tokens are injected by the consumer. The Infisical client implements SecretsAdapter, so it can be passed directly to createSecretsClient. createOidcTokenProvider exchanges a consumer-supplied identity token in memory and caches the short-lived provider token only until shortly before expiry.

listSecretNames() returns names only. checkCatalog() returns a value-free readiness report. parseValueFreeCatalog() accepts only strict version-1 catalog metadata. run() injects values into one non-shell child process without writing a file or printing the environment.

The provider-specific CLI installs as clossys-secrets-infisical. It is also still installed under its previous, now deprecated name, which will be dropped a release cycle after the deprecation was announced — see the changelog. Both names point at the same entry point, so a consumer already invoking the old one keeps working and can switch on its own schedule.

Its catalog, check, list, get, and run commands never print secret values. get reports presence only, and the CLI exposes no mutation command. qualify is the one offline readiness operation: it compares a value-free catalog to a names-only availability snapshot, without provider configuration, credentials, or network access. It exits 0 when every required name is available, 1 when one or more required names are missing, and 2 for malformed input.

clossys-secrets-infisical catalog --catalog ./secret-catalog.json
clossys-secrets-infisical qualify --catalog ./secret-catalog.json --available ./available-names.json
clossys-secrets-infisical check --catalog ./secret-catalog.json
clossys-secrets-infisical list
clossys-secrets-infisical get APP_SIGNING_KEY
clossys-secrets-infisical run -- node server.js

The availability snapshot is strict version-1 names-only metadata; fields other than version and names are rejected so a value cannot be accepted by mistake:

{
  "version": 1,
  "names": ["APP_SIGNING_KEY", "OPTIONAL_WEBHOOK_KEY"]
}

The qualify report prints only ok and missingRequired; it does not echo the snapshot or catalog descriptions.

For maintenance, createInfisicalMaintenanceClient(config, authorize) is a separate constructor. Each replacement requires a consumer authorization callback and provider-side least-privilege write permission. An optional verification callback can check the replacement, but failed verification never automatically restores a locally read value because that could overwrite a concurrent update. Credential issuance, revocation, and rotation policy remain consumer responsibilities for the actual mechanics of issuing and replacing a credential; this package's own revocation and rotation modules add the record-keeping — custody, state, and queueing — around that consumer-owned authority.

API

| Export | Kind | Purpose | | --- | --- | --- | | createSecretsClient(adapter) | function | Creates a frozen client with async get/require and sync getSync/requireSync methods. No mutable global adapter is used. | | createEnvSecretsAdapter(source?) | function | Creates a synchronous adapter over a late-bound environment source. Defaults to process.env. | | createTestSecretsAdapter(initial?) | function | Creates a mutable in-memory adapter for tests, with key-only inspection. | | defineSecretCatalog(entries) | function | Returns a detached, frozen SecretCatalog with version: 1. Catalog entries contain metadata only. | | SecretError | class | Base safe error carrying a SecretErrorCode and requested key. | | MissingSecretError | class | Reports an unavailable required key. | | SecretAccessError | class | Replaces an adapter failure without retaining provider error text. | | AsyncSecretAdapterError | class | Reports use of a sync client method with an async adapter. | | SecretsAdapter / SyncSecretsAdapter | types | Provider adapter contracts. | | SecretsClient | type | Async and sync client contract returned by createSecretsClient. | | SecretKey | type | The string key accepted by adapters and clients. | | SecretCatalog / SecretCatalogEntry | types | Value-free catalog metadata contracts. | | TestSecretsAdapter | type | In-memory adapter contract returned by createTestSecretsAdapter. | | MaybePromise | type | Adapter return helper for sync or async providers. | | SecretErrorCode | type | Stable safe error-code union. | | defineKeyCustody(entries) | function | Builds a frozen, value-free custody manifest: who owns each declared key and where it lives. | | custodyOf(manifest, key) | function | The custody record for one key, or undefined if never declared. | | unownedKeys(manifest) | function | Every declared key with no recorded owner. | | KeyCustodyManifest / KeyCustodyRecord | types | Custody manifest and per-key custody metadata contracts. | | CustodyStore | type | The string label naming where a key's value lives; taxonomy is provider-owned, not fixed here. | | evaluateRotation(record, policy, custody, now?) | function | Judges one key's rotation state: current / stale / unowned / unverifiable. | | rotationQueue(evaluations) | function | Every key whose state is not current. | | summarizeRotationMetric(evaluations) | function | The package metric: p95 key age, the count of unowned keys, and the count of owned-but-unverifiable keys. | | sameDigest(a, b) | function | Compares two caller-supplied, opaque rotation digests for equality. | | RotationState | type | The closed four-member state union: current | stale | unowned | unverifiable. | | RotationPolicy / RotationRecord / RotationEvaluation / RotationMetric | types | Rotation policy, observed rotation history, judged outcome, and the summarized metric. | | evaluateCredential(evidence) | function | Judges ephemeral-job or manually-rotatable value-free lifecycle evidence with satisfied / violated / indeterminate and exit code 0 / 1 / 2. | | defineCredentialEvidence(evidence) | function | Freezes complete, value-free credential evidence after requiring a satisfied evaluation. | | CredentialClass / CredentialProvider / CredentialEvidence / CredentialEvaluation | types | Credential lifecycle classes, known providers, evidence union, and value-free ternary result. | | CredentialVerdict / CredentialExitCode / CredentialReason | types | Closed verdict, numeric exit-code, and safe reason vocabularies. | | EphemeralJobCredentialEvidence / ManuallyRotatableCredentialEvidence | types | Per-job expiry evidence and separately owner-provenanced manual-rotation evidence. | | defineRevocationPath(path) | function | Records where revocation authority lives for a key; performs no revocation. | | recordRevocation(record) | function | Builds a frozen, value-free record that a key was revoked. | | isRevoked(records, key) | function | Whether any record revokes the given key. | | latestRevocation(records, key) | function | The most recent revocation record for a key, if any. | | RevocationPath / RevocationRecord | types | Revocation-authority pointer and revocation-event metadata contracts. | | defineDistributionManifest(entries) | function | Builds a frozen manifest declaring which principal may resolve which name. | | mayResolve(manifest, principal, key) | function | Whether the manifest declares that a principal may resolve a key. | | principalsFor(manifest, key) | function | Every principal declared for a key. | | keysFor(manifest, principal) | function | Every key declared for a principal. | | DistributionManifest / DistributionEntry | types | Distribution manifest and per-key principal-list contracts. | | Principal | type | An opaque identifier for whoever may resolve a key. |

@clossys/locksmith/infisical API

| Export | Kind | Purpose | | --- | --- | --- | | createInfisicalClient(config) | function | Creates injected read, list, readiness, and child-process injection operations. | | createAccessTokenProvider(tokenOrFactory) | function | Supplies a late-bound access token. | | createOidcTokenProvider(options) | function | Exchanges an injected identity token for a short-lived provider token. | | parseValueFreeCatalog(value) | function | Parses strict non-empty version-1 catalog metadata. | | createInfisicalMaintenanceClient(config, authorize) | function | Creates a separately policy-gated replacement client. | | InfisicalError | class | Safe provider error with stable code, optional status, and optional key. | | InfisicalClient / InfisicalClientConfig | types | Provider read, list, readiness, run, and configuration contracts. | | InfisicalAccessTokenProvider / OidcTokenProviderOptions | types | Token-provider contracts. | | SecretReadinessEntry / SecretReadinessReport | types | Value-free readiness result. | | InfisicalRunOptions / InfisicalRunResult | types | Child-process injection inputs and exit result. | | InfisicalMaintenanceClient | type | Permission-gated existing-secret replacement contract. | | InfisicalMutationPolicy / InfisicalMutationRequest | types | Consumer authorization callback and non-secret request metadata. | | ReplaceSecretOptions / ReplaceSecretResult | types | Optional verification callback and value-free replacement outcome. | | InfisicalErrorCode | type | Stable provider error-code union. |

Ownership boundary

The root entry owns resolution, custody, rotation, credential-lifecycle judgement, and revocation record-keeping, and the distribution manifest. It has no provider SDK, network calls, authentication, global adapter registry, project identifier, folder convention, or repository topology. The explicit ./infisical subpath owns provider integration and its value-safe operational CLI. Credential issuance, revocation execution, and rotation mechanics belong to an identity or access-control layer this package never calls — it records policy and state, and emits a queue; the plane's own explicit authority acts on it.

Hard boundaries

This value-free guarantee is scoped, not package-wide. It covers exactly the five verb modules no-value-escapes.test.ts proves it for -- custody, rotation, revocation, distribution, and credential -- 5 of this package's 22 non-test modules. Within that scope, and only within it: no code path reads, logs, prints, or transports a secret value. Those five modules handle names, owners, ages, stores, rotation policies, credential lifecycle metadata, revocation records, and digests only, and the test enforces this two ways -- statically, by asserting none of the five imports the resolution client/adapters, the Infisical subtree, or any other value-reading or I/O capability; and at runtime, by asserting every record these modules produce has a closed, exact field set that a decoy value-shaped string cannot ride through.

Everything outside that scope handles values, by design. The root entry's createSecretsClient(...).require() / .get() (see Resolution above) and the ./infisical subpath's client.get() and run() (which injects every requested value into a child process's environment -- see Infisical subpath and CLI below) all resolve or transport a real secret value directly. Auditing this package means auditing those entry points; the guarantee above does not extend to them, and no test in this package claims that it does. Every test is hermetic; no test resolves a real credential. Scanning for leaked values is a different package's job and stays there.

Requirements

Node 20+. ESM only. No runtime dependencies and no Infisical SDK or external CLI dependency.

Licence

MIT