v0.3.2
This commit is contained in:
+4
-137
@@ -1,138 +1,5 @@
|
|||||||
# ---> Node
|
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
node_modules/
|
||||||
jspm_packages/
|
dist/
|
||||||
|
artifacts/
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
coverage/
|
||||||
web_modules/
|
*.log
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# vuepress v2.x temp and cache directory
|
|
||||||
.temp
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# vitepress build output
|
|
||||||
**/.vitepress/dist
|
|
||||||
|
|
||||||
# vitepress cache directory
|
|
||||||
**/.vitepress/cache
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
|
||||||
.docusaurus
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2026 egmont11
|
Copyright (c) 2026 SlopDetect contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
in the Software without restriction, including without limitation the rights
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
following conditions:
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
The above copyright notice and this permission notice shall be included in all
|
||||||
portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|||||||
@@ -1,3 +1,74 @@
|
|||||||
# Slop-detect
|
# SlopDetect
|
||||||
|
|
||||||
Firefox rozšíření pro detekci AI generovaného obsahu.
|
SlopDetect is a privacy-first Firefox extension that inspects webpages for machine-readable AI provenance and separately flags clusters of common generated-site patterns.
|
||||||
|
|
||||||
|
It currently provides:
|
||||||
|
|
||||||
|
- Automatic scanning of ordinary HTTP and HTTPS pages after load
|
||||||
|
- An in-page result card for flagged pages that collapses into a persistent score chip
|
||||||
|
- Local C2PA/Content Credentials parsing and validation for accessible images
|
||||||
|
- Recognition of AI-related C2PA digital source types
|
||||||
|
- Separate states for valid, detected, invalid, disclosed, inaccessible, and unknown evidence
|
||||||
|
- Publisher-disclosure discovery in HTML metadata, structured data, and visible labels
|
||||||
|
- A bounded, on-device heuristic scan for common visual, typography, layout, motion, and copy patterns
|
||||||
|
- Unsigned generator-metadata clues for images whose C2PA data was removed but whose export metadata survived
|
||||||
|
- A mixed-provenance page summary and optional in-page image badges
|
||||||
|
- A stable adapter API for provider-specific image and text watermark detectors
|
||||||
|
- On-device processing with no telemetry or remote detector calls
|
||||||
|
|
||||||
|
SlopDetect does **not** infer that unmarked content is human-made. Its page-pattern score is a suspicion signal, not proof that AI authored a website. Deliberate human designs can match the same patterns, and generated sites can avoid them.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Requirements: Node.js 22 or newer and Firefox 128 or newer.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
npm run check
|
||||||
|
npm test
|
||||||
|
npm run lint
|
||||||
|
npm run build
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
The unpacked extension is written to `dist/`. A distributable XPI-compatible ZIP is produced in `artifacts/` with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run package
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- `src/content/` discovers visible page text, image URLs, disclosures, and bounded rendered-style heuristics, and renders image badges.
|
||||||
|
- `src/background/` fetches bounded image resources, verifies C2PA manifests, coordinates watermark adapters, caches results, and aggregates page status.
|
||||||
|
- `src/popup/` presents the current scan and requests optional cross-origin image access.
|
||||||
|
- `src/options/` manages privacy, badge, and resource-limit settings.
|
||||||
|
- `src/shared/` contains the message, evidence, and result contracts.
|
||||||
|
|
||||||
|
Automatic operation requires persistent access to HTTP and HTTPS pages so the content script can inspect their rendered DOM and the background process can read image bytes. It does not run on Firefox settings pages or other privileged browser documents. Private-network image requests are rejected unless the scanned page has the same origin, redirects are rejected, credentials are omitted, and asset size/concurrency are bounded.
|
||||||
|
|
||||||
|
Automatic scans begin shortly after `document_idle`, inspect the largest visible images first, and perform at most one bounded follow-up scan when a page adds substantial dynamic content. The default automatic image limit is 10 with a 10 MB per-image automatic cap; manual toolbar scans can inspect up to 50 using the configured size cap. Pages with no reportable status receive no in-page UI and no toolbar badge. Dismissing an alert keeps it dismissed until the page URL changes.
|
||||||
|
|
||||||
|
## Detection semantics
|
||||||
|
|
||||||
|
- **Verified AI:** a trusted, valid credential contains an AI-generation/manipulation source type.
|
||||||
|
- **Detected AI:** a readable credential or compatible watermark indicates AI origin without a trusted signer.
|
||||||
|
- **Publisher disclosed:** the publisher explicitly labels content as AI-generated.
|
||||||
|
- **AI-style patterns suspected:** at least two deterministic clues cross the configured suspicion threshold. The page remains origin-unknown.
|
||||||
|
- **Generator metadata detected:** unsigned image bytes name a known AI image tool or contain a recognizable workflow block. This can be removed or forged.
|
||||||
|
- **Invalid credential:** provenance data exists but validation reports an integrity or trust failure.
|
||||||
|
- **No supported evidence:** no compatible evidence was found; origin remains unknown.
|
||||||
|
|
||||||
|
Text watermark schemes are currently provider-specific. `src/background/detectors.ts` intentionally ships with an empty registry until a public detector can be integrated and tested. This avoids false claims from generic AI-writing classifiers.
|
||||||
|
|
||||||
|
The heuristic catalog currently covers purple/cyan and radial gradients, gradient text, side-tab borders, hairline shadows, extreme radii, icon tiles, oversized and italic-serif heroes, hero eyebrows, overused fonts, nested and repeated card grids, decorative looping motion, marquees, generic SaaS vocabulary, em-dash cadence, manufactured contrast, “theater” framing, and stock product promises. It is inspired by the deterministic patterns documented in the [Impeccable slop catalog](https://impeccable.style/slop/), but implemented independently and conservatively.
|
||||||
|
|
||||||
|
## C2PA runtime
|
||||||
|
|
||||||
|
The build packages `@contentauth/c2pa-web`, its worker, and its WebAssembly binary locally. Snapshots of the official C2PA signing and timestamp trust lists are also bundled. Remote manifests and OCSP fetching are disabled, so inspecting an asset does not cause the C2PA SDK to contact locations named by that asset. No executable code is loaded from a CDN. The extension CSP permits WebAssembly while continuing to restrict script and worker sources to the extension itself.
|
||||||
|
|
||||||
|
`@contentauth/c2pa-web` 0.14.4 applies an HTTPS-only check to explicit worker URLs. Firefox identifies packaged extension resources with `moz-extension:` instead, so `src/background/c2pa.ts` contains a narrow compatibility adapter: the SDK's scheme check sees its required HTTPS shape, while the browser receives and loads the original same-extension worker URL. The adapter rejects non-extension worker locations.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Source code in this repository is available under the MIT License. Third-party packages retain their respective licenses.
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Security policy
|
||||||
|
|
||||||
|
Please report security issues privately to the repository owner rather than opening a public issue containing exploit details.
|
||||||
|
|
||||||
|
SlopDetect treats webpage content, resource URLs, metadata, and provenance manifests as untrusted input. The implementation bounds asset size and concurrency, rejects unsafe URL schemes and cross-origin private-network targets, omits credentials from media requests, disallows redirects, validates extension messages, and renders page annotations through an isolated Shadow DOM without injecting untrusted HTML.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Third-party notices
|
||||||
|
|
||||||
|
SlopDetect uses `@contentauth/c2pa-web` and related Content Authenticity Initiative packages under their published licenses. See the package metadata and the [c2pa-js repository](https://github.com/contentauth/c2pa-js) for the applicable terms.
|
||||||
|
|
||||||
|
The bundled `C2PA-TRUST-LIST.pem` and `C2PA-TSA-TRUST-LIST.pem` files are snapshots from the [C2PA Conformance Program public repository](https://github.com/c2pa-org/conformance-public), published under CC BY 4.0. The Coalition for Content Provenance and Authenticity maintains those lists; inclusion does not imply endorsement of SlopDetect.
|
||||||
|
|
||||||
|
Other runtime and development dependencies are listed in `package.json` and retain their respective licenses.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['dist/**', 'artifacts/**', 'node_modules/**'] },
|
||||||
|
js.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
files: ['scripts/**/*.mjs'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: { URL: 'readonly' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['src/**/*.{ts,tsx}', 'tests/**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
Blob: 'readonly',
|
||||||
|
browser: 'readonly',
|
||||||
|
crypto: 'readonly',
|
||||||
|
document: 'readonly',
|
||||||
|
fetch: 'readonly',
|
||||||
|
globalThis: 'readonly',
|
||||||
|
HTMLElement: 'readonly',
|
||||||
|
HTMLImageElement: 'readonly',
|
||||||
|
MutationObserver: 'readonly',
|
||||||
|
Node: 'readonly',
|
||||||
|
URL: 'readonly',
|
||||||
|
window: 'readonly'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
Generated
+6440
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "slopdetect",
|
||||||
|
"version": "0.3.2",
|
||||||
|
"private": true,
|
||||||
|
"description": "A privacy-first Firefox extension for AI provenance and generated-site pattern detection.",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "node scripts/clean.mjs",
|
||||||
|
"build": "node scripts/build.mjs",
|
||||||
|
"check": "tsc --noEmit",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"package": "npm run build && web-ext build --source-dir dist --artifacts-dir artifacts --overwrite-dest",
|
||||||
|
"start": "npm run build && web-ext run --source-dir dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@contentauth/c2pa-web": "0.14.4",
|
||||||
|
"preact": "10.29.8",
|
||||||
|
"webextension-polyfill": "0.12.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "10.0.1",
|
||||||
|
"@types/firefox-webext-browser": "143.0.0",
|
||||||
|
"@types/webextension-polyfill": "0.12.6",
|
||||||
|
"esbuild": "0.28.2",
|
||||||
|
"eslint": "10.9.1",
|
||||||
|
"typescript": "6.0.3",
|
||||||
|
"typescript-eslint": "8.69.0",
|
||||||
|
"vitest": "4.1.11",
|
||||||
|
"web-ext": "10.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">
|
||||||
|
<rect width="96" height="96" rx="22" fill="#17152a"/>
|
||||||
|
<path d="M48 17 76 29v19c0 18-11 29-28 34C31 77 20 66 20 48V29l28-12Z" fill="#7c5cff"/>
|
||||||
|
<path d="m33 49 10 10 21-23" fill="none" stroke="#fff" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 337 B |
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "SlopDetect",
|
||||||
|
"version": "0.3.2",
|
||||||
|
"description": "Automatically inspect AI provenance and flag common generated-site patterns.",
|
||||||
|
"browser_specific_settings": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "slopdetect@local",
|
||||||
|
"strict_min_version": "140.0",
|
||||||
|
"data_collection_permissions": {
|
||||||
|
"required": ["none"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gecko_android": {
|
||||||
|
"strict_min_version": "142.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"permissions": ["storage"],
|
||||||
|
"host_permissions": ["http://*/*", "https://*/*"],
|
||||||
|
"content_scripts": [{
|
||||||
|
"matches": ["http://*/*", "https://*/*"],
|
||||||
|
"js": ["content.js"],
|
||||||
|
"run_at": "document_idle"
|
||||||
|
}],
|
||||||
|
"background": {
|
||||||
|
"scripts": ["background.js"]
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"default_title": "Scan this page with SlopDetect",
|
||||||
|
"default_popup": "popup.html",
|
||||||
|
"default_icon": "icons/icon.svg"
|
||||||
|
},
|
||||||
|
"options_ui": {
|
||||||
|
"page": "options.html",
|
||||||
|
"open_in_tab": true
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"48": "icons/icon.svg",
|
||||||
|
"96": "icons/icon.svg"
|
||||||
|
},
|
||||||
|
"content_security_policy": {
|
||||||
|
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; object-src 'self'"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>SlopDetect settings</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script src="options.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>SlopDetect</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,656 @@
|
|||||||
|
Subject CN=Google C2PA Media Services 1P ICA G3, O=Google LLC, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC3DCCAmOgAwIBAgIUQfqlIUd2IVjaf5ss/439Fgke7j4wCgYIKoZIzj0EAwMw
|
||||||
|
QzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkdvb2dsZSBMTEMxHzAdBgNVBAMMFkdv
|
||||||
|
b2dsZSBDMlBBIFJvb3QgQ0EgRzMwHhcNMjUwNTA4MjIzNjI2WhcNMzAwNTA4MjIz
|
||||||
|
NjI2WjBRMQswCQYDVQQGEwJVUzETMBEGA1UECgwKR29vZ2xlIExMQzEtMCsGA1UE
|
||||||
|
AwwkR29vZ2xlIEMyUEEgTWVkaWEgU2VydmljZXMgMVAgSUNBIEczMHYwEAYHKoZI
|
||||||
|
zj0CAQYFK4EEACIDYgAEuCPlUxSiltqnB2lx2ES7FK+TVZWmAxRzzDjTzKZ8umoq
|
||||||
|
yvCqSLOkZBrOieaLqrp+rnzt0EADWWH3X62NqzEXRewW6rb/lS7VXkVCM02gC0Zg
|
||||||
|
JW7+PCsZgLoUBUQ+nkN5o4IBCDCCAQQwFwYDVR0gBBAwDjAMBgorBgEEAYPoXgEB
|
||||||
|
MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSUEGDAWBggrBgEFBQcDBAYKKwYBBAGD6F4C
|
||||||
|
ATASBgNVHRMBAf8ECDAGAQH/AgEAMGQGCCsGAQUFBwEBBFgwVjAsBggrBgEFBQcw
|
||||||
|
AoYgaHR0cDovL3BraS5nb29nL2MycGEvcm9vdC1nMy5jcnQwJgYIKwYBBQUHMAGG
|
||||||
|
Gmh0dHA6Ly9jMnBhLW9jc3AucGtpLmdvb2cvMB8GA1UdIwQYMBaAFJxc2IlTQ+da
|
||||||
|
1YHbA94ZfwQqKi2qMB0GA1UdDgQWBBTae+G9tCyKheAQ1muax0rx+t/2NzAKBggq
|
||||||
|
hkjOPQQDAwNnADBkAjACxtEE3NW13bwN1u/51ericNF6rkEhYVESDO6Jqb5cX37H
|
||||||
|
wg0X9S2rH+vXaoFZIHsCMC03wCKKomDHgqV47UtyyHpZlo5IZACW72Xdc4gipdWM
|
||||||
|
EmhvPk88dvxbYtn+LVd9zA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Google C2PA Mobile A 1P ICA G3 L1, O=Google LLC, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC2TCCAmCgAwIBAgIUdEQo46dHfO396b1NFkYHqblfVzAwCgYIKoZIzj0EAwMw
|
||||||
|
QzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkdvb2dsZSBMTEMxHzAdBgNVBAMMFkdv
|
||||||
|
b2dsZSBDMlBBIFJvb3QgQ0EgRzMwHhcNMjUwNTA4MjIzNjI3WhcNMzAwNTA4MjIz
|
||||||
|
NjI3WjBOMQswCQYDVQQGEwJVUzETMBEGA1UECgwKR29vZ2xlIExMQzEqMCgGA1UE
|
||||||
|
AwwhR29vZ2xlIEMyUEEgTW9iaWxlIEEgMVAgSUNBIEczIEwxMHYwEAYHKoZIzj0C
|
||||||
|
AQYFK4EEACIDYgAEiso7FbPFqaTmLWL6vJq0Q0FTLVILWVg1nyG1kR2JkEc5E9WW
|
||||||
|
k62YSJreXbX6axJqDGecSk5kvqcb9EVJ+wymonRaqy7Gk9c6fi9Hr1mK2mfJZRlX
|
||||||
|
CIXeMXeneVG8NMJ8o4IBCDCCAQQwFwYDVR0gBBAwDjAMBgorBgEEAYPoXgEBMA4G
|
||||||
|
A1UdDwEB/wQEAwIBBjAfBgNVHSUEGDAWBggrBgEFBQcDBAYKKwYBBAGD6F4CATAS
|
||||||
|
BgNVHRMBAf8ECDAGAQH/AgEAMGQGCCsGAQUFBwEBBFgwVjAsBggrBgEFBQcwAoYg
|
||||||
|
aHR0cDovL3BraS5nb29nL2MycGEvcm9vdC1nMy5jcnQwJgYIKwYBBQUHMAGGGmh0
|
||||||
|
dHA6Ly9jMnBhLW9jc3AucGtpLmdvb2cvMB8GA1UdIwQYMBaAFJxc2IlTQ+da1YHb
|
||||||
|
A94ZfwQqKi2qMB0GA1UdDgQWBBSu91pCmLI7HftmLBUUmRZ631vvlDAKBggqhkjO
|
||||||
|
PQQDAwNnADBkAjA6bs5IFiYOZjmln6Bii/ShnzrqTn4GxKdCZhP79ul9iPz+mQyn
|
||||||
|
pORTyTY84SsxzicCMCI2tAB6n8FQ5BkN1apEUG5gcJlrrNd1rdqtBNXJZHXqYG1m
|
||||||
|
7T4gYBB4PHNYYFBGfA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Google C2PA Mobile A 1P ICA G3, O=Google LLC, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC1jCCAl2gAwIBAgIUXKoTHSLJjkXHIxvKY29YmxgOzoowCgYIKoZIzj0EAwMw
|
||||||
|
QzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkdvb2dsZSBMTEMxHzAdBgNVBAMMFkdv
|
||||||
|
b2dsZSBDMlBBIFJvb3QgQ0EgRzMwHhcNMjUwNTA4MjIzNjI4WhcNMzAwNTA4MjIz
|
||||||
|
NjI4WjBLMQswCQYDVQQGEwJVUzETMBEGA1UECgwKR29vZ2xlIExMQzEnMCUGA1UE
|
||||||
|
AwweR29vZ2xlIEMyUEEgTW9iaWxlIEEgMVAgSUNBIEczMHYwEAYHKoZIzj0CAQYF
|
||||||
|
K4EEACIDYgAEx5s4bjuNRDlYKtdj4Dd14esDWyBAk+fNPHFGlDcWX2lTdxgeeKYF
|
||||||
|
caw7ZdhD5fWAqUZ++M5wdXXUoGHDFAwfPmTaRKzZupUu7uMFZtolBzAuT2I51meH
|
||||||
|
EfMHU95kRkTIo4IBCDCCAQQwDgYDVR0PAQH/BAQDAgEGMB8GA1UdJQQYMBYGCCsG
|
||||||
|
AQUFBwMEBgorBgEEAYPoXgIBMBIGA1UdEwEB/wQIMAYBAf8CAQAwFwYDVR0gBBAw
|
||||||
|
DjAMBgorBgEEAYPoXgEBMGQGCCsGAQUFBwEBBFgwVjAsBggrBgEFBQcwAoYgaHR0
|
||||||
|
cDovL3BraS5nb29nL2MycGEvcm9vdC1nMy5jcnQwJgYIKwYBBQUHMAGGGmh0dHA6
|
||||||
|
Ly9jMnBhLW9jc3AucGtpLmdvb2cvMB8GA1UdIwQYMBaAFJxc2IlTQ+da1YHbA94Z
|
||||||
|
fwQqKi2qMB0GA1UdDgQWBBQOccStRBCOVzAFalyyAs6XkvF+IDAKBggqhkjOPQQD
|
||||||
|
AwNnADBkAjBjAj22bX3vKs/3Q4K0qE7jPwY3BljcaGyhTg9Gk6ni0+3TxuluLCq+
|
||||||
|
zITXauG0qy0CMFfS4bUxKdwCjWOnamLCA9xiaO82my0UN0kvxNMFflLsmeO4PL+N
|
||||||
|
I/u3EJ347o+4jA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Google C2PA Mobile B 1P ICA G3 L1, O=Google LLC, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC2zCCAmCgAwIBAgIUBpMF/2hh9GWQEhKn4uebRk7j2PcwCgYIKoZIzj0EAwMw
|
||||||
|
QzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkdvb2dsZSBMTEMxHzAdBgNVBAMMFkdv
|
||||||
|
b2dsZSBDMlBBIFJvb3QgQ0EgRzMwHhcNMjUwNTA4MjIzNjI3WhcNMzAwNTA4MjIz
|
||||||
|
NjI3WjBOMQswCQYDVQQGEwJVUzETMBEGA1UECgwKR29vZ2xlIExMQzEqMCgGA1UE
|
||||||
|
AwwhR29vZ2xlIEMyUEEgTW9iaWxlIEIgMVAgSUNBIEczIEwxMHYwEAYHKoZIzj0C
|
||||||
|
AQYFK4EEACIDYgAEKGv3Fd3J1vOPfrR744lpYlGc05lZ57UyVDKGHTPj71PuwW19
|
||||||
|
oHO932WXXf+K4jBkotgXeBChrhrTiUGxBdrmYRB1m6MyAJ/wT3xw06YRcGxoiW0b
|
||||||
|
IhbMN4YNIAMW76Noo4IBCDCCAQQwFwYDVR0gBBAwDjAMBgorBgEEAYPoXgEBMA4G
|
||||||
|
A1UdDwEB/wQEAwIBBjAfBgNVHSUEGDAWBggrBgEFBQcDBAYKKwYBBAGD6F4CATAS
|
||||||
|
BgNVHRMBAf8ECDAGAQH/AgEAMGQGCCsGAQUFBwEBBFgwVjAsBggrBgEFBQcwAoYg
|
||||||
|
aHR0cDovL3BraS5nb29nL2MycGEvcm9vdC1nMy5jcnQwJgYIKwYBBQUHMAGGGmh0
|
||||||
|
dHA6Ly9jMnBhLW9jc3AucGtpLmdvb2cvMB8GA1UdIwQYMBaAFJxc2IlTQ+da1YHb
|
||||||
|
A94ZfwQqKi2qMB0GA1UdDgQWBBRLBf7gDA1hOViJqHtuE8btC3KB8TAKBggqhkjO
|
||||||
|
PQQDAwNpADBmAjEAhfcU5E3IKMRw/Wxd9YfUdxTpi5HM99JiHG0KmFxcPGd8tDBA
|
||||||
|
XkoxEV/OYIEeyVl4AjEA1QH3goidp0++w4rQ6P8wbdw3BtlkSJpGsQ4WQc0i5bWz
|
||||||
|
KyN/WtDOZj/RG3+bqaw+
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Google C2PA Mobile B 1P ICA G3, O=Google LLC, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC1zCCAl2gAwIBAgIUac3GAHq5vTwO71pE75SNo8wcJVcwCgYIKoZIzj0EAwMw
|
||||||
|
QzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkdvb2dsZSBMTEMxHzAdBgNVBAMMFkdv
|
||||||
|
b2dsZSBDMlBBIFJvb3QgQ0EgRzMwHhcNMjUwNTA4MjIzNjI3WhcNMzAwNTA4MjIz
|
||||||
|
NjI3WjBLMQswCQYDVQQGEwJVUzETMBEGA1UECgwKR29vZ2xlIExMQzEnMCUGA1UE
|
||||||
|
AwweR29vZ2xlIEMyUEEgTW9iaWxlIEIgMVAgSUNBIEczMHYwEAYHKoZIzj0CAQYF
|
||||||
|
K4EEACIDYgAEbns1saor1bu/tYfe1ozY2yZwBgkcmIcmr552foAPn5v4zmS61OEs
|
||||||
|
C5VwKHBgi9uTuXZ8SSV1KSPyysqrgm542XLPWsMlkuWRfneQBANeLAk6aEBqDASo
|
||||||
|
DJ7Aoube5tuao4IBCDCCAQQwDgYDVR0PAQH/BAQDAgEGMB8GA1UdJQQYMBYGCCsG
|
||||||
|
AQUFBwMEBgorBgEEAYPoXgIBMBIGA1UdEwEB/wQIMAYBAf8CAQAwFwYDVR0gBBAw
|
||||||
|
DjAMBgorBgEEAYPoXgEBMGQGCCsGAQUFBwEBBFgwVjAsBggrBgEFBQcwAoYgaHR0
|
||||||
|
cDovL3BraS5nb29nL2MycGEvcm9vdC1nMy5jcnQwJgYIKwYBBQUHMAGGGmh0dHA6
|
||||||
|
Ly9jMnBhLW9jc3AucGtpLmdvb2cvMB8GA1UdIwQYMBaAFJxc2IlTQ+da1YHbA94Z
|
||||||
|
fwQqKi2qMB0GA1UdDgQWBBT4N2ZAjPoDJR1+UgU4KwafcJT5mDAKBggqhkjOPQQD
|
||||||
|
AwNoADBlAjEAg2SjBAAmAOvOLd1kKYZQzkiD6KSXe4+3zALTs5SQCK3xmmkxsFzJ
|
||||||
|
bBCIiuTbxsrIAjAhQm+LpSxSwkIZiwWI7rGiJMv7BCj38HhiQpmR5lr+anbBOQty
|
||||||
|
UsWhYsCGlDmiWzA=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Google C2PA Root CA G3, O=Google LLC, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICLjCCAbOgAwIBAgIUUZK4AROFKiXQZ1UG7FG6qPGc1g8wCgYIKoZIzj0EAwMw
|
||||||
|
QzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkdvb2dsZSBMTEMxHzAdBgNVBAMMFkdv
|
||||||
|
b2dsZSBDMlBBIFJvb3QgQ0EgRzMwIBcNMjUwNTA4MjIzMjIxWhgPMjA1MDA1MDgy
|
||||||
|
MjMyMjFaMEMxCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApHb29nbGUgTExDMR8wHQYD
|
||||||
|
VQQDDBZHb29nbGUgQzJQQSBSb290IENBIEczMHYwEAYHKoZIzj0CAQYFK4EEACID
|
||||||
|
YgAEhv9f/juKcPpe3Fm7eAISMuSyS+tBxn0aYHC83J+qAsFWREGN9p6PN/OBoouP
|
||||||
|
zpOFRxvrlWoWmAI3p1lXyPg4E3eg7SNChgopUIpihGu6qlhP8rLXf3p8bhI5FTQ2
|
||||||
|
MaF2o2YwZDASBgNVHRMBAf8ECDAGAQH/AgECMA4GA1UdDwEB/wQEAwIBBjAfBgNV
|
||||||
|
HSMEGDAWgBScXNiJU0PnWtWB2wPeGX8EKiotqjAdBgNVHQ4EFgQUnFzYiVND51rV
|
||||||
|
gdsD3hl/BCoqLaowCgYIKoZIzj0EAwMDaQAwZgIxAIyVEe5bdUMkk6BthEWy9QSE
|
||||||
|
Mb74BOyK8/8pgMX0NPwLlo1ikLNY78ov+k21vZrEZQIxANQ91muDXgPjAMAkzAlK
|
||||||
|
i32Z9VBB37ynTveKVC7ofTW0ZFfIIYYpWUR1+C4m2yRkOQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=SSL.com C2PA RSA Root CA 2025, O=SSL Corporation, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=SSL.com C2PA ECC Root CA 2025, O=SSL Corporation, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICRTCCAcugAwIBAgIUHTAeXakkTyAFDkZfyu8YyCO9ubgwCgYIKoZIzj0EAwIw
|
||||||
|
TzEmMCQGA1UEAwwdU1NMLmNvbSBDMlBBIEVDQyBSb290IENBIDIwMjUxGDAWBgNV
|
||||||
|
BAoMD1NTTCBDb3Jwb3JhdGlvbjELMAkGA1UEBhMCVVMwIBcNMjUwNzE4MTU1MTQ5
|
||||||
|
WhgPMjA1MDA3MTIxNTUxNDlaME8xJjAkBgNVBAMMHVNTTC5jb20gQzJQQSBFQ0Mg
|
||||||
|
Um9vdCBDQSAyMDI1MRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xCzAJBgNVBAYT
|
||||||
|
AlVTMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEo3WOe9pIesN2XWe6KQdiUO+cU1mr
|
||||||
|
+Bs8opia0I+IA5m8oYhBJmJWPxLea7PH6tlW6f5wqkIOaeJkJ7X1pz3IHPjO8qkX
|
||||||
|
imKjiUwt/B7IoEj6rhoqkAV4AMO2BMxbS1MUo2YwZDASBgNVHRMBAf8ECDAGAQH/
|
||||||
|
AgECMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUU50WGoV8zfcRPowvdPbBkJ1O
|
||||||
|
g6swHwYDVR0jBBgwFoAUU50WGoV8zfcRPowvdPbBkJ1Og6swCgYIKoZIzj0EAwID
|
||||||
|
aAAwZQIxALxN0Q+F+9KXOnYUKcW70UuxgitT8iwjTBGCIXJbJw4XaMtVdVGf4HwY
|
||||||
|
NITBgr+rWwIwczO4trazVV27OTCl8xNR0IH4UuyFgBVuzMsIj8IPkQw5mVhtvhY7
|
||||||
|
nNgp3Dyy2LeE
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject emailAddress=ca@trufo.ai, CN=Trufo C2PA Root CA (2025, ECC P384), OU=CA Division, O=Trufo Inc., L=New York, ST=New York, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDOTCCAr6gAwIBAgIUbXqcPd1r9yQm/fznG9RlSWyXiwswCgYIKoZIzj0EAwMw
|
||||||
|
gagxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3
|
||||||
|
IFlvcmsxEzARBgNVBAoMClRydWZvIEluYy4xFDASBgNVBAsMC0NBIERpdmlzaW9u
|
||||||
|
MRowGAYJKoZIhvcNAQkBFgtjYUB0cnVmby5haTEsMCoGA1UEAwwjVHJ1Zm8gQzJQ
|
||||||
|
QSBSb290IENBICgyMDI1LCBFQ0MgUDM4NCkwHhcNMjUxMjMwMTkwNTAzWhcNNDUx
|
||||||
|
MjI1MTkwNTAzWjCBqDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREw
|
||||||
|
DwYDVQQHDAhOZXcgWW9yazETMBEGA1UECgwKVHJ1Zm8gSW5jLjEUMBIGA1UECwwL
|
||||||
|
Q0EgRGl2aXNpb24xGjAYBgkqhkiG9w0BCQEWC2NhQHRydWZvLmFpMSwwKgYDVQQD
|
||||||
|
DCNUcnVmbyBDMlBBIFJvb3QgQ0EgKDIwMjUsIEVDQyBQMzg0KTB2MBAGByqGSM49
|
||||||
|
AgEGBSuBBAAiA2IABAp0qnhIwMtN6LeGdBVtHLPn85ecetr/lqcXFk8ypK9ukJzU
|
||||||
|
8LLv55Kh/MYTgEnuIKEOPhDxLDRdahc0mAjRnql4kLk395abw9WZjrBPek3qjv0q
|
||||||
|
ITR8VPYFABuZ5FRKx6OBpjCBozAdBgNVHQ4EFgQUA9Vfr36D5QQdWYAnSjT/Rf3r
|
||||||
|
SXgwHwYDVR0jBBgwFoAUA9Vfr36D5QQdWYAnSjT/Rf3rSXgwEgYDVR0TAQH/BAgw
|
||||||
|
BgEB/wIBAjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgorBgEEAYPoPAEB
|
||||||
|
MCQwIgYIKwYBBQUHAgEWFmh0dHBzOi8vdHJ1Zm8uYWkvY3BjcHMwCgYIKoZIzj0E
|
||||||
|
AwMDaQAwZgIxAMUeYWZyxS2maiVkNETL29RAuLn/gHYTkt97l6evXwHLN46v28mI
|
||||||
|
39BIf6slyWnrCwIxAPRs/FJ+DoA0d/PCkrF946S+pG7vRqLnjB9OhMdmrMPvzaqx
|
||||||
|
KQYOBVx7SE4Kz48W8A==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=vivo Content Provenance and Authenticity Root CA, O=vivo Mobile Communication Co., Ltd., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC0zCCAjSgAwIBAgIJAMIN3t2/xTDAMAoGCCqGSM49BAMEMHYxOTA3BgNVBAMM
|
||||||
|
MHZpdm8gQ29udGVudCBQcm92ZW5hbmNlIGFuZCBBdXRoZW50aWNpdHkgUm9vdCBD
|
||||||
|
QTEsMCoGA1UECgwjdml2byBNb2JpbGUgQ29tbXVuaWNhdGlvbiBDby4sIEx0ZC4x
|
||||||
|
CzAJBgNVBAYTAkNOMCAXDTI1MDkxODA5MDkxM1oYDzIwNTUwOTExMDkwOTEzWjB2
|
||||||
|
MTkwNwYDVQQDDDB2aXZvIENvbnRlbnQgUHJvdmVuYW5jZSBhbmQgQXV0aGVudGlj
|
||||||
|
aXR5IFJvb3QgQ0ExLDAqBgNVBAoMI3Zpdm8gTW9iaWxlIENvbW11bmljYXRpb24g
|
||||||
|
Q28uLCBMdGQuMQswCQYDVQQGEwJDTjCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE
|
||||||
|
ANQigNuQ6RnYFSK2BWkaksR3fRFFytX4nzX3E4hLV/N/7m/XttNHxNDase0cAXwR
|
||||||
|
9pO23p6fb/9NbifILv2wzPn2ASMr306Y2frv/UjJ5J6WgHet2OFywRaPjFjJWRuk
|
||||||
|
4JCy0qoSxyyx230g8GYdacZzIqSzVTW7/Xz4mjnWDTOB/Db+o2YwZDASBgNVHRMB
|
||||||
|
Af8ECDAGAQH/AgECMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUgB0dwwUcpK4u
|
||||||
|
7az42vKC2y+nnY8wHwYDVR0jBBgwFoAUgB0dwwUcpK4u7az42vKC2y+nnY8wCgYI
|
||||||
|
KoZIzj0EAwQDgYwAMIGIAkIB9cjlRbw8I0+0VAIOENCEzKOfp2risHuo3KVqyaSV
|
||||||
|
K/T6KzMNvpq3ihA6ZjnuH9i5y5XWV//awWbFJDSuT0Bq+pwCQgHuthBjGmDl/nyc
|
||||||
|
r3MxJErDVYZBh4b140LQBU60MtiPERVYwme03ukGImPW/OKbEWYmnhHyMAf1ym/3
|
||||||
|
UK/lOv3G2w==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Xiaomi Root CA(EC-P384), O=Xiaomi Inc., L=Beijing, ST=Beijing, C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICeDCCAf6gAwIBAgITAJQBmyrWaBV3JOroB8uDgC0MxDAKBggqhkjOPQQDAzBp
|
||||||
|
MQswCQYDVQQGEwJDTjEQMA4GA1UECBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamlu
|
||||||
|
ZzEUMBIGA1UEChMLWGlhb21pIEluYy4xIDAeBgNVBAMTF1hpYW9taSBSb290IENB
|
||||||
|
KEVDLVAzODQpMCAXDTI1MDkyNTAxMjI0MFoYDzIwNTAwOTE5MDEyMjQwWjBpMQsw
|
||||||
|
CQYDVQQGEwJDTjEQMA4GA1UECBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzEU
|
||||||
|
MBIGA1UEChMLWGlhb21pIEluYy4xIDAeBgNVBAMTF1hpYW9taSBSb290IENBKEVD
|
||||||
|
LVAzODQpMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEDOfbCvQLZmlcAW7uOnEiCJ3K
|
||||||
|
pVxu+qYxrwBDbSIRsDc2xg9o94+8pX+mveme5SsSsKM/0ouu5+ECwhkxRof/1BDQ
|
||||||
|
21cK+n4HBY0G7P3RltI6moeDv4bdVvC4ni+yJpFvo2YwZDAdBgNVHQ4EFgQUm8DQ
|
||||||
|
DCIUeaz1o+Cc286YCgebcc4wHwYDVR0jBBgwFoAUm8DQDCIUeaz1o+Cc286YCgeb
|
||||||
|
cc4wEgYDVR0TAQH/BAgwBgEB/wIBAjAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0E
|
||||||
|
AwMDaAAwZQIwUfj3heoA8iZfAMqHrZwarUM71v3Y1gqlltdgOzv9mix5O/EDxHcQ
|
||||||
|
m26a9NrrMTXmAjEAgwjR42dkym44QlV+QXR05vuMf1o/bdAXmJKvhjDgPTK9VYRa
|
||||||
|
gFLaTqFtjuZfDg3L
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=DigiCert RSA4096 Root for C2PA G1, O=DigiCert, Inc., C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=DigiCert ECC P384 Root for C2PA G1, O=DigiCert, Inc., C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICSTCCAc+gAwIBAgIQCZ2yarc/T5eBQYVHCJTdyjAKBggqhkjOPQQDAzBTMQsw
|
||||||
|
CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKzApBgNVBAMTIkRp
|
||||||
|
Z2lDZXJ0IEVDQyBQMzg0IFJvb3QgZm9yIEMyUEEgRzEwIBcNMjUwODI3MDAwMDAw
|
||||||
|
WhgPMjA1MDA4MjYyMzU5NTlaMFMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdp
|
||||||
|
Q2VydCwgSW5jLjErMCkGA1UEAxMiRGlnaUNlcnQgRUNDIFAzODQgUm9vdCBmb3Ig
|
||||||
|
QzJQQSBHMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNHiy4U9/SpPSnQ3ogU+q3mn
|
||||||
|
M/f9zmLuqF8krS0LNUC0T81Nu57WCGSb6149CVpkRRC6BYlfGcBWVYXar4aX10oX
|
||||||
|
ggSiFPi93ovkAfUN9wikKqVu6amI7tRQPKXJ95UsFaNmMGQwEgYDVR0TAQH/BAgw
|
||||||
|
BgEB/wIBAjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPQYEa4JgolO/Ju0M/b0
|
||||||
|
370CTpQ2MB8GA1UdIwQYMBaAFPQYEa4JgolO/Ju0M/b0370CTpQ2MAoGCCqGSM49
|
||||||
|
BAMDA2gAMGUCMQCg31cyC7If2VPMUvqEr08WB6QhRVpAa+GNRlKUOJlSMq/y2tm+
|
||||||
|
80RTxpV2RsYC9UsCMGkKQ0Yx4FIs1RhYtXVIgTyHrSxGne4/457DlIS+IMIJN5Yd
|
||||||
|
gMNj6G6tzM47QDGl2w==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=DigiCert RSA4096 L1 Claim Signing ICA for C2PA G1, O=DigiCert, Inc., C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGxDCCBKygAwIBAgIQJ4BVBbBmDXnyAtI0JLlo5TANBgkqhkiG9w0BAQwFADBS
|
||||||
|
MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKjAoBgNVBAMT
|
||||||
|
IURpZ2lDZXJ0IFJTQTQwOTYgUm9vdCBmb3IgQzJQQSBHMTAeFw0yNTA4MjcwMDAw
|
||||||
|
MDBaFw0zMDA4MjYyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdp
|
||||||
|
Q2VydCwgSW5jLjE6MDgGA1UEAxMxRGlnaUNlcnQgUlNBNDA5NiBMMSBDbGFpbSBT
|
||||||
|
aWduaW5nIElDQSBmb3IgQzJQQSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
|
||||||
|
AgoCggIBAKiOlc644cuaOoCElG+yGHBmAtfXRw3fSBfRTCf/9yaMQiyEGCxP8HKT
|
||||||
|
fxtdqD8zmXqePLCLzGM2ccGJx3w5RzpFdOhopwGIKsTm+C1wHmZc5Lws5wst7rFQ
|
||||||
|
stF8nIN9TBP5H6elZcoNaUhqCJ0bxmeXp6J0iiRKiuLF2j0Grfv4cBL8+XlLRE3g
|
||||||
|
Of1yqEsEZktfK9qeCUuugltxXwL1WP8IKgtkMeLo0QZQSAg4R+gyX6C1j64nlAtV
|
||||||
|
RewqSrug+Rkquz94doEscaq3w+ypp8pJ9ovpn9Zdx6PArh4uK90YB/ThB92Voi2x
|
||||||
|
K8T9D8xnzpONwBfKPiUS502160mZuF09Xgm6zBNRPFKmAdhN73QV4i5Hn2rv5104
|
||||||
|
AR4QGvz5Egh/bZ5RElIutD/CvVrOS1fykm6ZIVuxqHUx0pxWkAsjRyCE+fuhczhW
|
||||||
|
QfVy2EUst98kNjUZC/OFF4TxfcWt7xu7CKNZb3rE7nrbMAbPLKPZjAlT7GlmEhaa
|
||||||
|
dw/Y3ap0sPnO3BtsBP3kOSed3lP3zEfokBLSYsQtwDWzdWanGE3nB2HvBmcviYdi
|
||||||
|
P48gXP/5uybnVlKUHXb0gBtdYzNTBx3ynoUrWtbNwvMuTKv6mmnuLxhGiEWEK+BY
|
||||||
|
StlfOiPvUdpyr+TMsaujPpWq9IUX6qmsOcl1gfujK/lJ3kuvSUm3AgMBAAGjggGE
|
||||||
|
MIIBgDASBgNVHRMBAf8ECDAGAQH/AgEAMBcGA1UdIAQQMA4wDAYKKwYBBAGD6F4B
|
||||||
|
ATAOBgNVHQ8BAf8EBAMCAQYwgYYGCCsGAQUFBwEBBHoweDAoBggrBgEFBQcwAYYc
|
||||||
|
aHR0cDovL29jc3Aub25lLmRpZ2ljZXJ0LmNvbTBMBggrBgEFBQcwAoZAaHR0cDov
|
||||||
|
L2NhY2VydHMub25lLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFJTQTQwOTZSb290Zm9y
|
||||||
|
QzJQQUcxLmNydDBNBgNVHR8ERjBEMEKgQKA+hjxodHRwOi8vY3JsLm9uZS5kaWdp
|
||||||
|
Y2VydC5jb20vRGlnaUNlcnRSU0E0MDk2Um9vdGZvckMyUEFHMS5jcmwwKQYDVR0l
|
||||||
|
BCIwIAYKKwYBBAGD6F4CAQYIKwYBBQUHAwQGCCsGAQUFBwMkMB0GA1UdDgQWBBTC
|
||||||
|
mxzaIdNwH6lU5HUf8IFpzcgbRTAfBgNVHSMEGDAWgBRW6yc4mj5I1nKAhZt6wwdR
|
||||||
|
WgTz5TANBgkqhkiG9w0BAQwFAAOCAgEAt8Kn2LQMxbL10BAx5ebm1G/TMTrgQN9y
|
||||||
|
f18ZE5qNk3ipoFFtijZzRT5zidKi+/956V11nPMgm90W/CMtapHImj3Da4eoShzl
|
||||||
|
7N6hPjGYuHJUFfCYaJFxgz0r2sdFYc0drpOlfLa+WVUmgXM8QhbWiHcyk37z8Ega
|
||||||
|
6p1fjFVH6QqKWwXhSuR5aH0QflBHJKdWRkRZSTNBU2Hj1FOqhZBPPM+jvLGWgkQg
|
||||||
|
hpQTrO9pTILZ0nb3rTkvClimBewUwTkjoB8M4UHifHcXJ7/RnFzj+KHR5kWdzVTz
|
||||||
|
uCLcIlTNeACYQZaAj/eT9vzI1n6IUoU04R/9STijYHr05VbH1jM94rDJzjE8Hjf+
|
||||||
|
f8YV9X/38Rc3gcH1hLzAKqwIyF/XieaquJLXY90O1Ads/WTrNRlf5A4109FKnELd
|
||||||
|
w8R9vOneDvslbrHxu/MmoanzJlqxW1Sw6tlU0G2kMfyIH9y0RLmTiJndW9UXyzMU
|
||||||
|
tGID1N2lhdO0BmwBuzCbZWRHCD0NcP9l5Kc8fCL95OgdpRbivEFeGynZZiCjvrJ9
|
||||||
|
tT+uGWQiBbTlEgR57e1KsEPFKxb0M81ewNEo+vhxaSoWvXv84ZM/Zc9a0ZqPMEPr
|
||||||
|
3073hQvLguizQTWJQstDjzlmOWF7K6hnI70rTNLhZBEFC3XJlBYxbUMlGdH4ebLc
|
||||||
|
9IUUXRE00ws=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=DigiCert ECC P384 L1 Claim Signing ICA for C2PA G1, O=DigiCert, Inc., C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDdzCCAv2gAwIBAgIQUhYZxg4djFjAWbZwab2qqTAKBggqhkjOPQQDAzBTMQsw
|
||||||
|
CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKzApBgNVBAMTIkRp
|
||||||
|
Z2lDZXJ0IEVDQyBQMzg0IFJvb3QgZm9yIEMyUEEgRzEwHhcNMjUwODI3MDAwMDAw
|
||||||
|
WhcNMzAwODI2MjM1OTU5WjBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl
|
||||||
|
cnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IEVDQyBQMzg0IEwxIENsYWltIFNp
|
||||||
|
Z25pbmcgSUNBIGZvciBDMlBBIEcxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEJ2hi
|
||||||
|
wBWq/Dldnu/FxGwfHDAmRxezmmKQtIWSd/HYfiJW2T9wNJmSPywB5Kl7WBJXyaxy
|
||||||
|
Uwhn8tpDqBypcT0hlN8Mi8Cql9yj9t2MJXN3KshCyBiQWjcKnZoOFhW83E1oo4IB
|
||||||
|
hDCCAYAwEgYDVR0TAQH/BAgwBgEB/wIBADAXBgNVHSAEEDAOMAwGCisGAQQBg+he
|
||||||
|
AQEwTQYDVR0fBEYwRDBCoECgPoY8aHR0cDovL2NybC5vbmUuZGlnaWNlcnQuY29t
|
||||||
|
L0RpZ2lDZXJ0RUNDUDM4NFJvb3Rmb3JDMlBBRzEuY3JsMA4GA1UdDwEB/wQEAwIB
|
||||||
|
BjCBhgYIKwYBBQUHAQEEejB4MCgGCCsGAQUFBzABhhxodHRwOi8vb2NzcC5vbmUu
|
||||||
|
ZGlnaWNlcnQuY29tMEwGCCsGAQUFBzAChkBodHRwOi8vY2FjZXJ0cy5vbmUuZGln
|
||||||
|
aWNlcnQuY29tL0RpZ2lDZXJ0RUNDUDM4NFJvb3Rmb3JDMlBBRzEuY3J0MCkGA1Ud
|
||||||
|
JQQiMCAGCisGAQQBg+heAgEGCCsGAQUFBwMEBggrBgEFBQcDJDAdBgNVHQ4EFgQU
|
||||||
|
JqGs/hW9GFhOR/R0fNP8EaPxlocwHwYDVR0jBBgwFoAU9BgRrgmCiU78m7Qz9vTf
|
||||||
|
vQJOlDYwCgYIKoZIzj0EAwMDaAAwZQIwa6ohDos9g13+HZlgKR45C5K3qug4QUi3
|
||||||
|
tHu79IL601lgQSDgpnclOmJOuWxZFxfGAjEA3kAU5JV6no1hQpZdtb11g+B5wCHW
|
||||||
|
bUVPYZLXZ1Ie2RaND1QmLeYYKbuqsC58ZtSw
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Adobe Product Issuing CA vault-a-or2.adobe.net cai, O=Adobe Inc, L=San Jose, ST=California, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDhDCCAwugAwIBAgIUTNz+Jl3kNFEJK5md8Tp/Cwsm/B4wCgYIKoZIzj0EAwMw
|
||||||
|
bzELMAkGA1UEBhMCVVMxETAPBgNVBAcTCFNhbiBKb3NlMRMwEQYDVQQKEwpBZG9i
|
||||||
|
ZSBJbmMuMRAwDgYDVQQLEwdQcm9kdWN0MSYwJAYDVQQDEx1BZG9iZSBQcm9kdWN0
|
||||||
|
IEludGVybWVkaWF0ZSBDQTAeFw0yNTExMTkxODI5NDRaFw0zMDExMjAwMDMwMTRa
|
||||||
|
MIGGMTswOQYDVQQDEzJBZG9iZSBQcm9kdWN0IElzc3VpbmcgQ0EgdmF1bHQtYS1v
|
||||||
|
cjIuYWRvYmUubmV0IGNhaTESMBAGA1UEChMJQWRvYmUgSW5jMQswCQYDVQQGEwJV
|
||||||
|
UzERMA8GA1UEBxMIU2FuIEpvc2UxEzARBgNVBAgTCkNhbGlmb3JuaWEwdjAQBgcq
|
||||||
|
hkjOPQIBBgUrgQQAIgNiAASArJB/ZRkabuyoSoGy/6JS124LqMOeKRTruZ4GIxZ3
|
||||||
|
Rg2UKXeWDINVsgXVewJalg98T0HgeXyXM8Ia0y0dLQUhxT++kVaascgJBuevPs8z
|
||||||
|
5Z+n+grN03x6ttUORdJ4O9qjggFOMIIBSjASBgNVHRMBAf8ECDAGAQH/AgEAMB8G
|
||||||
|
A1UdIwQYMBaAFBldp+77+M3A85KlrD7gVNgW7D8UMFsGCCsGAQUFBwEBBE8wTTBL
|
||||||
|
BggrBgEFBQcwAoY/aHR0cDovL3BraS1jZG4uYWRvYmUubmV0L2NhL2Fkb2JlX2lu
|
||||||
|
dGVybmFsX2ludGVybWVkaWF0ZV9wcm9kdWN0ME0GA1UdHwRGMEQwQqBAoD6GPGh0
|
||||||
|
dHA6Ly9wa2ktY2RuLmFkb2JlLm5ldC9hZG9iZV9pbnRlcm5hbF9pbnRlcm1lZGlh
|
||||||
|
dGVfcHJvZHVjdDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFC76762PJu/wKMyE
|
||||||
|
92MRuG8aw1sIMBcGA1UdIAQQMA4wDAYKKwYBBAGD6F4BATAfBgNVHSUEGDAWBgor
|
||||||
|
BgEEAYPoXgIBBggrBgEFBQcDBDAKBggqhkjOPQQDAwNnADBkAjAfjQ+ZR+qrZcyW
|
||||||
|
iOVr07JrjClkluOhKPreYGCIQjRXvZPiFPDorO1df9tEIPmJ2Q4CMAIfpEWAmTsA
|
||||||
|
ggP/MIJIBoTabnBjhwmUcaqRdjK/LuT3KvwkZzZ6R7iPDkQs0d+dQA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Irdeto C2PA Root CA G1, O=Irdeto BV, C=NL
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICLDCCAbGgAwIBAgIUL8JDbqsQllkL9gozEdKhKc9GFAcwCgYIKoZIzj0EAwMw
|
||||||
|
QjELMAkGA1UEBhMCTkwxEjAQBgNVBAoMCUlyZGV0byBCVjEfMB0GA1UEAwwWSXJk
|
||||||
|
ZXRvIEMyUEEgUm9vdCBDQSBHMTAgFw0yNTEyMDUxMjQ0NDBaGA8yMDUwMTIwNTEy
|
||||||
|
NDQ0MFowQjELMAkGA1UEBhMCTkwxEjAQBgNVBAoMCUlyZGV0byBCVjEfMB0GA1UE
|
||||||
|
AwwWSXJkZXRvIEMyUEEgUm9vdCBDQSBHMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA
|
||||||
|
BEOtgkWt4t4r3Fvn2vDshY1KcZ63/VTaZN7K2uiAcCeztiH+ZSFf4hA5cSCABRJ+
|
||||||
|
VkWZYLvnhNLsCVbtEij6mDN5hf4VIUdUNgUonGZ6tkL1Djs3CUIkjD+nH/HXbCSj
|
||||||
|
pqNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBAjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
|
||||||
|
BBYEFGs1MrTyCBABc00WKVK0njUakyplMB8GA1UdIwQYMBaAFGs1MrTyCBABc00W
|
||||||
|
KVK0njUakyplMAoGCCqGSM49BAMDA2kAMGYCMQCB1h+esIhAsddoGkZI1aXwwCFM
|
||||||
|
wL/cHskaM8JZsvUfcecGQmG08ZVesfUeCzei0SECMQCLjWbR4LY1mgfF+PzlieDZ
|
||||||
|
9DTcd5qOpJaSf90/8AnQA/UWGtRGqJsrmERInDoXEOA=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject emailAddress=ca@tauth.io, CN=Tauth Root CA, OU=CA Division, O=Tauth Labs Inc., L=New York, ST=New York, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDjzCCAxWgAwIBAgIUW6zwDse+LBk8Yvy/u3p7lSjnaA4wCgYIKoZIzj0EAwMw
|
||||||
|
gZcxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3
|
||||||
|
IFlvcmsxGDAWBgNVBAoMD1RhdXRoIExhYnMgSW5jLjEUMBIGA1UECwwLQ0EgRGl2
|
||||||
|
aXNpb24xFjAUBgNVBAMMDVRhdXRoIFJvb3QgQ0ExGjAYBgkqhkiG9w0BCQEWC2Nh
|
||||||
|
QHRhdXRoLmlvMB4XDTI2MDEwNTE2MDgzMVoXDTQ1MTIzMTE2MDgzMVowgZcxCzAJ
|
||||||
|
BgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3IFlvcmsx
|
||||||
|
GDAWBgNVBAoMD1RhdXRoIExhYnMgSW5jLjEUMBIGA1UECwwLQ0EgRGl2aXNpb24x
|
||||||
|
FjAUBgNVBAMMDVRhdXRoIFJvb3QgQ0ExGjAYBgkqhkiG9w0BCQEWC2NhQHRhdXRo
|
||||||
|
LmlvMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETgpV7Q9tsMzXFPduTyVt71IRRfU0
|
||||||
|
yeZGVuXmVO6zyUXxqGCg07jafnMyv24pRwJ9Bk+ROhslNWFS0NhXjIINDuk4h5Ji
|
||||||
|
KcEEGBJIlGl7jOB/MPLF/HU7CfURT2ayXKXmo4IBHjCCARowHQYDVR0OBBYEFB3R
|
||||||
|
AQfd1ulUQKBf+J34TnS1CxI+MIHXBgNVHSMEgc8wgcyAFB3RAQfd1ulUQKBf+J34
|
||||||
|
TnS1CxI+oYGdpIGaMIGXMQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsx
|
||||||
|
ETAPBgNVBAcMCE5ldyBZb3JrMRgwFgYDVQQKDA9UYXV0aCBMYWJzIEluYy4xFDAS
|
||||||
|
BgNVBAsMC0NBIERpdmlzaW9uMRYwFAYDVQQDDA1UYXV0aCBSb290IENBMRowGAYJ
|
||||||
|
KoZIhvcNAQkBFgtjYUB0YXV0aC5pb4IUW6zwDse+LBk8Yvy/u3p7lSjnaA4wDwYD
|
||||||
|
VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIx
|
||||||
|
AJzhLaivtrjOMyEAuoHQRIhN2u7kw2iWZZyAT6FJH4+GBYudax0dfewXeIvC0MH5
|
||||||
|
SwIwAKojdigAO86Xvk6++B875ZpTjQIysPX0bGumbmAwcAmsqum9JLLIRCDz+7Kt
|
||||||
|
WrRP
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=HUAWEI C2PA ECC384 Root CA E346, O=Huawei Technologies Co., Ltd., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICoDCCAiagAwIBAgIIKsblXSNLwF8wCgYIKoZIzj0EAwMwXzELMAkGA1UEBhMC
|
||||||
|
Q04xJjAkBgNVBAoMHUh1YXdlaSBUZWNobm9sb2dpZXMgQ28uLCBMdGQuMSgwJgYD
|
||||||
|
VQQDDB9IVUFXRUkgQzJQQSBFQ0MzODQgUm9vdCBDQSBFMzQ2MB4XDTI2MDQxMzAx
|
||||||
|
NTQxMFoXDTQ2MDQwODAxNTQxMFowXzELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUh1
|
||||||
|
YXdlaSBUZWNobm9sb2dpZXMgQ28uLCBMdGQuMSgwJgYDVQQDDB9IVUFXRUkgQzJQ
|
||||||
|
QSBFQ0MzODQgUm9vdCBDQSBFMzQ2MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOrBo
|
||||||
|
SivfM2LRlG3B4r52hE0vDjQR2iWv9sKTZXzkscQIasoH+uYBDDie9VcosH4Mfq/U
|
||||||
|
xN8ojhW7BZW38LT38SmYpJPZirtzKTyCD4ha4dU8tLcHoJsOn3p+kBw09Bvoo4Gu
|
||||||
|
MIGrMB8GA1UdIwQYMBaAFM/OyEcEDscO78w/okatAmz35ypMMB0GA1UdDgQWBBTP
|
||||||
|
zshHBA7HDu/MP6JGrQJs9+cqTDASBgNVHRMBAf8ECDAGAQH/AgECMEUGCCsGAQUF
|
||||||
|
BwELBDkwNzA1BggrBgEFBQcwBYYpaHR0cDovL2NhLmh1YXdlaWNsb3VkLmNvbS9y
|
||||||
|
ZXBvc2l0b3J5Lmh0bWwwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUC
|
||||||
|
MDCsqpRvJIPw6OAQi2Rj0CWnwJgK75YK5gj+K9xomIDu8ai/2ExrifL8JgROQOeZ
|
||||||
|
egIxAL3tT3Dp84wP18ohYU83gkRcA8j9B3T4BRpxY1Ew9IJMYJ2zqz0weFjAEGNC
|
||||||
|
9Z36XQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Huanyu Trust C2PA EC-384 Root CA, O=Huanyu Trust Ltd, C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICTTCCAdOgAwIBAgIUCqsLOXYNHdz0EBDsWKcrsiBcF10wCgYIKoZIzj0EAwMw
|
||||||
|
UzELMAkGA1UEBhMCQ04xGTAXBgNVBAoTEEh1YW55dSBUcnVzdCBMdGQxKTAnBgNV
|
||||||
|
BAMTIEh1YW55dSBUcnVzdCBDMlBBIEVDLTM4NCBSb290IENBMCAXDTI2MDQxMzA2
|
||||||
|
MDUwOVoYDzIwNTEwNDEzMDYwNTA5WjBTMQswCQYDVQQGEwJDTjEZMBcGA1UEChMQ
|
||||||
|
SHVhbnl1IFRydXN0IEx0ZDEpMCcGA1UEAxMgSHVhbnl1IFRydXN0IEMyUEEgRUMt
|
||||||
|
Mzg0IFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARDnAlELrIETK81oB6E
|
||||||
|
/ZfbkGSH9YPhGdFpXTzDCHD3IsKPQdPPv/mF319XjdrZMECxpH9rbmWGuWrhq6K8
|
||||||
|
zIGO27H2MUE658XNu6aO5xcZV/YdAMkG/YWPYo9CmCT4/SujZjBkMBIGA1UdEwEB
|
||||||
|
/wQIMAYBAf8CAQIwHwYDVR0jBBgwFoAUYc0ZfGc1yTPouBQYj81mEEdaefswHQYD
|
||||||
|
VR0OBBYEFGHNGXxnNckz6LgUGI/NZhBHWnn7MA4GA1UdDwEB/wQEAwIBBjAKBggq
|
||||||
|
hkjOPQQDAwNoADBlAjEAqfQxNSnmGxtt1QBKJbaY8AyUiaJxzX5MnH0l/ielnc6D
|
||||||
|
qBhIcALvKKc3a8MizR01AjB85bwUFBgs6WlIWZ6sFDDbCQzrWpPU11YQnj/mGom7
|
||||||
|
yXjL/2axHPWPY2Do/ldP5mc=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Verimago Root CA, O=Verimago LLC, ST=Washington, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICTjCCAdSgAwIBAgIVAOFSVdVKiaIUGZlbNzPTi5bS/Gy+MAoGCCqGSM49BAMD
|
||||||
|
MFQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRUwEwYDVQQKEwxW
|
||||||
|
ZXJpbWFnbyBMTEMxGTAXBgNVBAMTEFZlcmltYWdvIFJvb3QgQ0EwHhcNMjYwNTE1
|
||||||
|
MDE0NjUwWhcNNDYwNTE1MDE0NjUwWjBUMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
|
||||||
|
V2FzaGluZ3RvbjEVMBMGA1UEChMMVmVyaW1hZ28gTExDMRkwFwYDVQQDExBWZXJp
|
||||||
|
bWFnbyBSb290IENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAExn74thYXg89OFcXC
|
||||||
|
obav4kqQAt0YDK4h28ovPP5RJWs77BBRwUhbdb0C6SMVFJ+MuyUK/cBoua+cSIOh
|
||||||
|
N6prHNqeHbZMi3nzlbVfEbDDWm9hy+OxXtOLlJQYL5zjUgkco2YwZDASBgNVHRMB
|
||||||
|
Af8ECDAGAQH/AgECMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUAkmvpVkpKvS1
|
||||||
|
2ZHgaoysoshW7XowHwYDVR0jBBgwFoAUAkmvpVkpKvS12ZHgaoysoshW7XowCgYI
|
||||||
|
KoZIzj0EAwMDaAAwZQIwDqzc2oVzZezm4GeER3jPUDWNj4zsqdXciOoGO0JkAHmC
|
||||||
|
vyUBt8FH9vZK9d6bZ9DEAjEA5eFk9oFV8cm8pyJ2+hKo5ji/iip8A0T5U2GwHQyN
|
||||||
|
W4GXdTaTwkzPD7X6whoLKKB/
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Verimago Claim Signing Issuing CA, O=Verimago LLC, ST=Washington, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDTDCCAtOgAwIBAgIUNcPH/7deevUcZvKM0EQGUJN99PMwCgYIKoZIzj0EAwMw
|
||||||
|
VDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xFTATBgNVBAoTDFZl
|
||||||
|
cmltYWdvIExMQzEZMBcGA1UEAxMQVmVyaW1hZ28gUm9vdCBDQTAeFw0yNjA1MTUw
|
||||||
|
MTQ2NTBaFw0zMTA1MTUwNzQ2NTBaMGUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
|
||||||
|
YXNoaW5ndG9uMRUwEwYDVQQKEwxWZXJpbWFnbyBMTEMxKjAoBgNVBAMTIVZlcmlt
|
||||||
|
YWdvIENsYWltIFNpZ25pbmcgSXNzdWluZyBDQTB2MBAGByqGSM49AgEGBSuBBAAi
|
||||||
|
A2IABPB0fzGODSoLVNkTQWekSBkL6rD2h2//WP+4f1pdl9K8K0Z8Bt0SrlOXNtrp
|
||||||
|
I/ytOybRwV7nH3TDAWkZV833jc8Gjgqs7P0Q4ZZc4+4WIey3eD9084B8rLXs3/K/
|
||||||
|
q7NyHaOCAVMwggFPMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEG
|
||||||
|
MCkGA1UdJQQiMCAGCisGAQQBg+heAgEGCCsGAQUFBwMEBggrBgEFBQcDJDAXBgNV
|
||||||
|
HSAEEDAOMAwGCisGAQQBg+heAQEwHQYDVR0OBBYEFCM0wx5MWMCrBF5eYt8J9b41
|
||||||
|
TMwcMB8GA1UdIwQYMBaAFAJJr6VZKSr0tdmR4GqMrKLIVu16MDsGA1UdHwQ0MDIw
|
||||||
|
MKAuoCyGKmh0dHA6Ly9jYS52ZXJpbWFnby5pby9jcmwvaW50ZXJtZWRpYXRlLmNy
|
||||||
|
bDBoBggrBgEFBQcBAQRcMFowJgYIKwYBBQUHMAGGGmh0dHA6Ly9jYS52ZXJpbWFn
|
||||||
|
by5pby9vY3NwMDAGCCsGAQUFBzAChiRodHRwOi8vY2EudmVyaW1hZ28uaW8vY2Vy
|
||||||
|
dHMvcm9vdC5wZW0wCgYIKoZIzj0EAwMDZwAwZAIwDq2qaR9as+lyVFcwsUTlMIm/
|
||||||
|
OGxgW4dqVg7jlsBST+3xvS0nIpZUS7lcF44FVcFdAjA5abgOYS/ie7NXxp3smYtu
|
||||||
|
FoKzc/V7TAaL0VS/HGB3x7sTa+D6HIY3/ls33Dy/4Gk=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Snowball ECC P384 Root CA for C2PA G1, O=Snowball Technology Co., Ltd., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICiTCCAg6gAwIBAgIUVlsRGj1G9vJ4ws2jMYyFlFcKUokwCgYIKoZIzj0EAwMw
|
||||||
|
ZTELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHVNub3diYWxsIFRlY2hub2xvZ3kgQ28u
|
||||||
|
LCBMdGQuMS4wLAYDVQQDDCVTbm93YmFsbCBFQ0MgUDM4NCBSb290IENBIGZvciBD
|
||||||
|
MlBBIEcxMB4XDTI2MDYxODA5MzkxOVoXDTQ2MDYxMzA5MzkxOVowZTELMAkGA1UE
|
||||||
|
BhMCQ04xJjAkBgNVBAoMHVNub3diYWxsIFRlY2hub2xvZ3kgQ28uLCBMdGQuMS4w
|
||||||
|
LAYDVQQDDCVTbm93YmFsbCBFQ0MgUDM4NCBSb290IENBIGZvciBDMlBBIEcxMHYw
|
||||||
|
EAYHKoZIzj0CAQYFK4EEACIDYgAELJUrcxeVmmYrb8Kl/YVAYYFiyuiqiIa6z/1n
|
||||||
|
FoCpB2JV/Qkwjdns7rOvLheWBi1s880NcwqD/KD9t9/SOgWTfD+WOAhtPioAql2Y
|
||||||
|
YUl0toOUzoXaAOB3+HIQRiKsL2RIo38wfTASBgNVHRMBAf8ECDAGAQH/AgECMA4G
|
||||||
|
A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU6R4F1vboIaKQly8jCAQX2lQhWPQwHwYD
|
||||||
|
VR0jBBgwFoAU6R4F1vboIaKQly8jCAQX2lQhWPQwFwYDVR0gBBAwDjAMBgorBgEE
|
||||||
|
AYPoXgEBMAoGCCqGSM49BAMDA2kAMGYCMQD6/xszsnZoYt+gYIsgYKTMFARxRpB5
|
||||||
|
N16s2u2hgkuFs4zyLco1z7gOk0mv6EbBkm0CMQCcsqIQnr45MVjDUdn9L//VdGDO
|
||||||
|
6H+P67vBdmvb3LiLTB+Ey/JYYfnHHIKzJrRG4ls=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Snowball ECC P384 Claim Signing ICA for C2PA G1, O=Snowball Technology Co., Ltd., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDrjCCAzSgAwIBAgIUMPJ1KkDp+fvHaZOBsdKSnPm/hdowCgYIKoZIzj0EAwMw
|
||||||
|
ZTELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHVNub3diYWxsIFRlY2hub2xvZ3kgQ28u
|
||||||
|
LCBMdGQuMS4wLAYDVQQDDCVTbm93YmFsbCBFQ0MgUDM4NCBSb290IENBIGZvciBD
|
||||||
|
MlBBIEcxMB4XDTI2MDYxODA5NDQ1OVoXDTMxMDYxOTA5NDQ1OVowbzELMAkGA1UE
|
||||||
|
BhMCQ04xJjAkBgNVBAoMHVNub3diYWxsIFRlY2hub2xvZ3kgQ28uLCBMdGQuMTgw
|
||||||
|
NgYDVQQDDC9Tbm93YmFsbCBFQ0MgUDM4NCBDbGFpbSBTaWduaW5nIElDQSBmb3Ig
|
||||||
|
QzJQQSBHMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJvqMpOfFPJuwAV3s2YGMbe7
|
||||||
|
mRj48rsUZsJrMSOWU3SnssFtPEWTFh8QyM88zaFuprKC8nm53vnEMkGG+YtmZdBf
|
||||||
|
zsmayZqs9Rq6EQchDSF2vVNvE9wxAqhKJIawhYSTe6OCAZkwggGVMBIGA1UdEwEB
|
||||||
|
/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMCkGA1UdJQQiMCAGCisGAQQBg+he
|
||||||
|
AgEGCCsGAQUFBwMEBggrBgEFBQcDJDAdBgNVHQ4EFgQUt0FFkasBa70GGSB6Ntn5
|
||||||
|
2UCzqwYwHwYDVR0jBBgwFoAU6R4F1vboIaKQly8jCAQX2lQhWPQwFwYDVR0gBBAw
|
||||||
|
DjAMBgorBgEEAYPoXgEBMIGUBggrBgEFBQcBAQSBhzCBhDAtBggrBgEFBQcwAYYh
|
||||||
|
aHR0cDovL29jc3AuYzJwYS5zbm93YmFsbHRlY2guY29tMFMGCCsGAQUFBzAChkdo
|
||||||
|
dHRwOi8vY2FjZXJ0cy5jMnBhLnNub3diYWxsdGVjaC5jb20vU25vd2JhbGxFQ0NQ
|
||||||
|
Mzg0Um9vdENBZm9yQzJQQUcxLmNydDBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
|
||||||
|
Y3JsLmMycGEuc25vd2JhbGx0ZWNoLmNvbS9Tbm93YmFsbEVDQ1AzODRSb290Q0Fm
|
||||||
|
b3JDMlBBRzEuY3JsMAoGCCqGSM49BAMDA2gAMGUCMQCp0U69fhqepzep6uvRFmGg
|
||||||
|
e+mgVJX1a2JsC3m+W6GAA62UruIAskoTmrEC0iZXMwYCMAcKDRNtDSwk9balyQ/8
|
||||||
|
RUPGGwGS2AvOsUC0H84jMBNCL+DlPg7dJz1gc32QCv9sWA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Encypher C2PA Root CA 2026, OU=CA Division, O=Encypher Corp., L=San Francisco, ST=California, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDFDCCApqgAwIBAgIUS2zOIYRKaa59snU3NcNFadfTtoAwCgYIKoZIzj0EAwMw
|
||||||
|
gY4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1T
|
||||||
|
YW4gRnJhbmNpc2NvMRcwFQYDVQQKDA5FbmN5cGhlciBDb3JwLjEUMBIGA1UECwwL
|
||||||
|
Q0EgRGl2aXNpb24xIzAhBgNVBAMMGkVuY3lwaGVyIEMyUEEgUm9vdCBDQSAyMDI2
|
||||||
|
MB4XDTI2MDUyMjEzNDk1NloXDTQ2MDUxNzEzNDk1NlowgY4xCzAJBgNVBAYTAlVT
|
||||||
|
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRcw
|
||||||
|
FQYDVQQKDA5FbmN5cGhlciBDb3JwLjEUMBIGA1UECwwLQ0EgRGl2aXNpb24xIzAh
|
||||||
|
BgNVBAMMGkVuY3lwaGVyIEMyUEEgUm9vdCBDQSAyMDI2MHYwEAYHKoZIzj0CAQYF
|
||||||
|
K4EEACIDYgAE27XmRBc4mE0dYfUhLiuPiAnY3tRDMke9evylB+VgyMspT1svR9h3
|
||||||
|
KXxr8tml+1oM82US+pw2jJenfUxoUJGVDY/lvwl+4Q+7vawXFd26yPv/CQSnhI+w
|
||||||
|
05+yiSiwshIOo4G2MIGzMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQD
|
||||||
|
AgEGMB0GA1UdDgQWBBS3gC7ps3/HLQbb3uuqL4U8gDnKEDAfBgNVHSMEGDAWgBS3
|
||||||
|
gC7ps3/HLQbb3uuqL4U8gDnKEDBNBgNVHSAERjBEMEIGCisGAQQBg+heAQEwNDAy
|
||||||
|
BggrBgEFBQcCARYmaHR0cHM6Ly9jYS5lbmN5cGhlci5jb20vcmVwb3NpdG9yeS9j
|
||||||
|
cHMwCgYIKoZIzj0EAwMDaAAwZQIwYeb83YEb/Oh9uELuvO9jpb85orRWWQiceqf0
|
||||||
|
sCwxtJw07/giyy/hI1BFvbQZcfkLAjEAj8jfwuJSJ9LjeeYL/VXStQWDHewxbIou
|
||||||
|
IKdYpS9ZBidpsi+01tpQY1j7apLAXMHO
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Encypher C2PA Issuing CA 2026, OU=CA Division, O=Encypher Corp., L=San Francisco, ST=California, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID6zCCA3GgAwIBAgIUXzMIOXOrlyOjfKthGAPSSBM5n3UwCgYIKoZIzj0EAwMw
|
||||||
|
gY4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1T
|
||||||
|
YW4gRnJhbmNpc2NvMRcwFQYDVQQKDA5FbmN5cGhlciBDb3JwLjEUMBIGA1UECwwL
|
||||||
|
Q0EgRGl2aXNpb24xIzAhBgNVBAMMGkVuY3lwaGVyIEMyUEEgUm9vdCBDQSAyMDI2
|
||||||
|
MB4XDTI2MDUyMjEzNDk1NloXDTMxMDUyMzEzNDk1NlowgZExCzAJBgNVBAYTAlVT
|
||||||
|
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRcw
|
||||||
|
FQYDVQQKDA5FbmN5cGhlciBDb3JwLjEUMBIGA1UECwwLQ0EgRGl2aXNpb24xJjAk
|
||||||
|
BgNVBAMMHUVuY3lwaGVyIEMyUEEgSXNzdWluZyBDQSAyMDI2MHYwEAYHKoZIzj0C
|
||||||
|
AQYFK4EEACIDYgAEINEuEOHZL9o8vKynpXV9O+04syCIj+WeydlgTMJBMhiZ8MhE
|
||||||
|
yig+4R0kmGpX/RZjgSSQh/aQ4NYpv9SHx1r1t3agJEpfYv/jZB4AfeGC47OiEREe
|
||||||
|
wKHBWD1cVg6qrFtVo4IBiTCCAYUwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8B
|
||||||
|
Af8EBAMCAQYwIAYDVR0lBBkwFwYKKwYBBAGD6F4CAQYJKoZIhvcvAQEFMB0GA1Ud
|
||||||
|
DgQWBBT5K7GC8GHRLCg4CpI6Owg8ubeZ8DAfBgNVHSMEGDAWgBS3gC7ps3/HLQbb
|
||||||
|
3uuqL4U8gDnKEDA3BgNVHR8EMDAuMCygKqAohiZodHRwOi8vY2EuZW5jeXBoZXIu
|
||||||
|
Y29tL2NybC9yb290LWNhLmNybDB1BggrBgEFBQcBAQRpMGcwJwYIKwYBBQUHMAGG
|
||||||
|
G2h0dHA6Ly9jYS5lbmN5cGhlci5jb20vb2NzcDA8BggrBgEFBQcwAoYwaHR0cDov
|
||||||
|
L2NhLmVuY3lwaGVyLmNvbS9yZXBvc2l0b3J5L2lzc3VpbmctY2EuY3J0ME0GA1Ud
|
||||||
|
IARGMEQwQgYKKwYBBAGD6F4BATA0MDIGCCsGAQUFBwIBFiZodHRwczovL2NhLmVu
|
||||||
|
Y3lwaGVyLmNvbS9yZXBvc2l0b3J5L2NwczAKBggqhkjOPQQDAwNoADBlAjABzhlt
|
||||||
|
QpFyat0q5DZPJh+XvWnRAUej/4yrnPkQgtZZufnTVg9csupYAtdD8EVvkJ8CMQDK
|
||||||
|
aReb1d/xaGyypi9HwEqVRaEClhopAogb+8VtoUbBD/3mKv0rGlYOUa9heE2DRkw=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=TrustAsia C2PA RSA Root CA, O=TrustAsia Technologies, Inc., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
qtj6+iKCvpx9jmvr
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=TrustAsia C2PA ECC Root CA, O=TrustAsia Technologies, Inc., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICWjCCAd+gAwIBAgIUdUWMu3G69tNb9bwz/0KTupxLm1kwCgYIKoZIzj0EAwMw
|
||||||
|
WTELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs
|
||||||
|
IEluYy4xIzAhBgNVBAMMGlRydXN0QXNpYSBDMlBBIEVDQyBSb290IENBMCAXDTI2
|
||||||
|
MDYwNTAzNTAwMFoYDzIwNTEwNjA1MDM0OTU5WjBZMQswCQYDVQQGEwJDTjElMCMG
|
||||||
|
A1UECgwcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEjMCEGA1UEAwwaVHJ1
|
||||||
|
c3RBc2lhIEMyUEEgRUNDIFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQL
|
||||||
|
ERY0d7ug9MiCqmXnDjDs2med9zfTqTWOcG3OD5Q6FSEgNUcjT5Qy7xzKn1MfqaY4
|
||||||
|
Ye3sf+Do7cCDR6AiFW6SqLQobfJ70jMDsSQYlrCCjMLvb+NdNPhED43s8fgehxyj
|
||||||
|
ZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQIwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQW
|
||||||
|
BBT13DKCd7Jlz2rEe0vBlwo/CoR2mDAfBgNVHSMEGDAWgBT13DKCd7Jlz2rEe0vB
|
||||||
|
lwo/CoR2mDAKBggqhkjOPQQDAwNpADBmAjEAwp2Jie8k6/haTc/df3QsX+Ttg7Wp
|
||||||
|
ARtYTwJSP4Z+ggpf39sZ+7QPOE50BqBj9Q6zAjEAjZ+5glED9tav6r3jWuMOmKTB
|
||||||
|
kobBXzvjqTDLGG570BbSQfNmS3HRC6u5YTIpElpb
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=RealReel C2PA Root CA, O=Whole Earth Labs LLC, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICPzCCAcWgAwIBAgIUK7g4QvDKVbdq1t88l76ZMwiegK0wCgYIKoZIzj0EAwMw
|
||||||
|
TDELMAkGA1UEBhMCVVMxHTAbBgNVBAoMFFdob2xlIEVhcnRoIExhYnMgTExDMR4w
|
||||||
|
HAYDVQQDDBVSZWFsUmVlbCBDMlBBIFJvb3QgQ0EwIBcNMjYwNzI0MDAyNzAyWhgP
|
||||||
|
MjA1MTA3MjQwMDI3MDJaMEwxCzAJBgNVBAYTAlVTMR0wGwYDVQQKDBRXaG9sZSBF
|
||||||
|
YXJ0aCBMYWJzIExMQzEeMBwGA1UEAwwVUmVhbFJlZWwgQzJQQSBSb290IENBMHYw
|
||||||
|
EAYHKoZIzj0CAQYFK4EEACIDYgAEJVWLUwyoBcqwmv3cUuX4/fpVanvQdjJKG4Ca
|
||||||
|
zLx5XO/eu7qgxCurzpLTuRAC3Lr+tY4LA5shXTSFYqNRRlQnxkYugqEfqDJcUhks
|
||||||
|
1evDAlYZdBB3X134zjk9DHQElntEo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEBMA4G
|
||||||
|
A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoaV/7ghFZ89nsoENzOrkL+Pmwg0wHwYD
|
||||||
|
VR0jBBgwFoAUoaV/7ghFZ89nsoENzOrkL+Pmwg0wCgYIKoZIzj0EAwMDaAAwZQIw
|
||||||
|
F8nY6P9IvWgtd9dKjnelTOZwICUuxVHbd/uHvU9hjwez+B33zyL1iP+8E/HUe9yD
|
||||||
|
AjEAmUnK720BAi3UXkj8kZHs74wnIy4oh8buxmiecO1akF4I3B+0+18SkclV6ytF
|
||||||
|
+Zms
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Castlabs C2PA ECC P-384 Root CA, OU=C2PA Trust Infrastructure, O=Castlabs, L=Berlin, ST=Berlin, C=DE
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICyDCCAk2gAwIBAgIUN5SkK2Lb2Ts/j1AC3K8Y3eVydFQwCgYIKoZIzj0EAwMw
|
||||||
|
gZAxKDAmBgNVBAMMH0Nhc3RsYWJzIEMyUEEgRUNDIFAtMzg0IFJvb3QgQ0ExCzAJ
|
||||||
|
BgNVBAYTAkRFMQ8wDQYDVQQHDAZCZXJsaW4xETAPBgNVBAoMCENhc3RsYWJzMSIw
|
||||||
|
IAYDVQQLDBlDMlBBIFRydXN0IEluZnJhc3RydWN0dXJlMQ8wDQYDVQQIDAZCZXJs
|
||||||
|
aW4wHhcNMjYwNzA4MDE0NDMwWhcNMzYwNzA1MDE0NDMwWjCBkDEoMCYGA1UEAwwf
|
||||||
|
Q2FzdGxhYnMgQzJQQSBFQ0MgUC0zODQgUm9vdCBDQTELMAkGA1UEBhMCREUxDzAN
|
||||||
|
BgNVBAcMBkJlcmxpbjERMA8GA1UECgwIQ2FzdGxhYnMxIjAgBgNVBAsMGUMyUEEg
|
||||||
|
VHJ1c3QgSW5mcmFzdHJ1Y3R1cmUxDzANBgNVBAgMBkJlcmxpbjB2MBAGByqGSM49
|
||||||
|
AgEGBSuBBAAiA2IABPXhd9L+pjQVnUXH6I7+oFSrd6JpO4A+CYDm/AuDZgJvwiuJ
|
||||||
|
lrM9dC3CAE7UAK8IcQEo85qzuak0TfLdPaJWD26+q3tJ/S7NW4sOKhH09KRN7iFq
|
||||||
|
XbsOe8FOxaQql/UrMqNmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB
|
||||||
|
Af8CAQIwHQYDVR0OBBYEFHNKe2PoAANQBj4/25eRv2VY4Fy7MB8GA1UdIwQYMBaA
|
||||||
|
FHNKe2PoAANQBj4/25eRv2VY4Fy7MAoGCCqGSM49BAMDA2kAMGYCMQDUlNJD49Ux
|
||||||
|
zwBccYHiz0wgmbIXBDdgpVrLfWTQnbi21BhfPBQ6j0HJ24FY36L5oj8CMQCv5+bK
|
||||||
|
gBIkJq9USBx2FjjDkukYjQMI/WDD6SH6QGTdotsH3cQQ6bjr9C+0ruxxx08=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
@@ -0,0 +1,496 @@
|
|||||||
|
Subject CN=Google C2PA Core Time-Stamping ICA G3, O=Google LLC, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICzzCCAlagAwIBAgIURQCDbnITAsVkpJ5kM3b6jwm3ZPQwCgYIKoZIzj0EAwMw
|
||||||
|
QzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkdvb2dsZSBMTEMxHzAdBgNVBAMMFkdv
|
||||||
|
b2dsZSBDMlBBIFJvb3QgQ0EgRzMwHhcNMjUwNTA4MjIzNjI2WhcNNDAwNTA4MjIz
|
||||||
|
NjI2WjBSMQswCQYDVQQGEwJVUzETMBEGA1UECgwKR29vZ2xlIExMQzEuMCwGA1UE
|
||||||
|
AwwlR29vZ2xlIEMyUEEgQ29yZSBUaW1lLVN0YW1waW5nIElDQSBHMzB2MBAGByqG
|
||||||
|
SM49AgEGBSuBBAAiA2IABKN99/G9CCofRVkl4FL5qSDf/tsuj0Uh2E8K1c0Dcd1n
|
||||||
|
KixZbsCcJDJyInm5ApFfuabKR5+nxTRzE35exSVE6TEijjTVuBb+GsGrM+rGISwj
|
||||||
|
T/8B5ODBf/A4a8VyrSVLCqOB+zCB+DAXBgNVHSAEEDAOMAwGCisGAQQBg+heAQEw
|
||||||
|
DgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBIGA1UdEwEB/wQI
|
||||||
|
MAYBAf8CAQAwZAYIKwYBBQUHAQEEWDBWMCwGCCsGAQUFBzAChiBodHRwOi8vcGtp
|
||||||
|
Lmdvb2cvYzJwYS9yb290LWczLmNydDAmBggrBgEFBQcwAYYaaHR0cDovL2MycGEt
|
||||||
|
b2NzcC5wa2kuZ29vZy8wHwYDVR0jBBgwFoAUnFzYiVND51rVgdsD3hl/BCoqLaow
|
||||||
|
HQYDVR0OBBYEFN5Vl4xgdDsD4mq0RAZll2HK5fiOMAoGCCqGSM49BAMDA2cAMGQC
|
||||||
|
MEHGBo0dSnwBldblTYF0fGBdzHBCW0oRhGP/pYfclCTYgcyo+UdR5nYuiHZpKFhQ
|
||||||
|
cQIwcAumLdMem8XpEJsAEedT9O0lo+ksaufwbJ93BVh5HG3h37rxij8nE064uhpS
|
||||||
|
PiMt
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Google C2PA Pixel Time-Stamping ICA G3, O=Google LLC, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC0TCCAlegAwIBAgIUP2ie1DhmCwB0kqG0zYX4MkF5VsgwCgYIKoZIzj0EAwMw
|
||||||
|
QzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkdvb2dsZSBMTEMxHzAdBgNVBAMMFkdv
|
||||||
|
b2dsZSBDMlBBIFJvb3QgQ0EgRzMwHhcNMjUwNTA4MjIzNjQ1WhcNNDAwNTA4MjIz
|
||||||
|
NjQ1WjBTMQswCQYDVQQGEwJVUzETMBEGA1UECgwKR29vZ2xlIExMQzEvMC0GA1UE
|
||||||
|
AwwmR29vZ2xlIEMyUEEgUGl4ZWwgVGltZS1TdGFtcGluZyBJQ0EgRzMwdjAQBgcq
|
||||||
|
hkjOPQIBBgUrgQQAIgNiAAQ+8JePdivzSowun2WW4rDH0LXX9Iurbgh3xNHoKtIw
|
||||||
|
ei3+ayp1TC1AKjLbF+ZeNxLXHGaF6lwC20eGLp/gK7moar9ob8ltCnWFZXTZE1gK
|
||||||
|
BMORiCbTf1iVnRUykBcdr8ajgfswgfgwFwYDVR0gBBAwDjAMBgorBgEEAYPoXgEB
|
||||||
|
MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcDCDASBgNVHRMBAf8E
|
||||||
|
CDAGAQH/AgEAMGQGCCsGAQUFBwEBBFgwVjAsBggrBgEFBQcwAoYgaHR0cDovL3Br
|
||||||
|
aS5nb29nL2MycGEvcm9vdC1nMy5jcnQwJgYIKwYBBQUHMAGGGmh0dHA6Ly9jMnBh
|
||||||
|
LW9jc3AucGtpLmdvb2cvMB8GA1UdIwQYMBaAFJxc2IlTQ+da1YHbA94ZfwQqKi2q
|
||||||
|
MB0GA1UdDgQWBBSnltnit0LAWi0rd0qr3tBEM58EuzAKBggqhkjOPQQDAwNoADBl
|
||||||
|
AjEAsVIYGAu0tVemHYqq082P164T0B6NWstqZU2RTv8ZMV21tdBJYwnwDWWSYLmF
|
||||||
|
8s7kAjA59j5cHDebfKTUPcnxyhMUIJJ+qU6x8wM5E86O0FZyhb2G/kr3HrLkqzmB
|
||||||
|
Mo9QvzU=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Google C2PA Root CA G3, O=Google LLC, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICLjCCAbOgAwIBAgIUUZK4AROFKiXQZ1UG7FG6qPGc1g8wCgYIKoZIzj0EAwMw
|
||||||
|
QzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkdvb2dsZSBMTEMxHzAdBgNVBAMMFkdv
|
||||||
|
b2dsZSBDMlBBIFJvb3QgQ0EgRzMwIBcNMjUwNTA4MjIzMjIxWhgPMjA1MDA1MDgy
|
||||||
|
MjMyMjFaMEMxCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApHb29nbGUgTExDMR8wHQYD
|
||||||
|
VQQDDBZHb29nbGUgQzJQQSBSb290IENBIEczMHYwEAYHKoZIzj0CAQYFK4EEACID
|
||||||
|
YgAEhv9f/juKcPpe3Fm7eAISMuSyS+tBxn0aYHC83J+qAsFWREGN9p6PN/OBoouP
|
||||||
|
zpOFRxvrlWoWmAI3p1lXyPg4E3eg7SNChgopUIpihGu6qlhP8rLXf3p8bhI5FTQ2
|
||||||
|
MaF2o2YwZDASBgNVHRMBAf8ECDAGAQH/AgECMA4GA1UdDwEB/wQEAwIBBjAfBgNV
|
||||||
|
HSMEGDAWgBScXNiJU0PnWtWB2wPeGX8EKiotqjAdBgNVHQ4EFgQUnFzYiVND51rV
|
||||||
|
gdsD3hl/BCoqLaowCgYIKoZIzj0EAwMDaQAwZgIxAIyVEe5bdUMkk6BthEWy9QSE
|
||||||
|
Mb74BOyK8/8pgMX0NPwLlo1ikLNY78ov+k21vZrEZQIxANQ91muDXgPjAMAkzAlK
|
||||||
|
i32Z9VBB37ynTveKVC7ofTW0ZFfIIYYpWUR1+C4m2yRkOQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=SSL.com C2PA RSA Root CA 2025, O=SSL Corporation, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=SSL.com C2PA ECC Root CA 2025, O=SSL Corporation, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICRTCCAcugAwIBAgIUHTAeXakkTyAFDkZfyu8YyCO9ubgwCgYIKoZIzj0EAwIw
|
||||||
|
TzEmMCQGA1UEAwwdU1NMLmNvbSBDMlBBIEVDQyBSb290IENBIDIwMjUxGDAWBgNV
|
||||||
|
BAoMD1NTTCBDb3Jwb3JhdGlvbjELMAkGA1UEBhMCVVMwIBcNMjUwNzE4MTU1MTQ5
|
||||||
|
WhgPMjA1MDA3MTIxNTUxNDlaME8xJjAkBgNVBAMMHVNTTC5jb20gQzJQQSBFQ0Mg
|
||||||
|
Um9vdCBDQSAyMDI1MRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xCzAJBgNVBAYT
|
||||||
|
AlVTMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEo3WOe9pIesN2XWe6KQdiUO+cU1mr
|
||||||
|
+Bs8opia0I+IA5m8oYhBJmJWPxLea7PH6tlW6f5wqkIOaeJkJ7X1pz3IHPjO8qkX
|
||||||
|
imKjiUwt/B7IoEj6rhoqkAV4AMO2BMxbS1MUo2YwZDASBgNVHRMBAf8ECDAGAQH/
|
||||||
|
AgECMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUU50WGoV8zfcRPowvdPbBkJ1O
|
||||||
|
g6swHwYDVR0jBBgwFoAUU50WGoV8zfcRPowvdPbBkJ1Og6swCgYIKoZIzj0EAwID
|
||||||
|
aAAwZQIxALxN0Q+F+9KXOnYUKcW70UuxgitT8iwjTBGCIXJbJw4XaMtVdVGf4HwY
|
||||||
|
NITBgr+rWwIwczO4trazVV27OTCl8xNR0IH4UuyFgBVuzMsIj8IPkQw5mVhtvhY7
|
||||||
|
nNgp3Dyy2LeE
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject emailAddress=ca@trufo.ai, CN=Trufo C2PA Root CA (2025, ECC P384), OU=CA Division, O=Trufo Inc., L=New York, ST=New York, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDOTCCAr6gAwIBAgIUbXqcPd1r9yQm/fznG9RlSWyXiwswCgYIKoZIzj0EAwMw
|
||||||
|
gagxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3
|
||||||
|
IFlvcmsxEzARBgNVBAoMClRydWZvIEluYy4xFDASBgNVBAsMC0NBIERpdmlzaW9u
|
||||||
|
MRowGAYJKoZIhvcNAQkBFgtjYUB0cnVmby5haTEsMCoGA1UEAwwjVHJ1Zm8gQzJQ
|
||||||
|
QSBSb290IENBICgyMDI1LCBFQ0MgUDM4NCkwHhcNMjUxMjMwMTkwNTAzWhcNNDUx
|
||||||
|
MjI1MTkwNTAzWjCBqDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREw
|
||||||
|
DwYDVQQHDAhOZXcgWW9yazETMBEGA1UECgwKVHJ1Zm8gSW5jLjEUMBIGA1UECwwL
|
||||||
|
Q0EgRGl2aXNpb24xGjAYBgkqhkiG9w0BCQEWC2NhQHRydWZvLmFpMSwwKgYDVQQD
|
||||||
|
DCNUcnVmbyBDMlBBIFJvb3QgQ0EgKDIwMjUsIEVDQyBQMzg0KTB2MBAGByqGSM49
|
||||||
|
AgEGBSuBBAAiA2IABAp0qnhIwMtN6LeGdBVtHLPn85ecetr/lqcXFk8ypK9ukJzU
|
||||||
|
8LLv55Kh/MYTgEnuIKEOPhDxLDRdahc0mAjRnql4kLk395abw9WZjrBPek3qjv0q
|
||||||
|
ITR8VPYFABuZ5FRKx6OBpjCBozAdBgNVHQ4EFgQUA9Vfr36D5QQdWYAnSjT/Rf3r
|
||||||
|
SXgwHwYDVR0jBBgwFoAUA9Vfr36D5QQdWYAnSjT/Rf3rSXgwEgYDVR0TAQH/BAgw
|
||||||
|
BgEB/wIBAjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgorBgEEAYPoPAEB
|
||||||
|
MCQwIgYIKwYBBQUHAgEWFmh0dHBzOi8vdHJ1Zm8uYWkvY3BjcHMwCgYIKoZIzj0E
|
||||||
|
AwMDaQAwZgIxAMUeYWZyxS2maiVkNETL29RAuLn/gHYTkt97l6evXwHLN46v28mI
|
||||||
|
39BIf6slyWnrCwIxAPRs/FJ+DoA0d/PCkrF946S+pG7vRqLnjB9OhMdmrMPvzaqx
|
||||||
|
KQYOBVx7SE4Kz48W8A==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=DigiCert RSA4096 TSA ICA for C2PA G1, O=DigiCert, Inc., C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGoTCCBImgAwIBAgIQHlk+WwlryKtFqaffcO7jpjANBgkqhkiG9w0BAQwFADBS
|
||||||
|
MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKjAoBgNVBAMT
|
||||||
|
IURpZ2lDZXJ0IFJTQTQwOTYgUm9vdCBmb3IgQzJQQSBHMTAeFw0yNTA4MjcwMDAw
|
||||||
|
MDBaFw00MDA4MjYyMzU5NTlaMFUxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdp
|
||||||
|
Q2VydCwgSW5jLjEtMCsGA1UEAxMkRGlnaUNlcnQgUlNBNDA5NiBUU0EgSUNBIGZv
|
||||||
|
ciBDMlBBIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyK6dRasG
|
||||||
|
6TUERcSoXIhQ1/Vk0FXmVELjxjqeQMAFCNJkUm8MEdV0g5va49TO/n7N1stKakEZ
|
||||||
|
Bu2wtNElU7Iz2ht048r5f9mCksUHQzpyKxytCsaE/sWmth4StqtRqjlF2sXrMRVc
|
||||||
|
5t/bHMOXxclMpuGMWggwKHPxSqQKd2/AuTIzfWshQPUrU/JLxv0BbRCXGm1tQ9CQ
|
||||||
|
BNrl1xQFxOZ0D+j4eDRYqZNo/jlKzAzY1OMN92rErcbTyZ6Senr7MYwsKKHjeRCO
|
||||||
|
tfcSmQCp7qw2mlF9QkqFSLPNirv4rSYfrkrWsrue6cYWSKGB+sEEyTOHTGNxi4e4
|
||||||
|
NvX+1n8I3jPmbPQEgb3O+UTOxKa+mULBhwHaXeV1wfczgEBRdRXfn+Y7btJjLr8S
|
||||||
|
zroU0lTZTyarII/WKYKbHJwWF/Tni1Eu4/EnRxMQJz+gXBadQAM3RhfeYqiz53rd
|
||||||
|
aQgPosKqls1+0hJ4KVVcstiTeytSuG6bhDHL14fgSSl7aGndFU+6wBz+KIBVxs9j
|
||||||
|
38cQSiqJWgm30erGj2DTCEPDpFvBvSaDHm59kviaFLq/RgHqM1wDwXtyHrimMKmd
|
||||||
|
cCW+PWlhFqdpUiQ90a5JVIAJyWiOZ7Ko7GsG4y1nohJ9Wvm0V0AoQw+TFOPHRbVO
|
||||||
|
nhY/UIV9OZWZE9jTsWq2SD216kVvF4eCWi8CAwEAAaOCAW4wggFqMBIGA1UdEwEB
|
||||||
|
/wQIMAYBAf8CAQAwFwYDVR0gBBAwDjAMBgorBgEEAYPoXgEBMBMGA1UdJQQMMAoG
|
||||||
|
CCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIBBjCBhgYIKwYBBQUHAQEEejB4MCgGCCsG
|
||||||
|
AQUFBzABhhxodHRwOi8vb2NzcC5vbmUuZGlnaWNlcnQuY29tMEwGCCsGAQUFBzAC
|
||||||
|
hkBodHRwOi8vY2FjZXJ0cy5vbmUuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0UlNBNDA5
|
||||||
|
NlJvb3Rmb3JDMlBBRzEuY3J0ME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9jcmwu
|
||||||
|
b25lLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFJTQTQwOTZSb290Zm9yQzJQQUcxLmNy
|
||||||
|
bDAdBgNVHQ4EFgQURnZBGfLaiWXQIbuFjJH9X1GWG7wwHwYDVR0jBBgwFoAUVusn
|
||||||
|
OJo+SNZygIWbesMHUVoE8+UwDQYJKoZIhvcNAQEMBQADggIBAFB06NmfYUkxUr55
|
||||||
|
hxDUOKmwrB7w5KKSnNkF90JJw3pXILQjutAs5rD6KuExSPwa/bMe1fMx0Eh+ma/U
|
||||||
|
4tPwJMyzyjNppXHbOhE7elQbqnXUw6eeLoxwKxTGQfKGwNA6DqNGHnNcT8325pxl
|
||||||
|
b0YmQ4IAoy93b9tt51q8suRH/3JhEbEFNHKlpU4lJKGWrirqL3D8g75ghKwA7E/2
|
||||||
|
q9sWUmxOm5oHN8AoiGD3NXUCdGKWGhCSVxJB0c0mH6hJ9Pa+HO9aKRIdGb2EdciP
|
||||||
|
mIICOtrXMHIZd9VZJoVORQ0SI/9d92W9KAkZPFsa4xySVh/AnmsL5JVSa88JudFd
|
||||||
|
0yNRjdII4mqGvUu+FpwxAq0RUhnzAP9pUoEvq0lWd+LTxfkvvKg4hcL8nvHgxNqK
|
||||||
|
JEIdGXcouuFGp7ZPjw5OX6b4lycjtZrw+dvsihQ3KHpHqcDHSmMUHHk6mrj+0iP6
|
||||||
|
xgrUpk02Q9kMEvN5Hf9M9DR6IgEv5b9B20x/ZaeEY7/PNlG7dAiBi+TN1s1/p0/W
|
||||||
|
GqP622vsQp/xmykypJd1H59AbdjW4kq16XkxQav3vDg4XzG09/1ztUICatcdVvgG
|
||||||
|
hCKOlPVNVK4Cp8+ylNKbApOzwZJ8s+wj8PnQBPtqSymVufdkGkyQiR0+aDJ9VXld
|
||||||
|
gTGEoa59zjtlgSyXKpTfifENxWpk
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=DigiCert ECC P384 TSA ICA for C2PA G1, O=DigiCert, Inc., C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDVTCCAtqgAwIBAgIQa/vORh9Mst4bJJF5DoeAazAKBggqhkjOPQQDAzBTMQsw
|
||||||
|
CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKzApBgNVBAMTIkRp
|
||||||
|
Z2lDZXJ0IEVDQyBQMzg0IFJvb3QgZm9yIEMyUEEgRzEwHhcNMjUwODI3MDAwMDAw
|
||||||
|
WhcNNDAwODI2MjM1OTU5WjBWMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl
|
||||||
|
cnQsIEluYy4xLjAsBgNVBAMTJURpZ2lDZXJ0IEVDQyBQMzg0IFRTQSBJQ0EgZm9y
|
||||||
|
IEMyUEEgRzEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT8G1ndHhFlZDBFZ0YyRmd6
|
||||||
|
x6AKtDvnSWQLr3tfRq4mDLw4veOCBgfqPJH2ofVoFuNVUpnaWIJQKx5tush8yQgG
|
||||||
|
NUDqcDtjcWqz+tBVNRPF7N1bXCrmPVHnOVxMlq9ykeOjggFuMIIBajASBgNVHRMB
|
||||||
|
Af8ECDAGAQH/AgEAMIGGBggrBgEFBQcBAQR6MHgwKAYIKwYBBQUHMAGGHGh0dHA6
|
||||||
|
Ly9vY3NwLm9uZS5kaWdpY2VydC5jb20wTAYIKwYBBQUHMAKGQGh0dHA6Ly9jYWNl
|
||||||
|
cnRzLm9uZS5kaWdpY2VydC5jb20vRGlnaUNlcnRFQ0NQMzg0Um9vdGZvckMyUEFH
|
||||||
|
MS5jcnQwFwYDVR0gBBAwDjAMBgorBgEEAYPoXgEBME0GA1UdHwRGMEQwQqBAoD6G
|
||||||
|
PGh0dHA6Ly9jcmwub25lLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEVDQ1AzODRSb290
|
||||||
|
Zm9yQzJQQUcxLmNybDATBgNVHSUEDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMC
|
||||||
|
AQYwHQYDVR0OBBYEFCCVvm3C1NqJFBP6pBOCIhPGHwgRMB8GA1UdIwQYMBaAFPQY
|
||||||
|
Ea4JgolO/Ju0M/b0370CTpQ2MAoGCCqGSM49BAMDA2kAMGYCMQD5QJg0LWan44KK
|
||||||
|
sMic+nWXydVCGoa7hzlNDYvZfuA/kx/SPUWqssA3TDPPyxwziOkCMQCopKRPtJ87
|
||||||
|
CaQGUou1I+7hjCQWZvy0FaNEe/XYwK1kN/xc3U5ZqFqE50744xaOwsw=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=DigiCert ECC P384 Root for C2PA G1, O=DigiCert, Inc., C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICSTCCAc+gAwIBAgIQCZ2yarc/T5eBQYVHCJTdyjAKBggqhkjOPQQDAzBTMQsw
|
||||||
|
CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKzApBgNVBAMTIkRp
|
||||||
|
Z2lDZXJ0IEVDQyBQMzg0IFJvb3QgZm9yIEMyUEEgRzEwIBcNMjUwODI3MDAwMDAw
|
||||||
|
WhgPMjA1MDA4MjYyMzU5NTlaMFMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdp
|
||||||
|
Q2VydCwgSW5jLjErMCkGA1UEAxMiRGlnaUNlcnQgRUNDIFAzODQgUm9vdCBmb3Ig
|
||||||
|
QzJQQSBHMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNHiy4U9/SpPSnQ3ogU+q3mn
|
||||||
|
M/f9zmLuqF8krS0LNUC0T81Nu57WCGSb6149CVpkRRC6BYlfGcBWVYXar4aX10oX
|
||||||
|
ggSiFPi93ovkAfUN9wikKqVu6amI7tRQPKXJ95UsFaNmMGQwEgYDVR0TAQH/BAgw
|
||||||
|
BgEB/wIBAjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPQYEa4JgolO/Ju0M/b0
|
||||||
|
370CTpQ2MB8GA1UdIwQYMBaAFPQYEa4JgolO/Ju0M/b0370CTpQ2MAoGCCqGSM49
|
||||||
|
BAMDA2gAMGUCMQCg31cyC7If2VPMUvqEr08WB6QhRVpAa+GNRlKUOJlSMq/y2tm+
|
||||||
|
80RTxpV2RsYC9UsCMGkKQ0Yx4FIs1RhYtXVIgTyHrSxGne4/457DlIS+IMIJN5Yd
|
||||||
|
gMNj6G6tzM47QDGl2w==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=DigiCert RSA4096 Root for C2PA G1, O=DigiCert, Inc., C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Irdeto C2PA Root CA G1, O=Irdeto BV, C=NL
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICLDCCAbGgAwIBAgIUL8JDbqsQllkL9gozEdKhKc9GFAcwCgYIKoZIzj0EAwMw
|
||||||
|
QjELMAkGA1UEBhMCTkwxEjAQBgNVBAoMCUlyZGV0byBCVjEfMB0GA1UEAwwWSXJk
|
||||||
|
ZXRvIEMyUEEgUm9vdCBDQSBHMTAgFw0yNTEyMDUxMjQ0NDBaGA8yMDUwMTIwNTEy
|
||||||
|
NDQ0MFowQjELMAkGA1UEBhMCTkwxEjAQBgNVBAoMCUlyZGV0byBCVjEfMB0GA1UE
|
||||||
|
AwwWSXJkZXRvIEMyUEEgUm9vdCBDQSBHMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA
|
||||||
|
BEOtgkWt4t4r3Fvn2vDshY1KcZ63/VTaZN7K2uiAcCeztiH+ZSFf4hA5cSCABRJ+
|
||||||
|
VkWZYLvnhNLsCVbtEij6mDN5hf4VIUdUNgUonGZ6tkL1Djs3CUIkjD+nH/HXbCSj
|
||||||
|
pqNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBAjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
|
||||||
|
BBYEFGs1MrTyCBABc00WKVK0njUakyplMB8GA1UdIwQYMBaAFGs1MrTyCBABc00W
|
||||||
|
KVK0njUakyplMAoGCCqGSM49BAMDA2kAMGYCMQCB1h+esIhAsddoGkZI1aXwwCFM
|
||||||
|
wL/cHskaM8JZsvUfcecGQmG08ZVesfUeCzei0SECMQCLjWbR4LY1mgfF+PzlieDZ
|
||||||
|
9DTcd5qOpJaSf90/8AnQA/UWGtRGqJsrmERInDoXEOA=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=vivo Content Provenance and Authenticity Root CA, O=vivo Mobile Communication Co., Ltd., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC0zCCAjSgAwIBAgIJAMIN3t2/xTDAMAoGCCqGSM49BAMEMHYxOTA3BgNVBAMM
|
||||||
|
MHZpdm8gQ29udGVudCBQcm92ZW5hbmNlIGFuZCBBdXRoZW50aWNpdHkgUm9vdCBD
|
||||||
|
QTEsMCoGA1UECgwjdml2byBNb2JpbGUgQ29tbXVuaWNhdGlvbiBDby4sIEx0ZC4x
|
||||||
|
CzAJBgNVBAYTAkNOMCAXDTI1MDkxODA5MDkxM1oYDzIwNTUwOTExMDkwOTEzWjB2
|
||||||
|
MTkwNwYDVQQDDDB2aXZvIENvbnRlbnQgUHJvdmVuYW5jZSBhbmQgQXV0aGVudGlj
|
||||||
|
aXR5IFJvb3QgQ0ExLDAqBgNVBAoMI3Zpdm8gTW9iaWxlIENvbW11bmljYXRpb24g
|
||||||
|
Q28uLCBMdGQuMQswCQYDVQQGEwJDTjCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE
|
||||||
|
ANQigNuQ6RnYFSK2BWkaksR3fRFFytX4nzX3E4hLV/N/7m/XttNHxNDase0cAXwR
|
||||||
|
9pO23p6fb/9NbifILv2wzPn2ASMr306Y2frv/UjJ5J6WgHet2OFywRaPjFjJWRuk
|
||||||
|
4JCy0qoSxyyx230g8GYdacZzIqSzVTW7/Xz4mjnWDTOB/Db+o2YwZDASBgNVHRMB
|
||||||
|
Af8ECDAGAQH/AgECMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUgB0dwwUcpK4u
|
||||||
|
7az42vKC2y+nnY8wHwYDVR0jBBgwFoAUgB0dwwUcpK4u7az42vKC2y+nnY8wCgYI
|
||||||
|
KoZIzj0EAwQDgYwAMIGIAkIB9cjlRbw8I0+0VAIOENCEzKOfp2risHuo3KVqyaSV
|
||||||
|
K/T6KzMNvpq3ihA6ZjnuH9i5y5XWV//awWbFJDSuT0Bq+pwCQgHuthBjGmDl/nyc
|
||||||
|
r3MxJErDVYZBh4b140LQBU60MtiPERVYwme03ukGImPW/OKbEWYmnhHyMAf1ym/3
|
||||||
|
UK/lOv3G2w==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject emailAddress=ca@tauth.io, CN=Tauth Root CA, OU=CA Division, O=Tauth Labs Inc., L=New York, ST=New York, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDjzCCAxWgAwIBAgIUW6zwDse+LBk8Yvy/u3p7lSjnaA4wCgYIKoZIzj0EAwMw
|
||||||
|
gZcxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3
|
||||||
|
IFlvcmsxGDAWBgNVBAoMD1RhdXRoIExhYnMgSW5jLjEUMBIGA1UECwwLQ0EgRGl2
|
||||||
|
aXNpb24xFjAUBgNVBAMMDVRhdXRoIFJvb3QgQ0ExGjAYBgkqhkiG9w0BCQEWC2Nh
|
||||||
|
QHRhdXRoLmlvMB4XDTI2MDEwNTE2MDgzMVoXDTQ1MTIzMTE2MDgzMVowgZcxCzAJ
|
||||||
|
BgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3IFlvcmsx
|
||||||
|
GDAWBgNVBAoMD1RhdXRoIExhYnMgSW5jLjEUMBIGA1UECwwLQ0EgRGl2aXNpb24x
|
||||||
|
FjAUBgNVBAMMDVRhdXRoIFJvb3QgQ0ExGjAYBgkqhkiG9w0BCQEWC2NhQHRhdXRo
|
||||||
|
LmlvMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETgpV7Q9tsMzXFPduTyVt71IRRfU0
|
||||||
|
yeZGVuXmVO6zyUXxqGCg07jafnMyv24pRwJ9Bk+ROhslNWFS0NhXjIINDuk4h5Ji
|
||||||
|
KcEEGBJIlGl7jOB/MPLF/HU7CfURT2ayXKXmo4IBHjCCARowHQYDVR0OBBYEFB3R
|
||||||
|
AQfd1ulUQKBf+J34TnS1CxI+MIHXBgNVHSMEgc8wgcyAFB3RAQfd1ulUQKBf+J34
|
||||||
|
TnS1CxI+oYGdpIGaMIGXMQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsx
|
||||||
|
ETAPBgNVBAcMCE5ldyBZb3JrMRgwFgYDVQQKDA9UYXV0aCBMYWJzIEluYy4xFDAS
|
||||||
|
BgNVBAsMC0NBIERpdmlzaW9uMRYwFAYDVQQDDA1UYXV0aCBSb290IENBMRowGAYJ
|
||||||
|
KoZIhvcNAQkBFgtjYUB0YXV0aC5pb4IUW6zwDse+LBk8Yvy/u3p7lSjnaA4wDwYD
|
||||||
|
VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIx
|
||||||
|
AJzhLaivtrjOMyEAuoHQRIhN2u7kw2iWZZyAT6FJH4+GBYudax0dfewXeIvC0MH5
|
||||||
|
SwIwAKojdigAO86Xvk6++B875ZpTjQIysPX0bGumbmAwcAmsqum9JLLIRCDz+7Kt
|
||||||
|
WrRP
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=HUAWEI C2PA ECC384 Root CA E346, O=Huawei Technologies Co., Ltd., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICoDCCAiagAwIBAgIIKsblXSNLwF8wCgYIKoZIzj0EAwMwXzELMAkGA1UEBhMC
|
||||||
|
Q04xJjAkBgNVBAoMHUh1YXdlaSBUZWNobm9sb2dpZXMgQ28uLCBMdGQuMSgwJgYD
|
||||||
|
VQQDDB9IVUFXRUkgQzJQQSBFQ0MzODQgUm9vdCBDQSBFMzQ2MB4XDTI2MDQxMzAx
|
||||||
|
NTQxMFoXDTQ2MDQwODAxNTQxMFowXzELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUh1
|
||||||
|
YXdlaSBUZWNobm9sb2dpZXMgQ28uLCBMdGQuMSgwJgYDVQQDDB9IVUFXRUkgQzJQ
|
||||||
|
QSBFQ0MzODQgUm9vdCBDQSBFMzQ2MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOrBo
|
||||||
|
SivfM2LRlG3B4r52hE0vDjQR2iWv9sKTZXzkscQIasoH+uYBDDie9VcosH4Mfq/U
|
||||||
|
xN8ojhW7BZW38LT38SmYpJPZirtzKTyCD4ha4dU8tLcHoJsOn3p+kBw09Bvoo4Gu
|
||||||
|
MIGrMB8GA1UdIwQYMBaAFM/OyEcEDscO78w/okatAmz35ypMMB0GA1UdDgQWBBTP
|
||||||
|
zshHBA7HDu/MP6JGrQJs9+cqTDASBgNVHRMBAf8ECDAGAQH/AgECMEUGCCsGAQUF
|
||||||
|
BwELBDkwNzA1BggrBgEFBQcwBYYpaHR0cDovL2NhLmh1YXdlaWNsb3VkLmNvbS9y
|
||||||
|
ZXBvc2l0b3J5Lmh0bWwwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUC
|
||||||
|
MDCsqpRvJIPw6OAQi2Rj0CWnwJgK75YK5gj+K9xomIDu8ai/2ExrifL8JgROQOeZ
|
||||||
|
egIxAL3tT3Dp84wP18ohYU83gkRcA8j9B3T4BRpxY1Ew9IJMYJ2zqz0weFjAEGNC
|
||||||
|
9Z36XQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Huanyu Trust C2PA EC-384 Root CA, O=Huanyu Trust Ltd, C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICTTCCAdOgAwIBAgIUCqsLOXYNHdz0EBDsWKcrsiBcF10wCgYIKoZIzj0EAwMw
|
||||||
|
UzELMAkGA1UEBhMCQ04xGTAXBgNVBAoTEEh1YW55dSBUcnVzdCBMdGQxKTAnBgNV
|
||||||
|
BAMTIEh1YW55dSBUcnVzdCBDMlBBIEVDLTM4NCBSb290IENBMCAXDTI2MDQxMzA2
|
||||||
|
MDUwOVoYDzIwNTEwNDEzMDYwNTA5WjBTMQswCQYDVQQGEwJDTjEZMBcGA1UEChMQ
|
||||||
|
SHVhbnl1IFRydXN0IEx0ZDEpMCcGA1UEAxMgSHVhbnl1IFRydXN0IEMyUEEgRUMt
|
||||||
|
Mzg0IFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARDnAlELrIETK81oB6E
|
||||||
|
/ZfbkGSH9YPhGdFpXTzDCHD3IsKPQdPPv/mF319XjdrZMECxpH9rbmWGuWrhq6K8
|
||||||
|
zIGO27H2MUE658XNu6aO5xcZV/YdAMkG/YWPYo9CmCT4/SujZjBkMBIGA1UdEwEB
|
||||||
|
/wQIMAYBAf8CAQIwHwYDVR0jBBgwFoAUYc0ZfGc1yTPouBQYj81mEEdaefswHQYD
|
||||||
|
VR0OBBYEFGHNGXxnNckz6LgUGI/NZhBHWnn7MA4GA1UdDwEB/wQEAwIBBjAKBggq
|
||||||
|
hkjOPQQDAwNoADBlAjEAqfQxNSnmGxtt1QBKJbaY8AyUiaJxzX5MnH0l/ielnc6D
|
||||||
|
qBhIcALvKKc3a8MizR01AjB85bwUFBgs6WlIWZ6sFDDbCQzrWpPU11YQnj/mGom7
|
||||||
|
yXjL/2axHPWPY2Do/ldP5mc=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Snowball ECC P384 Root CA for C2PA G1, O=Snowball Technology Co., Ltd., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICiTCCAg6gAwIBAgIUVlsRGj1G9vJ4ws2jMYyFlFcKUokwCgYIKoZIzj0EAwMw
|
||||||
|
ZTELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHVNub3diYWxsIFRlY2hub2xvZ3kgQ28u
|
||||||
|
LCBMdGQuMS4wLAYDVQQDDCVTbm93YmFsbCBFQ0MgUDM4NCBSb290IENBIGZvciBD
|
||||||
|
MlBBIEcxMB4XDTI2MDYxODA5MzkxOVoXDTQ2MDYxMzA5MzkxOVowZTELMAkGA1UE
|
||||||
|
BhMCQ04xJjAkBgNVBAoMHVNub3diYWxsIFRlY2hub2xvZ3kgQ28uLCBMdGQuMS4w
|
||||||
|
LAYDVQQDDCVTbm93YmFsbCBFQ0MgUDM4NCBSb290IENBIGZvciBDMlBBIEcxMHYw
|
||||||
|
EAYHKoZIzj0CAQYFK4EEACIDYgAELJUrcxeVmmYrb8Kl/YVAYYFiyuiqiIa6z/1n
|
||||||
|
FoCpB2JV/Qkwjdns7rOvLheWBi1s880NcwqD/KD9t9/SOgWTfD+WOAhtPioAql2Y
|
||||||
|
YUl0toOUzoXaAOB3+HIQRiKsL2RIo38wfTASBgNVHRMBAf8ECDAGAQH/AgECMA4G
|
||||||
|
A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU6R4F1vboIaKQly8jCAQX2lQhWPQwHwYD
|
||||||
|
VR0jBBgwFoAU6R4F1vboIaKQly8jCAQX2lQhWPQwFwYDVR0gBBAwDjAMBgorBgEE
|
||||||
|
AYPoXgEBMAoGCCqGSM49BAMDA2kAMGYCMQD6/xszsnZoYt+gYIsgYKTMFARxRpB5
|
||||||
|
N16s2u2hgkuFs4zyLco1z7gOk0mv6EbBkm0CMQCcsqIQnr45MVjDUdn9L//VdGDO
|
||||||
|
6H+P67vBdmvb3LiLTB+Ey/JYYfnHHIKzJrRG4ls=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Snowball ECC P384 TSA ICA for C2PA G1, O=Snowball Technology Co., Ltd., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDjjCCAxSgAwIBAgIUVFsaHdwprWWlk08S4C67gq5M6qwwCgYIKoZIzj0EAwMw
|
||||||
|
ZTELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHVNub3diYWxsIFRlY2hub2xvZ3kgQ28u
|
||||||
|
LCBMdGQuMS4wLAYDVQQDDCVTbm93YmFsbCBFQ0MgUDM4NCBSb290IENBIGZvciBD
|
||||||
|
MlBBIEcxMB4XDTI2MDYxODA5NDIxNFoXDTQxMDYxODA5NDIxNFowZTELMAkGA1UE
|
||||||
|
BhMCQ04xJjAkBgNVBAoMHVNub3diYWxsIFRlY2hub2xvZ3kgQ28uLCBMdGQuMS4w
|
||||||
|
LAYDVQQDDCVTbm93YmFsbCBFQ0MgUDM4NCBUU0EgSUNBIGZvciBDMlBBIEcxMHYw
|
||||||
|
EAYHKoZIzj0CAQYFK4EEACIDYgAEsI3OjbQCcr6lFS0Y1T8vaS8KcApdpQUAAee7
|
||||||
|
EGSXvyuTfXcK9AUSYHxVZRiJjGliHgyVQL5C2tYEL4JOxw3ttxlvKVkuQI2MHd05
|
||||||
|
7yOyrgn1YC/tsw6dRJhrt+u+7xFso4IBgzCCAX8wEgYDVR0TAQH/BAgwBgEB/wIB
|
||||||
|
ADAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwHQYDVR0OBBYE
|
||||||
|
FNBjlXQUhXxUL3xl/vB43xqIyE7NMB8GA1UdIwQYMBaAFOkeBdb26CGikJcvIwgE
|
||||||
|
F9pUIVj0MBcGA1UdIAQQMA4wDAYKKwYBBAGD6F4BATCBlAYIKwYBBQUHAQEEgYcw
|
||||||
|
gYQwLQYIKwYBBQUHMAGGIWh0dHA6Ly9vY3NwLmMycGEuc25vd2JhbGx0ZWNoLmNv
|
||||||
|
bTBTBggrBgEFBQcwAoZHaHR0cDovL2NhY2VydHMuYzJwYS5zbm93YmFsbHRlY2gu
|
||||||
|
Y29tL1Nub3diYWxsRUNDUDM4NFJvb3RDQWZvckMyUEFHMS5jcnQwVAYDVR0fBE0w
|
||||||
|
SzBJoEegRYZDaHR0cDovL2NybC5jMnBhLnNub3diYWxsdGVjaC5jb20vU25vd2Jh
|
||||||
|
bGxFQ0NQMzg0Um9vdENBZm9yQzJQQUcxLmNybDAKBggqhkjOPQQDAwNoADBlAjB0
|
||||||
|
7SyNJ2s9Mdz78HJwm5aU3J5NdZF8XbNhPcKB76QqrbNjtb2ehkqceoYx3wkotCsC
|
||||||
|
MQDSJY/v5rMUu9DeqNTNnHSvBVTXEzqZj+k47QlVnv9Auu29JHFXrY+Gj+b3Cx6G
|
||||||
|
ZcE=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Encypher C2PA Root CA 2026, OU=CA Division, O=Encypher Corp., L=San Francisco, ST=California, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDFDCCApqgAwIBAgIUS2zOIYRKaa59snU3NcNFadfTtoAwCgYIKoZIzj0EAwMw
|
||||||
|
gY4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1T
|
||||||
|
YW4gRnJhbmNpc2NvMRcwFQYDVQQKDA5FbmN5cGhlciBDb3JwLjEUMBIGA1UECwwL
|
||||||
|
Q0EgRGl2aXNpb24xIzAhBgNVBAMMGkVuY3lwaGVyIEMyUEEgUm9vdCBDQSAyMDI2
|
||||||
|
MB4XDTI2MDUyMjEzNDk1NloXDTQ2MDUxNzEzNDk1NlowgY4xCzAJBgNVBAYTAlVT
|
||||||
|
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRcw
|
||||||
|
FQYDVQQKDA5FbmN5cGhlciBDb3JwLjEUMBIGA1UECwwLQ0EgRGl2aXNpb24xIzAh
|
||||||
|
BgNVBAMMGkVuY3lwaGVyIEMyUEEgUm9vdCBDQSAyMDI2MHYwEAYHKoZIzj0CAQYF
|
||||||
|
K4EEACIDYgAE27XmRBc4mE0dYfUhLiuPiAnY3tRDMke9evylB+VgyMspT1svR9h3
|
||||||
|
KXxr8tml+1oM82US+pw2jJenfUxoUJGVDY/lvwl+4Q+7vawXFd26yPv/CQSnhI+w
|
||||||
|
05+yiSiwshIOo4G2MIGzMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQD
|
||||||
|
AgEGMB0GA1UdDgQWBBS3gC7ps3/HLQbb3uuqL4U8gDnKEDAfBgNVHSMEGDAWgBS3
|
||||||
|
gC7ps3/HLQbb3uuqL4U8gDnKEDBNBgNVHSAERjBEMEIGCisGAQQBg+heAQEwNDAy
|
||||||
|
BggrBgEFBQcCARYmaHR0cHM6Ly9jYS5lbmN5cGhlci5jb20vcmVwb3NpdG9yeS9j
|
||||||
|
cHMwCgYIKoZIzj0EAwMDaAAwZQIwYeb83YEb/Oh9uELuvO9jpb85orRWWQiceqf0
|
||||||
|
sCwxtJw07/giyy/hI1BFvbQZcfkLAjEAj8jfwuJSJ9LjeeYL/VXStQWDHewxbIou
|
||||||
|
IKdYpS9ZBidpsi+01tpQY1j7apLAXMHO
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Encypher C2PA TSA Issuing CA 2026, OU=CA Division, O=Encypher Corp., L=San Francisco, ST=California, C=US
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID3zCCA2WgAwIBAgIUfkelmX2xvvmLxUBI5ayBZXdGOz4wCgYIKoZIzj0EAwMw
|
||||||
|
gY4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1T
|
||||||
|
YW4gRnJhbmNpc2NvMRcwFQYDVQQKDA5FbmN5cGhlciBDb3JwLjEUMBIGA1UECwwL
|
||||||
|
Q0EgRGl2aXNpb24xIzAhBgNVBAMMGkVuY3lwaGVyIEMyUEEgUm9vdCBDQSAyMDI2
|
||||||
|
MB4XDTI2MDUyNDE1NTUxOVoXDTMxMDUyNTE1NTUxOVowgZUxCzAJBgNVBAYTAlVT
|
||||||
|
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRcw
|
||||||
|
FQYDVQQKDA5FbmN5cGhlciBDb3JwLjEUMBIGA1UECwwLQ0EgRGl2aXNpb24xKjAo
|
||||||
|
BgNVBAMMIUVuY3lwaGVyIEMyUEEgVFNBIElzc3VpbmcgQ0EgMjAyNjB2MBAGByqG
|
||||||
|
SM49AgEGBSuBBAAiA2IABAPWSlEq6MdUceWdJ5nsZSwXTBq+enJovg6OTABtcAdS
|
||||||
|
uEDTcD1t9eajKVlPvK7xqbG18InxR0Ave8UA6jNTFD+4f/fjqR/HbJAtX2l7lcv5
|
||||||
|
lM9PiYJER64WF7Diyibcm6OCAXkwggF1MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD
|
||||||
|
VR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMB0GA1UdDgQWBBRACKf4
|
||||||
|
2ACoV/5pGlYPM5hBkBcWlTAfBgNVHSMEGDAWgBS3gC7ps3/HLQbb3uuqL4U8gDnK
|
||||||
|
EDA3BgNVHR8EMDAuMCygKqAohiZodHRwOi8vY2EuZW5jeXBoZXIuY29tL2NybC9y
|
||||||
|
b290LWNhLmNybDByBggrBgEFBQcBAQRmMGQwJwYIKwYBBQUHMAGGG2h0dHA6Ly9j
|
||||||
|
YS5lbmN5cGhlci5jb20vb2NzcDA5BggrBgEFBQcwAoYtaHR0cDovL2NhLmVuY3lw
|
||||||
|
aGVyLmNvbS9yZXBvc2l0b3J5L3Jvb3QtY2EuY3J0ME0GA1UdIARGMEQwQgYKKwYB
|
||||||
|
BAGD6F4BATA0MDIGCCsGAQUFBwIBFiZodHRwczovL2NhLmVuY3lwaGVyLmNvbS9y
|
||||||
|
ZXBvc2l0b3J5L2NwczAKBggqhkjOPQQDAwNoADBlAjBXL0geKILQqoIKi9wD74Hr
|
||||||
|
id0nXiRZhyMrI39yGXDjq5/rqsQhCdOR+Gs4XIX9lhUCMQCb7yodaeskIdoHYjvV
|
||||||
|
y59amQPG8tMRAXIaybH/Df/Dwb2YD/D+hbgMn0MufkrB4/w=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=TrustAsia C2PA RSA Root CA, O=TrustAsia Technologies, Inc., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
qtj6+iKCvpx9jmvr
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=TrustAsia C2PA ECC Root CA, O=TrustAsia Technologies, Inc., C=CN
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICWjCCAd+gAwIBAgIUdUWMu3G69tNb9bwz/0KTupxLm1kwCgYIKoZIzj0EAwMw
|
||||||
|
WTELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs
|
||||||
|
IEluYy4xIzAhBgNVBAMMGlRydXN0QXNpYSBDMlBBIEVDQyBSb290IENBMCAXDTI2
|
||||||
|
MDYwNTAzNTAwMFoYDzIwNTEwNjA1MDM0OTU5WjBZMQswCQYDVQQGEwJDTjElMCMG
|
||||||
|
A1UECgwcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEjMCEGA1UEAwwaVHJ1
|
||||||
|
c3RBc2lhIEMyUEEgRUNDIFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQL
|
||||||
|
ERY0d7ug9MiCqmXnDjDs2med9zfTqTWOcG3OD5Q6FSEgNUcjT5Qy7xzKn1MfqaY4
|
||||||
|
Ye3sf+Do7cCDR6AiFW6SqLQobfJ70jMDsSQYlrCCjMLvb+NdNPhED43s8fgehxyj
|
||||||
|
ZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQIwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQW
|
||||||
|
BBT13DKCd7Jlz2rEe0vBlwo/CoR2mDAfBgNVHSMEGDAWgBT13DKCd7Jlz2rEe0vB
|
||||||
|
lwo/CoR2mDAKBggqhkjOPQQDAwNpADBmAjEAwp2Jie8k6/haTc/df3QsX+Ttg7Wp
|
||||||
|
ARtYTwJSP4Z+ggpf39sZ+7QPOE50BqBj9Q6zAjEAjZ+5glED9tav6r3jWuMOmKTB
|
||||||
|
kobBXzvjqTDLGG570BbSQfNmS3HRC6u5YTIpElpb
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Subject CN=Castlabs C2PA ECC P-384 Root CA, OU=C2PA Trust Infrastructure, O=Castlabs, L=Berlin, ST=Berlin, C=DE
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICyDCCAk2gAwIBAgIUN5SkK2Lb2Ts/j1AC3K8Y3eVydFQwCgYIKoZIzj0EAwMw
|
||||||
|
gZAxKDAmBgNVBAMMH0Nhc3RsYWJzIEMyUEEgRUNDIFAtMzg0IFJvb3QgQ0ExCzAJ
|
||||||
|
BgNVBAYTAkRFMQ8wDQYDVQQHDAZCZXJsaW4xETAPBgNVBAoMCENhc3RsYWJzMSIw
|
||||||
|
IAYDVQQLDBlDMlBBIFRydXN0IEluZnJhc3RydWN0dXJlMQ8wDQYDVQQIDAZCZXJs
|
||||||
|
aW4wHhcNMjYwNzA4MDE0NDMwWhcNMzYwNzA1MDE0NDMwWjCBkDEoMCYGA1UEAwwf
|
||||||
|
Q2FzdGxhYnMgQzJQQSBFQ0MgUC0zODQgUm9vdCBDQTELMAkGA1UEBhMCREUxDzAN
|
||||||
|
BgNVBAcMBkJlcmxpbjERMA8GA1UECgwIQ2FzdGxhYnMxIjAgBgNVBAsMGUMyUEEg
|
||||||
|
VHJ1c3QgSW5mcmFzdHJ1Y3R1cmUxDzANBgNVBAgMBkJlcmxpbjB2MBAGByqGSM49
|
||||||
|
AgEGBSuBBAAiA2IABPXhd9L+pjQVnUXH6I7+oFSrd6JpO4A+CYDm/AuDZgJvwiuJ
|
||||||
|
lrM9dC3CAE7UAK8IcQEo85qzuak0TfLdPaJWD26+q3tJ/S7NW4sOKhH09KRN7iFq
|
||||||
|
XbsOe8FOxaQql/UrMqNmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB
|
||||||
|
Af8CAQIwHQYDVR0OBBYEFHNKe2PoAANQBj4/25eRv2VY4Fy7MB8GA1UdIwQYMBaA
|
||||||
|
FHNKe2PoAANQBj4/25eRv2VY4Fy7MAoGCCqGSM49BAMDA2kAMGYCMQDUlNJD49Ux
|
||||||
|
zwBccYHiz0wgmbIXBDdgpVrLfWTQnbi21BhfPBQ6j0HJ24FY36L5oj8CMQCv5+bK
|
||||||
|
gBIkJq9USBx2FjjDkukYjQMI/WDD6SH6QGTdotsH3cQQ6bjr9C+0ruxxx08=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
The PEM files in this directory are snapshots of the official C2PA Conformance
|
||||||
|
Program trust lists from https://github.com/c2pa-org/conformance-public.
|
||||||
|
|
||||||
|
They are bundled so credential trust validation remains local and reproducible.
|
||||||
|
Refresh both files before a release and record the update in the release notes.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { cp, mkdir } from 'node:fs/promises';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { build } from 'esbuild';
|
||||||
|
|
||||||
|
const root = new URL('../', import.meta.url);
|
||||||
|
const outdir = new URL('../dist/', import.meta.url);
|
||||||
|
const rootPath = fileURLToPath(root);
|
||||||
|
const outdirPath = fileURLToPath(outdir);
|
||||||
|
|
||||||
|
await mkdir(outdir, { recursive: true });
|
||||||
|
await cp(new URL('../public/', import.meta.url), outdir, { recursive: true });
|
||||||
|
await cp(
|
||||||
|
new URL('../node_modules/@contentauth/c2pa-web/dist/resources/c2pa_bg.wasm', import.meta.url),
|
||||||
|
new URL('../dist/c2pa_bg.wasm', import.meta.url)
|
||||||
|
);
|
||||||
|
await cp(
|
||||||
|
new URL('../node_modules/@contentauth/c2pa-web/dist/c2pa_worker.js', import.meta.url),
|
||||||
|
new URL('../dist/c2pa_worker.js', import.meta.url)
|
||||||
|
);
|
||||||
|
|
||||||
|
await build({
|
||||||
|
absWorkingDir: rootPath,
|
||||||
|
entryPoints: {
|
||||||
|
background: 'src/background/index.ts',
|
||||||
|
content: 'src/content/index.ts',
|
||||||
|
popup: 'src/popup/index.tsx',
|
||||||
|
options: 'src/options/index.tsx'
|
||||||
|
},
|
||||||
|
outdir: outdirPath,
|
||||||
|
bundle: true,
|
||||||
|
format: 'iife',
|
||||||
|
platform: 'browser',
|
||||||
|
target: ['firefox128'],
|
||||||
|
jsx: 'automatic',
|
||||||
|
jsxImportSource: 'preact',
|
||||||
|
sourcemap: true,
|
||||||
|
minify: true,
|
||||||
|
legalComments: 'eof',
|
||||||
|
logLevel: 'info'
|
||||||
|
});
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { rm } from 'node:fs/promises';
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
rm(new URL('../dist', import.meta.url), { recursive: true, force: true }),
|
||||||
|
rm(new URL('../artifacts', import.meta.url), { recursive: true, force: true })
|
||||||
|
]);
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import { createC2pa } from '@contentauth/c2pa-web';
|
||||||
|
import type { Evidence } from '../shared/types';
|
||||||
|
|
||||||
|
const AI_SOURCE_TYPES = [
|
||||||
|
'trainedalgorithmicmedia',
|
||||||
|
'compositewithtrainedalgorithmicmedia',
|
||||||
|
'algorithmicallyenhanced',
|
||||||
|
'algorithmicmedia'
|
||||||
|
];
|
||||||
|
|
||||||
|
interface RecordLike {
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
let c2paPromise: ReturnType<typeof createC2pa> | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* c2pa-web 0.14.4 rejects every explicit worker URL whose protocol is not
|
||||||
|
* `https:`, including Firefox's same-extension `moz-extension:` URLs. The
|
||||||
|
* package only reads `protocol` and `toString()` before constructing Worker,
|
||||||
|
* so this adapter lets its HTTPS-only guard validate while Worker still
|
||||||
|
* receives the real, CSP-approved packaged URL. Keep this isolated until the
|
||||||
|
* upstream SDK accepts browser-extension schemes directly.
|
||||||
|
*/
|
||||||
|
export class FirefoxExtensionWorkerUrl extends URL {
|
||||||
|
readonly #packagedUrl: string;
|
||||||
|
|
||||||
|
constructor(packagedUrl: string) {
|
||||||
|
super('https://slopdetect.invalid/c2pa_worker.js');
|
||||||
|
if (!packagedUrl.startsWith('moz-extension:')) {
|
||||||
|
throw new Error('C2PA worker must be a packaged Firefox extension resource.');
|
||||||
|
}
|
||||||
|
this.#packagedUrl = packagedUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
override toString(): string {
|
||||||
|
return this.#packagedUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initialiseC2pa(): ReturnType<typeof createC2pa> {
|
||||||
|
const [trustList, timestampTrustList] = await Promise.all([
|
||||||
|
fetch(browser.runtime.getURL('trust/C2PA-TRUST-LIST.pem')).then((response) => response.text()),
|
||||||
|
fetch(browser.runtime.getURL('trust/C2PA-TSA-TRUST-LIST.pem')).then((response) => response.text())
|
||||||
|
]);
|
||||||
|
return createC2pa({
|
||||||
|
wasmSrc: browser.runtime.getURL('c2pa_bg.wasm'),
|
||||||
|
workerSrc: new FirefoxExtensionWorkerUrl(browser.runtime.getURL('c2pa_worker.js')),
|
||||||
|
settings: {
|
||||||
|
trust: { trustAnchors: `${trustList}\n${timestampTrustList}` },
|
||||||
|
verify: {
|
||||||
|
verifyTrust: true,
|
||||||
|
verifyAfterReading: true,
|
||||||
|
verifyTimestampTrust: true,
|
||||||
|
ocspFetch: false,
|
||||||
|
remoteManifestFetch: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getC2pa(): ReturnType<typeof createC2pa> {
|
||||||
|
c2paPromise ??= initialiseC2pa();
|
||||||
|
return c2paPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is RecordLike {
|
||||||
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function walk(value: unknown, visit: (key: string, value: unknown) => void): void {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach((item) => walk(item, visit));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isRecord(value)) return;
|
||||||
|
for (const [key, child] of Object.entries(value)) {
|
||||||
|
visit(key, child);
|
||||||
|
walk(child, visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectStrings(value: unknown): string[] {
|
||||||
|
const strings: string[] = [];
|
||||||
|
walk(value, (_key, child) => {
|
||||||
|
if (typeof child === 'string') strings.push(child);
|
||||||
|
});
|
||||||
|
return strings;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectValidationCodes(value: unknown): string[] {
|
||||||
|
const codes = new Set<string>();
|
||||||
|
walk(value, (key, child) => {
|
||||||
|
if (/^(code|status|validation_status|validationStatus)$/i.test(key) && typeof child === 'string') {
|
||||||
|
codes.add(child);
|
||||||
|
}
|
||||||
|
if (/validation_status|validationStatus/i.test(key) && Array.isArray(child)) {
|
||||||
|
for (const entry of child) {
|
||||||
|
if (typeof entry === 'string') codes.add(entry);
|
||||||
|
else if (isRecord(entry) && typeof entry.code === 'string') codes.add(entry.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return [...codes];
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFirstString(value: unknown, matchingKeys: RegExp): string | undefined {
|
||||||
|
let found: string | undefined;
|
||||||
|
walk(value, (key, child) => {
|
||||||
|
if (!found && matchingKeys.test(key) && typeof child === 'string') found = child;
|
||||||
|
});
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasTrustedCredential(value: unknown): boolean {
|
||||||
|
let trusted = false;
|
||||||
|
walk(value, (key, child) => {
|
||||||
|
if (/trusted/i.test(key) && child === true) trusted = true;
|
||||||
|
if (/validation_state|validationState/i.test(key) && child === 'Trusted') trusted = true;
|
||||||
|
});
|
||||||
|
return trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasInvalidValidationState(value: unknown): boolean {
|
||||||
|
let invalid = false;
|
||||||
|
walk(value, (key, child) => {
|
||||||
|
if (/validation_state|validationState/i.test(key) && child === 'Invalid') invalid = true;
|
||||||
|
});
|
||||||
|
return invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsManifest(value: unknown): boolean {
|
||||||
|
if (!isRecord(value)) return false;
|
||||||
|
return Boolean(value.active_manifest || value.activeManifest || value.manifests || value.claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInvalidStatus(code: string): boolean {
|
||||||
|
return /invalid|mismatch|malformed|error|failure|revoked|expired/i.test(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function serialisable(value: unknown): unknown {
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(value, (_key, item) => (typeof item === 'bigint' ? item.toString() : item)));
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMissingManifestError(error: unknown): boolean {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
return /manifest.*(?:not found|absent)|jumbf.*not found|no claim|unsupported.*(?:mime|format)/i.test(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function inspectC2pa(blob: Blob, includeRaw: boolean): Promise<Evidence[]> {
|
||||||
|
let reader: Awaited<ReturnType<Awaited<ReturnType<typeof createC2pa>>['reader']['fromBlob']>> | undefined;
|
||||||
|
try {
|
||||||
|
const c2pa = await getC2pa();
|
||||||
|
reader = await c2pa.reader.fromBlob(blob.type || 'application/octet-stream', blob);
|
||||||
|
if (!reader) return [];
|
||||||
|
const store: unknown = await reader.manifestStore();
|
||||||
|
return evidenceFromManifestStore(store, includeRaw);
|
||||||
|
} catch (error) {
|
||||||
|
if (isMissingManifestError(error)) return [];
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
return [{
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
kind: 'limitation',
|
||||||
|
strength: 'none',
|
||||||
|
verdict: 'unknown',
|
||||||
|
title: 'C2PA inspection failed',
|
||||||
|
detail: message.slice(0, 300),
|
||||||
|
detector: '@contentauth/c2pa-web'
|
||||||
|
}];
|
||||||
|
} finally {
|
||||||
|
await reader?.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function evidenceFromManifestStore(store: unknown, includeRaw: boolean): Evidence[] {
|
||||||
|
if (!containsManifest(store)) return [];
|
||||||
|
|
||||||
|
const strings = collectStrings(store);
|
||||||
|
const aiGenerated = strings.some((value) => {
|
||||||
|
const normalised = value.toLowerCase().replace(/[^a-z]/g, '');
|
||||||
|
return AI_SOURCE_TYPES.some((type) => normalised.includes(type));
|
||||||
|
});
|
||||||
|
const statuses = collectValidationCodes(store);
|
||||||
|
const invalid = hasInvalidValidationState(store) || statuses.some(isInvalidStatus);
|
||||||
|
const trusted = hasTrustedCredential(store);
|
||||||
|
const issuer = findFirstString(store, /issuer|common_name|organization|signer/i);
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return [{
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
kind: 'c2pa',
|
||||||
|
strength: 'invalid',
|
||||||
|
verdict: 'invalid',
|
||||||
|
title: 'Invalid Content Credential',
|
||||||
|
detail: `C2PA provenance was present but validation reported: ${statuses.join(', ') || 'an integrity error'}.`,
|
||||||
|
detector: '@contentauth/c2pa-web',
|
||||||
|
issuer,
|
||||||
|
raw: includeRaw ? serialisable(store) : undefined
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
kind: 'c2pa',
|
||||||
|
strength: trusted ? 'verified' : 'detected',
|
||||||
|
verdict: aiGenerated ? 'ai' : 'not-ai-asserted',
|
||||||
|
title: aiGenerated ? 'AI provenance credential' : 'Content Credential',
|
||||||
|
detail: aiGenerated
|
||||||
|
? `A ${trusted ? 'trusted and valid' : 'readable but not trusted'} C2PA claim identifies AI generation or manipulation.`
|
||||||
|
: `A ${trusted ? 'trusted and valid' : 'readable but not trusted'} C2PA claim was found without an AI-generation assertion.`,
|
||||||
|
detector: '@contentauth/c2pa-web',
|
||||||
|
issuer,
|
||||||
|
raw: includeRaw ? serialisable(store) : undefined
|
||||||
|
}];
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
interface CacheEntry<T> {
|
||||||
|
value: T;
|
||||||
|
touchedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ResultCache<T> {
|
||||||
|
readonly #entries = new Map<string, CacheEntry<T>>();
|
||||||
|
#clock = 0;
|
||||||
|
|
||||||
|
public constructor(private readonly maximumEntries = 200) {}
|
||||||
|
|
||||||
|
public get(key: string): T | undefined {
|
||||||
|
const entry = this.#entries.get(key);
|
||||||
|
if (!entry) return undefined;
|
||||||
|
entry.touchedAt = ++this.#clock;
|
||||||
|
return entry.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set(key: string, value: T): void {
|
||||||
|
this.#entries.set(key, { value, touchedAt: ++this.#clock });
|
||||||
|
if (this.#entries.size <= this.maximumEntries) return;
|
||||||
|
|
||||||
|
const oldest = [...this.#entries.entries()].sort((left, right) => left[1].touchedAt - right[1].touchedAt)[0];
|
||||||
|
if (oldest) this.#entries.delete(oldest[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hashBlob(blob: Blob): Promise<string> {
|
||||||
|
const digest = await crypto.subtle.digest('SHA-256', await blob.arrayBuffer());
|
||||||
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import type { Evidence, ImageCandidate, TextCandidate } from '../shared/types';
|
||||||
|
|
||||||
|
export interface ImageWatermarkDetector {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
supports(mimeType: string): boolean;
|
||||||
|
detect(blob: Blob, candidate: ImageCandidate): Promise<Evidence[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextWatermarkDetector {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly minimumTokens: number;
|
||||||
|
supports(language?: string): boolean;
|
||||||
|
detect(candidate: TextCandidate): Promise<Evidence[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watermark schemes currently require a provider-specific public SDK or API.
|
||||||
|
// Adapters are deliberately empty until a detector can be tested end-to-end.
|
||||||
|
export const imageWatermarkDetectors: readonly ImageWatermarkDetector[] = [];
|
||||||
|
export const textWatermarkDetectors: readonly TextWatermarkDetector[] = [];
|
||||||
|
|
||||||
|
export async function runImageWatermarkDetectors(
|
||||||
|
blob: Blob,
|
||||||
|
candidate: ImageCandidate
|
||||||
|
): Promise<Evidence[]> {
|
||||||
|
const compatible = imageWatermarkDetectors.filter((detector) => detector.supports(blob.type));
|
||||||
|
const results = await Promise.allSettled(compatible.map((detector) => detector.detect(blob, candidate)));
|
||||||
|
return results.flatMap((result) => (result.status === 'fulfilled' ? result.value : []));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runTextWatermarkDetectors(candidate: TextCandidate): Promise<Evidence[]> {
|
||||||
|
const compatible = textWatermarkDetectors.filter(
|
||||||
|
(detector) => candidate.tokenEstimate >= detector.minimumTokens && detector.supports()
|
||||||
|
);
|
||||||
|
const results = await Promise.allSettled(compatible.map((detector) => detector.detect(candidate)));
|
||||||
|
return results.flatMap((result) => (result.status === 'fulfilled' ? result.value : []));
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import type { Evidence, PageDisclosureCandidate } from '../shared/types';
|
||||||
|
|
||||||
|
const AI_DISCLOSURE = /\b(ai[- ]generated|generated (?:or manipulated )?by ai|artificially generated|created with (?:generative )?ai|synthetic media|trainedalgorithmicmedia)\b/i;
|
||||||
|
const AI_GENERATOR = /\b(chatgpt|openai|claude|anthropic|gemini|midjourney|dall[ -]?e|stable diffusion|firefly|copilot|framer ai|wix ai)\b/i;
|
||||||
|
|
||||||
|
function evidenceId(prefix: string, value: string): string {
|
||||||
|
let hash = 0;
|
||||||
|
for (const character of value) hash = (hash * 31 + character.charCodeAt(0)) | 0;
|
||||||
|
return `${prefix}-${Math.abs(hash)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function analyseDisclosures(candidates: PageDisclosureCandidate[]): Evidence[] {
|
||||||
|
const evidence: Evidence[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const value = candidate.value.trim().slice(0, 500);
|
||||||
|
if (!value) continue;
|
||||||
|
const identity = `${candidate.source}:${value}`.toLowerCase();
|
||||||
|
if (seen.has(identity)) continue;
|
||||||
|
seen.add(identity);
|
||||||
|
|
||||||
|
if (AI_DISCLOSURE.test(value) || /^(true|yes|1)$/i.test(value) && /ai/i.test(candidate.source)) {
|
||||||
|
evidence.push({
|
||||||
|
id: evidenceId('disclosure', identity),
|
||||||
|
kind: 'disclosure',
|
||||||
|
strength: 'disclosed',
|
||||||
|
verdict: 'ai',
|
||||||
|
title: 'Publisher AI disclosure',
|
||||||
|
detail: `${candidate.source}: ${value}`
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidate.source.toLowerCase().includes('generator') && AI_GENERATOR.test(value)) {
|
||||||
|
evidence.push({
|
||||||
|
id: evidenceId('generator', identity),
|
||||||
|
kind: 'generator',
|
||||||
|
strength: 'clue',
|
||||||
|
verdict: 'unknown',
|
||||||
|
title: 'AI-related generator metadata',
|
||||||
|
detail: `${candidate.source}: ${value}. This is a tooling clue, not proof that the page content was AI-generated.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return evidence;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Evidence, HeuristicFinding } from '../shared/types';
|
||||||
|
|
||||||
|
export function evidenceFromHeuristics(findings: HeuristicFinding[]): Evidence[] {
|
||||||
|
return findings.map((finding) => ({
|
||||||
|
id: `heuristic:${finding.ruleId}`,
|
||||||
|
kind: 'heuristic',
|
||||||
|
strength: 'clue',
|
||||||
|
verdict: 'unknown',
|
||||||
|
title: finding.title,
|
||||||
|
detail: `${finding.detail}${finding.sample ? ` Example: ${finding.sample}` : ''}`,
|
||||||
|
detector: 'SlopDetect deterministic page heuristics',
|
||||||
|
category: finding.category,
|
||||||
|
score: finding.score,
|
||||||
|
occurrences: finding.occurrences
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function heuristicScore(evidence: Evidence[]): number {
|
||||||
|
const heuristicEvidence = evidence.filter((item) => item.kind === 'heuristic');
|
||||||
|
const base = heuristicEvidence.reduce((sum, item) => sum + (item.score || 0), 0);
|
||||||
|
const categories = new Set(heuristicEvidence.map((item) => item.category).filter(Boolean));
|
||||||
|
return base + Math.max(0, categories.size - 1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import type { Evidence } from '../shared/types';
|
||||||
|
|
||||||
|
const MAX_METADATA_SCAN_BYTES = 2 * 1024 * 1024;
|
||||||
|
|
||||||
|
interface GeneratorSignature {
|
||||||
|
name: string;
|
||||||
|
pattern: RegExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIGNATURES: GeneratorSignature[] = [
|
||||||
|
{ name: 'OpenAI / DALL·E', pattern: /\b(?:openai|dall[ -]?e)\b/i },
|
||||||
|
{ name: 'Midjourney', pattern: /\bmidjourney\b/i },
|
||||||
|
{ name: 'Stable Diffusion', pattern: /\bstable[ _-]?diffusion\b/i },
|
||||||
|
{ name: 'AUTOMATIC1111', pattern: /\bautomatic1111\b/i },
|
||||||
|
{ name: 'ComfyUI', pattern: /\bcomfyui\b|"(?:ckpt_name|sampler_name|positive)"\s*:/i },
|
||||||
|
{ name: 'InvokeAI', pattern: /\binvokeai\b/i },
|
||||||
|
{ name: 'Fooocus', pattern: /\bfooocus\b/i },
|
||||||
|
{ name: 'NovelAI', pattern: /\bnovelai\b/i },
|
||||||
|
{ name: 'Stable Diffusion parameters', pattern: /\bsteps:\s*\d+[^\0]{0,300}\bsampler:\s*[^,\0]+[^\0]{0,300}\bcfg scale:\s*[\d.]+/i }
|
||||||
|
];
|
||||||
|
|
||||||
|
function evidenceId(name: string): string {
|
||||||
|
return `image-generator:${name.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function inspectImageGeneratorMetadata(blob: Blob): Promise<Evidence[]> {
|
||||||
|
const head = await blob.slice(0, Math.min(blob.size, MAX_METADATA_SCAN_BYTES)).arrayBuffer();
|
||||||
|
const text = new TextDecoder('latin1').decode(head);
|
||||||
|
const found = SIGNATURES.filter((signature) => signature.pattern.test(text));
|
||||||
|
if (found.length === 0) return [];
|
||||||
|
|
||||||
|
const names = [...new Set(found.map((signature) => signature.name))];
|
||||||
|
return [{
|
||||||
|
id: evidenceId(names.join('-')),
|
||||||
|
kind: 'generator',
|
||||||
|
strength: 'detected',
|
||||||
|
verdict: 'ai',
|
||||||
|
title: 'AI generator metadata detected',
|
||||||
|
detail: `The image bytes contain metadata associated with ${names.join(', ')}. This survives some exports, but it is unsigned and can be removed or forged.`,
|
||||||
|
detector: 'SlopDetect image metadata signatures'
|
||||||
|
}];
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import browser from 'webextension-polyfill';
|
||||||
|
import { scanPage } from './scanner';
|
||||||
|
import type { RuntimeRequest, RuntimeResponse, ScanResult } from '../shared/types';
|
||||||
|
|
||||||
|
Object.assign(globalThis, { browser });
|
||||||
|
|
||||||
|
const tabResults = new Map<number, ScanResult>();
|
||||||
|
|
||||||
|
browser.runtime.onMessage.addListener((message: unknown, sender: browser.Runtime.MessageSender) => {
|
||||||
|
const request = message as RuntimeRequest;
|
||||||
|
if (!request || typeof request !== 'object' || typeof request.type !== 'string') return undefined;
|
||||||
|
|
||||||
|
if (request.type === 'analyse-page') {
|
||||||
|
return scanPage(request.page, request.mode)
|
||||||
|
.then((result): RuntimeResponse<ScanResult> => {
|
||||||
|
if (sender.tab?.id !== undefined) {
|
||||||
|
tabResults.set(sender.tab.id, result);
|
||||||
|
const badge = ['verified-ai', 'provenance-detected', 'watermark-detected', 'publisher-disclosed'].includes(result.summary.status) ? 'AI'
|
||||||
|
: result.summary.status === 'invalid-credential' ? '!'
|
||||||
|
: result.summary.status === 'mixed-provenance' ? '≋'
|
||||||
|
: result.summary.status === 'heuristic-suspected' ? 'S'
|
||||||
|
: '';
|
||||||
|
const color = ['verified-ai', 'watermark-detected'].includes(result.summary.status) ? '#6847ee'
|
||||||
|
: result.summary.status === 'invalid-credential' ? '#bd2535'
|
||||||
|
: result.summary.status === 'heuristic-suspected' ? '#b05d16'
|
||||||
|
: result.summary.status === 'mixed-provenance' ? '#4761a8' : '#956100';
|
||||||
|
void browser.action.setBadgeText({ tabId: sender.tab.id, text: badge });
|
||||||
|
void browser.action.setBadgeBackgroundColor({ tabId: sender.tab.id, color });
|
||||||
|
}
|
||||||
|
return { ok: true, data: result };
|
||||||
|
})
|
||||||
|
.catch((error): RuntimeResponse => ({
|
||||||
|
ok: false,
|
||||||
|
error: error instanceof Error ? error.message : String(error)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.type === 'get-tab-result') {
|
||||||
|
return Promise.resolve({ ok: true, data: tabResults.get(request.tabId) } satisfies RuntimeResponse<ScanResult | undefined>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.type === 'clear-tab-result') {
|
||||||
|
tabResults.delete(request.tabId);
|
||||||
|
return Promise.resolve({ ok: true } satisfies RuntimeResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.tabs.onRemoved.addListener((tabId) => tabResults.delete(tabId));
|
||||||
|
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
||||||
|
if (changeInfo.status !== 'loading' && changeInfo.url === undefined) return;
|
||||||
|
tabResults.delete(tabId);
|
||||||
|
void browser.action.setBadgeText({ tabId, text: '' });
|
||||||
|
});
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import { inspectC2pa } from './c2pa';
|
||||||
|
import { ResultCache, hashBlob } from './cache';
|
||||||
|
import { analyseDisclosures } from './disclosures';
|
||||||
|
import { evidenceFromHeuristics } from './heuristics';
|
||||||
|
import { inspectImageGeneratorMetadata } from './image-metadata';
|
||||||
|
import { runImageWatermarkDetectors, runTextWatermarkDetectors, textWatermarkDetectors } from './detectors';
|
||||||
|
import { aggregateSummary, createAssetResult } from '../shared/status';
|
||||||
|
import {
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
type AssetResult,
|
||||||
|
type Evidence,
|
||||||
|
type ExtensionSettings,
|
||||||
|
type ImageCandidate,
|
||||||
|
type PageCandidate,
|
||||||
|
type ScanResult,
|
||||||
|
type TextCandidate
|
||||||
|
} from '../shared/types';
|
||||||
|
|
||||||
|
const imageCache = new ResultCache<Evidence[]>(200);
|
||||||
|
const AUTO_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
||||||
|
|
||||||
|
async function loadSettings(): Promise<ExtensionSettings> {
|
||||||
|
const saved = await browser.storage.local.get('settings');
|
||||||
|
return { ...DEFAULT_SETTINGS, ...(saved.settings as Partial<ExtensionSettings> | undefined) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrivateHost(hostname: string): boolean {
|
||||||
|
const host = hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
||||||
|
if (host === 'localhost' || host === '::1' || host.endsWith('.local')) return true;
|
||||||
|
if (/^127\./.test(host) || /^10\./.test(host) || /^192\.168\./.test(host)) return true;
|
||||||
|
const private172 = /^172\.(\d{1,2})\./.exec(host);
|
||||||
|
return private172 ? Number(private172[1]) >= 16 && Number(private172[1]) <= 31 : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeAssetUrl(value: string, pageUrl: string): URL | undefined {
|
||||||
|
try {
|
||||||
|
const url = new URL(value);
|
||||||
|
if (!['http:', 'https:', 'data:'].includes(url.protocol) || url.username || url.password) return undefined;
|
||||||
|
if (url.protocol === 'data:') return url;
|
||||||
|
const page = new URL(pageUrl);
|
||||||
|
if (isPrivateHost(url.hostname) && url.origin !== page.origin) return undefined;
|
||||||
|
return url;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchImage(candidate: ImageCandidate, maximumBytes: number, pageUrl: string): Promise<Blob> {
|
||||||
|
const url = safeAssetUrl(candidate.url, pageUrl);
|
||||||
|
if (!url) throw new Error('Unsupported or unsafe image URL scheme.');
|
||||||
|
const response = await fetch(url.href, { credentials: 'omit', cache: 'force-cache', redirect: 'error' });
|
||||||
|
if (!response.ok) throw new Error(`Image request returned HTTP ${response.status}.`);
|
||||||
|
|
||||||
|
const declaredLength = Number(response.headers.get('content-length') || 0);
|
||||||
|
if (declaredLength > maximumBytes) throw new Error(`Image exceeds the ${Math.round(maximumBytes / 1048576)} MB scan limit.`);
|
||||||
|
const blob = await response.blob();
|
||||||
|
if (blob.size > maximumBytes) throw new Error(`Image exceeds the ${Math.round(maximumBytes / 1048576)} MB scan limit.`);
|
||||||
|
if (blob.type && !blob.type.startsWith('image/')) throw new Error(`Resource is ${blob.type}, not an image.`);
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scanImage(
|
||||||
|
candidate: ImageCandidate,
|
||||||
|
settings: ExtensionSettings,
|
||||||
|
pageUrl: string
|
||||||
|
): Promise<AssetResult> {
|
||||||
|
try {
|
||||||
|
const blob = await fetchImage(candidate, settings.maxImageBytes, pageUrl);
|
||||||
|
const cacheKey = `${await hashBlob(blob)}:${settings.includeRawEvidence}`;
|
||||||
|
let evidence = imageCache.get(cacheKey);
|
||||||
|
if (!evidence) {
|
||||||
|
evidence = [
|
||||||
|
...(await inspectC2pa(blob, settings.includeRawEvidence)),
|
||||||
|
...(await inspectImageGeneratorMetadata(blob)),
|
||||||
|
...(await runImageWatermarkDetectors(blob, candidate))
|
||||||
|
];
|
||||||
|
imageCache.set(cacheKey, evidence);
|
||||||
|
}
|
||||||
|
return createAssetResult(candidate.id, 'image', candidate.alt || 'Image', evidence, candidate.url);
|
||||||
|
} catch (error) {
|
||||||
|
const detail = error instanceof Error ? error.message : String(error);
|
||||||
|
return createAssetResult(candidate.id, 'image', candidate.alt || 'Image', [{
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
kind: 'limitation',
|
||||||
|
strength: 'none',
|
||||||
|
verdict: 'unknown',
|
||||||
|
title: 'Image could not be inspected',
|
||||||
|
detail
|
||||||
|
}], candidate.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scanText(candidate: TextCandidate): Promise<AssetResult> {
|
||||||
|
const evidence = await runTextWatermarkDetectors(candidate);
|
||||||
|
if (evidence.length === 0 && candidate.tokenEstimate >= 200 && textWatermarkDetectors.length === 0) {
|
||||||
|
evidence.push({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
kind: 'limitation',
|
||||||
|
strength: 'none',
|
||||||
|
verdict: 'unknown',
|
||||||
|
title: 'No compatible text-watermark detector',
|
||||||
|
detail: 'The text was collected, but no public interoperable detector is bundled for its watermark scheme.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return createAssetResult(candidate.id, 'text', `Text sample (${candidate.tokenEstimate} tokens)`, evidence);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mapWithConcurrency<T, R>(
|
||||||
|
items: T[],
|
||||||
|
concurrency: number,
|
||||||
|
mapper: (item: T) => Promise<R>
|
||||||
|
): Promise<R[]> {
|
||||||
|
const results = new Array<R>(items.length);
|
||||||
|
let cursor = 0;
|
||||||
|
const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
||||||
|
while (cursor < items.length) {
|
||||||
|
const index = cursor++;
|
||||||
|
const item = items[index];
|
||||||
|
if (item !== undefined) results[index] = await mapper(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(workers);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function scanPage(page: PageCandidate, mode: 'auto' | 'manual' = 'manual'): Promise<ScanResult> {
|
||||||
|
const settings = await loadSettings();
|
||||||
|
const imageLimit = mode === 'auto'
|
||||||
|
? Math.min(settings.maxImagesPerPage, settings.maxAutoImagesPerPage)
|
||||||
|
: settings.maxImagesPerPage;
|
||||||
|
const imageSettings = mode === 'auto'
|
||||||
|
? { ...settings, maxImageBytes: Math.min(settings.maxImageBytes, AUTO_IMAGE_MAX_BYTES) }
|
||||||
|
: settings;
|
||||||
|
const selectedImages = [...page.images]
|
||||||
|
.sort((left, right) => right.width * right.height - left.width * left.height)
|
||||||
|
.slice(0, imageLimit);
|
||||||
|
const pageEvidence = [
|
||||||
|
...analyseDisclosures(page.disclosures),
|
||||||
|
...(settings.enableHeuristicDetection ? evidenceFromHeuristics(page.heuristics || []) : [])
|
||||||
|
];
|
||||||
|
const pageResult = createAssetResult('page', 'page', page.title || page.url, pageEvidence, page.url);
|
||||||
|
const [images, texts] = await Promise.all([
|
||||||
|
mapWithConcurrency(selectedImages, 3, (candidate) => scanImage(candidate, imageSettings, page.url)),
|
||||||
|
mapWithConcurrency(page.texts, 2, scanText)
|
||||||
|
]);
|
||||||
|
const warnings: string[] = [];
|
||||||
|
if (page.images.length > selectedImages.length) {
|
||||||
|
warnings.push(`${page.images.length - selectedImages.length} images were skipped because of the ${mode} scan limit.`);
|
||||||
|
}
|
||||||
|
if (textWatermarkDetectors.length === 0 && page.texts.some((text) => text.tokenEstimate >= 200)) {
|
||||||
|
warnings.push('Text was not classified: no compatible public watermark detector is bundled.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scanId: crypto.randomUUID(),
|
||||||
|
pageUrl: page.url,
|
||||||
|
pageTitle: page.title,
|
||||||
|
scannedAt: new Date().toISOString(),
|
||||||
|
summary: aggregateSummary(pageResult, images, texts, page.images.length, page.texts.length, settings.heuristicThreshold),
|
||||||
|
page: pageResult,
|
||||||
|
images,
|
||||||
|
texts,
|
||||||
|
warnings
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import type { AssetResult, ScanResult } from '../shared/types';
|
||||||
|
import { IMAGE_ID_ATTRIBUTE } from './discovery';
|
||||||
|
|
||||||
|
const HOST_ID = 'slopdetect-badge-layer';
|
||||||
|
let refreshListener: (() => void) | undefined;
|
||||||
|
|
||||||
|
function badgePresentation(result: AssetResult): { text: string; className: string; title: string } | undefined {
|
||||||
|
if (result.verdict === 'invalid') return { text: '!', className: 'invalid', title: 'Invalid provenance credential' };
|
||||||
|
if (result.verdict === 'ai' && result.strength === 'verified') {
|
||||||
|
return { text: 'AI', className: 'ai', title: 'Verified AI provenance' };
|
||||||
|
}
|
||||||
|
if (result.verdict === 'ai') return { text: 'AI', className: 'detected', title: 'AI origin detected or disclosed' };
|
||||||
|
if (result.strength === 'verified' || result.strength === 'detected') {
|
||||||
|
return { text: 'CC', className: 'credential', title: 'Content Credential found' };
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearBadges(): void {
|
||||||
|
document.getElementById(HOST_ID)?.remove();
|
||||||
|
if (refreshListener) {
|
||||||
|
window.removeEventListener('scroll', refreshListener, true);
|
||||||
|
window.removeEventListener('resize', refreshListener);
|
||||||
|
refreshListener = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderBadges(result: ScanResult): void {
|
||||||
|
clearBadges();
|
||||||
|
const marked = result.images
|
||||||
|
.map((image) => ({ image, presentation: badgePresentation(image) }))
|
||||||
|
.filter((entry) => entry.presentation !== undefined);
|
||||||
|
if (marked.length === 0) return;
|
||||||
|
|
||||||
|
const host = document.createElement('div');
|
||||||
|
host.id = HOST_ID;
|
||||||
|
host.style.cssText = 'all:initial;position:fixed;inset:0;z-index:2147483647;pointer-events:none;';
|
||||||
|
const root = host.attachShadow({ mode: 'closed' });
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
.badge { position: fixed; min-width: 24px; height: 24px; padding: 0 6px; box-sizing: border-box;
|
||||||
|
display: grid; place-items: center; color: white; border: 2px solid white; border-radius: 999px;
|
||||||
|
font: 700 11px/1 system-ui, sans-serif; box-shadow: 0 2px 8px #0008; }
|
||||||
|
.ai { background: #6847ee; } .detected { background: #9b6500; }
|
||||||
|
.credential { background: #18735c; } .invalid { background: #bd2535; }
|
||||||
|
`;
|
||||||
|
root.append(style);
|
||||||
|
|
||||||
|
const placements: Array<{ badge: HTMLDivElement; target: Element }> = [];
|
||||||
|
for (const entry of marked) {
|
||||||
|
const target = document.querySelector(`[${IMAGE_ID_ATTRIBUTE}="${window.CSS.escape(entry.image.id)}"]`);
|
||||||
|
if (!target || !entry.presentation) continue;
|
||||||
|
const badge = document.createElement('div');
|
||||||
|
badge.className = `badge ${entry.presentation.className}`;
|
||||||
|
badge.textContent = entry.presentation.text;
|
||||||
|
badge.title = entry.presentation.title;
|
||||||
|
badge.setAttribute('aria-label', entry.presentation.title);
|
||||||
|
root.append(badge);
|
||||||
|
placements.push({ badge, target });
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = (): void => {
|
||||||
|
for (const placement of placements) {
|
||||||
|
const rect = placement.target.getBoundingClientRect();
|
||||||
|
placement.badge.style.left = `${Math.max(2, rect.right - 28)}px`;
|
||||||
|
placement.badge.style.top = `${Math.max(2, rect.top + 4)}px`;
|
||||||
|
placement.badge.style.display = rect.bottom < 0 || rect.top > window.innerHeight ? 'none' : 'grid';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
position();
|
||||||
|
refreshListener = position;
|
||||||
|
window.addEventListener('scroll', position, true);
|
||||||
|
window.addEventListener('resize', position);
|
||||||
|
document.documentElement.append(host);
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
import type {
|
||||||
|
ImageCandidate,
|
||||||
|
PageCandidate,
|
||||||
|
PageDisclosureCandidate,
|
||||||
|
TextCandidate
|
||||||
|
} from '../shared/types';
|
||||||
|
import { discoverHeuristics } from './heuristics';
|
||||||
|
|
||||||
|
const IMAGE_ID_ATTRIBUTE = 'data-slopdetect-image-id';
|
||||||
|
const MAX_TEXT_BLOCKS = 8;
|
||||||
|
const MIN_TEXT_CHARACTERS = 300;
|
||||||
|
|
||||||
|
function isVisible(element: Element): boolean {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeImageCandidate(element: Element, url: string, alt = ''): ImageCandidate | undefined {
|
||||||
|
if (!url || !isVisible(element)) return undefined;
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
if (rect.width < 16 || rect.height < 16) return undefined;
|
||||||
|
const existingId = element.getAttribute(IMAGE_ID_ATTRIBUTE);
|
||||||
|
const id = existingId || crypto.randomUUID();
|
||||||
|
element.setAttribute(IMAGE_ID_ATTRIBUTE, id);
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
url: new URL(url, document.baseURI).href,
|
||||||
|
alt: alt.trim().slice(0, 200),
|
||||||
|
width: Math.round(rect.width),
|
||||||
|
height: Math.round(rect.height)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function discoverImages(): ImageCandidate[] {
|
||||||
|
const candidates: ImageCandidate[] = [];
|
||||||
|
const seenUrls = new Set<string>();
|
||||||
|
|
||||||
|
for (const image of document.querySelectorAll('img')) {
|
||||||
|
const candidate = makeImageCandidate(image, image.currentSrc || image.src, image.alt);
|
||||||
|
if (candidate && !seenUrls.has(candidate.url)) {
|
||||||
|
candidates.push(candidate);
|
||||||
|
seenUrls.add(candidate.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const image of document.querySelectorAll('svg image')) {
|
||||||
|
const href = image.getAttribute('href') || image.getAttribute('xlink:href') || '';
|
||||||
|
const candidate = makeImageCandidate(image, href, image.getAttribute('aria-label') || 'SVG image');
|
||||||
|
if (candidate && !seenUrls.has(candidate.url)) {
|
||||||
|
candidates.push(candidate);
|
||||||
|
seenUrls.add(candidate.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = [...document.querySelectorAll('body *')].slice(0, 2000);
|
||||||
|
for (const element of elements) {
|
||||||
|
const background = window.getComputedStyle(element).backgroundImage;
|
||||||
|
const match = /^url\(["']?(.*?)["']?\)$/.exec(background);
|
||||||
|
if (!match?.[1]) continue;
|
||||||
|
const candidate = makeImageCandidate(element, match[1], element.getAttribute('aria-label') || 'Background image');
|
||||||
|
if (candidate && !seenUrls.has(candidate.url)) {
|
||||||
|
candidates.push(candidate);
|
||||||
|
seenUrls.add(candidate.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function estimateTokens(text: string): number {
|
||||||
|
return text.trim() ? Math.ceil(text.trim().split(/\s+/).length * 1.3) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function discoverText(): TextCandidate[] {
|
||||||
|
const selectors = 'article p, main p, [role="main"] p, article li, main li, blockquote';
|
||||||
|
const values = [...document.querySelectorAll<HTMLElement>(selectors)]
|
||||||
|
.filter(isVisible)
|
||||||
|
.map((element) => element.innerText.trim())
|
||||||
|
.filter((text) => text.length >= MIN_TEXT_CHARACTERS);
|
||||||
|
|
||||||
|
const unique = [...new Set(values)].sort((left, right) => right.length - left.length).slice(0, MAX_TEXT_BLOCKS);
|
||||||
|
if (unique.length === 0) {
|
||||||
|
const root = document.querySelector<HTMLElement>('article, main, [role="main"]') || document.body;
|
||||||
|
const combined = root?.innerText.trim().slice(0, 24_000) || '';
|
||||||
|
if (combined.length >= MIN_TEXT_CHARACTERS) unique.push(combined);
|
||||||
|
}
|
||||||
|
|
||||||
|
return unique.map((text) => ({ id: crypto.randomUUID(), text, tokenEstimate: estimateTokens(text) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushDisclosure(
|
||||||
|
output: PageDisclosureCandidate[],
|
||||||
|
source: string,
|
||||||
|
value: string | null | undefined
|
||||||
|
): void {
|
||||||
|
const trimmed = value?.trim();
|
||||||
|
if (trimmed) output.push({ source, value: trimmed.slice(0, 2000) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function discoverDisclosures(): PageDisclosureCandidate[] {
|
||||||
|
const candidates: PageDisclosureCandidate[] = [];
|
||||||
|
const interestingMeta = /ai|artificial|synthetic|provenance|credential|generator|content-origin/i;
|
||||||
|
|
||||||
|
for (const meta of document.querySelectorAll<HTMLMetaElement>('meta[name], meta[property]')) {
|
||||||
|
const name = meta.name || meta.getAttribute('property') || '';
|
||||||
|
if (interestingMeta.test(name)) {
|
||||||
|
pushDisclosure(candidates, `meta:${name}`, meta.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const link of document.querySelectorAll<HTMLLinkElement>('link[rel]')) {
|
||||||
|
if (/credential|provenance|c2pa|manifest/i.test(link.rel)) {
|
||||||
|
pushDisclosure(candidates, `link:${link.rel}`, link.href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const script of document.querySelectorAll<HTMLScriptElement>('script[type="application/ld+json"]')) {
|
||||||
|
if (/"(?:aiGenerated|isAIGenerated|contentOrigin|digitalSourceType)"\s*:\s*(?:true|"[^"]*(?:trainedAlgorithmicMedia|synthetic|AI)[^"]*")/i.test(script.textContent || '')) {
|
||||||
|
pushDisclosure(candidates, 'structured-data', script.textContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelSelectors = 'figcaption, small, [class*="disclosure" i], [class*="provenance" i], [class*="credit" i], [aria-label*="AI-generated" i]';
|
||||||
|
for (const element of document.querySelectorAll<HTMLElement>(labelSelectors)) {
|
||||||
|
const value = (element.innerText || element.getAttribute('aria-label') || '').trim();
|
||||||
|
if (value.length <= 300 && /\b(?:AI[- ]generated|generated by AI|artificially generated|created with AI|synthetic media)\b/i.test(value)) {
|
||||||
|
pushDisclosure(candidates, 'visible-label', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const element of document.querySelectorAll<HTMLElement>('[data-ai-generated], [data-content-origin]')) {
|
||||||
|
pushDisclosure(
|
||||||
|
candidates,
|
||||||
|
element.hasAttribute('data-ai-generated') ? 'data-ai-generated' : 'data-content-origin',
|
||||||
|
element.getAttribute('data-ai-generated') || element.getAttribute('data-content-origin')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function discoverPage(): PageCandidate {
|
||||||
|
return {
|
||||||
|
url: location.href,
|
||||||
|
title: document.title,
|
||||||
|
images: discoverImages(),
|
||||||
|
texts: discoverText(),
|
||||||
|
disclosures: discoverDisclosures(),
|
||||||
|
heuristics: discoverHeuristics()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { IMAGE_ID_ATTRIBUTE };
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
import type { HeuristicCategory, HeuristicFinding } from '../shared/types';
|
||||||
|
|
||||||
|
const MAX_ELEMENTS = 1500;
|
||||||
|
const MAX_STYLED_ELEMENTS = 700;
|
||||||
|
|
||||||
|
interface MutableFinding extends HeuristicFinding {
|
||||||
|
samples: Set<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function finaliseFinding(finding: MutableFinding): HeuristicFinding {
|
||||||
|
return {
|
||||||
|
ruleId: finding.ruleId,
|
||||||
|
category: finding.category,
|
||||||
|
title: finding.title,
|
||||||
|
detail: finding.detail,
|
||||||
|
score: finding.score,
|
||||||
|
occurrences: finding.occurrences,
|
||||||
|
sample: finding.sample
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function visible(element: HTMLElement): boolean {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
return rect.width >= 12 && rect.height >= 8 && style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pixels(value: string): number {
|
||||||
|
const parsed = Number.parseFloat(value);
|
||||||
|
return Number.isFinite(parsed) ? parsed : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function durationSeconds(value: string): number {
|
||||||
|
return value.split(',').reduce((maximum, item) => {
|
||||||
|
const trimmed = item.trim();
|
||||||
|
const parsed = Number.parseFloat(trimmed);
|
||||||
|
return Math.max(maximum, trimmed.endsWith('ms') ? parsed / 1000 : parsed);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shortText(element: HTMLElement): string {
|
||||||
|
return (element.innerText || element.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCard(element: HTMLElement, style: CSSStyleDeclaration): boolean {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
if (rect.width < 120 || rect.height < 55) return false;
|
||||||
|
const hasEdge = pixels(style.borderTopWidth) > 0 || style.boxShadow !== 'none';
|
||||||
|
const hasSurface = style.backgroundColor !== 'rgba(0, 0, 0, 0)' || style.backgroundImage !== 'none';
|
||||||
|
return pixels(style.borderRadius) >= 8 && (hasEdge || hasSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
function structureSignature(element: HTMLElement): string {
|
||||||
|
const children = [...element.children].slice(0, 8);
|
||||||
|
return `${element.tagName}:${children.map((child) => child.tagName).join(',')}:${Boolean(element.querySelector('h2,h3,h4'))}:${Boolean(element.querySelector('p'))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normaliseFont(fontFamily: string): string {
|
||||||
|
return fontFamily.split(',')[0]?.replace(/["']/g, '').trim().toLowerCase() || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFinding(
|
||||||
|
findings: Map<string, MutableFinding>,
|
||||||
|
ruleId: string,
|
||||||
|
category: HeuristicCategory,
|
||||||
|
title: string,
|
||||||
|
detail: string,
|
||||||
|
score: number,
|
||||||
|
sample = ''
|
||||||
|
): void {
|
||||||
|
const existing = findings.get(ruleId);
|
||||||
|
if (existing) {
|
||||||
|
existing.occurrences += 1;
|
||||||
|
if (sample) existing.samples.add(sample);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
findings.set(ruleId, {
|
||||||
|
ruleId,
|
||||||
|
category,
|
||||||
|
title,
|
||||||
|
detail,
|
||||||
|
score,
|
||||||
|
occurrences: 1,
|
||||||
|
sample: sample || undefined,
|
||||||
|
samples: new Set(sample ? [sample] : [])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function analyseCopyPatterns(text: string): HeuristicFinding[] {
|
||||||
|
const findings = new Map<string, MutableFinding>();
|
||||||
|
const clean = text.replace(/\s+/g, ' ').trim();
|
||||||
|
const words = clean ? clean.split(/\s+/).length : 0;
|
||||||
|
const buzzwords = clean.match(/\b(?:seamlessly|streamline|empower|supercharge|elevate|unlock|revolutioni[sz]e|cutting-edge|next-generation|world-class|enterprise-grade|game-changer|transformative|delve|ever-evolving|digital landscape)\b/gi) || [];
|
||||||
|
const uniqueBuzzwords = [...new Set(buzzwords.map((word) => word.toLowerCase()))];
|
||||||
|
if (uniqueBuzzwords.length >= 3) {
|
||||||
|
addFinding(findings, 'copy-marketing-buzzwords', 'copy', 'Dense generic marketing vocabulary', 'Several stock phrases commonly produced by generated SaaS copy appear together.', 2, uniqueBuzzwords.slice(0, 6).join(', '));
|
||||||
|
}
|
||||||
|
|
||||||
|
const emDashes = (clean.match(/—/g) || []).length;
|
||||||
|
if (emDashes >= 3 && (words === 0 || emDashes / words >= 0.003)) {
|
||||||
|
addFinding(findings, 'copy-em-dash-cadence', 'copy', 'Em-dash-heavy cadence', 'The page uses em dashes frequently enough to be a weak generated-copy signal.', 1, `${emDashes} em dashes in about ${words} words`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const aphorisms = clean.match(/\bnot\s+(?:just\s+)?[^.!?]{1,50}[.!?]\s+(?:it(?:'s| is)|we(?:'re| are)|a|the)\s+[^.!?]{1,55}[.!?]/gi) || [];
|
||||||
|
if (aphorisms.length >= 2) {
|
||||||
|
addFinding(findings, 'copy-aphoristic-contrast', 'copy', 'Repeated manufactured contrast', 'Multiple “not X, but Y” style fragments create a recurring generated-marketing cadence.', 2, aphorisms[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const theater = clean.match(/\b(?:growth|innovation|productivity|marketing|security|compliance|performance) theater\b/gi) || [];
|
||||||
|
if (theater.length > 0) {
|
||||||
|
addFinding(findings, 'copy-theater-framing', 'copy', '“Theater” framing', 'Dismissing a practice as “theater” is a recurring generated-copy tic.', 1, theater[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stockPromises = clean.match(/\b(?:build the future|everything you need|ship faster|work smarter|future of work|designed for modern teams|from idea to impact)\b/gi) || [];
|
||||||
|
if (stockPromises.length >= 2) {
|
||||||
|
addFinding(findings, 'copy-stock-promises', 'copy', 'Stock AI-product promises', 'Several interchangeable startup promises appear on the same page.', 2, stockPromises.slice(0, 3).join(', '));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...findings.values()].map(finaliseFinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function discoverHeuristics(): HeuristicFinding[] {
|
||||||
|
const findings = new Map<string, MutableFinding>();
|
||||||
|
const elements = [...document.querySelectorAll<HTMLElement>('body *')].slice(0, MAX_ELEMENTS);
|
||||||
|
const styled = elements.filter(visible).slice(0, MAX_STYLED_ELEMENTS);
|
||||||
|
const cards: HTMLElement[] = [];
|
||||||
|
const fontCounts = new Map<string, number>();
|
||||||
|
|
||||||
|
for (const element of styled) {
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
const text = shortText(element);
|
||||||
|
const background = style.backgroundImage.toLowerCase();
|
||||||
|
const radius = pixels(style.borderRadius);
|
||||||
|
const font = normaliseFont(style.fontFamily);
|
||||||
|
if (font) fontCounts.set(font, (fontCounts.get(font) || 0) + 1);
|
||||||
|
if (isCard(element, style)) cards.push(element);
|
||||||
|
|
||||||
|
if (/repeating-(?:linear|radial)-gradient/.test(background)) {
|
||||||
|
addFinding(findings, 'visual-repeating-gradient', 'visual', 'Decorative repeating-gradient texture', 'Repeating gradient stripes or grids are a recurring generated-interface decoration.', 2);
|
||||||
|
}
|
||||||
|
if (/radial-gradient/.test(background) && rect.width >= 180 && rect.height >= 100) {
|
||||||
|
addFinding(findings, 'visual-radial-glow', 'visual', 'Decorative radial glow', 'A large radial-gradient halo is used as surface decoration.', 2);
|
||||||
|
}
|
||||||
|
if (/gradient/.test(background) && /(?:rgb\([^)]*(?:128|139|147|168|124|99)[^)]*\)|#(?:7c3aed|8b5cf6|9333ea|6366f1|06b6d4|22d3ee))/i.test(background)) {
|
||||||
|
addFinding(findings, 'visual-purple-cyan-gradient', 'visual', 'Purple/cyan gradient palette', 'Purple-to-blue or cyan gradients are a common generated landing-page default.', 2);
|
||||||
|
}
|
||||||
|
if (/gradient/.test(background) && (style.backgroundClip === 'text' || style.getPropertyValue('-webkit-background-clip') === 'text') && (style.color === 'transparent' || /rgba\([^)]*,\s*0\)/.test(style.color))) {
|
||||||
|
addFinding(findings, 'visual-gradient-text', 'visual', 'Gradient text', 'A heading or metric uses a decorative gradient fill.', 2, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sideWidths = [style.borderTopWidth, style.borderRightWidth, style.borderBottomWidth, style.borderLeftWidth].map(pixels);
|
||||||
|
const thickSides = sideWidths.filter((width) => width >= 4).length;
|
||||||
|
if (radius >= 8 && thickSides === 1 && sideWidths.filter((width) => width <= 2).length >= 3) {
|
||||||
|
addFinding(findings, 'visual-side-accent', 'visual', 'Side-tab accent border', 'A rounded panel has one thick colored edge, a recognizable generated-UI pattern.', 2, text);
|
||||||
|
}
|
||||||
|
if (radius >= 8 && sideWidths.some((width) => width > 0 && width <= 1.5) && style.boxShadow !== 'none' && /\s(?:1[6-9]|[2-9]\d)px/.test(style.boxShadow)) {
|
||||||
|
addFinding(findings, 'visual-hairline-shadow', 'visual', 'Hairline border with wide shadow', 'A thin border is paired with a large diffuse shadow.', 1, text);
|
||||||
|
}
|
||||||
|
if (radius >= 24 && rect.width >= 120 && rect.width <= 520 && rect.height >= 55 && rect.height <= 320 && !/^(button|input)$/i.test(element.tagName)) {
|
||||||
|
addFinding(findings, 'visual-extreme-radius', 'visual', 'Over-rounded cards', 'Small content panels use very large corner radii, producing the common soft-blob aesthetic.', 1, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSmallSquare = rect.width >= 28 && rect.width <= 84 && rect.height >= 28 && rect.height <= 84 && Math.abs(rect.width - rect.height) <= 14;
|
||||||
|
if (isSmallSquare && radius >= 6 && element.parentElement?.querySelector(':scope > h2, :scope > h3, :scope > h4')) {
|
||||||
|
addFinding(findings, 'typography-icon-tile', 'typography', 'Icon tile stacked above a heading', 'A rounded-square icon container sits above a card heading, a frequent generated feature-card template.', 2, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^H1$/.test(element.tagName) && text.split(/\s+/).length >= 6 && pixels(style.fontSize) >= 48) {
|
||||||
|
addFinding(findings, 'typography-oversized-hero', 'typography', 'Oversized sentence-length hero', 'A long sentence is rendered at display size in the opening hierarchy.', 1, text);
|
||||||
|
}
|
||||||
|
if (/^H1$/.test(element.tagName) && style.fontStyle === 'italic' && /serif/.test(style.fontFamily) && pixels(style.fontSize) >= 44) {
|
||||||
|
addFinding(findings, 'typography-italic-serif-hero', 'typography', 'Italic serif hero headline', 'An oversized italic serif hero is a common recent AI-startup default.', 2, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextHeading = element.nextElementSibling;
|
||||||
|
const uppercaseShort = text.length >= 2 && text.length <= 40 && text === text.toUpperCase() && /[A-Z]/.test(text);
|
||||||
|
const pill = radius >= rect.height * 0.4 && rect.width < 260;
|
||||||
|
if ((uppercaseShort || pill) && pixels(style.fontSize) <= 15 && nextHeading && /^H[1-3]$/.test(nextHeading.tagName)) {
|
||||||
|
addFinding(findings, 'typography-hero-eyebrow', 'typography', 'Hero eyebrow or pill', 'A small tracked label or pill appears directly above a large heading.', 2, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const animation = style.animationName;
|
||||||
|
const infinite = style.animationIterationCount.split(',').some((value) => value.trim() === 'infinite');
|
||||||
|
if (animation !== 'none' && infinite && durationSeconds(style.animationDuration) <= 4) {
|
||||||
|
if (/(?:status|pulse|ping|dot|live|online)/i.test(`${element.className} ${element.id} ${animation}`)) {
|
||||||
|
addFinding(findings, 'motion-pulsing-status', 'motion', 'Decorative pulsing status', 'A continuously animated status-like element may imply activity that is not data-driven.', 1, text);
|
||||||
|
} else if (rect.width >= 150) {
|
||||||
|
addFinding(findings, 'motion-looping-decoration', 'motion', 'Continuous decorative animation', 'A large element loops a short animation indefinitely.', 1, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (element.tagName === 'MARQUEE') {
|
||||||
|
addFinding(findings, 'motion-marquee', 'motion', 'Auto-scrolling marquee', 'Content scrolls continuously instead of allowing the visitor to read at their own pace.', 2, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const overusedFonts = ['inter', 'geist', 'space grotesk', 'instrument serif'];
|
||||||
|
const overusedCount = [...fontCounts.entries()].reduce((sum, [font, count]) => sum + (overusedFonts.some((name) => font.includes(name)) ? count : 0), 0);
|
||||||
|
if (styled.length >= 20 && overusedCount / styled.length >= 0.55) {
|
||||||
|
addFinding(findings, 'typography-overused-font', 'typography', 'Overused generated-UI font', 'Most visible elements use Inter, Geist, Space Grotesk, or Instrument Serif.', 1, `${Math.round(overusedCount / styled.length * 100)}% of sampled elements`);
|
||||||
|
}
|
||||||
|
if (styled.length >= 30 && fontCounts.size === 1) {
|
||||||
|
addFinding(findings, 'typography-single-font', 'typography', 'Single font throughout the page', 'The sampled page uses one font family for every role, flattening typographic hierarchy.', 1, [...fontCounts.keys()][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nestedCards = 0;
|
||||||
|
const cardSet = new Set(cards);
|
||||||
|
for (const card of cards) {
|
||||||
|
let parent = card.parentElement;
|
||||||
|
while (parent && parent !== document.body) {
|
||||||
|
if (cardSet.has(parent)) {
|
||||||
|
nestedCards += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nestedCards >= 2) {
|
||||||
|
addFinding(findings, 'layout-nested-cards', 'layout', 'Cards nested inside cards', 'Several bordered or elevated panels are nested, creating generated “cardocalypse” depth.', 2, `${nestedCards} nested cards`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const parent of styled) {
|
||||||
|
const children = [...parent.children].filter((child): child is HTMLElement => child instanceof HTMLElement && visible(child));
|
||||||
|
if (children.length < 3) continue;
|
||||||
|
const groups = new Map<string, HTMLElement[]>();
|
||||||
|
for (const child of children) {
|
||||||
|
const signature = structureSignature(child);
|
||||||
|
const group = groups.get(signature) || [];
|
||||||
|
group.push(child);
|
||||||
|
groups.set(signature, group);
|
||||||
|
}
|
||||||
|
if ([...groups.values()].some((group) => group.length >= 3 && group[0]?.querySelector('h2,h3,h4') && group[0]?.querySelector('p'))) {
|
||||||
|
addFinding(findings, 'layout-identical-card-grid', 'layout', 'Repeated identical feature cards', 'Three or more sibling cards repeat the same icon/heading/copy structure.', 3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageText = (document.body?.innerText || '').slice(0, 80_000);
|
||||||
|
for (const finding of analyseCopyPatterns(pageText)) {
|
||||||
|
findings.set(finding.ruleId, { ...finding, samples: new Set(finding.sample ? [finding.sample] : []) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...findings.values()]
|
||||||
|
.map(finaliseFinding)
|
||||||
|
.sort((left, right) => right.score - left.score || right.occurrences - left.occurrences)
|
||||||
|
.slice(0, 30);
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import browser from 'webextension-polyfill';
|
||||||
|
import { clearBadges, renderBadges } from './badges';
|
||||||
|
import { discoverPage } from './discovery';
|
||||||
|
import { clearNotice, renderNotice } from './notice';
|
||||||
|
import {
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
type ContentRequest,
|
||||||
|
type ExtensionSettings,
|
||||||
|
type RuntimeResponse,
|
||||||
|
type ScanResult
|
||||||
|
} from '../shared/types';
|
||||||
|
|
||||||
|
interface SlopDetectGlobal {
|
||||||
|
__slopdetectInstalled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = globalThis as typeof globalThis & SlopDetectGlobal;
|
||||||
|
const RESCAN_COOLDOWN_MS = 10_000;
|
||||||
|
const MAX_AUTOMATIC_SCANS_PER_URL = 2;
|
||||||
|
|
||||||
|
if (!state.__slopdetectInstalled) {
|
||||||
|
state.__slopdetectInstalled = true;
|
||||||
|
let scheduledScan: number | undefined;
|
||||||
|
let lastScanAt = 0;
|
||||||
|
let lastUrl = location.href;
|
||||||
|
let automaticScansForUrl = 0;
|
||||||
|
let mutationCount = 0;
|
||||||
|
let scanSequence = 0;
|
||||||
|
|
||||||
|
const loadSettings = async (): Promise<ExtensionSettings> => {
|
||||||
|
const saved = await browser.storage.local.get('settings');
|
||||||
|
return { ...DEFAULT_SETTINGS, ...(saved.settings as Partial<ExtensionSettings> | undefined) };
|
||||||
|
};
|
||||||
|
|
||||||
|
const scan = async (mode: 'auto' | 'manual'): Promise<RuntimeResponse<ScanResult>> => {
|
||||||
|
const settings = await loadSettings();
|
||||||
|
if (mode === 'auto' && !settings.autoScan) return { ok: false, error: 'Automatic scanning is disabled.' };
|
||||||
|
|
||||||
|
const requestedUrl = location.href;
|
||||||
|
const sequence = ++scanSequence;
|
||||||
|
const response = await browser.runtime.sendMessage({ type: 'analyse-page', page: discoverPage(), mode }) as RuntimeResponse<ScanResult>;
|
||||||
|
if (!response.ok || !response.data) return response;
|
||||||
|
if (sequence !== scanSequence || requestedUrl !== location.href || response.data.pageUrl !== location.href) {
|
||||||
|
return { ok: false, error: 'The page changed before scanning finished.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
lastScanAt = Date.now();
|
||||||
|
mutationCount = 0;
|
||||||
|
if (mode === 'auto') automaticScansForUrl += 1;
|
||||||
|
if (settings.showPageBadges) renderBadges(response.data);
|
||||||
|
else clearBadges();
|
||||||
|
if (settings.showInPageNotice) renderNotice(response.data);
|
||||||
|
else clearNotice();
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
const scheduleAutomaticScan = (delay: number, respectCooldown = true): void => {
|
||||||
|
if (automaticScansForUrl >= MAX_AUTOMATIC_SCANS_PER_URL) return;
|
||||||
|
if (scheduledScan !== undefined) window.clearTimeout(scheduledScan);
|
||||||
|
const cooldown = respectCooldown ? Math.max(0, RESCAN_COOLDOWN_MS - (Date.now() - lastScanAt)) : 0;
|
||||||
|
scheduledScan = window.setTimeout(() => {
|
||||||
|
scheduledScan = undefined;
|
||||||
|
void scan('auto').catch(() => {
|
||||||
|
// Protected or unloading pages may disappear during an automatic scan.
|
||||||
|
});
|
||||||
|
}, Math.max(delay, cooldown));
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForNavigation = (): void => {
|
||||||
|
if (location.href === lastUrl) return;
|
||||||
|
lastUrl = location.href;
|
||||||
|
automaticScansForUrl = 0;
|
||||||
|
mutationCount = 0;
|
||||||
|
clearBadges();
|
||||||
|
clearNotice(true);
|
||||||
|
scheduleAutomaticScan(350, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
browser.runtime.onMessage.addListener((message: unknown) => {
|
||||||
|
const request = message as ContentRequest;
|
||||||
|
if (request?.type === 'clear-badges') {
|
||||||
|
clearBadges();
|
||||||
|
clearNotice(true);
|
||||||
|
return Promise.resolve({ ok: true } satisfies RuntimeResponse);
|
||||||
|
}
|
||||||
|
if (request?.type !== 'scan-page') return undefined;
|
||||||
|
return scan('manual');
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.storage.onChanged.addListener((changes, areaName) => {
|
||||||
|
if (areaName !== 'local' || !changes.settings) return;
|
||||||
|
const settings = { ...DEFAULT_SETTINGS, ...(changes.settings.newValue as Partial<ExtensionSettings> | undefined) };
|
||||||
|
if (!settings.showPageBadges) clearBadges();
|
||||||
|
if (!settings.showInPageNotice) clearNotice();
|
||||||
|
if (settings.autoScan) scheduleAutomaticScan(150, false);
|
||||||
|
else if (scheduledScan !== undefined) {
|
||||||
|
window.clearTimeout(scheduledScan);
|
||||||
|
scheduledScan = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
resetForNavigation();
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
const additions = [...mutation.addedNodes].filter((node) => {
|
||||||
|
if (!(node instanceof HTMLElement)) return false;
|
||||||
|
return node.id !== 'slopdetect-page-notice' && node.id !== 'slopdetect-badge-layer';
|
||||||
|
});
|
||||||
|
mutationCount += additions.length;
|
||||||
|
}
|
||||||
|
if (mutationCount >= 25) scheduleAutomaticScan(1600);
|
||||||
|
});
|
||||||
|
observer.observe(document.documentElement, { childList: true, subtree: true });
|
||||||
|
window.addEventListener('popstate', resetForNavigation);
|
||||||
|
window.addEventListener('hashchange', resetForNavigation);
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
if (automaticScansForUrl === 0) scheduleAutomaticScan(200, false);
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
|
scheduleAutomaticScan(200, false);
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
import type { Evidence, PageStatus, ScanResult } from '../shared/types';
|
||||||
|
|
||||||
|
const HOST_ID = 'slopdetect-page-notice';
|
||||||
|
const AUTO_COLLAPSE_MS = 9000;
|
||||||
|
let dismissedUrl: string | undefined;
|
||||||
|
|
||||||
|
interface NoticePresentation {
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
summary: string;
|
||||||
|
tone: 'purple' | 'amber' | 'orange' | 'blue' | 'red';
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRESENTATION: Partial<Record<PageStatus, NoticePresentation>> = {
|
||||||
|
'verified-ai': { icon: 'AI', title: 'Verified AI provenance', summary: 'A valid credential identifies AI-generated or AI-edited content.', tone: 'purple' },
|
||||||
|
'provenance-detected': { icon: 'AI', title: 'AI provenance detected', summary: 'AI provenance was found, but its signer is not trusted locally.', tone: 'amber' },
|
||||||
|
'watermark-detected': { icon: 'AI', title: 'AI watermark detected', summary: 'A compatible machine-readable watermark was detected.', tone: 'purple' },
|
||||||
|
'publisher-disclosed': { icon: 'AI', title: 'Publisher disclosed AI', summary: 'The publisher explicitly labels content on this page as AI-generated.', tone: 'amber' },
|
||||||
|
'heuristic-suspected': { icon: 'S', title: 'AI-style patterns suspected', summary: 'Several generated-site patterns appear together. This is a suspicion, not proof.', tone: 'orange' },
|
||||||
|
'mixed-provenance': { icon: '≋', title: 'Mixed provenance signals', summary: 'The page contains a mixture of AI, credential, or heuristic signals.', tone: 'blue' },
|
||||||
|
'invalid-credential': { icon: '!', title: 'Invalid provenance credential', summary: 'A credential was found but failed integrity validation.', tone: 'red' }
|
||||||
|
};
|
||||||
|
|
||||||
|
export function shouldShowNotice(result: ScanResult): boolean {
|
||||||
|
return PRESENTATION[result.summary.status] !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function strongestFindings(result: ScanResult): Evidence[] {
|
||||||
|
const rank = { invalid: 5, verified: 4, detected: 3, disclosed: 2, clue: 1, none: 0 } as const;
|
||||||
|
return [result.page, ...result.images, ...result.texts]
|
||||||
|
.flatMap((item) => item.evidence)
|
||||||
|
.filter((evidence) => evidence.kind !== 'limitation')
|
||||||
|
.sort((left, right) => rank[right.strength] - rank[left.strength] || (right.score || 0) - (left.score || 0))
|
||||||
|
.slice(0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearNotice(resetDismissal = false): void {
|
||||||
|
document.getElementById(HOST_ID)?.remove();
|
||||||
|
if (resetDismissal) dismissedUrl = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderNotice(result: ScanResult): void {
|
||||||
|
clearNotice();
|
||||||
|
if (dismissedUrl === result.pageUrl) return;
|
||||||
|
const presentation = PRESENTATION[result.summary.status];
|
||||||
|
if (!presentation) return;
|
||||||
|
|
||||||
|
const host = document.createElement('aside');
|
||||||
|
host.id = HOST_ID;
|
||||||
|
host.style.cssText = 'all:initial;position:fixed;right:18px;bottom:18px;z-index:2147483646;';
|
||||||
|
const root = host.attachShadow({ mode: 'closed' });
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
:host { color-scheme: light dark; }
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
.wrap { --accent:#b05d16; width:min(360px,calc(100vw - 24px)); color:#211f2c; font:13px/1.45 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif; filter:drop-shadow(0 12px 32px #15122038); }
|
||||||
|
.wrap.purple { --accent:#6847ee; } .wrap.amber { --accent:#956100; } .wrap.orange { --accent:#b05d16; }
|
||||||
|
.wrap.blue { --accent:#4761a8; } .wrap.red { --accent:#bd2535; }
|
||||||
|
.card { overflow:hidden; background:#fff; border:1px solid #dedbe8; border-radius:16px; box-shadow:0 10px 30px #1512202b; }
|
||||||
|
.head { display:grid; grid-template-columns:36px 1fr auto; gap:10px; align-items:center; padding:12px; }
|
||||||
|
.mark { width:36px;height:36px;border-radius:11px;display:grid;place-items:center;background:var(--accent);color:#fff;font-weight:900;font-size:12px; }
|
||||||
|
h2 { margin:0;font-size:14px;line-height:1.25;color:inherit; }
|
||||||
|
.score { margin-top:2px;color:#686476;font-size:11px;font-weight:650; }
|
||||||
|
button { appearance:none;border:0;font:inherit;cursor:pointer; }
|
||||||
|
.icon { width:30px;height:30px;border-radius:8px;background:transparent;color:#686476;font-size:18px; }
|
||||||
|
.icon:hover { background:#f1eff7;color:#211f2c; }
|
||||||
|
.body { padding:0 14px 14px;border-top:1px solid #ece9f2; }
|
||||||
|
.summary { margin:11px 0 9px;color:#555160; }
|
||||||
|
ul { list-style:none;margin:0;padding:0;display:grid;gap:6px; }
|
||||||
|
li { padding:7px 8px;border-radius:9px;background:#f6f5fa; }
|
||||||
|
li strong { display:block;font-size:11px; }
|
||||||
|
li span { display:block;margin-top:1px;color:#686476;font-size:10px; }
|
||||||
|
.hint { margin:9px 0 0;color:#777281;font-size:10px; }
|
||||||
|
.chip { margin-left:auto;display:flex;align-items:center;gap:8px;padding:7px 10px 7px 7px;border-radius:999px;background:#fff;color:#211f2c;border:1px solid #dedbe8;box-shadow:0 6px 20px #1512202b;font-weight:750; }
|
||||||
|
.chip .mark { width:27px;height:27px;border-radius:50%;font-size:10px; }
|
||||||
|
.hidden { display:none; }
|
||||||
|
@media (prefers-reduced-motion:no-preference) { .card,.chip { animation:enter .2s ease-out; } @keyframes enter { from { opacity:0;transform:translateY(7px) } } }
|
||||||
|
@media (prefers-color-scheme:dark) {
|
||||||
|
.wrap { color:#f4f1fb; } .card,.chip { background:#211f2c;border-color:#454150; } .score,.summary,li span,.hint { color:#b2adbd; }
|
||||||
|
.body { border-color:#3b3846; } li { background:#2d2a39; } .icon { color:#b2adbd; } .icon:hover { background:#353140;color:#fff; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const wrap = document.createElement('div');
|
||||||
|
wrap.className = `wrap ${presentation.tone}`;
|
||||||
|
const card = document.createElement('section');
|
||||||
|
card.className = 'card';
|
||||||
|
card.setAttribute('role', 'status');
|
||||||
|
card.setAttribute('aria-live', 'polite');
|
||||||
|
|
||||||
|
const scoreLabel = result.summary.heuristicScore > 0
|
||||||
|
? `Slop score ${result.summary.heuristicScore} · ${result.summary.heuristicFindings} pattern${result.summary.heuristicFindings === 1 ? '' : 's'}`
|
||||||
|
: `${result.summary.aiItems} AI evidence item${result.summary.aiItems === 1 ? '' : 's'}`;
|
||||||
|
const head = document.createElement('div');
|
||||||
|
head.className = 'head';
|
||||||
|
const mark = document.createElement('div');
|
||||||
|
mark.className = 'mark';
|
||||||
|
mark.textContent = presentation.icon;
|
||||||
|
const heading = document.createElement('div');
|
||||||
|
const title = document.createElement('h2');
|
||||||
|
title.textContent = presentation.title;
|
||||||
|
const score = document.createElement('div');
|
||||||
|
score.className = 'score';
|
||||||
|
score.textContent = scoreLabel;
|
||||||
|
heading.append(title, score);
|
||||||
|
head.append(mark, heading);
|
||||||
|
|
||||||
|
const close = document.createElement('button');
|
||||||
|
close.className = 'icon';
|
||||||
|
close.type = 'button';
|
||||||
|
close.title = 'Dismiss for this page';
|
||||||
|
close.setAttribute('aria-label', 'Dismiss SlopDetect result for this page');
|
||||||
|
close.textContent = '×';
|
||||||
|
head.append(close);
|
||||||
|
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.className = 'body';
|
||||||
|
const summary = document.createElement('p');
|
||||||
|
summary.className = 'summary';
|
||||||
|
summary.textContent = presentation.summary;
|
||||||
|
body.append(summary);
|
||||||
|
|
||||||
|
const findings = strongestFindings(result);
|
||||||
|
if (findings.length > 0) {
|
||||||
|
const list = document.createElement('ul');
|
||||||
|
for (const finding of findings) {
|
||||||
|
const item = document.createElement('li');
|
||||||
|
const itemTitle = document.createElement('strong');
|
||||||
|
itemTitle.textContent = finding.title;
|
||||||
|
const meta = document.createElement('span');
|
||||||
|
meta.textContent = finding.kind === 'heuristic'
|
||||||
|
? `${finding.category || 'pattern'} · +${finding.score || 0}${(finding.occurrences || 0) > 1 ? ` · ${finding.occurrences} matches` : ''}`
|
||||||
|
: finding.strength;
|
||||||
|
item.append(itemTitle, meta);
|
||||||
|
list.append(item);
|
||||||
|
}
|
||||||
|
body.append(list);
|
||||||
|
}
|
||||||
|
const hint = document.createElement('p');
|
||||||
|
hint.className = 'hint';
|
||||||
|
hint.textContent = 'Click the toolbar icon for complete evidence and limitations.';
|
||||||
|
body.append(hint);
|
||||||
|
card.append(head, body);
|
||||||
|
|
||||||
|
const chip = document.createElement('button');
|
||||||
|
chip.className = 'chip hidden';
|
||||||
|
chip.type = 'button';
|
||||||
|
chip.setAttribute('aria-label', `${presentation.title}. ${scoreLabel}. Show details.`);
|
||||||
|
const chipMark = document.createElement('span');
|
||||||
|
chipMark.className = 'mark';
|
||||||
|
chipMark.textContent = presentation.icon;
|
||||||
|
const chipText = document.createElement('span');
|
||||||
|
chipText.textContent = result.summary.heuristicScore > 0 ? `Score ${result.summary.heuristicScore}` : presentation.title;
|
||||||
|
chip.append(chipMark, chipText);
|
||||||
|
|
||||||
|
const collapseTimer = window.setTimeout(() => {
|
||||||
|
card.classList.add('hidden');
|
||||||
|
chip.classList.remove('hidden');
|
||||||
|
}, AUTO_COLLAPSE_MS);
|
||||||
|
close.addEventListener('click', () => {
|
||||||
|
window.clearTimeout(collapseTimer);
|
||||||
|
dismissedUrl = result.pageUrl;
|
||||||
|
host.remove();
|
||||||
|
});
|
||||||
|
chip.addEventListener('click', () => {
|
||||||
|
window.clearTimeout(collapseTimer);
|
||||||
|
chip.classList.add('hidden');
|
||||||
|
card.classList.remove('hidden');
|
||||||
|
});
|
||||||
|
card.addEventListener('click', () => window.clearTimeout(collapseTimer), { once: true });
|
||||||
|
|
||||||
|
wrap.append(card, chip);
|
||||||
|
root.append(style, wrap);
|
||||||
|
document.documentElement.append(host);
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
declare module '*.css';
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { render } from 'preact';
|
||||||
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
import browser from 'webextension-polyfill';
|
||||||
|
import { DEFAULT_SETTINGS, type ExtensionSettings } from '../shared/types';
|
||||||
|
import './options.css';
|
||||||
|
|
||||||
|
function App(): preact.JSX.Element {
|
||||||
|
const [settings, setSettings] = useState<ExtensionSettings>(DEFAULT_SETTINGS);
|
||||||
|
const [notice, setNotice] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void browser.storage.local.get('settings').then((saved) => {
|
||||||
|
setSettings({ ...DEFAULT_SETTINGS, ...(saved.settings as Partial<ExtensionSettings> | undefined) });
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const update = <K extends keyof ExtensionSettings>(key: K, value: ExtensionSettings[K]): void => {
|
||||||
|
setSettings((current) => ({ ...current, [key]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async (): Promise<void> => {
|
||||||
|
const safeSettings = {
|
||||||
|
...settings,
|
||||||
|
maxImageBytes: Math.min(100, Math.max(1, settings.maxImageBytes / 1048576)) * 1048576,
|
||||||
|
maxImagesPerPage: Math.min(200, Math.max(1, Math.round(settings.maxImagesPerPage))),
|
||||||
|
maxAutoImagesPerPage: Math.min(30, Math.max(0, Math.round(settings.maxAutoImagesPerPage))),
|
||||||
|
heuristicThreshold: Math.min(20, Math.max(3, Math.round(settings.heuristicThreshold)))
|
||||||
|
};
|
||||||
|
await browser.storage.local.set({ settings: safeSettings });
|
||||||
|
setSettings(safeSettings);
|
||||||
|
setNotice('Settings saved.');
|
||||||
|
window.setTimeout(() => setNotice(''), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <main class="shell">
|
||||||
|
<header class="heading"><img src="icons/icon.svg" alt="" /><div><h1>SlopDetect settings</h1><div class="muted">Privacy-first AI provenance inspection</div></div></header>
|
||||||
|
|
||||||
|
<section class="card">
|
||||||
|
<h2>Automatic protection</h2>
|
||||||
|
<p class="muted">SlopDetect runs locally after ordinary HTTP and HTTPS pages load. Pages without meaningful evidence stay visually untouched.</p>
|
||||||
|
<div class="setting">
|
||||||
|
<div><label htmlFor="auto-scan">Scan pages automatically</label><p class="muted">Runs on initial load and once more when a page fills itself dynamically.</p></div>
|
||||||
|
<input id="auto-scan" type="checkbox" checked={settings.autoScan} onChange={(event) => update('autoScan', event.currentTarget.checked)} />
|
||||||
|
</div>
|
||||||
|
<div class="setting">
|
||||||
|
<div><label htmlFor="page-notice">Show in-page results</label><p class="muted">Displays a compact result card only for flagged pages, then collapses it to a score chip.</p></div>
|
||||||
|
<input id="page-notice" type="checkbox" checked={settings.showInPageNotice} onChange={(event) => update('showInPageNotice', event.currentTarget.checked)} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card">
|
||||||
|
<h2>Scanning</h2>
|
||||||
|
<div class="setting">
|
||||||
|
<div><label htmlFor="badges">Show badges on inspected images</label><p class="muted">Adds small AI, Content Credential, or invalid-credential markers to the page.</p></div>
|
||||||
|
<input id="badges" type="checkbox" checked={settings.showPageBadges} onChange={(event) => update('showPageBadges', event.currentTarget.checked)} />
|
||||||
|
</div>
|
||||||
|
<div class="setting">
|
||||||
|
<div><label htmlFor="heuristics">Detect AI-style page patterns</label><p class="muted">Scores deterministic visual, layout, motion, typography, and copy clues. These are suspicions, not provenance proof.</p></div>
|
||||||
|
<input id="heuristics" type="checkbox" checked={settings.enableHeuristicDetection} onChange={(event) => update('enableHeuristicDetection', event.currentTarget.checked)} />
|
||||||
|
</div>
|
||||||
|
<div class="setting">
|
||||||
|
<div><label htmlFor="threshold">Suspicion threshold</label><p class="muted">Lower values flag more pages but increase false positives. Default: 6.</p></div>
|
||||||
|
<input id="threshold" type="number" min="3" max="20" value={settings.heuristicThreshold} onInput={(event) => update('heuristicThreshold', Number(event.currentTarget.value))} />
|
||||||
|
</div>
|
||||||
|
<div class="setting">
|
||||||
|
<div><label htmlFor="raw">Include raw credential data</label><p class="muted">Useful for auditing, but consumes more memory. It remains local.</p></div>
|
||||||
|
<input id="raw" type="checkbox" checked={settings.includeRawEvidence} onChange={(event) => update('includeRawEvidence', event.currentTarget.checked)} />
|
||||||
|
</div>
|
||||||
|
<div class="setting">
|
||||||
|
<div><label htmlFor="auto-limit">Images in automatic scans</label><p class="muted">Checks the largest visible images first. Lower values reduce page-load work; zero scans only the site itself.</p></div>
|
||||||
|
<input id="auto-limit" type="number" min="0" max="30" value={settings.maxAutoImagesPerPage} onInput={(event) => update('maxAutoImagesPerPage', Number(event.currentTarget.value))} />
|
||||||
|
</div>
|
||||||
|
<div class="setting">
|
||||||
|
<div><label htmlFor="limit">Images in manual scans</label><p class="muted">A toolbar rescan may inspect more images than the automatic page-load scan.</p></div>
|
||||||
|
<input id="limit" type="number" min="1" max="200" value={settings.maxImagesPerPage} onInput={(event) => update('maxImagesPerPage', Number(event.currentTarget.value))} />
|
||||||
|
</div>
|
||||||
|
<div class="setting">
|
||||||
|
<div><label htmlFor="size">Maximum image size (MB)</label><p class="muted">Resources larger than this are left as unknown.</p></div>
|
||||||
|
<input id="size" type="number" min="1" max="100" value={Math.round(settings.maxImageBytes / 1048576)} onInput={(event) => update('maxImageBytes', Number(event.currentTarget.value) * 1048576)} />
|
||||||
|
</div>
|
||||||
|
<div class="row"><button class="button" onClick={save}>Save settings</button><span class="notice">{notice}</span></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card">
|
||||||
|
<h2>Detection policy</h2>
|
||||||
|
<p>SlopDetect keeps verified provenance, unsigned generator metadata, and stylistic suspicions separate. Heuristics can produce false positives and never prove who authored a site. C2PA inspection happens locally using <code>@contentauth/c2pa-web</code>. Remote services are not enabled.</p>
|
||||||
|
</section>
|
||||||
|
</main>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = document.getElementById('app');
|
||||||
|
if (app) render(<App />, app);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
@import '../ui/base.css';
|
||||||
|
|
||||||
|
body { min-height: 100vh; }
|
||||||
|
.shell { width: min(760px, calc(100% - 32px)); margin: 40px auto; display: grid; gap: 18px; }
|
||||||
|
.heading { display: flex; align-items: center; gap: 14px; }
|
||||||
|
.heading img { width: 48px; height: 48px; }
|
||||||
|
h1 { margin: 0; font-size: 28px; }
|
||||||
|
h2 { margin: 0 0 8px; font-size: 17px; }
|
||||||
|
p { line-height: 1.55; }
|
||||||
|
.setting { display: flex; justify-content: space-between; gap: 24px; align-items: start; padding: 12px 0; border-bottom: 1px solid #e3e0eb; }
|
||||||
|
.setting:last-child { border: 0; }
|
||||||
|
.setting label { font-weight: 700; }
|
||||||
|
.setting p { margin: 4px 0 0; font-size: 13px; }
|
||||||
|
.setting input[type="number"] { width: 90px; padding: 7px; border: 1px solid #aaa5b8; border-radius: 8px; }
|
||||||
|
.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||||
|
.notice { font-size: 13px; min-height: 20px; color: #276755; }
|
||||||
|
code { background: #ece9f4; padding: 2px 5px; border-radius: 4px; }
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.setting { border-color: #393645; }
|
||||||
|
code { background: #312e3d; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { render } from 'preact';
|
||||||
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
import browser from 'webextension-polyfill';
|
||||||
|
import type { AssetResult, PageStatus, RuntimeResponse, ScanResult } from '../shared/types';
|
||||||
|
import './popup.css';
|
||||||
|
|
||||||
|
const STATUS: Record<PageStatus, { title: string; detail: string }> = {
|
||||||
|
'verified-ai': { title: 'Verified AI provenance', detail: 'A valid provenance credential identifies AI-generated or manipulated content.' },
|
||||||
|
'provenance-detected': { title: 'AI provenance detected', detail: 'A readable credential identifies AI origin, but its signer is not trusted by the bundled trust list.' },
|
||||||
|
'watermark-detected': { title: 'AI watermark detected', detail: 'A compatible watermark detector found machine-readable AI evidence.' },
|
||||||
|
'publisher-disclosed': { title: 'Publisher disclosed AI', detail: 'The page contains an explicit AI-content disclosure.' },
|
||||||
|
'heuristic-suspected': { title: 'AI-style patterns suspected', detail: 'Several visual, layout, motion, or copy patterns associated with generated sites appear together. This is not proof of AI authorship.' },
|
||||||
|
'mixed-provenance': { title: 'Mixed provenance', detail: 'The inspected items contain different or partial provenance signals.' },
|
||||||
|
'invalid-credential': { title: 'Invalid credential', detail: 'Provenance metadata was found but failed integrity validation.' },
|
||||||
|
'no-supported-evidence': { title: 'No supported evidence', detail: 'No compatible marking was found. This does not mean the content is human-made.' },
|
||||||
|
'unable-to-inspect': { title: 'Unable to inspect', detail: 'The page or its relevant resources could not be accessed.' }
|
||||||
|
};
|
||||||
|
|
||||||
|
async function activeTab(): Promise<browser.Tabs.Tab> {
|
||||||
|
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||||
|
const tab = tabs[0];
|
||||||
|
if (!tab?.id) throw new Error('No active browser tab is available.');
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EvidenceList({ result }: { result: ScanResult }): preact.JSX.Element | null {
|
||||||
|
const items = [result.page, ...result.images, ...result.texts].filter(
|
||||||
|
(item) => item.evidence.length > 0 && (item.strength !== 'none' || item.evidence.some((entry) => entry.kind === 'limitation'))
|
||||||
|
);
|
||||||
|
if (items.length === 0) return null;
|
||||||
|
|
||||||
|
return <div class="results">
|
||||||
|
{items.map((item: AssetResult) => <details class="item" key={item.id}>
|
||||||
|
<summary>{item.kind === 'image' ? 'Image' : item.kind === 'text' ? 'Text' : 'Page'}: {item.label.slice(0, 80)}</summary>
|
||||||
|
{item.evidence.map((evidence) => <div class="evidence" key={evidence.id}>
|
||||||
|
<strong>{evidence.title}</strong>
|
||||||
|
<span>{evidence.detail}</span>
|
||||||
|
{evidence.kind === 'heuristic' && <div class="evidence-meta">{evidence.category} · +{evidence.score} point{evidence.score === 1 ? '' : 's'}{(evidence.occurrences || 0) > 1 ? ` · ${evidence.occurrences} matches` : ''}</div>}
|
||||||
|
{evidence.issuer && <div>Issuer: {evidence.issuer}</div>}
|
||||||
|
</div>)}
|
||||||
|
</details>)}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function App(): preact.JSX.Element {
|
||||||
|
const [result, setResult] = useState<ScanResult>();
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
const tab = await activeTab();
|
||||||
|
const response = await browser.runtime.sendMessage({ type: 'get-tab-result', tabId: tab.id }) as RuntimeResponse<ScanResult | undefined>;
|
||||||
|
if (response.ok && response.data?.pageUrl === tab.url) setResult(response.data);
|
||||||
|
} catch {
|
||||||
|
// The active tab may be a protected Firefox page.
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const scan = async (): Promise<void> => {
|
||||||
|
setBusy(true);
|
||||||
|
setError('');
|
||||||
|
try {
|
||||||
|
const tab = await activeTab();
|
||||||
|
const response = await browser.tabs.sendMessage(tab.id!, { type: 'scan-page' }) as RuntimeResponse<ScanResult>;
|
||||||
|
if (!response.ok || !response.data) throw new Error(response.error || 'The scan did not return a result.');
|
||||||
|
setResult(response.data);
|
||||||
|
} catch (scanError) {
|
||||||
|
setError(scanError instanceof Error ? scanError.message : String(scanError));
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const status = result ? STATUS[result.summary.status] : undefined;
|
||||||
|
return <main class="shell">
|
||||||
|
<header class="header">
|
||||||
|
<div><div class="eyebrow">Automatic page check</div><h1>SlopDetect</h1></div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="toolbar-actions">
|
||||||
|
<button class="button" disabled={busy} onClick={scan}>{busy ? 'Scanning…' : result ? 'Scan again' : 'Scan this page'}</button>
|
||||||
|
<button class="button secondary" onClick={() => browser.runtime.openOptionsPage()}>Settings</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && <div class="error">{error}</div>}
|
||||||
|
|
||||||
|
{!result ? <section class="empty-copy">
|
||||||
|
<h2>No result for this page yet</h2>
|
||||||
|
<p class="muted">Pages scan automatically after loading. Use the button above to request a fresh full scan.</p>
|
||||||
|
</section> : <>
|
||||||
|
<section class={`card status ${result.summary.status}`}>
|
||||||
|
<div class="status-label"><span></span>Current page</div>
|
||||||
|
<h2>{status?.title}</h2>
|
||||||
|
<p class="muted">{status?.detail}</p>
|
||||||
|
</section>
|
||||||
|
<section class="stats">
|
||||||
|
<div class="stat"><strong>{result.summary.heuristicScore}</strong><span>slop score</span></div>
|
||||||
|
<div class="stat"><strong>{result.summary.inspectedImages}/{result.summary.totalImages}</strong><span>images inspected</span></div>
|
||||||
|
<div class="stat"><strong>{result.summary.heuristicFindings}</strong><span>style clues</span></div>
|
||||||
|
</section>
|
||||||
|
{result.warnings.length > 0 && <ul class="warnings">{result.warnings.map((warning) => <li key={warning}>{warning}</li>)}</ul>}
|
||||||
|
<EvidenceList result={result} />
|
||||||
|
<p class="muted footnote">No mark found means unknown, not human-made. Scanned {new Date(result.scannedAt).toLocaleString()}.</p>
|
||||||
|
</>}
|
||||||
|
</main>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = document.getElementById('app');
|
||||||
|
if (app) render(<App />, app);
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
@import '../ui/base.css';
|
||||||
|
|
||||||
|
body { width: 360px; }
|
||||||
|
.shell { padding: 13px; display: grid; gap: 10px; }
|
||||||
|
.header { display: flex; align-items: center; min-height: 32px; }
|
||||||
|
.eyebrow { margin-bottom: 1px; color: #777281; font-size: 9px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; }
|
||||||
|
h1 { font-size: 17px; line-height: 1.1; margin: 0; }
|
||||||
|
.toolbar-actions { display: grid; grid-template-columns: 1fr auto; gap: 7px; }
|
||||||
|
.toolbar-actions .button { min-height: 36px; padding: 8px 13px; border-radius: 9px; }
|
||||||
|
.status { --status:#74707e; border-left: 4px solid var(--status); padding: 11px 12px; }
|
||||||
|
.status.verified-ai, .status.watermark-detected { --status:#6847ee; }
|
||||||
|
.status.provenance-detected, .status.publisher-disclosed { --status:#956100; }
|
||||||
|
.status.heuristic-suspected { --status:#b05d16; }
|
||||||
|
.status.mixed-provenance { --status:#4761a8; }
|
||||||
|
.status.invalid-credential { --status:#bd2535; }
|
||||||
|
.status-label { display:flex;align-items:center;gap:6px;margin-bottom:4px;color:#777281;font-size:9px;font-weight:800;letter-spacing:.08em;text-transform:uppercase; }
|
||||||
|
.status-label span { width:7px;height:7px;border-radius:50%;background:var(--status); }
|
||||||
|
.status h2 { margin: 0 0 3px; font-size: 14px; }
|
||||||
|
.status p { margin: 0; font-size: 12px; line-height: 1.4; }
|
||||||
|
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px; }
|
||||||
|
.stat { text-align: center; padding: 7px 4px; border-radius: 8px; background: #f1eff7; }
|
||||||
|
.stat strong { display: block; font-size: 15px; }
|
||||||
|
.stat span { font-size: 10px; color: #686476; }
|
||||||
|
.warnings { margin: 0; padding-left: 20px; font-size: 12px; color: #73510d; }
|
||||||
|
.results { display: grid; gap: 6px; max-height: 220px; overflow: auto; }
|
||||||
|
details.item { border: 1px solid #e2dfeb; border-radius: 9px; padding: 8px 10px; }
|
||||||
|
details.item summary { cursor: pointer; font-size: 12px; font-weight: 700; }
|
||||||
|
.evidence { margin-top: 8px; padding-top: 7px; border-top: 1px solid #ece9f2; font-size: 11px; line-height: 1.4; }
|
||||||
|
.evidence strong { display: block; }
|
||||||
|
.evidence-meta { margin-top: 3px; color: #7a4a15; font-weight: 700; text-transform: capitalize; }
|
||||||
|
.empty-copy { border-top: 1px solid #e2dfeb; padding: 10px 2px 2px; }
|
||||||
|
.empty-copy h2 { font-size: 13px; margin: 0 0 4px; }
|
||||||
|
.empty-copy p { font-size: 11px; line-height: 1.45; margin: 0; }
|
||||||
|
.footnote { margin: 0; font-size: 10px; line-height: 1.4; }
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.stat { background: #2d2a39; }
|
||||||
|
.stat span { color: #aaa5b8; }
|
||||||
|
details.item { border-color: #3b3846; }
|
||||||
|
.evidence { border-color: #3b3846; }
|
||||||
|
.evidence-meta { color: #e4a55f; }
|
||||||
|
.warnings { color: #e4c16f; }
|
||||||
|
.eyebrow, .status-label { color: #aaa5b8; }
|
||||||
|
.empty-copy { border-color: #3b3846; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import type {
|
||||||
|
AssetResult,
|
||||||
|
Evidence,
|
||||||
|
EvidenceStrength,
|
||||||
|
OriginVerdict,
|
||||||
|
PageStatus,
|
||||||
|
ScanSummary
|
||||||
|
} from './types';
|
||||||
|
import { heuristicScore } from '../background/heuristics';
|
||||||
|
|
||||||
|
const strengthRank: Record<EvidenceStrength, number> = {
|
||||||
|
none: 0,
|
||||||
|
clue: 1,
|
||||||
|
disclosed: 2,
|
||||||
|
detected: 3,
|
||||||
|
verified: 4,
|
||||||
|
invalid: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
export function strongestEvidence(evidence: Evidence[]): EvidenceStrength {
|
||||||
|
return evidence.reduce<EvidenceStrength>(
|
||||||
|
(strongest, item) => (strengthRank[item.strength] > strengthRank[strongest] ? item.strength : strongest),
|
||||||
|
'none'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verdictFromEvidence(evidence: Evidence[]): OriginVerdict {
|
||||||
|
if (evidence.some((item) => item.verdict === 'invalid')) return 'invalid';
|
||||||
|
if (evidence.some((item) => item.verdict === 'ai')) return 'ai';
|
||||||
|
if (evidence.some((item) => item.verdict === 'not-ai-asserted')) return 'not-ai-asserted';
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAssetResult(
|
||||||
|
id: string,
|
||||||
|
kind: AssetResult['kind'],
|
||||||
|
label: string,
|
||||||
|
evidence: Evidence[],
|
||||||
|
sourceUrl?: string
|
||||||
|
): AssetResult {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
kind,
|
||||||
|
label,
|
||||||
|
sourceUrl,
|
||||||
|
verdict: verdictFromEvidence(evidence),
|
||||||
|
strength: strongestEvidence(evidence),
|
||||||
|
evidence
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aggregateSummary(
|
||||||
|
page: AssetResult,
|
||||||
|
images: AssetResult[],
|
||||||
|
texts: AssetResult[],
|
||||||
|
totalImages: number,
|
||||||
|
totalTextBlocks: number,
|
||||||
|
heuristicThreshold = 6
|
||||||
|
): ScanSummary {
|
||||||
|
const items = [page, ...images, ...texts];
|
||||||
|
const aiItems = items.filter((item) => item.verdict === 'ai').length;
|
||||||
|
const invalidItems = items.filter((item) => item.verdict === 'invalid').length;
|
||||||
|
const disclosedItems = items.filter((item) => item.strength === 'disclosed').length;
|
||||||
|
const inaccessibleItems = items.filter((item) =>
|
||||||
|
item.evidence.some((evidence) => evidence.kind === 'limitation')
|
||||||
|
).length;
|
||||||
|
const evidenceBearingItems = items.filter((item) => item.strength !== 'none').length;
|
||||||
|
const pageHeuristicScore = heuristicScore(page.evidence);
|
||||||
|
const heuristicFindings = page.evidence.filter((evidence) => evidence.kind === 'heuristic').length;
|
||||||
|
|
||||||
|
let status: PageStatus;
|
||||||
|
if (invalidItems > 0) status = 'invalid-credential';
|
||||||
|
else if (aiItems > 0 && aiItems < items.length && evidenceBearingItems > aiItems) status = 'mixed-provenance';
|
||||||
|
else if (items.some((item) => item.verdict === 'ai' && item.strength === 'verified')) status = 'verified-ai';
|
||||||
|
else if (items.some((item) => item.evidence.some((evidence) => evidence.kind === 'watermark' && evidence.verdict === 'ai'))) status = 'watermark-detected';
|
||||||
|
else if (items.some((item) => item.verdict === 'ai' && item.strength === 'detected')) status = 'provenance-detected';
|
||||||
|
else if (items.some((item) => item.verdict === 'ai' && item.strength === 'disclosed')) status = 'publisher-disclosed';
|
||||||
|
else if (pageHeuristicScore >= heuristicThreshold && heuristicFindings >= 2) status = 'heuristic-suspected';
|
||||||
|
else if (inaccessibleItems > 0 && images.length === 0 && texts.length === 0) status = 'unable-to-inspect';
|
||||||
|
else status = 'no-supported-evidence';
|
||||||
|
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
inspectedImages: images.length,
|
||||||
|
totalImages,
|
||||||
|
inspectedTextBlocks: texts.length,
|
||||||
|
totalTextBlocks,
|
||||||
|
aiItems,
|
||||||
|
invalidItems,
|
||||||
|
disclosedItems,
|
||||||
|
inaccessibleItems,
|
||||||
|
heuristicScore: pageHeuristicScore,
|
||||||
|
heuristicFindings
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
export type EvidenceStrength = 'verified' | 'detected' | 'disclosed' | 'clue' | 'invalid' | 'none';
|
||||||
|
export type OriginVerdict = 'ai' | 'not-ai-asserted' | 'unknown' | 'invalid';
|
||||||
|
export type AssetKind = 'image' | 'text' | 'page';
|
||||||
|
|
||||||
|
export interface Evidence {
|
||||||
|
id: string;
|
||||||
|
kind: 'c2pa' | 'watermark' | 'disclosure' | 'generator' | 'heuristic' | 'limitation';
|
||||||
|
strength: EvidenceStrength;
|
||||||
|
verdict: OriginVerdict;
|
||||||
|
title: string;
|
||||||
|
detail: string;
|
||||||
|
detector?: string;
|
||||||
|
issuer?: string;
|
||||||
|
category?: HeuristicCategory;
|
||||||
|
score?: number;
|
||||||
|
occurrences?: number;
|
||||||
|
raw?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HeuristicCategory = 'visual' | 'typography' | 'layout' | 'motion' | 'copy';
|
||||||
|
|
||||||
|
export interface HeuristicFinding {
|
||||||
|
ruleId: string;
|
||||||
|
category: HeuristicCategory;
|
||||||
|
title: string;
|
||||||
|
detail: string;
|
||||||
|
score: number;
|
||||||
|
occurrences: number;
|
||||||
|
sample?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImageCandidate {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
alt: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextCandidate {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
tokenEstimate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageDisclosureCandidate {
|
||||||
|
source: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageCandidate {
|
||||||
|
url: string;
|
||||||
|
title: string;
|
||||||
|
images: ImageCandidate[];
|
||||||
|
texts: TextCandidate[];
|
||||||
|
disclosures: PageDisclosureCandidate[];
|
||||||
|
heuristics: HeuristicFinding[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetResult {
|
||||||
|
id: string;
|
||||||
|
kind: AssetKind;
|
||||||
|
label: string;
|
||||||
|
sourceUrl?: string;
|
||||||
|
verdict: OriginVerdict;
|
||||||
|
strength: EvidenceStrength;
|
||||||
|
evidence: Evidence[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PageStatus =
|
||||||
|
| 'verified-ai'
|
||||||
|
| 'provenance-detected'
|
||||||
|
| 'watermark-detected'
|
||||||
|
| 'publisher-disclosed'
|
||||||
|
| 'heuristic-suspected'
|
||||||
|
| 'mixed-provenance'
|
||||||
|
| 'invalid-credential'
|
||||||
|
| 'no-supported-evidence'
|
||||||
|
| 'unable-to-inspect';
|
||||||
|
|
||||||
|
export interface ScanSummary {
|
||||||
|
status: PageStatus;
|
||||||
|
inspectedImages: number;
|
||||||
|
totalImages: number;
|
||||||
|
inspectedTextBlocks: number;
|
||||||
|
totalTextBlocks: number;
|
||||||
|
aiItems: number;
|
||||||
|
invalidItems: number;
|
||||||
|
disclosedItems: number;
|
||||||
|
inaccessibleItems: number;
|
||||||
|
heuristicScore: number;
|
||||||
|
heuristicFindings: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScanResult {
|
||||||
|
scanId: string;
|
||||||
|
pageUrl: string;
|
||||||
|
pageTitle: string;
|
||||||
|
scannedAt: string;
|
||||||
|
summary: ScanSummary;
|
||||||
|
page: AssetResult;
|
||||||
|
images: AssetResult[];
|
||||||
|
texts: AssetResult[];
|
||||||
|
warnings: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtensionSettings {
|
||||||
|
autoScan: boolean;
|
||||||
|
showInPageNotice: boolean;
|
||||||
|
showPageBadges: boolean;
|
||||||
|
includeRawEvidence: boolean;
|
||||||
|
maxImageBytes: number;
|
||||||
|
maxImagesPerPage: number;
|
||||||
|
maxAutoImagesPerPage: number;
|
||||||
|
enableHeuristicDetection: boolean;
|
||||||
|
heuristicThreshold: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_SETTINGS: ExtensionSettings = {
|
||||||
|
autoScan: true,
|
||||||
|
showInPageNotice: true,
|
||||||
|
showPageBadges: true,
|
||||||
|
includeRawEvidence: false,
|
||||||
|
maxImageBytes: 25 * 1024 * 1024,
|
||||||
|
maxImagesPerPage: 50,
|
||||||
|
maxAutoImagesPerPage: 10,
|
||||||
|
enableHeuristicDetection: true,
|
||||||
|
heuristicThreshold: 6
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RuntimeRequest =
|
||||||
|
| { type: 'analyse-page'; page: PageCandidate; mode?: 'auto' | 'manual' }
|
||||||
|
| { type: 'get-tab-result'; tabId: number }
|
||||||
|
| { type: 'clear-tab-result'; tabId: number };
|
||||||
|
|
||||||
|
export type ContentRequest = { type: 'scan-page' } | { type: 'clear-badges' };
|
||||||
|
|
||||||
|
export interface RuntimeResponse<T = unknown> {
|
||||||
|
ok: boolean;
|
||||||
|
data?: T;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
background: #f6f5fb;
|
||||||
|
color: #211f2c;
|
||||||
|
font-synthesis: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body { margin: 0; }
|
||||||
|
button, input { font: inherit; }
|
||||||
|
button { cursor: pointer; }
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #6847ee;
|
||||||
|
color: white;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.button:hover { background: #5738d5; }
|
||||||
|
.button:disabled { cursor: wait; opacity: .65; }
|
||||||
|
.button.secondary { background: #e7e3f9; color: #31266a; }
|
||||||
|
.button.danger { background: #f7dfe2; color: #7e1826; }
|
||||||
|
|
||||||
|
.muted { color: #686476; }
|
||||||
|
.card { background: white; border: 1px solid #dedbe8; border-radius: 14px; padding: 14px; }
|
||||||
|
.error { color: #9d1f31; background: #fbe9ec; border-radius: 10px; padding: 10px; }
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root { background: #171620; color: #f4f1fb; }
|
||||||
|
.card { background: #211f2c; border-color: #393645; }
|
||||||
|
.muted { color: #aaa5b8; }
|
||||||
|
.button.secondary { background: #38324f; color: #eee9ff; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { evidenceFromManifestStore, FirefoxExtensionWorkerUrl } from '../src/background/c2pa';
|
||||||
|
|
||||||
|
describe('C2PA evidence interpretation', () => {
|
||||||
|
it('recognises a trusted AI digital source type', () => {
|
||||||
|
const result = evidenceFromManifestStore({
|
||||||
|
active_manifest: 'example:claim',
|
||||||
|
validation_state: 'Trusted',
|
||||||
|
manifests: {
|
||||||
|
'example:claim': {
|
||||||
|
signature_info: { issuer: 'Example CA' },
|
||||||
|
assertions: [{ data: { digitalSourceType: 'http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia' } }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
expect(result[0]).toMatchObject({ strength: 'verified', verdict: 'ai', issuer: 'Example CA' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not claim trusted status for a merely valid signer', () => {
|
||||||
|
const result = evidenceFromManifestStore({
|
||||||
|
active_manifest: 'example:claim',
|
||||||
|
validation_state: 'Valid',
|
||||||
|
manifests: {
|
||||||
|
'example:claim': { source_type: 'trainedAlgorithmicMedia' }
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
expect(result[0]).toMatchObject({ strength: 'detected', verdict: 'ai' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prioritises a failed integrity status', () => {
|
||||||
|
const result = evidenceFromManifestStore({
|
||||||
|
active_manifest: 'example:claim',
|
||||||
|
validation_state: 'Invalid',
|
||||||
|
manifests: { 'example:claim': {} },
|
||||||
|
validation_status: [{ code: 'assertion.dataHash.mismatch' }]
|
||||||
|
}, false);
|
||||||
|
expect(result[0]).toMatchObject({ strength: 'invalid', verdict: 'invalid' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns no evidence when a manifest is absent', () => {
|
||||||
|
expect(evidenceFromManifestStore({ validation_state: 'Valid' }, false)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Firefox worker URL compatibility', () => {
|
||||||
|
it('passes the pinned SDK HTTPS guard but resolves to the packaged extension worker', () => {
|
||||||
|
const packaged = 'moz-extension://12345678-1234-1234-1234-123456789abc/c2pa_worker.js';
|
||||||
|
const workerUrl = new FirefoxExtensionWorkerUrl(packaged);
|
||||||
|
expect(workerUrl.protocol).toBe('https:');
|
||||||
|
expect(workerUrl.toString()).toBe(packaged);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('refuses non-extension worker resources', () => {
|
||||||
|
expect(() => new FirefoxExtensionWorkerUrl('https://example.com/worker.js')).toThrow(/packaged Firefox extension resource/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { ResultCache, hashBlob } from '../src/background/cache';
|
||||||
|
|
||||||
|
describe('result cache', () => {
|
||||||
|
it('evicts the least recently used entry', () => {
|
||||||
|
const cache = new ResultCache<number>(2);
|
||||||
|
cache.set('one', 1);
|
||||||
|
cache.set('two', 2);
|
||||||
|
expect(cache.get('one')).toBe(1);
|
||||||
|
cache.set('three', 3);
|
||||||
|
expect(cache.get('two')).toBeUndefined();
|
||||||
|
expect(cache.get('one')).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hashes identical content consistently', async () => {
|
||||||
|
const first = await hashBlob(new Blob(['same content']));
|
||||||
|
const second = await hashBlob(new Blob(['same content']));
|
||||||
|
expect(first).toBe(second);
|
||||||
|
expect(first).toMatch(/^[a-f0-9]{64}$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { analyseDisclosures } from '../src/background/disclosures';
|
||||||
|
|
||||||
|
describe('publisher disclosure parsing', () => {
|
||||||
|
it('recognises explicit AI-origin metadata', () => {
|
||||||
|
const result = analyseDisclosures([{ source: 'meta:ai-generated', value: 'true' }]);
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0]).toMatchObject({ strength: 'disclosed', verdict: 'ai' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('treats generator metadata as a clue rather than proof', () => {
|
||||||
|
const result = analyseDisclosures([{ source: 'meta:generator', value: 'Framer AI' }]);
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0]).toMatchObject({ strength: 'clue', verdict: 'unknown' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores ordinary website-generator metadata', () => {
|
||||||
|
expect(analyseDisclosures([{ source: 'meta:generator', value: 'WordPress 7.0' }])).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { analyseCopyPatterns } from '../src/content/heuristics';
|
||||||
|
import { evidenceFromHeuristics, heuristicScore } from '../src/background/heuristics';
|
||||||
|
|
||||||
|
describe('page heuristics', () => {
|
||||||
|
it('finds dense buzzwords and em-dash cadence without declaring an AI verdict', () => {
|
||||||
|
const text = 'Supercharge your workflow with our world-class, enterprise-grade platform — move faster — work smarter — without compromise.';
|
||||||
|
const findings = analyseCopyPatterns(text);
|
||||||
|
expect(findings.map((finding) => finding.ruleId)).toEqual(expect.arrayContaining([
|
||||||
|
'copy-marketing-buzzwords',
|
||||||
|
'copy-em-dash-cadence'
|
||||||
|
]));
|
||||||
|
expect(evidenceFromHeuristics(findings).every((item) => item.verdict === 'unknown')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds a diversity point when independent categories agree', () => {
|
||||||
|
const evidence = evidenceFromHeuristics([
|
||||||
|
{ ruleId: 'one', category: 'visual', title: 'One', detail: 'One', score: 2, occurrences: 1 },
|
||||||
|
{ ruleId: 'two', category: 'layout', title: 'Two', detail: 'Two', score: 3, occurrences: 1 }
|
||||||
|
]);
|
||||||
|
expect(heuristicScore(evidence)).toBe(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores ordinary direct prose', () => {
|
||||||
|
expect(analyseCopyPatterns('The archive contains monthly invoices. Select a year, then download the required PDF.')).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { inspectImageGeneratorMetadata } from '../src/background/image-metadata';
|
||||||
|
|
||||||
|
describe('image generator metadata clues', () => {
|
||||||
|
it('recognises a Stable Diffusion parameters block', async () => {
|
||||||
|
const blob = new Blob(['PNG metadata\0Steps: 30, Sampler: Euler a, CFG scale: 7, Seed: 42']);
|
||||||
|
const evidence = await inspectImageGeneratorMetadata(blob);
|
||||||
|
expect(evidence[0]).toMatchObject({ kind: 'generator', strength: 'detected', verdict: 'ai' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not flag an ordinary image byte sequence', async () => {
|
||||||
|
expect(await inspectImageGeneratorMetadata(new Blob(['ordinary image metadata']))).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { shouldShowNotice } from '../src/content/notice';
|
||||||
|
import type { PageStatus, ScanResult } from '../src/shared/types';
|
||||||
|
|
||||||
|
function resultWithStatus(status: PageStatus): ScanResult {
|
||||||
|
return {
|
||||||
|
scanId: 'scan',
|
||||||
|
pageUrl: 'https://example.com/',
|
||||||
|
pageTitle: 'Example',
|
||||||
|
scannedAt: new Date(0).toISOString(),
|
||||||
|
summary: {
|
||||||
|
status,
|
||||||
|
inspectedImages: 0,
|
||||||
|
totalImages: 0,
|
||||||
|
inspectedTextBlocks: 0,
|
||||||
|
totalTextBlocks: 0,
|
||||||
|
aiItems: 0,
|
||||||
|
invalidItems: 0,
|
||||||
|
disclosedItems: 0,
|
||||||
|
inaccessibleItems: 0,
|
||||||
|
heuristicScore: status === 'heuristic-suspected' ? 7 : 0,
|
||||||
|
heuristicFindings: status === 'heuristic-suspected' ? 3 : 0
|
||||||
|
},
|
||||||
|
page: { id: 'page', kind: 'page', label: 'Example', verdict: 'unknown', strength: 'none', evidence: [] },
|
||||||
|
images: [],
|
||||||
|
texts: [],
|
||||||
|
warnings: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('in-page result visibility', () => {
|
||||||
|
it('shows meaningful heuristic and provenance results', () => {
|
||||||
|
expect(shouldShowNotice(resultWithStatus('heuristic-suspected'))).toBe(true);
|
||||||
|
expect(shouldShowNotice(resultWithStatus('verified-ai'))).toBe(true);
|
||||||
|
expect(shouldShowNotice(resultWithStatus('invalid-credential'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stays silent on unknown or inaccessible pages', () => {
|
||||||
|
expect(shouldShowNotice(resultWithStatus('no-supported-evidence'))).toBe(false);
|
||||||
|
expect(shouldShowNotice(resultWithStatus('unable-to-inspect'))).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { aggregateSummary, createAssetResult, strongestEvidence, verdictFromEvidence } from '../src/shared/status';
|
||||||
|
import type { Evidence } from '../src/shared/types';
|
||||||
|
|
||||||
|
const evidence = (overrides: Partial<Evidence>): Evidence => ({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
kind: 'c2pa',
|
||||||
|
strength: 'none',
|
||||||
|
verdict: 'unknown',
|
||||||
|
title: 'Evidence',
|
||||||
|
detail: 'Test evidence',
|
||||||
|
...overrides
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('evidence precedence', () => {
|
||||||
|
it('treats invalid credentials as stronger than positive evidence', () => {
|
||||||
|
const items = [
|
||||||
|
evidence({ strength: 'verified', verdict: 'ai' }),
|
||||||
|
evidence({ strength: 'invalid', verdict: 'invalid' })
|
||||||
|
];
|
||||||
|
expect(strongestEvidence(items)).toBe('invalid');
|
||||||
|
expect(verdictFromEvidence(items)).toBe('invalid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('never turns empty evidence into a human-origin assertion', () => {
|
||||||
|
const result = createAssetResult('one', 'image', 'Unsigned image', []);
|
||||||
|
expect(result.verdict).toBe('unknown');
|
||||||
|
expect(result.strength).toBe('none');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('page aggregation', () => {
|
||||||
|
const unknownPage = createAssetResult('page', 'page', 'Page', []);
|
||||||
|
|
||||||
|
it('reports verified AI when a trusted AI credential is present', () => {
|
||||||
|
const image = createAssetResult('image', 'image', 'Image', [
|
||||||
|
evidence({ strength: 'verified', verdict: 'ai' })
|
||||||
|
]);
|
||||||
|
expect(aggregateSummary(unknownPage, [image], [], 1, 0).status).toBe('verified-ai');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports mixed provenance when AI and non-AI assertions coexist', () => {
|
||||||
|
const ai = createAssetResult('ai', 'image', 'AI', [evidence({ strength: 'verified', verdict: 'ai' })]);
|
||||||
|
const camera = createAssetResult('camera', 'image', 'Camera', [
|
||||||
|
evidence({ strength: 'verified', verdict: 'not-ai-asserted' })
|
||||||
|
]);
|
||||||
|
expect(aggregateSummary(unknownPage, [ai, camera], [], 2, 0).status).toBe('mixed-provenance');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prioritises invalid credentials', () => {
|
||||||
|
const invalid = createAssetResult('bad', 'image', 'Invalid', [
|
||||||
|
evidence({ strength: 'invalid', verdict: 'invalid' })
|
||||||
|
]);
|
||||||
|
expect(aggregateSummary(unknownPage, [invalid], [], 1, 0).status).toBe('invalid-credential');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports suspicion without asserting AI authorship when heuristic clues cross the threshold', () => {
|
||||||
|
const suspectedPage = createAssetResult('page', 'page', 'Page', [
|
||||||
|
evidence({ kind: 'heuristic', strength: 'clue', category: 'visual', score: 3 }),
|
||||||
|
evidence({ kind: 'heuristic', strength: 'clue', category: 'layout', score: 2 })
|
||||||
|
]);
|
||||||
|
const summary = aggregateSummary(suspectedPage, [], [], 0, 0, 6);
|
||||||
|
expect(summary.status).toBe('heuristic-suspected');
|
||||||
|
expect(suspectedPage.verdict).toBe('unknown');
|
||||||
|
expect(summary.heuristicScore).toBe(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not elevate a single weak style clue', () => {
|
||||||
|
const page = createAssetResult('page', 'page', 'Page', [
|
||||||
|
evidence({ kind: 'heuristic', strength: 'clue', category: 'visual', score: 2 })
|
||||||
|
]);
|
||||||
|
expect(aggregateSummary(page, [], [], 0, 0, 6).status).toBe('no-supported-evidence');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "preact",
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": ["firefox-webext-browser", "vitest/globals"]
|
||||||
|
},
|
||||||
|
"include": ["src", "tests", "scripts"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user