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

content-credentials-view

v0.2.0

Published

React components for displaying and validating C2PA Content Credentials

Readme

content-credentials-view

React components for C2PA Content Credentials.

Read the provenance record attached to a file and render it so a person can understand what it actually says — who signed it, what happened to it since, and whether that signature still holds.

Early development. The API is not stable before 1.0, and minor versions may break it.

Built against @contentauth/c2pa-web, the current Content Authenticity Initiative SDK.


Install

pnpm add content-credentials-view @contentauth/c2pa-web react

react and @contentauth/c2pa-web are peer dependencies, so you own both versions. Copy the WASM binary somewhere you serve, once, at install time; this is the path you pass as wasmSrc below:

cp node_modules/@contentauth/c2pa-web/dist/resources/c2pa_bg.wasm public/c2pa.wasm

Ten lines that work

import { C2paProvider, ContentCredentials } from 'content-credentials-view';
import 'content-credentials-view/styles.css';

// Hoisted, not inline: this object is compared by value, and a new one on every
// render would discard the worker and the WASM instance.
const ANCHORS = { trustAnchors: '/anchors.pem' };

export function Photo({ file }: { file: File }) {
  return (
    <C2paProvider wasmSrc="/c2pa.wasm" trustAnchors={ANCHORS}>
      <ContentCredentials source={file} />
    </C2paProvider>
  );
}

source takes a File, a Blob, a URL string, or null to read nothing yet. Pass trustAnchors={null} to opt out of trust checking explicitly — everything still renders, and the status says the signer was not checked.

The stylesheet is optional. Without it the markup is semantic and readable, and every part carries a data-cc-part attribute to style against.

What this is, and what it is not

This renders C2PA manifest data. It does not verify that content is true. A valid manifest tells you an asset was signed by someone and has not changed since. It does not tell you the content is accurate. A camera can take a cryptographically perfect photograph of a screen showing a fake.

No manifest is the normal state of the internet. Fewer than 1% of published news images carry one, and that is among the most motivated publishers. Metadata is also stripped routinely by image pipelines, CDNs, and social uploads. This library never renders "no credentials" as a warning, and neither should you.

This cannot detect AI-generated images. Nothing reliably can. If that is what you need, this is the wrong tool.

A verdict here is this library's reading, not the verdict. What you see is @contentauth/c2pa-web's validation of the file, interpreted against the trust anchors you configured. Another C2PA tool — a different SDK version, a different trust list, a different reading of the specification — can and does reach a different conclusion about the same file. An independent security analysis in April 2026 found conforming validators returning contradictory verdicts on identical assets (arXiv:2604.24890v1, 23 April 2026, examining specification versions 2.2–2.4). Treat a status as one tool's answer, not a fact about the file.

Trust anchors are your responsibility

trustAnchors is a required prop and this library ships no default. A bundled trust list would mean the library decides who you trust, which is not a decision a dependency should make for you.

Without anchors, everything still renders, but the status is trust-not-configured and the interface says so. That state means a signature is present and intact, not this signer is trustworthy.

This matters more than it sounds. C2PA certificates are paid, there is no free issuer equivalent to Let's Encrypt, and few certificate authorities are on the official trust list. An untrusted signer is the common case, not an alarm — and it is genuinely different from a file that was tampered with. Telling those two apart clearly is the main reason this library exists.

Provider props

Wrap the subtree that inspects assets in one C2paProvider. It creates a single SDK instance — one web worker, one WASM instance — lazily, on the first read.

| Prop | Required | What it is | | -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | wasmSrc | yes | URL to fetch the WASM binary from, or an already-instantiated WebAssembly.Module. You host it. | | trustAnchors | yes | What to check signers against, or null to opt out explicitly. No default, for the reason above. | | workerSrc | no | URL of a self-hosted worker script, for a strict Content Security Policy that disallows blob: workers. Without it the SDK builds its worker from a blob: URL. | | settings | no | Further SDK settings, passed through untouched. trustAnchors is merged into settings.trust and wins over any trust keys set there. |

Memoize trustAnchors and settings. Both are compared by value rather than by identity, so an inline object literal does not rebuild the worker on every render. That comparison serializes the object, which means key order counts: { userAnchors, trustAnchors } and { trustAnchors, userAnchors } hold the same anchors but read as a change, and a change discards the worker and the WASM instance and builds new ones. Hoist both objects to module scope, or wrap them in useMemo, and the question never arises. workerSrc is compared by its href, so an inline new URL(...) is safe.

Each provider owns its own instance. Two providers in one tree create two workers even with identical props, and a nested provider shadows the one above it for its own subtree — a page-level provider plus a widget-level one costs two of everything. Mount a second one only when you mean to.

