From d2c6f58e39819c5c23252c74f36e88c3319d3d03 Mon Sep 17 00:00:00 2001 From: Tim Murphy Date: Tue, 5 May 2026 23:35:29 -0400 Subject: [PATCH 1/7] fix: restore ingredient thumbnails with c2pa-web --- src/lib/asset.ts | 55 +++++++++++++++------ src/lib/resolveThumbnails.ts | 66 ++++++++++++++++++++++++++ src/routes/verify/stores/c2paReader.ts | 5 +- 3 files changed, 111 insertions(+), 15 deletions(-) create mode 100644 src/lib/resolveThumbnails.ts diff --git a/src/lib/asset.ts b/src/lib/asset.ts index c3d10613a..e0bab907b 100644 --- a/src/lib/asset.ts +++ b/src/lib/asset.ts @@ -35,7 +35,12 @@ import { } from './selectors/validationResult'; import { selectWeb3 } from './selectors/web3Info'; import { selectWebsite } from './selectors/website'; -import { loadThumbnail, type ThumbnailInfo } from './thumbnail'; +import { toAbsoluteIdentifier } from './resolveThumbnails'; +import { + loadThumbnail, + type ThumbnailInfo, + type ThumbnailResult, +} from './thumbnail'; import type { Disposable } from './types'; const MANIFEST_STORE_MIME_TYPE = 'application/x-c2pa-manifest-store'; @@ -126,9 +131,11 @@ export function getIngredientDataType( export async function resultToAssetMap({ manifestStore, source, + thumbnails, }: { manifestStore: ManifestStore; source: Blob | File; + thumbnails: Map; }): Promise { const assetMap: AssetDataMap = {}; const disposers: (() => void)[] = []; @@ -170,6 +177,26 @@ export async function resultToAssetMap({ } } + async function lookupThumbnail( + ref: Thumbnail | null | undefined, + containingManifestLabel: string, + ): Promise { + const blob = ref?.identifier + ? thumbnails.get(toAbsoluteIdentifier(ref.identifier, containingManifestLabel)) + : undefined; + + if (!blob) { + return loadThumbnail(ref?.format, undefined); + } + + const url = URL.createObjectURL(blob); + + return loadThumbnail(ref?.format, { + url, + dispose: () => URL.revokeObjectURL(url), + }); + } + if (!isManifest && (!manifestStore || hasError || hasOtgp)) { // Fallback for raw files: render the original source file directly const url = URL.createObjectURL(source); @@ -224,14 +251,11 @@ export async function resultToAssetMap({ runtimeValidationStatuses: ManifestLabelValidationStatusMap, id: string, ): Promise { - const manifest = manifestStore.manifests?.[manifestStore.active_manifest || '']; + const activeManifestLabel = manifestStore.active_manifest || ''; + const manifest = manifestStore.manifests?.[activeManifestLabel]; if (!manifest) throw new Error('Active manifest not found'); - // 0.17.x SDK dropped internal thumbnail generation. Pass undefined to skip WASM fetch. - let thumbnail = await loadThumbnail( - manifest.thumbnail?.format, - undefined - ); + let thumbnail = await lookupThumbnail(manifest.thumbnail, activeManifestLabel); if ( !thumbnail.info && @@ -241,7 +265,7 @@ export async function resultToAssetMap({ // Fallback for active manifest: render the original source file directly const url = URL.createObjectURL(source); thumbnail = await loadThumbnail( - source.type, + source.type, { url, dispose: () => URL.revokeObjectURL(url) } ); } @@ -256,6 +280,7 @@ export async function resultToAssetMap({ manifestStore, runtimeValidationStatuses, id, + activeManifestLabel, ), manifestData: await getManifestData(manifest, rootValidationResult), dataType: null, @@ -277,15 +302,14 @@ export async function resultToAssetMap({ manifestStore: ManifestStore, runtimeValidationStatuses: ManifestLabelValidationStatusMap, id: string, + containingManifestLabel: string, ): Promise { const ingredientManifestLabel = ingredient.active_manifest; const ingredientManifest = ingredientManifestLabel ? manifestStore.manifests?.[ingredientManifestLabel] : null; - // 0.17.x SDK dropped internal thumbnail generation. Skip WASM fetch for ingredients. - const thumbnail = await loadThumbnail( - ingredient.thumbnail?.format, - undefined, - ); + const thumbnail = ingredientManifest && ingredientManifest.thumbnail + ? await lookupThumbnail(ingredientManifest.thumbnail, ingredientManifestLabel ?? containingManifestLabel) + : await lookupThumbnail(ingredient.thumbnail, containingManifestLabel); const activeManifestValidationResults = ingredient.validation_results?.activeManifest ?? undefined; @@ -307,12 +331,13 @@ export async function resultToAssetMap({ title: ingredient.title ?? null, thumbnail: thumbnail.info, mimeType: ingredient.format || '', - children: (showChildren && ingredientManifest?.ingredients) + children: (showChildren && ingredientManifest?.ingredients && ingredientManifestLabel) ? await processIngredients( ingredientManifest.ingredients, manifestStore, runtimeValidationStatuses, id, + ingredientManifestLabel, ) : [], manifestData: await getManifestData(ingredientManifest, validationResult), @@ -462,6 +487,7 @@ export async function resultToAssetMap({ manifestStore: ManifestStore, runtimeValidationStatuses: ManifestLabelValidationStatusMap, id: string, + containingManifestLabel: string, ): Promise { const ingredientIds = ingredients.map(async (ingredient, idx) => { const ingredientId = `${id}.${idx}`; @@ -471,6 +497,7 @@ export async function resultToAssetMap({ manifestStore, runtimeValidationStatuses, ingredientId, + containingManifestLabel, ); return ingredientId; diff --git a/src/lib/resolveThumbnails.ts b/src/lib/resolveThumbnails.ts new file mode 100644 index 000000000..910c2b59a --- /dev/null +++ b/src/lib/resolveThumbnails.ts @@ -0,0 +1,66 @@ +// Copyright 2021-2024 Adobe, Copyright 2025 The C2PA Contributors + +import type { ManifestStore, Reader } from '@contentauth/c2pa-web'; + +/** + * Convert a relative thumbnail URI to absolute form. Already absolute + * identifiers pass through unchanged. + */ +export function toAbsoluteIdentifier( + identifier: string, + manifestLabel: string, +): string { + if (identifier.startsWith('self#jumbf=/c2pa/')) return identifier; + + if (identifier.startsWith('self#jumbf=')) { + const path = identifier.slice('self#jumbf='.length); + + return `self#jumbf=/c2pa/${manifestLabel}/${path}`; + } + + return identifier; +} + +/** Pre-fetch every embedded thumbnail's bytes, keyed by absolute identifier. */ +export async function resolveThumbnails( + manifestStore: ManifestStore, + reader: Reader, +): Promise> { + const refs = new Map(); + + for (const [label, manifest] of Object.entries(manifestStore.manifests || {})) { + if (manifest.thumbnail?.identifier && manifest.thumbnail.format) { + refs.set( + toAbsoluteIdentifier(manifest.thumbnail.identifier, label), + manifest.thumbnail.format, + ); + } + + for (const ingredient of manifest.ingredients || []) { + if (ingredient.thumbnail?.identifier && ingredient.thumbnail.format) { + refs.set( + toAbsoluteIdentifier(ingredient.thumbnail.identifier, label), + ingredient.thumbnail.format, + ); + } + } + } + + const entries = await Promise.all( + Array.from(refs.entries()).map( + async ([identifier, format]): Promise<[string, Blob] | null> => { + try { + const bytes = await reader.resourceToBytes(identifier); + + return [identifier, new Blob([new Uint8Array(bytes)], { type: format })]; + } catch (err) { + console.warn(`Failed to resolve thumbnail ${identifier}:`, err); + + return null; + } + }, + ), + ); + + return new Map(entries.filter((e): e is [string, Blob] => e !== null)); +} diff --git a/src/routes/verify/stores/c2paReader.ts b/src/routes/verify/stores/c2paReader.ts index 8180b0d99..22c1bac0b 100644 --- a/src/routes/verify/stores/c2paReader.ts +++ b/src/routes/verify/stores/c2paReader.ts @@ -1,6 +1,7 @@ // Copyright 2021-2024 Adobe, Copyright 2025 The C2PA Contributors import { resultToAssetMap, type AssetDataMap } from '$lib/asset'; +import { resolveThumbnails } from '$lib/resolveThumbnails'; import { getLegacySdk, getSdk, getOfficialToolkitSettings, getLegacyToolkitSettings } from '$lib/sdk'; import type { Loadable } from '$lib/types'; import { @@ -226,8 +227,10 @@ export function createC2paReader(): C2paReaderStore { }); } + const thumbnails = await resolveThumbnails(finalStore, currentReader); + const { assetMap, dispose: assetMapDisposer } = - await resultToAssetMap({ manifestStore: finalStore, source }); + await resultToAssetMap({ manifestStore: finalStore, source, thumbnails }); dispose = () => { assetMapDisposer(); From ab926d391280878e94dab985881d324a16b1960c Mon Sep 17 00:00:00 2001 From: Tim Murphy Date: Wed, 6 May 2026 22:11:57 -0400 Subject: [PATCH 2/7] fix: avoid silent fallback in ingredient thumbnail lookup --- src/lib/asset.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/asset.ts b/src/lib/asset.ts index e0bab907b..15e8179c8 100644 --- a/src/lib/asset.ts +++ b/src/lib/asset.ts @@ -307,8 +307,8 @@ export async function resultToAssetMap({ const ingredientManifestLabel = ingredient.active_manifest; const ingredientManifest = ingredientManifestLabel ? manifestStore.manifests?.[ingredientManifestLabel] : null; - const thumbnail = ingredientManifest && ingredientManifest.thumbnail - ? await lookupThumbnail(ingredientManifest.thumbnail, ingredientManifestLabel ?? containingManifestLabel) + const thumbnail = ingredientManifestLabel && ingredientManifest?.thumbnail + ? await lookupThumbnail(ingredientManifest.thumbnail, ingredientManifestLabel) : await lookupThumbnail(ingredient.thumbnail, containingManifestLabel); const activeManifestValidationResults = From 5885cae550a6f98d3cd1e0224be81909d53feec2 Mon Sep 17 00:00:00 2001 From: Tim Murphy Date: Thu, 7 May 2026 09:43:15 -0400 Subject: [PATCH 3/7] fix: suppress ingredient thumbnails from untrusted manifests --- src/lib/asset.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/lib/asset.ts b/src/lib/asset.ts index 15e8179c8..d92bd6be5 100644 --- a/src/lib/asset.ts +++ b/src/lib/asset.ts @@ -307,9 +307,26 @@ export async function resultToAssetMap({ const ingredientManifestLabel = ingredient.active_manifest; const ingredientManifest = ingredientManifestLabel ? manifestStore.manifests?.[ingredientManifestLabel] : null; + // The code prioritizes the signed ingredient's own c2pa.thumbnail.claim, if present, + // over the c2pa.thumbnail.ingredient assertion that the consuming manifest's signer + // references via ingredient.thumbnail. When a signed ingredient already provides + // its own claim thumbnail, a consuming signer's separate ingredient assertion + // thumbnail could be an intentional override pointing at a misleading image. The + // claim thumbnail more faithfully represents the ingredient. + // + // When no claim thumbnail is available for an ingredient, we fall back to + // c2pa.thumbnail.ingredient only if the containing manifest is trusted. If it is + // untrusted, we suppress the thumbnail so an untrusted signer can't force a false + // thumbnail to display for an ingredient that has no claim thumbnail of its own. A + // signed ingredient's own claim thumbnail is still shown when present (untrusted + // state is flagged in the UI). + const containingManifestTrust = (manifestStore.manifests?.[containingManifestLabel] as Manifest & { trust_source?: 'legacy' | 'none' | 'official' })?.trust_source; + const thumbnail = ingredientManifestLabel && ingredientManifest?.thumbnail ? await lookupThumbnail(ingredientManifest.thumbnail, ingredientManifestLabel) - : await lookupThumbnail(ingredient.thumbnail, containingManifestLabel); + : containingManifestTrust !== 'none' + ? await lookupThumbnail(ingredient.thumbnail, containingManifestLabel) + : await loadThumbnail(undefined, undefined); const activeManifestValidationResults = ingredient.validation_results?.activeManifest ?? undefined; From f6e732547a9add6189e7d6c10e2f0ca835ceafd2 Mon Sep 17 00:00:00 2001 From: Tim Murphy Date: Thu, 7 May 2026 10:45:00 -0400 Subject: [PATCH 4/7] fix: detect untrusted containing manifest via validation status --- src/lib/asset.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/asset.ts b/src/lib/asset.ts index d92bd6be5..a63bb04ae 100644 --- a/src/lib/asset.ts +++ b/src/lib/asset.ts @@ -320,11 +320,12 @@ export async function resultToAssetMap({ // thumbnail to display for an ingredient that has no claim thumbnail of its own. A // signed ingredient's own claim thumbnail is still shown when present (untrusted // state is flagged in the UI). - const containingManifestTrust = (manifestStore.manifests?.[containingManifestLabel] as Manifest & { trust_source?: 'legacy' | 'none' | 'official' })?.trust_source; + const containingManifestUntrusted = (runtimeValidationStatuses[containingManifestLabel] ?? []) + .some((s) => s.code.includes('signingCredential.untrusted') || s.code.includes('signingCredential.invalid')); const thumbnail = ingredientManifestLabel && ingredientManifest?.thumbnail ? await lookupThumbnail(ingredientManifest.thumbnail, ingredientManifestLabel) - : containingManifestTrust !== 'none' + : !containingManifestUntrusted ? await lookupThumbnail(ingredient.thumbnail, containingManifestLabel) : await loadThumbnail(undefined, undefined); From 2793b4dcf3bbc1bfac78620087f98d8f24bf8769 Mon Sep 17 00:00:00 2001 From: Tim Murphy Date: Fri, 5 Jun 2026 21:14:07 -0400 Subject: [PATCH 5/7] fix: update c2pa-web to 0.9.0 --- package.json | 2 +- pnpm-lock.yaml | 48 +++++++++++++----------------------------------- 2 files changed, 14 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 333d3f24d..29adf208a 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "dependencies": { "@adobe/spectrum-css-workflow-icons": "^5.0.0", "@ccx-public/ingest": "file:etc/packages/ingest-4.3.0.tgz", - "@contentauth/c2pa-web": "^0.7.1", + "@contentauth/c2pa-web": "^0.9.0", "@intl/adobe-locales": "file:etc/packages/adobe-locales-2.35.2.tgz", "@mapbox/mapbox-sdk": "^0.13.7", "@netlify/functions": "^2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 743ac479d..3fd70775f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: file:etc/packages/ingest-4.3.0.tgz version: file:etc/packages/ingest-4.3.0.tgz '@contentauth/c2pa-web': - specifier: ^0.7.1 - version: 0.7.1 + specifier: ^0.9.0 + version: 0.9.0 '@intl/adobe-locales': specifier: file:etc/packages/adobe-locales-2.35.2.tgz version: file:etc/packages/adobe-locales-2.35.2.tgz @@ -1265,14 +1265,14 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@contentauth/c2pa-types@0.4.3': - resolution: {integrity: sha512-uJJPpv8kS/wLaokb4lPGycyK9yf663H4Db/QDkdWuxxVM8d0UR4ZaIrJXWBeYO9M8gqH8JYJA/WBRkchboJWSQ==} + '@contentauth/c2pa-types@0.4.6': + resolution: {integrity: sha512-CLRasu8IFh1zNTqa1/nnR8t5BqWefJQ3EbIZGJAjn1RrPTq+LVOKLeN8qhDVZhOpsY7wUTNT3qVfBzMGQFWQaQ==} - '@contentauth/c2pa-wasm@0.5.1': - resolution: {integrity: sha512-1ka6GRXkkCnGShUd5F5YiAXRXSa+IXPxMIvS6Lm1Epk06KqT/4WWFiDgKaM4ZK7Sxez8eq5QMnrG2rtW7ZYwWw==} + '@contentauth/c2pa-wasm@0.6.3': + resolution: {integrity: sha512-lC86ECrIzLyV0AdsRuQ5A5SsBuGqXrJsvh/3JoXHufjGIIWiCDXIx5Qf6BLjsHQEWw+CN3BqaX/fPX4rj08rSQ==} - '@contentauth/c2pa-web@0.7.1': - resolution: {integrity: sha512-fZTpIDw+DnL+saDp1+PUnq6dhx4EAIBr1Z0hVCf8eHo4Phk6Ptbjnms9R4jU2AnftjwJ1ArQpCW6q3QjI1+ckg==} + '@contentauth/c2pa-web@0.9.0': + resolution: {integrity: sha512-EdKPkAoowH7cvCbLevr4KfsYdG9PUO6a3z4Na34wO+oyXYjzG2pyMJ2+CtELOaNXeZuzhx1lXPuFT4KKxbqKHQ==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -1812,92 +1812,78 @@ packages: resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.0': resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.0': resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.0': resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.0': resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.0': resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.0': resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.34.3': resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.34.3': resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-ppc64@0.34.3': resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.34.3': resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.34.3': resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.3': resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.34.3': resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.34.3': resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} @@ -3364,49 +3350,41 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] - libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -10558,14 +10536,14 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@contentauth/c2pa-types@0.4.3': {} + '@contentauth/c2pa-types@0.4.6': {} - '@contentauth/c2pa-wasm@0.5.1': {} + '@contentauth/c2pa-wasm@0.6.3': {} - '@contentauth/c2pa-web@0.7.1': + '@contentauth/c2pa-web@0.9.0': dependencies: - '@contentauth/c2pa-types': 0.4.3 - '@contentauth/c2pa-wasm': 0.5.1 + '@contentauth/c2pa-types': 0.4.6 + '@contentauth/c2pa-wasm': 0.6.3 highgain: 0.1.0 ts-deepmerge: 7.0.3 From 7943b9e3ad4cec4b71bff847ffb0674ca0fdbf2a Mon Sep 17 00:00:00 2001 From: Tim Murphy Date: Tue, 16 Jun 2026 11:24:52 -0400 Subject: [PATCH 6/7] fix: update c2pa-web to 0.11.0 --- package.json | 2 +- pnpm-lock.yaml | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 29adf208a..d8abc4169 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "dependencies": { "@adobe/spectrum-css-workflow-icons": "^5.0.0", "@ccx-public/ingest": "file:etc/packages/ingest-4.3.0.tgz", - "@contentauth/c2pa-web": "^0.9.0", + "@contentauth/c2pa-web": "^0.11.0", "@intl/adobe-locales": "file:etc/packages/adobe-locales-2.35.2.tgz", "@mapbox/mapbox-sdk": "^0.13.7", "@netlify/functions": "^2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3fd70775f..3675795d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: file:etc/packages/ingest-4.3.0.tgz version: file:etc/packages/ingest-4.3.0.tgz '@contentauth/c2pa-web': - specifier: ^0.9.0 - version: 0.9.0 + specifier: ^0.11.0 + version: 0.11.0 '@intl/adobe-locales': specifier: file:etc/packages/adobe-locales-2.35.2.tgz version: file:etc/packages/adobe-locales-2.35.2.tgz @@ -1265,14 +1265,14 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@contentauth/c2pa-types@0.4.6': - resolution: {integrity: sha512-CLRasu8IFh1zNTqa1/nnR8t5BqWefJQ3EbIZGJAjn1RrPTq+LVOKLeN8qhDVZhOpsY7wUTNT3qVfBzMGQFWQaQ==} + '@contentauth/c2pa-types@0.6.0': + resolution: {integrity: sha512-b9xPv9seNw0lY2CJtz4TeYl5Z3w5+ZHyhZtgG3M0jj7chtvY0C60LuruByxM8H/7tAiuyEp8Hhrx08pQZ/Fpyg==} - '@contentauth/c2pa-wasm@0.6.3': - resolution: {integrity: sha512-lC86ECrIzLyV0AdsRuQ5A5SsBuGqXrJsvh/3JoXHufjGIIWiCDXIx5Qf6BLjsHQEWw+CN3BqaX/fPX4rj08rSQ==} + '@contentauth/c2pa-wasm@0.8.0': + resolution: {integrity: sha512-QiTx7UIy0YZ2Os3zB9msT4bLqaKE3VnuTL28Io3L/4LIf5t2648iVKBOMhAFt+RMapNAdt4sE1A/Jsw3RjJRKg==} - '@contentauth/c2pa-web@0.9.0': - resolution: {integrity: sha512-EdKPkAoowH7cvCbLevr4KfsYdG9PUO6a3z4Na34wO+oyXYjzG2pyMJ2+CtELOaNXeZuzhx1lXPuFT4KKxbqKHQ==} + '@contentauth/c2pa-web@0.11.0': + resolution: {integrity: sha512-8DBV28iuRqvzrTEDZ7OvMwZ3a9AeKp3lcAinfIeHJ96fa1uUDJ8dC4kAlN/yjCljs0WSeucbwWeBwO4loEkaIA==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -10536,14 +10536,14 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@contentauth/c2pa-types@0.4.6': {} + '@contentauth/c2pa-types@0.6.0': {} - '@contentauth/c2pa-wasm@0.6.3': {} + '@contentauth/c2pa-wasm@0.8.0': {} - '@contentauth/c2pa-web@0.9.0': + '@contentauth/c2pa-web@0.11.0': dependencies: - '@contentauth/c2pa-types': 0.4.6 - '@contentauth/c2pa-wasm': 0.6.3 + '@contentauth/c2pa-types': 0.6.0 + '@contentauth/c2pa-wasm': 0.8.0 highgain: 0.1.0 ts-deepmerge: 7.0.3 From 4402acb8b74f8ad233f85f66afbd1c474e18a98e Mon Sep 17 00:00:00 2001 From: Tim Murphy Date: Tue, 16 Jun 2026 13:22:30 -0400 Subject: [PATCH 7/7] chore: update copyright header and comment formatting --- src/lib/asset.ts | 28 +++++++++++++++------------- src/lib/resolveThumbnails.ts | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/lib/asset.ts b/src/lib/asset.ts index 53de06860..4033553ef 100644 --- a/src/lib/asset.ts +++ b/src/lib/asset.ts @@ -314,19 +314,21 @@ export async function resultToAssetMap({ const ingredientManifestLabel = ingredient.active_manifest || (ingredient as ExtendedIngredient).activeManifest; const ingredientManifest = ingredientManifestLabel ? manifestStore.manifests?.[ingredientManifestLabel] : null; - // The code prioritizes the signed ingredient's own c2pa.thumbnail.claim, if present, - // over the c2pa.thumbnail.ingredient assertion that the consuming manifest's signer - // references via ingredient.thumbnail. When a signed ingredient already provides - // its own claim thumbnail, a consuming signer's separate ingredient assertion - // thumbnail could be an intentional override pointing at a misleading image. The - // claim thumbnail more faithfully represents the ingredient. - // - // When no claim thumbnail is available for an ingredient, we fall back to - // c2pa.thumbnail.ingredient only if the containing manifest is trusted. If it is - // untrusted, we suppress the thumbnail so an untrusted signer can't force a false - // thumbnail to display for an ingredient that has no claim thumbnail of its own. A - // signed ingredient's own claim thumbnail is still shown when present (untrusted - // state is flagged in the UI). + /** + * The code prioritizes the signed ingredient's own c2pa.thumbnail.claim, if present, + * over the c2pa.thumbnail.ingredient assertion that the consuming manifest's signer + * references via ingredient.thumbnail. When a signed ingredient already provides + * its own claim thumbnail, a consuming signer's separate ingredient assertion + * thumbnail could be an intentional override pointing at a misleading image. The + * claim thumbnail more faithfully represents the ingredient. + * + * When no claim thumbnail is available for an ingredient, we fall back to + * c2pa.thumbnail.ingredient only if the containing manifest is trusted. If it is + * untrusted, we suppress the thumbnail so an untrusted signer can't force a false + * thumbnail to display for an ingredient that has no claim thumbnail of its own. A + * signed ingredient's own claim thumbnail is still shown when present (untrusted + * state is flagged in the UI). + */ const containingManifestUntrusted = (runtimeValidationStatuses[containingManifestLabel] ?? []) .some((s) => s.code.includes('signingCredential.untrusted') || s.code.includes('signingCredential.invalid')); diff --git a/src/lib/resolveThumbnails.ts b/src/lib/resolveThumbnails.ts index 910c2b59a..78de569a6 100644 --- a/src/lib/resolveThumbnails.ts +++ b/src/lib/resolveThumbnails.ts @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Adobe, Copyright 2025 The C2PA Contributors +// Copyright 2021-2024 Adobe, Copyright 2026 The C2PA Contributors import type { ManifestStore, Reader } from '@contentauth/c2pa-web';