Infrastructure

Release, package, and maintainer reputation

Use this when security reviewers or maintainers want to publish a threshold signal about software without exposing every reviewer to the project, sponsor, or attacker.

Use this when security reviewers or maintainers want to publish a threshold signal about software without exposing every reviewer to the project, sponsor, or attacker.

Fit

  • Status: supported today.
  • NIP-85 kind: 30385 identifier assertion.
  • Subject examples: npm:nostr-veil@0.14.0, package-digest:npm:nostr-veil@0.14.0:sha256:<hex>, git:https://github.com/forgesworn/nostr-veil@36f74b0, maintainer:github:forgesworn.
  • Canonical helpers: canonicalNpmPackageSubject, canonicalPackageDigestSubject, canonicalGitRepositorySubject, canonicalGithubRepositorySubject, and canonicalMaintainerSubject.
  • Helpers: contributeIdentifierAssertion, aggregateIdentifierContributions.
  • Proof version: v2 recommended.
  • Useful metrics: rank as safety, review confidence, or maintenance confidence.

Subject design

  • Use kind 30385 because package names, release artefacts, commits, and maintainer identities are external identifiers.
  • Decide exactly what is being scored: package name, exact version, tarball digest, repository commit, signed release artefact, maintainer account, or project namespace.
  • Prefer digest-bound subjects for high-risk decisions. A package name or version can be republished, mirrored, or confused across registries.
  • Keep maintainer trust, release readiness, vulnerability review, and malware suspicion as separate profiles unless a policy explicitly combines them.

What to publish

  • A kind 30385 assertion with the canonical software identifier in d and the software-review namespace in k.
  • A rank profile explaining whether the value means release safety, audit confidence, maintenance confidence, compromise suspicion, or deploy readiness.
  • Proof v2 tags from the security-review circle, plus threshold, aggregate method, expiry, and revocation policy.
  • Links or companion records for provenance, signatures, SBOMs, reproducible builds, CI, vulnerability scans, and human audit notes.

Implementation recipe

  1. Decide the exact subject: package name, package version, tarball digest, repository commit, release artefact, or maintainer identity.
  2. Canonicalise that subject before signing with the package, git repository, GitHub repository, or maintainer helper so every reviewer scores the same string.
  3. Define what rank means: safety, audit confidence, maintenance confidence, compromise suspicion, or release readiness.
  4. Require proof v2, then verify the expected identifier, namespace, circle, threshold, and freshness.
  5. Combine the score with provenance, signatures, SBOMs, reproducible builds, CI, and human audit results. In production, put those required external checks in companionEvidence so the verifier can fail closed when they are missing or stale.

Worked example

examples/use-cases/release-package-maintainer-reputation.ts
import {
  NIP85_KINDS,
  aggregateIdentifierContributions,
  contributeIdentifierAssertion,
  createTrustCircle,
} from 'nostr-veil'
import {
  defaultMembers,
  externalProfileKind,
  memberIndex,
  proofVersion,
  verifyUseCaseAssertion,
  withCreatedAt,
} from './_shared.js'

const slug = 'release-package-maintainer-reputation'
const packageId = 'npm:nostr-veil@0.14.0'
const circle = createTrustCircle(defaultMembers.map(member => member.pub))

const contributions = defaultMembers.map((reviewer, index) =>
  contributeIdentifierAssertion(
    circle,
    packageId,
    externalProfileKind,
    { rank: 86 + index },
    reviewer.priv,
    memberIndex(circle, reviewer.pub),
    { proofVersion },
  ),
)

export const assertion = withCreatedAt(aggregateIdentifierContributions(
  circle,
  packageId,
  externalProfileKind,
  contributions,
  { proofVersion },
))

export const result = verifyUseCaseAssertion(slug, assertion, {
  kind: NIP85_KINDS.IDENTIFIER,
  subject: packageId,
  subjectTag: 'k',
  subjectTagValue: externalProfileKind,
  circleId: circle.circleId,
  minDistinctSigners: 3,
  freshAfter: assertion.created_at - 300,
})

