Varsel — AI-Transparency Marking: Trust Anchor, Specification and Verification
How to check that a Varsel document carries its machine-readable AI-content marking
| Field | Value |
|---|---|
| Document ID | ATV-VARSEL-001 |
| Version | 1.0 |
| Date | 2026-10-26 |
| Classification | External — public; may be shared with anyone who holds a Varsel document |
| Owner | CEO, Conshultocracy AB |
| Review cycle | On any change to the signing certificate, the manifest specification or the carriers; otherwise annual |
Why this document exists. Since release v315 (2026-10-18) the PDFs that Varsel returns from its screening and enrichment document routes carry a signed, machine-readable statement that the document contains AI-generated content, and since v316 (2026-10-18) the same statement travels in the report HTML and in the JSON preview response. The statement is signed with a certificate that we issued ourselves. A signature is only worth something if the public can check it against a published anchor, so this document publishes that anchor, the exact format of the statement, and two ways to verify it: a small reference script and a manual recipe with standard tools. It is written so a compliance team or an authority can run the checks without asking us anything.
It is written to be checkable. Every constant below is read from the deployed code, and a test in our code base fails if this document and the code drift apart.
Contents
- What the marking is, and what it is not
- The trust anchor
- The
manifest (specification
varsel-ai-transparency/1.0) - The three carriers
- How to verify
- What a result means, and what it does not
- Reporting a discrepancy
1. What the marking is, and what it is not
It is a machine-readable statement, embedded in the document, that parts of the document were produced by AI systems, naming the AI providers and models our system recorded during that run and the time the document was generated. It is the measure we apply under Article 50(2) of the EU AI Act (Regulation (EU) 2024/1689) as the provider of the Varsel AI system. A visible label on the cover of the PDF says the same thing in words, with the EU "AI" icon (alternative text for assistive technology: "EU AI label icon: this document contains AI-generated content"). The label and the marking are both applied on the screening and enrichment document routes and both are fail-open: a fault produces a document without them rather than no document (section 6).
It is not a statement about the accuracy of the screening, a statement that the content was reviewed, or a determination of anything about the screened entity. The signature binds the marking to the file; it vouches for nothing else. Varsel surfaces sourced signals; the decision on what they mean, and the accountability for that decision, stay with the reader of the report.
The certificate is self-signed. We issued it ourselves; it is not chained to a public certificate authority or a trust list. Consequences, stated plainly:
- A PDF viewer that validates signatures will report the signature as valid but from an untrusted or unknown identity until you add our certificate as a trust anchor in that viewer. That is the expected reading, not a fault.
- Trust therefore rests on this published anchor and on the fingerprint below, which you can compare with the copy published on our website and with the copy in this document. Two independent copies that agree are the control; nothing in the file itself can prove the certificate is ours.
- We chose not to buy a certificate from a recognised authority. There is no recurring cost in this design, and we say so rather than imply an upgrade path that is not decided.
2. The trust anchor
Production certificate (signs the documents returned by the production service since 2026-10-18, release v315):
| Field | Value |
|---|---|
| Subject | CN=Varsel AI transparency, O=Conshultocracy AB, C=SE |
| Issuer | the same (self-signed) |
| Serial number | 0x4b7198f4b5735a75f169def229249abbf4c5e4d0 |
| Valid | 2026-10-17 11:13:15 UTC to 2036-10-15 11:13:15 UTC |
| Key | ECDSA, curve P-256 (secp256r1); signature algorithm ecdsa-with-SHA256 |
| SHA-256 fingerprint of the DER-encoded certificate | 64082b8c14bc6ffc457d149d14b67a73256bd98b7ab717121c842fe60be91abf |
| File | varsel-ai-transparency-prod-2026-10-18.pem (SHA-256 of
the PEM file as published:
9103f4ab9c8e195b50b31d7b4da159cd733f188460de3e0608f7419b9a21ebb2) |
| Published at | https://www.conshultocracy.com/tools/varsel/ai-transparency (the canonical copy, live since 2026-10-26) and in this document |
-----BEGIN CERTIFICATE-----
MIIBtjCCAVygAwIBAgIUS3GY9LVzWnXxad7yKSSau/TF5NAwCgYIKoZIzj0EAwIw
SjEfMB0GA1UEAwwWVmFyc2VsIEFJIHRyYW5zcGFyZW5jeTEaMBgGA1UECgwRQ29u
c2h1bHRvY3JhY3kgQUIxCzAJBgNVBAYTAlNFMB4XDTI2MTAxNzExMTMxNVoXDTM2
MTAxNTExMTMxNVowSjEfMB0GA1UEAwwWVmFyc2VsIEFJIHRyYW5zcGFyZW5jeTEa
MBgGA1UECgwRQ29uc2h1bHRvY3JhY3kgQUIxCzAJBgNVBAYTAlNFMFkwEwYHKoZI
zj0CAQYIKoZIzj0DAQcDQgAE+S8i769dCmKBZc+0QX+GmoYmgf3a7ywSfdpekp9y
pEo0gzKOYZFoXxs9UdZndj+g0PFa21QtlT2+yZZP5agu96MgMB4wDAYDVR0TAQH/
BAIwADAOBgNVHQ8BAf8EBAMCBsAwCgYIKoZIzj0EAwIDSAAwRQIhALN+Z0OZHcGA
7Yo9arg1F9KQQFbs+BCEFduHtJITdnbZAiBzLOgsgkFRG6UoUHOGG4Z9EHvbMJc+
27rLF5YA7XdClw==
-----END CERTIFICATE-----
Key custody. The private key exists in exactly one place: as a secret of the production application on our hosting platform. It is not in source control, not in any file, not on any laptop, and it is never logged. The signer is rebuilt from that secret at call time and the key material never leaves the process. Our staging environment signs with a different key pair; its certificate is not published because no customer document is ever signed with it.
Rotation. If the key is replaced (compromise,
expiry, or a deliberate rotation), the new certificate is added to the
table above with its first-use date, and the old one stays listed so
that documents signed earlier remain verifiable. A document is verified
against the certificate that was in use on its generated_at
date. We will announce a rotation in the Trust and Compliance Pack's
revision history.
Documents generated before 2026-10-18 (production release v314 and earlier) carry no marking. The verifier reports them as unmarked (exit code 3); that is correct, not a failure.
3. The
manifest (specification varsel-ai-transparency/1.0)
The manifest is one JSON object. All three carriers carry the same object for the same document, built once per document from the same run data and the same clock reading.
| Field | Type | Value or meaning |
|---|---|---|
spec |
string | varsel-ai-transparency/1.0 |
statement |
string | This document contains AI-generated content. Parts of it were generated by AI systems; models lists the providers recorded during this run. |
system |
string | Varsel - Red Flag Screener |
provider |
string | Conshultocracy AB (559419-4697) (the provider of the AI
system, Art. 3(3)) |
generated_at |
string | UTC, YYYY-MM-DDTHH:MM:SSZ, no sub-seconds. One clock
reading per document: it is also the signing time of the PDF signature,
the time printed on the PDF cover and the date in the PDF file name |
ai_generated |
string | partial (the document mixes AI-produced text with
deterministic text and sourced data) |
digital_source_type |
string | http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia
(the IPTC vocabulary term for AI-generated content) |
models |
array | One entry per AI role that the run recorded, see below. May be empty for a document whose run recorded no AI call |
models_scope |
string | Providers recorded during this run. AI uses without a per-run provider record are not listed. |
Each entry of models has exactly these fields:
| Field | Values (closed lists) |
|---|---|
role |
one of baseline_risk_analysis,
market_reputation_analysis,
person_background_web_search,
adverse_media_web_search,
high_risk_country_web_search,
enrichment_concern_decomposition,
enrichment_narrative_synthesis,
enrichment_web_research |
provider |
one of google, openai,
perplexity, unknown |
model |
the model identifier recorded when the answer arrived (lower-case,
at most 64 characters), or unknown |
path |
primary, fallback or unknown
(whether the primary provider for that role answered or the fallback
did) |
count |
present only when the same provider, model and path answered more than once for that role (for example several persons in a person-background search) |
What the manifest says, and what it deliberately does not
contain. It lists what the run recorded. AI uses in the product
that have no per-run provider record yet are not listed, and
models_scope says so rather than implying completeness
(closing that gap is tracked work; see the Trust and Compliance Pack,
section 10). The manifest never contains the screened entity's name, any
person's name, a jurisdiction, a user or report identifier, a content
hash of the report or any finding. It is safe to forward with the
document.
Canonical form. The signed and hashed representation
is the JSON serialisation with keys sorted at every level, no
whitespace, and non-ASCII characters left as UTF-8 (in Python terms:
json.dumps(manifest, sort_keys=True, separators=(",", ":"), ensure_ascii=False)).
Its SHA-256, computed over the UTF-8 bytes, is carried next to it in
every carrier as manifest_sha256 so a verifier can detect a
manifest that was edited after hashing.
4. The three carriers
4.1 PDF (the screening report, its dossier variant and the enrichment PDF; applied fail-open)
| Element | Specification |
|---|---|
| XMP metadata packet | Exactly one x:xmpmeta packet in the file. In its
rdf:Description, namespace
https://varsel.nu/ns/ai-transparency/1.0/ (prefix
varsel): property varsel:aiManifest = the
canonical manifest text; varsel:aiManifestSha256 = its
SHA-256 (hex). In namespace
http://iptc.org/std/Iptc4xmpExt/2008-02-29/ (prefix
Iptc4xmpExt): Iptc4xmpExt:DigitalSourceType
with an rdf:resource attribute equal to the
digital_source_type value |
| Digital signature | One PAdES signature (CMS, detached, PDF signature dictionary) in a
signature field named VarselAITransparency. It covers the
entire file as returned, including the XMP packet: any
later change to the file (even a metadata edit) invalidates it. Reason
string:
Machine-readable AI-content marking by Varsel (Conshultocracy AB).
The signing time in the signature (/M) equals the
manifest's generated_at |
| Order of production | render, write XMP, sign, then the audit footer fingerprint reads the final bytes. The signature is the last modification; no timestamp authority is involved |
| Visible label | On the cover (screening and enrichment routes; rendered fail-open):
the EU "AI" icon with the alternative text
EU AI label icon: this document contains AI-generated content,
AI-generated content: parts of this document were produced by AI systems (EU AI Act Art. 50).
and, only when the signing key was configured for that run,
A machine-readable AI-transparency manifest is embedded in this file.,
plus the generation time in UTC |
4.2 HTML (the report HTML, from which the PDF is rendered)
Four <meta> elements in the document head, each
exactly once:
name |
content |
|---|---|
varsel-ai-transparency |
the canonical manifest text |
varsel-ai-transparency-sha256 |
its SHA-256 (hex) |
varsel-ai-transparency-signature |
base64 of the DER-encoded signature over the UTF-8 bytes of the canonical manifest (present only when signed) |
varsel-ai-transparency-cert-sha256 |
SHA-256 (hex) of the DER-encoded signing certificate (present only when signed) |
The signature algorithm follows the certificate's key:
ecdsa-p256-sha256 for the production certificate above
(ECDSA over SHA-256, DER-encoded signature).
4.3 JSON
(POST /screen/preview response)
One additive top-level key, ai_transparency, an object
with exactly these fields:
| Field | Meaning |
|---|---|
manifest |
the manifest object (section 3), or null when it could
not be built |
manifest_sha256 |
SHA-256 (hex) of the canonical form, or null |
signed |
true when a signature is present |
reason |
ok when signed; otherwise a short code, for example
no_signing_key |
signature |
null, or an object with algorithm (for
example ecdsa-p256-sha256), value (base64 DER
signature), certificate_sha256 (hex SHA-256 of the DER
certificate, to match against the anchor) and signed_over =
manifest_canonical_json_utf8 |
The certificate itself is not embedded in any carrier. A verifier obtains it from this document or from the published page and compares fingerprints; a self-asserted certificate inside the file would prove nothing.
5. How to verify
5.1 With the reference verifier (recommended; one file, no network)
The script verify_varsel_ai_transparency.py is published
next to the certificate on https://www.conshultocracy.com/tools/varsel/ai-transparency.
It contains no Varsel code and makes no network connection; it reads the
file you give it and prints every check it performs.
pip install cryptography pikepdf pyHanko
python verify_varsel_ai_transparency.py Varsel-Example-AB-2026-10-26.pdf varsel-ai-transparency-prod-2026-10-18.pem
Expected on a marked PDF: a first line with the file's SHA-256, a
list of [ok] checks, the lines
without anchor: intact=True valid=True trusted=False and
with anchor: intact=True valid=True trusted=True coverage=SignatureCoverageLevel.ENTIRE_FILE modification=ModificationLevel.NONE,
the manifest in full, and
RESULT: MARKED OK (verified against anchor ...).
Always pass the anchor. Without it the script can only
check that the file is internally consistent; it then reports
RESULT: MARKED, NOT VERIFIED and exits 4, never
MARKED OK, because a forged file signed with anyone's key
would pass the internal checks.
The same script reads a report HTML file
(python verify_varsel_ai_transparency.py report.html anchor.pem)
and a saved preview response
(python verify_varsel_ai_transparency.py --json preview.json anchor.pem).
Exit codes: 0 marked, every check passed and the
signature verified against the anchor; 4 marked and
internally consistent but no anchor given (unverified); 3
unmarked (no carrier, or a carrier without a signature, in all three
modes); 1 marked but at least one check failed (the failed
checks are named); 2 usage or environment error (bad
arguments, an anchor file that is not a PEM certificate, a missing
library, or an internal error while reading the file). The script never
ends in a Python traceback: a reading error is reported as exit 2.
Checks the script performs on a PDF, in order: exactly one XMP
packet; the manifest read by two independent readers (pikepdf and a
plain XML scan of the file bytes) agrees; the manifest's key set,
canonical form, stored hash, spec,
digital_source_type and generated_at format;
the DigitalSourceType attribute; exactly one signature in
the field VarselAITransparency; the signature is intact and
valid without any anchor; with the anchor it is trusted, covers the
entire file and shows no modification; the signing time equals
generated_at.
5.2 By hand, with standard tools
- Check the anchor you hold. Compare the fingerprint
with the one in section 2 and with the copy on our website:
openssl x509 -in varsel-ai-transparency-prod-2026-10-18.pem -noout -fingerprint -sha256 - Validate the PDF signature with pyHanko's command
line (
pip install pyhanko-cli), using our certificate as the only trust root:
Expected output line (checked by us on a production document, pyHanko CLI 0.6.0):pyhanko sign validate --trust varsel-ai-transparency-prod-2026-10-18.pem --trust-replace --no-revocation-check Varsel-Example-AB-2026-10-26.pdfVarselAITransparency:64082b8c14bc6ffc457d149d14b67a73256bd98b7ab717121c842fe60be91abf:INTACT:TRUSTED,UNTOUCHED. Without--trustthe same command reportsINTACT:UNTRUSTED,UNTOUCHEDand exits with "Validation failed", which is the self-signed reading described in section 1, not an altered document. - Read the manifest from the XMP packet:
Recompute the SHA-256 of the printed manifest text (UTF-8) and compare it with the stored hash. Any text editor orpython -c "import pikepdf,sys; m=pikepdf.open(sys.argv[1]).open_metadata(); print(m['{https://varsel.nu/ns/ai-transparency/1.0/}aiManifest']); print(m['{https://varsel.nu/ns/ai-transparency/1.0/}aiManifestSha256'])" Varsel-Example-AB-2026-10-26.pdfsha256sumwill do; the manifest text must be hashed exactly as printed, without a trailing newline. - HTML or JSON carrier: extract the manifest text and
the base64 signature, decode the signature, and verify it with the
certificate's public key as an ECDSA P-256 signature over SHA-256 of the
UTF-8 manifest bytes
(
openssl dgst -sha256 -verify <pubkey.pem> -signature <sig.der> <manifest.txt>after extracting the public key withopenssl x509 -pubkey -noout). Thecertificate_sha256value (or the-cert-sha256meta) must equal the anchor's fingerprint.
5.3 In a PDF viewer
Mainstream viewers show the signature panel for the field
VarselAITransparency. Until you import our certificate as a
trusted identity in that viewer, expect wording along the lines of
"signature is valid, but the signer's identity is unknown or not
trusted". That is the self-signed reading; the document has not been
altered. The exact wording differs between viewers and versions, and we
have not verified it in each one; the command-line checks above are the
authoritative route.
6. What a result means, and what it does not
A passing verification establishes that the file you hold is byte-for-byte the file our production service signed at the stated time, that it carries a manifest stating the document contains AI-generated content, and that the manifest lists the AI providers and models the run recorded.
It does not establish that the screening is accurate or complete, that any finding is true, that the document was reviewed by a person, or anything about the screened entity. The reader assesses the signals in the report and decides; Varsel does not decide, and the signature does not change that.
Known limits, stated plainly:
- Time is self-asserted.
generated_atand the signing time come from our server clock and are signed by us. No third-party timestamp authority is involved, by design (no network call in the document path). The time is therefore evidence of what we assert, signed, not independent evidence. - Marking and label are fail-open. If the signing key is unavailable or a library fault occurs, the service returns an unmarked document rather than no document, and logs the fault. An unmarked document from the production service after 2026-10-18 is therefore possible and is an incident we want to hear about (section 7). The cover of such a document still carries the first label line but not the sentence about the embedded manifest. The visible label itself is rendered by the same fail-open path, so a rendering fault can also produce a document without the label; the report's own AI-methodology section and per-section disclaimers are unaffected.
- A verification without the anchor proves nothing about the signer. The reference verifier reports such a run as unverified (exit 4) rather than as a pass.
- The marking does not survive re-rendering or extraction. Printing, scanning, screenshots, "print to PDF", conversion through another tool, and copying text out of the document all remove the metadata and the signature. The visible label survives printing; nothing machine-readable does. This is the known limitation of metadata-based marking and is documented in our feasibility assessment and Code of Practice gap analysis (AFG-VARSEL-001).
- A cached result is marked at the time the document is produced, not at the time the screening ran. The report's own metadata section states the screening time.
modelslists recorded AI uses only. AI uses without a per-run record are not in the list;models_scopestates this. The list can be empty for a run in which no recorded AI call succeeded.- The certificate is self-signed (section 1).
7. Reporting a discrepancy
If a document from our production service dated 2026-10-18 or later
verifies as unmarked, fails a check, or carries a manifest that
contradicts the report's own AI-methodology section, tell us at privacy@conshultocracy.com
with the file's SHA-256 (the verifier's first output line) and the
generated_at value. We treat that as a defect, not a
support question. Do not send the document itself unless we ask: the
hash and the time are enough to find the event in our audit trail, which
stores no document content.
- Owner: CEO, Conshultocracy AB
- Classification: External — public
- v1.0 (2026-10-26): first edition. Publishes the production trust
anchor in use since 2026-10-18 (release v315), the manifest
specification
varsel-ai-transparency/1.0as implemented in releases v315 and v316, the three carriers, the reference verifier and the manual recipe. - Next review: on any change to the certificate, the specification or the carriers; otherwise 2027-10-26.
- Contact: privacy@conshultocracy.com
This document describes Conshultocracy AB's AI-transparency marking for the Varsel service. It is provided so that holders of a Varsel document can verify the marking themselves. It is not legal advice, and it does not vary the terms of any agreement between us.