@qadar-ai/disclose-stamp
v0.1.0
Published
Disclose Stamp — publisher-side CLI that embeds C2PA + IPTC AI-content marking into assets (EU AI Act Art. 50). Transparency tool, not a compliance guarantee.
Maintainers
Readme
Disclose Stamp — @qadar-ai/disclose-stamp
A publisher-side CLI that embeds machine-readable AI-content marking into your image files
at publish time, so the marking travels with the asset even after it's downloaded — a C2PA
manifest carrying the IPTC DigitalSourceType (trainedAlgorithmicMedia /
compositedWithTrainedAlgorithmicMedia). It complements the on-page
Disclose widget (the visible EU AI Act Art. 50(4) disclosure).
Transparency tool, not a compliance guarantee. Stamp adds a machine-readable contribution alongside provider-side marking; it does not by itself discharge the provider's Art. 50(2) duty.
Local-only / privacy. Stamp runs entirely on your machine. Your assets never leave it — nothing is uploaded to Qadar, and signing is local.
Install & use
Requires Node ≥ 22.
# stamp every JPEG/PNG/WebP/AVIF under content/ as AI-generated
npx @qadar-ai/disclose-stamp "content/**/*.{jpg,png,webp,avif}" --level ai-generated
# one file, a specific level, write to a dist dir
npx @qadar-ai/disclose-stamp hero.png --level ai-modified --out dist/
# sign with your own (trusted) certificate instead of the self-signed default
npx @qadar-ai/disclose-stamp img/*.jpg --level ai-generated --cert chain.pem --key key.pem
# also bake a visible EU label badge into the pixels
npx @qadar-ai/disclose-stamp hero.png --level ai-modified --badge \
--badge-position top-right --badge-size lg --badge-tone blackLevels (consistent with the widget taxonomy): ai-assisted, ai-generated, ai-reviewed,
ai-modified. A per-file sidecar hero.jpg.disclose.json ({ "level": "ai-generated" })
overrides --level.
| Level | IPTC DigitalSourceType |
| --- | --- |
| ai-generated, ai-reviewed | trainedAlgorithmicMedia |
| ai-assisted, ai-modified | compositedWithTrainedAlgorithmicMedia |
Formats: JPEG, PNG, WebP, AVIF. Options: --level, --cert/--key, --out,
--overwrite, --badge (+ --badge-position/--badge-size/--badge-tone), --dry-run, --help.
Visible baked-in badge (--badge)
Composites the official EU label icon (matching the widget's badge — same size ladder and positions) into the image pixels, so a human-visible mark travels with the file:
--badge-positiontop-left(default) ·top-right·bottom-left·bottom-right·center--badge-sizesm(27px) ·md(38) ·lg(53) ·xl(74) ·2xl(104) ·3xl(146)--badge-toneblack(default, dark pill) ·white(light pill)
The badge family follows the level (AI · AI GENERATED · AI MODIFIED). The badge is composited before the C2PA manifest, so the manifest hashes the final pixels.
Known limit: a pixel badge survives re-encode but is destroyed by cropping and permanently alters the asset — so it is opt-in. The machine-readable C2PA/IPTC marking (always on) is the durable part.
Signing: self-signed vs trusted
By default Stamp generates a local self-signed certificate chain. The resulting C2PA marking is structurally valid and fully machine-readable — but provenance viewers will show the signer as "unverified" because the certificate isn't on the C2PA trust list.
Untrusted signer ≠ invalid AI assertion. The "unverified signer" note is about who signed (identity), not about the AI-disclosure content. The
DigitalSourceTypemarking is present and readable regardless. To remove the note, sign with a trusted certificate via--cert/--key.
(DE) Nicht vertrauenswürdiger Signierer ≠ ungültige AI-Kennzeichnung. Der Hinweis „Signierer
nicht verifiziert" betrifft nur die Identität des Signierers, nicht den Inhalt der
AI-Kennzeichnung. Die DigitalSourceType-Markierung ist vorhanden und maschinenlesbar. Mit
einem vertrauenswürdigen Zertifikat (--cert/--key) entfällt der Hinweis.
What survives
The embedded C2PA + IPTC marking is present after a download and local open, and major platforms (TikTok/YouTube/Meta/LinkedIn/Pinterest) read C2PA at upload and can re-label. Most social platforms re-encode and strip embedded metadata, so surviving a social re-upload needs durable watermarking — that is a planned v2 (not in this release).
API
import { generateSelfSignedSigner, stampBuffer, readStamp } from '@qadar-ai/disclose-stamp';
const signer = await generateSelfSignedSigner();
const stamped = stampBuffer(pngBuffer, 'image/png', 'ai-generated', signer, '0.1.0');
const info = await readStamp(stamped, 'image/png'); // { hasManifest, title, digitalSourceType }Publishing (maintainers)
Publishing is gated and manual — the Publish disclose-stamp to npm workflow only runs via
workflow_dispatch and defaults to a dry run. It uses npm OIDC Trusted Publishing (no
long-lived token). npm requires a package to exist before a trusted publisher can be configured,
so the first publish is manual:
- First publish (creates the package). From
stamp/withdistbuilt, sign in with 2FA andnpm publish— this creates@qadar-ai/disclose-stampon npm. (OIDC/CI cannot create a brand-new package.) - Add the Trusted Publisher. At
npmjs.com/package/@qadar-ai/disclose-stamp/access→ Trusted Publisher: Organization/userqadar-ai· Repositorydisclose· Workflow filenamepublish-stamp.yml· Environment namenpm-publish· Allowed action npm publish (must be explicitly selected for configs created after 2026-05-20). - Every later publish is CI-only. Push a
stamp-vX.Y.Ztag, then dispatch the workflow withdry_run=false. No token; npm authenticates via OIDC. Provenance attaches automatically once the repository is public.
License
MIT © Qadar AI. Built on the Content Authenticity Initiative's C2PA tools
(@contentauth/c2pa-node, Apache-2.0/MIT), @peculiar/x509, and sharp.