Limitations

  • Browser only. The underlying SDK assumes a browser and a Blob (upstream #147). There is no server-side path: no SSR, no React Server Components, no Next.js middleware. Components are client-only.
  • The WASM binary must be served by you or loaded from a CDN. Self-hosting instructions and a build-time checksum are in the docs.
  • Passing a URL instead of a Blob means the fetch is subject to CORS.
  • Revocation is not checked, and revoked almost never appears. The C2PA specification makes revocation checking optional, permits it only over OCSP, and forbids certificate revocation lists; @contentauth/c2pa-web 0.13.4 performs no revocation check by default, and the browser cannot reach an OCSP responder anyway. This library reports revoked only when the file itself carries evidence of revocation. A file that does not report revoked has not been checked — it has not been shown to be unrevoked. Verified against the SDK on 2026-08-14; see what the signature covers.
  • The signing time is not covered by the signature and can be replaced without breaking it. Every displayed time carries a note saying so. Same page.
  • Parts of a file can be excluded from the hash by the signer. This library renders nothing from the asset body, so nothing it shows can come from an excluded region — but that is a property of what it renders, not a check it performs. Same page.

Versioning

Tracks @contentauth/c2pa-web ^0.13.

The upstream SDK is pre-1.0 and moves quickly — 0.5 to 0.13 in under six months across 42 published versions. Upstream breaking changes will produce a minor version bump here until the SDK reaches 1.0. Tested version pairs are listed in COMPATIBILITY.md.

Privacy

Nothing leaves the browser. No telemetry, no analytics, no error reporting, no remote fonts. The only network requests are the WASM binary you configure and any URL you pass as a source.

Maintenance

Solo maintainer. Issues triaged weekly. Security reports answered within 48 hours. If I stop maintaining this, I will publish a deprecation notice pointing at a fork rather than let it rot silently.

License

MIT, matching c2pa-js and c2pa-rs.

API reference

Everything below is exported from content-credentials-view. Everything that does not import React is also exported from content-credentials-view/core, for using the read and interpretation logic without the components.

Components

| Export | Props | What it renders | | -------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------- | | C2paProvider | wasmSrc, trustAnchors, workerSrc?, settings? | Nothing of its own. Owns one SDK instance for its subtree. See Provider props. | | ContentCredentials | source, className?, labels?, children? | The whole thing: summary, claim fields, and provenance chain. | | ValidationSummary | interpretation, className?, labels?, children? | Headline, detail, learn-more link, and the raw codes behind a disclosure. | | ClaimSummary | manifest, className?, labels?, children? | Signer, issuer, signing time, generator, and assertion labels as a definition list. | | ProvenanceChain | tree, className?, labels?, children? | The ingredient tree, as an ARIA tree with arrow-key navigation. | | Disclosure | label, part, children, defaultOpen? | A <button aria-expanded> and its panel. |

Every component takes children as a render prop that replaces the contents while keeping the wrapper, its class, and its data-cc-* attributes.

Hooks

| Export | Signature | Returns | | ----------------------- | ------------------------------------------ | ----------------------------------------------------------- | | useContentCredentials | (source: CredentialSource) => CredentialsState | The read, discriminated on status. | | useC2pa | () => C2paContextValue | { load, error }. Throws outside a C2paProvider. |

CredentialsState is { status: 'idle' } | { status: 'loading' } | SettledCredentials. Narrowing away the first two gives you interpretation, activeManifest, tree, raw, and error with nothing left to null-check.

Reading and interpreting

| Export | Signature | What it does | | --------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------- | | readCredentials | (sdk, blob, { trustAnchors, signal? }) => Promise<CredentialsResult> | One read. Frees its reader on every path, including abort. | | interpret | (validation, options) => Interpretation | Codes to a status, a severity, and words. | | buildProvenanceTree | (store) => ProvenanceNode | The ingredient tree, with cycles and depth-capping marked rather than cut. | | isAbortError | (error) => boolean | Whether a caught value is an abort rather than a read failure. |

InterpretOptions takes trustConfigured (required), and optionally status, signer, format, supportedFormats, error, and messages.

Status

| Export | What it is | | ---------------------------------------------------------- | ------------------------------------------------------------------------ | | CREDENTIAL_STATUSES, STATUS_PRECEDENCE | Every status, and the order they resolve in. | | isError, isFailure, isPending, isPresent | Status predicates. none is not a failure and is not in isError. | | MAX_PROVENANCE_DEPTH | Where the ingredient walk stops. | | SignedAtAttestation, timestampAttestation | What a validator said about a signing time's timestamp, and how it is derived. |

Precedence, highest first: tampered → revoked → expired → unsupported → untrusted-signer → trust-not-configured → trusted → none.

revoked is in that list but is, in practice, unreachable — the SDK performs no revocation check. See Limitations.

Copy

| Export | What it is | | --------------------- | ---------------------------------------------------------------------------------------- | | CREDENTIAL_MESSAGES | Every status string. Clone and spread it to translate or re-voice. | | COMPONENT_LABELS | Field names, disclosure labels, provenance notes, and the signing-time notes. Same idea. | | SUPPORTED_FORMATS | The formats the reader handles, grouped for a person rather than as MIME types. |

Foreign values

Signer names, formats, assertion labels, codes, and error messages all come out of the file. They are normalized, length-capped, and fenced in quotes and bidi isolates before they reach any sentence this library wrote, so a signer called Reuters — verified original cannot borrow our voice.

| Export | What it is | | ------------------------------------- | ---------------------------------------------------------------------------- | | foreign(value, cap, style?) | Normalize, cap, and fence one value. style is 'quoted' or 'plain'. | | foreignRuns(text) | Split a message into our words and the file's. | | withForeign(text) | The same split as React nodes, each foreign run in its own <span>. | | withoutFencing(text) | Drop the isolates, keep the quotes. For plain-text output. | | FOREIGN_CAPS | The length caps, with the standard each is derived from. | | FOREIGN_OPEN, FOREIGN_CLOSE | The isolate characters, if you are splitting the string yourself. |

Styling hooks

Every element carries data-cc-part. The root and the summary also carry data-cc-status and data-cc-severity; chain nodes carry data-cc-node and data-cc-depth; foreign runs carry data-cc-part="foreign". Style against those rather than against the class names, which are not part of the API.

Documentation


Not affiliated with or endorsed by the C2PA or the Content Authenticity Initiative. For the official tooling see c2pa-js, the specification, and Content Credentials Verify.