Companion evidence

For a reviewed-release gate, require external checks in the signed deployment policy, then derive the supplied evidence from registry, SBOM, and vulnerability observations. Do not hand-write pass records: the resolver fails closed when the metadata is missing, unsafe, stale, or for a different package subject. The default package evidence ids are npm-provenance, sbom, and vulnerability-feed. Use collectPackageReleaseCompanionEvidence() when the verifier can fetch npm metadata, an SBOM document, and an OSV-style vulnerability report itself. Use resolvePackageReleaseCompanionEvidence() when those observations came from your own release infrastructure.

const policy = createDeploymentPolicy(RELEASE_PACKAGE_MAINTAINER_REPUTATION_PROFILE, {
  companionEvidence: packageReleaseCompanionEvidenceRequirements(subject, { maxAgeSeconds: 300 }),
  expectedSubject: subject,
  // accepted circles, metrics, freshness, and signature policy omitted here
})

const companionEvidence = await collectPackageReleaseCompanionEvidence({
  checkedAt: now,
  fetch,
  osv: true,
  sbomUrl,
  subject,
  verifyProvenance,
})

const result = verifyProductionDeployment(assertionFromRelay, bundle, {
  companionEvidence,
  now,
  trustedPublishers,
})

Use canonicalPackageDigestSubject() when the verifier is acting on a tarball or release artefact digest. Use canonicalNpmPackageSubject() only when a package-version-level review is precise enough for the action. For digest-bound gates, pass the observed artefact digest into collectPackageReleaseCompanionEvidence() or resolvePackageReleaseCompanionEvidence() so npm-provenance cannot pass for a different tarball. The lower-level building blocks are fetchNpmPackageVersionEvidence(), normaliseSbomEvidence(), fetchJsonSbomEvidence(), and fetchOsvVulnerabilityReport(). They do not claim a package is safe; they only produce the observations that the companion-evidence resolver can check against the exact subject.

What to verify

  • Strict syntax and a valid proof v2.
  • Kind 30385, with d equal to the canonical package, release, digest, commit, or maintainer subject and k equal to the software-review namespace.
  • The veil-ring matches an accepted reviewer circle with the right expertise and independence for the package class.
  • The threshold, freshness, and rank meaning match the consuming policy.
  • The external artefact checks still pass: registry metadata, digest, signature, provenance, SBOM, CI, and any required audit evidence. If the policy requires companionEvidence, missing, failed, stale, or wrong-subject evidence must reject the deployment decision.

What this proves

  • Distinct members of the security-review circle scored the exact package identifier.
  • The aggregate score can be independently recomputed.
  • Proof v2 prevents the package contribution being replayed as a user or event reputation proof.

What not to claim

  • Do not claim nostr-veil scanned the code or proved the package is safe. It proves reviewer consensus over a subject.
  • Do not claim a maintainer score applies to every release, or a release score applies to future releases.
  • Do not claim the proof replaces provenance, signatures, vulnerability scanning, or reproducible-build checks.

Failure handling

  • Reject assertions for ambiguous package identifiers, wrong namespaces, untrusted reviewer circles, stale reviews, or mismatched artefact digests.
  • Publish a downgrade, incident, or revocation assertion when a release is yanked, compromised, rebuilt, or found vulnerable.
  • Require a new assertion for materially different artefacts, even if the package name and version look similar.
  • Escalate disagreement between independent reviewer circles to the package manager or security UI instead of hiding the conflict.

Operational requirements

Risk to handleRequired control
nostr-veil does not scan code.Run static analysis, dependency review, tests, malware checks, and human audit before reviewers contribute.
A threshold score does not prove a package is safe.Treat it as reviewer consensus and combine it with signatures, provenance, SBOMs, reproducible builds, and incident response.
The package identifier is not canonical by default.Define canonical identifiers for registry, version, digest, repo, commit, and maintainer subjects. Prefer digest-bound subjects for high-risk releases.
Maintainer and release risk are different.Publish separate assertions for package versions, release artefacts, repositories, and maintainer identities.

