Skip to main content

Trust anchors

A trust anchor is a CA that the verifier accepts. Both verifier constructors take an options object with a trustAnchors array of PEM-encoded CA strings:

import { AssetSdk, StreamSdk } from '@limboai/verifox-sdk-verifier';

const assets = AssetSdk.create({ trustAnchors: [orgCaPem, partnerCaPem] });
const stream = StreamSdk.create(ledger, { trustAnchors: [orgCaPem] });

Your trustAnchors list is the policy for your own issuers. Add an anchor by appending it; revoke an anchor by removing it.

By default the verifier also trusts the official C2PA Conformance Program trust list on top of your list, so a manifest signed by any C2PA-conformant CA reaches Trusted even with an empty trustAnchors. To make your list the only policy — where an empty array rejects every chain — set trustC2paAnchors: false:

const assets = AssetSdk.create({ trustAnchors: [orgCaPem], trustC2paAnchors: false });
const stream = StreamSdk.create(ledger, { trustAnchors: [orgCaPem], trustC2paAnchors: false });

Why the verifier holds the trust list (not Limbo)

Trust is policy, and policy belongs to the relying party. Under the C2PA validation model (see C2PA 2.3 §15), a manifest reaches the Trusted state only when its signing chain validates against an anchor list known to the verifier. The spec does not prescribe who curates that list. Limbo holds it at the verifier for three reasons:

  1. Different relying parties trust different issuers. A newsroom's anchor list differs from a stock library's, and a platform ingesting uploads from many publishers needs a per-tenant list. A single canonical list inside the verifier would force every customer to inherit Limbo's judgment.
  2. Centralized trust is a central failure surface. If Limbo curated the list, one legal request, bug, or coercion at the central point would affect every customer's badges at once. Holding the list locally keeps that risk local.
  3. Browsers already operate this way. TLS trust roots ship with the platform or are pinned by the relying party. Limbo adopts the same model so existing security playbooks apply.

The trade-off is that anchor rotation becomes the customer's operational responsibility. The patterns below address that.

The default-on C2PA conformance list is not an exception to this model: it is the industry's own neutral conformance list, not Limbo's curation, it is applied in addition to your anchors rather than replacing them, and you can switch it off entirely with trustC2paAnchors: false. Your list remains the authority for your own issuers.

C2PA conformance trust list

The SDK bundles the official C2PA Conformance Program trust anchors and trusts them by default. These are the X.509 roots the C2PA certifies for conforming generator products, so content signed by any conformant issuer verifies as Trusted without you curating its CA yourself. The lists are also exported for inspection or explicit composition:

import { AssetSdk, C2PA_TRUST_LIST, C2PA_TSA_TRUST_LIST } from '@limboai/verifox-sdk-verifier';

// Default: official C2PA anchors + your own.
const verifier = AssetSdk.create({ trustAnchors: [orgCaPem] });

// Equivalent explicit form, with the bundled list turned off and re-added.
const explicit = AssetSdk.create({
trustAnchors: [orgCaPem, C2PA_TRUST_LIST(), C2PA_TSA_TRUST_LIST()],
trustC2paAnchors: false,
});

C2PA_TRUST_LIST() holds the signing-CA anchors; C2PA_TSA_TRUST_LIST() holds the time-stamping-authority anchors, applied together so timestamps validate under the same policy.

The bundled list can be stale

It is a snapshot vendored into the release, refreshed periodically — it lags the live C2PA list between releases. When you need the current list at all times, fetch it yourself (see Production options) and set trustC2paAnchors: false.

DEV_CA() is for development only

@limboai/verifox-sdk-verifier exports DEV_CA(), a trust anchor for Limbo's development signing environment.

import { AssetSdk, DEV_CA } from '@limboai/verifox-sdk-verifier';

const verifier = AssetSdk.create({ trustAnchors: [DEV_CA()] });
DEV_CA() is dev-only

DEV_CA() is a shared development anchor with no security guarantees. Anyone can sign content that it will trust. Do not ship DEV_CA() in a release build.

Production options

Two patterns cover most deployments.

PatternRotation flowRe-deploy needed?Network at boot?When to choose
Bake into bundleShip a new releaseYesNoRelease cadence accommodates anchor changes
Fetch at bootUpdate the ops endpointNoYes (TLS to a pinned host)Ops adds or removes anchors without a client redeploy

Bake into bundle:

import orgCa from './trust/org-ca.pem?raw';
import publicList from './trust/c2pa-public.pem?raw';

const verifier = AssetSdk.create({ trustAnchors: [orgCa, publicList] });

Fetch at boot:

const anchors: string[] = await fetch('https://config.example.com/trust.json').then(r => r.json());
const verifier = AssetSdk.create({ trustAnchors: anchors });

See also