Policy choices

  • Is the subject a package name, exact version, tarball digest, repository commit, maintainer account, or release artefact?
  • What review work is required before a member may contribute?
  • Does a compromised release get a new assertion or a revocation profile?
  • Should package and maintainer scores be separate?

Companion evidence smoke test

The companion-evidence smoke test runs the collector path for this profile and then verifies the derived evidence ids. The checked-in public report is a deterministic fixture dry run so the page stays reproducible; operators can run the same harness in live mode when they want npm, OSV, NIP-05, HTTPS, and relay I/O.

Passed 2026-05-18T14:10:09.000Z
Subject
npm:nostr-veil@0.28.1
Mode
deterministic fixture dry run
Evidence
3/3 passing
  • Collector Returned All Evidence
  • Fail Closed When Controls Missing
  • npm-provenance: pass
  • sbom: pass
  • vulnerability-feed: pass

Run the deterministic check with npm run test:companion-evidence. For live I/O, run npm run test:companion-evidence:live.

NIP-85 kind reference

NIP-85 defines the assertion kind by the subject being scored. The kind number is part of the proof v2 context, so deployments should verify both the number and the subject hint tag.

30382 User assertion

Nostr pubkey subjects. Subject hint: p.

30383 Event assertion

Nostr event id subjects. Subject hint: e.

30384 Addressable event assertion

NIP-33 address subjects. Subject hint: a.

30385 NIP-73/external identifier assertion

packages, relays, domains, vendors, and other identifiers. Subject hint: k.

10040 Trusted service provider declaration

provider metadata, not a score assertion. Subject hint: provider tags.

Spec: NIP-85 trusted assertions.

Live relay test

The opt-in relay test signs this canonical example as real Nostr event data, publishes it to wss://relay.trotters.cc, fetches it back by id, and re-runs the application, syntax, Nostr signature, canonical tag, and proof checks.

Passed 2026-05-17T21:54:12.000Z
Events
1/1 fetched from relay
Proof
3/3 threshold from a 3-member ring
Run
tf7bic-3b3a5ae6d6
  • Canonical example passes locally
  • Relay stored and returned every signed event
  • Fetched Nostr event signatures are valid
  • Fetched tags match the canonical example
  • NIP-85 syntax validation passes
  • nostr-veil proof verification passes
  • Deployment profile verifier passes
329da289cc4b...3fee6f9a

Run the same check with npm run test:use-cases:relay -- --write docs/use-case-relay-checks.json.

Safety checks

Each canonical use-case example is also exercised by an adversarial test harness. These are the failure modes a production verifier should reject before acting on the score.

Tampered metric

Published scores must still match the signed contribution aggregate.

Wrong subject

The d tag and subject hint must stay bound to the signed v2 proof.

Wrong kind

The assertion kind must match the profile and the signed v2 context.

Proof downgrade

New deployment profiles require proof v2.

Duplicate signer

Repeated key images must not increase the signer count.

Insufficient threshold

Removing a signature must fail the profile threshold.

Stale assertion

created_at must remain inside the freshness window.

Unknown circle

The circle ID must be accepted by deployment policy.

Unsigned policy

verifyProductionDeployment() should require a signed deployment bundle from a trusted publisher.

Relay mutation

Fetched event content and tags must match the Nostr signature.

Use validateUseCaseProfileDefinition() for custom profiles, then verifyProductionDeployment() with trusted bundle publishers, signed relay events, accepted circle manifests, expected subject, freshness, and threshold policy so these checks are not left to application glue. For application UI and audit logs, use verifyProductionDeploymentReport() or createProductionDecisionReport() so failures include issue codes, remediation text, a recommended action, pass/fail/not-checked status for the controls, and the profile's proofClaims, proofLimitations, requiredControls, and recommendedActions.

Next examples