This document specifies HTMLTrust, an HTML extension that allows
authors to cryptographically sign semantically meaningful regions of a web
page using a new <signed-section> element. Signed sections
carry the six wire attributes required by the selected signing profile, a
required signed-at claim, and optional additional claims
expressed as child <meta> elements.
User agents verify these signatures locally and expose results through a
DOM interface, allowing pages, extensions, and assistive technologies to
distinguish content whose authorship has been cryptographically attested
from content that has not.
This specification covers the HTML and DOM integration of HTMLTrust. The wire-level protocol — canonicalization, the signing payload, and the optional trust-directory HTTP API — is defined in a companion IETF Internet-Draft [[I-D.grey-htmltrust]] and is treated here as a normative dependency.
This is an unofficial draft prepared for Community Group review. It has no standing within the W3C Recommendation track. Implementations can prototype against this draft and file issues against the project repository. Open design questions are enumerated in § Open Issues.
Transport-layer security on the Web certifies the origin that served a response; it does not certify the author of any particular passage within that response. As machine-generated content and large-scale republication have become routine, readers and downstream systems increasingly lack a reliable way to determine who stands behind a given piece of text on a page, and whether that text has been altered since publication.
HTMLTrust addresses this gap by introducing a single new HTML element,
<signed-section>, whose attributes carry a cryptographic
signature over the canonicalized signed content of the element and whose
child <meta> elements carry in-band claims about
authorship, licensing, and other author-asserted metadata. Signature
verification is performed locally by the user agent and is layered: a
deterministic cryptographic check produces a yes/no result, and a separate
client-side trust decision interprets that result according to user
policy.
This specification defines the HTML element, its DOM interface, the user-agent processing model, and informative guidance for user interface and accessibility. The on-the-wire artifacts — canonicalization, the signing payload, encoding of hashes and signatures, the endorsement format, and the trust-directory API — are defined normatively in [[I-D.grey-htmltrust]] and are not reproduced here.
Conformance classes are user agents (typically browsers or
browser extensions that implement the processing model in
§ User-Agent Processing Model) and
authoring tools (content management systems, static-site
generators, and similar tools that emit
<signed-section> elements).
signed-section Element
The signed-section element represents a region of
flow content whose canonicalized text has been cryptographically signed
by an author identified by a resolvable key identifier.
<meta name="signed-at"> claim and zero or more
additional claim <meta> elements (see
§ In-band claim metadata), followed by flow
content.
A signed-section element MAY contain other
signed-section elements. Nested signed sections are
verified independently; an outer signature does not imply verification
of inner signatures, and an inner signature does not invalidate an
outer one. When the outer section is canonicalized, the nested element's
content contributes to the outer canonical content and the nested
signed-section emits the block boundary defined in
[[I-D.grey-htmltrust]].
The following content attributes are defined on the
signed-section element. All six MUST
be present on any element intended for verification; an element missing
any required attribute MUST be treated as
unverifiable (see § Verification lifecycle).
profile (required)htmltrust-signature-v1. An unknown value fails
with profile-unsupported; user agents must not retry a
failed verification under a legacy profile.signature-scope (required)url or origin. URL scope binds the
signature to the final response URL after its fragment is removed.
Origin scope allows the same signature to verify at another URL on
the same HTTPS origin. Authoring tools should emit URL scope.keyid (required)signature (required)content-hash (required)sha256:…). The set
of permitted algorithms and the canonical unpadded standard Base64
encoding are defined in [[I-D.grey-htmltrust]]. Values embedded in URL
paths or query strings are percent-encoded where required by the URL
grammar.algorithm (required)signature attribute (for example, ed25519,
ecdsa-p256, or rsa-pss-sha256). The set of
permitted values is defined in [[I-D.grey-htmltrust]].
A signed-section element MAY carry
any global attributes. The semantic attributes href,
src, alt, and aria-label are
covered by the signature when present on included descendants, as
defined by the fixed htmltrust-attrs-v1 profile in
[[I-D.grey-htmltrust]]. Other global attributes are not covered by the
signature in this revision.
Signed href and src values use the
htmltrust-safe-url-v1 policy. The policy consumes the
accepted HTML parser's decoded attribute value. User agents inspect
that value for controls before URL parsing, then resolve it against the
source snapshot's document base URL. The result must be an HTTPS URL
without credentials. Other schemes fail with
url-policy-violation; parse errors fail with
attribute-canonicalization-failed. Verification does not
fetch these signed URLs.
Canonicalization escapes U+0040 in text and signed attribute values
before emitting canonical bytes. User agents MUST
apply this escaping exactly as specified in [[I-D.grey-htmltrust]];
a literal text sequence beginning with @attr: therefore
cannot be interpreted as an attribute record.
Verification input MUST satisfy the portable
parser profile in [[I-D.grey-htmltrust]]. Validation operates on the
source octets and parser diagnostics before the recovered DOM tree is
accepted. User agents MUST return
parser-profile-unsupported for duplicate attributes,
malformed nesting, table foster-parenting cases, foreign-content
integration, ambiguous character references, or malformed HTML comments
identified by the profile. Outside raw-text elements, comments in the
signed input MUST have a closing
-->; their bodies MUST NOT
contain -- or end with -. A live DOM alone
cannot establish conformance because normal HTML parsing has already
discarded or repaired those source conditions.
Direct child <meta> elements of a
signed-section carry author-asserted claims about the
signed content. Every direct child <meta> is a claim
and MUST use the name/content
attribute pair. Missing name or content, an
empty normalized name, or duplicate normalized
name values make verification fail as defined in
[[I-D.grey-htmltrust]]. The following names are defined by this
specification; additional names from the claim: namespace
MAY appear.
authorsigned-atYYYY-MM-DDTHH:MM:SSZ form defined by
[[I-D.grey-htmltrust]]. It participates in the canonical signing
payload defined in [[I-D.grey-htmltrust]], is also included in the
canonical claims hash like every other direct child claim, and
MUST be present for a signature to verify.
It proves that the value was signed; independent timestamp evidence
is needed to prove when the signature existed.claim:Licenseclaim:CanonicalURLsignature-scope.
Claim <meta> elements MUST
appear as direct children of the signed-section element,
and MUST precede any other flow content
children of that element. Claims nested inside descendant elements are
not treated as claims of the enclosing signed-section.
Normalized claim names are case-sensitive. Only the exact ASCII name
signed-at has protocol timestamp semantics.
The signed-section element is parsed as a normal HTML
element using the rules in [[HTML]]. It introduces no new tokenizer
states, no new insertion modes, and no scoping behavior beyond that of
a generic flow-content element. A conforming user agent recognizes it as
a built-in HTML element and exposes {{HTMLSignedSectionElement}}. A
prototype MAY emulate the element with an autonomous custom element,
but that prototype is not a conforming user-agent implementation of the
native DOM interface in this section.
The default rendering of a signed-section element is that
of a transparent block container, equivalent to a <div>.
User agents MUST NOT apply any chrome-level
decoration (icons, borders, badges) to the rendered element itself.
User-agent trust indicators are surfaced through the mechanisms
described in § User Interface Guidance, not
by mutating the element's box.
User agents that implement this specification
MUST expose the following interface on
signed-section elements.
[Exposed=Window]
interface HTMLSignedSectionElement : HTMLElement {
[HTMLConstructor] constructor();
[CEReactions] attribute DOMString profile;
[CEReactions] attribute DOMString signatureScope;
[CEReactions] attribute DOMString keyid;
[CEReactions] attribute DOMString signature;
[CEReactions] attribute DOMString contentHash;
[CEReactions] attribute DOMString algorithm;
readonly attribute SignedSectionVerificationResult? verification;
Promise<SignedSectionVerificationResult> verify(optional VerifyOptions options = {});
};
dictionary VerifyOptions {
boolean force = false;
AbortSignal? signal;
};
dictionary SignedSectionVerificationResult {
SignedSectionCryptoOutcome crypto;
SignedSectionTrustOutcome trust;
DOMHighResTimeStamp timestamp;
};
enum SignedSectionFailureReason {
"incomplete",
"profile-unsupported",
"scope-unsupported",
"content-hash-mismatch",
"claim-missing",
"claim-malformed",
"claim-duplicate",
"timestamp-invalid",
"attribute-canonicalization-failed",
"url-policy-violation",
"parser-profile-unsupported",
"invalid-encoding",
"malformed-signature",
"signature-invalid",
"key-resolution-failed",
"malformed-key-document",
"key-revoked",
"algorithm-not-supported",
"algorithm-mismatch",
"origin-not-supported",
"resource-limit-exceeded",
"network-policy-blocked",
"source-refetch-failed",
"directory-unavailable"
};
dictionary SignedSectionCryptoOutcome {
boolean valid;
SignedSectionFailureReason? reason;
USVString? resolvedKeyURL;
DOMString? algorithm;
DOMString? profile;
DOMString? scope;
USVString? location;
DOMString inputState; // "source-only" | "stale" | "rendered-match"
};
dictionary SignedSectionTrustOutcome {
double score;
DOMString tier; // "trusted" | "neutral" | "untrusted" | "unknown"
sequence<DOMString> contributors;
SignedSectionFailureReason? reason;
};
The timestamp member records when the user agent commits the
result. It uses the same monotonic time origin as
performance.now(). This value is local lifecycle metadata. It
is separate from the signer-asserted signed-at value and does
not establish publication time.
The reflected IDL attributes profile,
signatureScope, keyid, signature,
contentHash, and algorithm correspond to the
content attributes profile, signature-scope,
keyid, signature, content-hash, and
algorithm respectively.
Mutating any of these attributes MUST reset the
{{HTMLSignedSectionElement/verification}} attribute to null
until verification is next performed (see
§ Verification lifecycle).
A valid cryptographic outcome MUST have a null
reason. An invalid outcome MUST use
the applicable {{SignedSectionFailureReason}} value.
directory-unavailable applies only to the trust outcome and
cannot change a valid cryptographic outcome.
verification attribute
The verification
getter returns the result of the most recent verification of the
element, or null if no verification has yet completed or
if the result has been invalidated by mutation.
verify() methodThe verify(options) method MUST return a promise that resolves with a {{SignedSectionVerificationResult}} representing the result of running the cryptographic-verification and trust-decision steps defined in § User-Agent Processing Model.
If options.force is true, any cached result
MUST be discarded and the verification
MUST be re-run. Page script cannot select a
trust directory. Directory selection belongs to user, administrator,
or embedding-verifier policy. If
options.signal is provided and aborted, the returned
promise MUST reject with an
AbortError {{DOMException}}.
The exact mapping from {{SignedSectionVerificationResult/crypto}} fields to bytes on the wire is defined in [[I-D.grey-htmltrust]] and is intentionally not duplicated here.
Verification of a signed-section proceeds in two layers,
which user agents MUST implement as
distinguishable steps and MUST expose
separately in the {{SignedSectionVerificationResult}} dictionary.
Cryptographic verification is deterministic over the server HTML before
browser extensions, page scripts, or post-parse page behavior can modify
it. A user agent MUST verify source octets. It
SHOULD use an original response snapshot
captured during navigation. If no such snapshot is available, it
MUST re-request the document from the server,
subject to the network and credential constraints in
§ Network, CSP, and privacy model. If neither
source is available, verification fails with
source-refetch-failed. A dynamically constructed element
with no source representation cannot receive a valid layer-1 result.
Before accepting a parsed tree, the user agent
MUST validate the portable parser profile from
the source octets and parser diagnostics as specified in
[[I-D.grey-htmltrust]]. Given an accepted source representation, the user
agent MUST:
htmltrust-signature-v1, and validate the exact
url or origin scope. Profile selection occurs
before canonicalization and has no legacy fallback.htmltrust-safe-url-v1.content-hash attribute. If the values do not match,
verification fails with
{{SignedSectionCryptoOutcome/valid}} = false and
{{SignedSectionCryptoOutcome/reason}} = "content-hash-mismatch".signed-at value, and construct the complete signing object
defined in [[I-D.grey-htmltrust]]. Derive its location from the final
response URL and the signed scope, then serialize the object with
RFC 8785 JCS.keyid to a public key using one of the
methods accepted by the user agent. If resolution fails,
verification fails with
{{SignedSectionCryptoOutcome/reason}} = "key-resolution-failed".signature against the canonical signing
payload using the public key and the algorithm identified by the
algorithm attribute. If the algorithm is not accepted
by the user agent, verification fails with
{{SignedSectionCryptoOutcome/reason}} = "algorithm-not-supported".A successful outcome exposes the accepted profile, scope, and derived location through {{SignedSectionCryptoOutcome}}. Scripts must not infer whole-page integrity from this result. In particular, origin scope permits same-origin replay and the v1 signed-attribute profile covers only the attributes named above.
Cryptographic verification MUST NOT contact
any network endpoint other than those required to resolve the
keyid or to obtain the source snapshot fallback described
above. In particular, a trust directory
MUST NOT be contacted as part of layer 1.
The {{SignedSectionCryptoOutcome/inputState}} value describes how the verified input relates to the currently rendered document:
source-onlyrendered-matchstaleTrust evaluation is contextual and client-side. Given a successful layer-1 result, the user agent SHOULD compute a {{SignedSectionTrustOutcome}} by composing inputs that MAY include:
Trust evaluation MUST NOT be performed for an element whose layer-1 result is invalid. An unverified endorsement MUST NOT contribute to the trust outcome. No remote authority's verdict MUST be privileged over the user's configured policy.
A user agent SHOULD initiate verification of
each signed-section element after the document's parser
has finished and before the element becomes the target of script
access to its {{HTMLSignedSectionElement/verification}} attribute.
User agents MAY defer verification until
first paint, until idle, or until the element enters the viewport,
and MAY coalesce verifications of multiple
elements.
A successful verification result MAY be
cached for the lifetime of the element. The result
MUST be invalidated, or marked
stale when the user agent retains the source-only
cryptographic result, when:
name or content
attribute is added, removed, or changed;href, src,
alt, or aria-label attribute is added,
removed, or changed;base element or final response URL changed;
ortrue.
Implementations SHOULD debounce
mutation-triggered re-verification to avoid pathological cost when
large signed regions are being progressively populated by scripts.
Authoring tools SHOULD NOT mutate the
descendants of a signed-section after page load, as
doing so will make any rendered-content verification stale or invalid;
Authoring tools can place runtime decoration outside the signed
section; this revision defines no in-band opt-out marker.
A user agent MUST NOT throw, log to console, or otherwise visibly fail in a way that distinguishes a signed section from an unsigned one when verification fails. Verification failure MUST be reported through the {{SignedSectionVerificationResult}} interface only. This prevents verification failure from being used as a side channel by hostile pages.
This section is informative.
User agents that present trust outcomes to end users are encouraged to do so in a way that separates cryptographic certainty from social trust. Cryptographic verification produces a deterministic yes/no result; trust is a matter of degree. Conflating the two produces indicators that are either over-confident or uselessly vague.
The on-the-wire threat model is described in detail in [[I-D.grey-htmltrust]]. This section addresses concerns specific to the HTML/DOM integration.
User-agent verification fetches use a provisional model aligned with
[[I-D.grey-htmltrust]]. Remote key documents, source-snapshot fallbacks,
and trust-directory requests MUST use
https URLs. Key and directory fetches
MUST NOT include ambient credentials by default.
A user agent MAY include credentials only for a
same-origin source refetch of the document being verified.
Verification fetches SHOULD omit the
Referer header. If a platform cannot omit it, the user agent
MUST trim it no less strictly than
strict-origin-when-cross-origin. Web JavaScript
implementations are subject to Fetch [[FETCH]] and CORS; key and
directory servers that expect web-page verifiers need to send the
appropriate CORS response headers. Browser extensions, native user
agents, crawlers, and command-line verifiers can have broader network
authority, but SHOULD apply the same credential
and referrer discipline.
Trust-directory consultation MUST be user opt-in. A user agent MUST NOT contact a directory during layer-2 trust evaluation unless the user, administrator, or embedding verifier policy has enabled that directory.
User agents MAY follow HTTPS-to-HTTPS redirects for verifier fetches, subject to a small implementation-defined redirect limit, and MUST NOT follow redirects to non-HTTPS URLs. User agents SHOULD impose finite timeouts, honor HTTP cache semantics, and report cache, timeout, redirect, CORS, and policy failures as explicit verification failures rather than as valid content.
A same-origin service worker can intercept a source refetch under the normal Service Workers model. User agents that capture an original response snapshot during navigation SHOULD use that snapshot for verification in preference to a refetch, because the snapshot is the decoded navigation response passed to the HTML parser, after any service-worker interception and before page script or extension mutation. A later refetch is a distinct input and MUST be reported as such. Service workers on unrelated origins MUST NOT be allowed to intercept key or directory fetches outside their normal scope.
User agents MUST enforce the resource ceilings
in [[I-D.grey-htmltrust]]: 1 MiB per source or signed section, 64 direct
claims, 4 KiB per normalized claim token, 64 KiB per key document,
256 KiB per endorsement response, three redirects, five seconds per
fetch, and four concurrent verifier fetches per document. Exceeding a
ceiling MUST produce
resource-limit-exceeded and a non-valid result.
Trust-directory queries performed during layer-2 evaluation reveal the requesting user agent's IP address and request headers to the contacted directory. User agents SHOULD either coalesce, batch, or proxy directory queries to limit fingerprinting, and MUST NOT contact a directory that the user has not enabled. Local cryptographic verification (layer 1) MUST NOT contact a directory, so a user who declines all directory queries can still receive cryptographic verification results.
The signed-section element introduces no new
script-execution or resource-load surface. Web JavaScript verifiers
operate under the page's CSP [[CSP3]] and Fetch [[FETCH]] constraints.
Browser-native and extension verifiers are acting on behalf of the user,
not the page, and a page MUST NOT be able to
suppress, redirect, or add credentials to user-agent key-resolution,
source-snapshot, or directory requests via CSP.
A hostile page could mount a denial-of-service attack by repeatedly
mutating the descendants of a signed-section to force
re-verification. User agents SHOULD cap the
rate of mutation-triggered re-verification per element and per
document, and MAY suspend automatic
re-verification entirely after a threshold, falling back to
on-demand verification via {{HTMLSignedSectionElement/verify()}}.
Because all direct child claim <meta> elements are
signed, metadata sanitizers that remove unknown direct child
<meta> elements from a signed-section will
break verification. This is intentional: silently dropping an
author-asserted claim changes what was signed. Authoring tools should
keep unrelated machine metadata outside the signed section and reserve
direct child <meta> elements for claims intended to be
covered by the claims hash.
The signed-at value is included both in the claims hash and
as a separate field in the signing payload defined by
[[I-D.grey-htmltrust]]. Duplicate or malformed normalized claim names
fail verification before signature verification, so this duplicate
binding creates an extraction convenience for user interfaces and
directories rather than an alternate timestamp authority. Freshness
warnings belong to trust policy. They must not be reported as
signature-invalid.
As required in § User-Agent Processing Model, verification failure MUST NOT be surfaced through observable side channels (console errors, network timing differences, layout shifts). The {{SignedSectionVerificationResult}} interface is the sole channel through which page scripts learn the outcome of verification.
The signing object defined in [[I-D.grey-htmltrust]] binds a signature
to the final response URL or its origin, according to the signed
signature-scope. User agents
MUST evaluate location binding as part of
layer-1 verification. A document URL that does not produce an allowed
HTTPS location
MUST produce
origin-not-supported. Copying signed content to another
location outside the signed scope changes the signing object, so the
copied signature produces signature-invalid.
URL scope includes the serialized path and query and excludes the fragment. It blocks same-origin replay at another URL. Origin scope is an explicit portability choice and permits that replay. A canonical link, document base URL, or page-supplied URL metadata cannot change either location.
The signed-section element MUST
have no implicit ARIA role and MUST NOT alter
the accessibility tree exposed for its descendants. Assistive
technologies MUST traverse a
signed-section as a transparent container, treating its
descendants as if they were children of its parent for purposes of
name computation and role inheritance.
User-agent trust indicators surfaced in chrome MUST have accessible names and roles, and SHOULD be reachable by keyboard. The disclosure surface described in § User Interface Guidance SHOULD be operable without pointer input and SHOULD meet the contrast and target-size requirements of WCAG 2.2 [[WCAG22]].
Authoring tools SHOULD NOT place semantic
landmarks (for example, <main>,
<nav>) as a direct child of
signed-section in a way that changes their effective
nesting; the element is transparent to landmark semantics.
The following design questions are open in this revision. Each is expected to be resolved before this document advances out of Community Group Draft status.
Verification prefers the original server HTML response snapshot, with a
source refetch fallback when no snapshot is available. Common runtime
decorations — copy-to-clipboard buttons injected into
<pre> elements, client-side syntax highlighting,
analytics or share-button injection — can still make rendered-content
verification stale. The Community Group is evaluating an opt-in marker
(for example, data-htmltrust-ignore on a subtree) that
excludes a region from canonicalization, paired with authoring guidance.
User agents MUST accept at least one
resolution method, but the spec does not yet mandate a preference
order when a keyid would resolve under more than one
method. The Community Group is considering whether to publish a
default preference order (for example, DID, then direct URL, then
directory reference) or to leave this entirely to user policy.
A conforming user agent MUST:
signed-section elements through the
{{HTMLSignedSectionElement}} interface defined in
§ DOM Interface.signed-section as transparent to the
accessibility tree and apply no chrome-level decoration to its
rendered box.A conforming authoring tool MUST:
signed-section elements that include all six
required attributes.<meta> elements as direct children of
the signed-section element, before any other flow
content.content-hash and signature values
that conform to the canonicalization and signing rules of
[[I-D.grey-htmltrust]].The current browser extension and JavaScript library are prototype implementations of the markup and verification protocol. They do not implement the browser-owned {{HTMLSignedSectionElement}} interface or native navigation-response capture required of a conforming user agent.
The author thanks early reviewers of the HTMLTrust whitepaper for feedback that shaped this draft, and acknowledges prior art in DKIM [[RFC6376]], JOSE [[RFC7515]], and Signed HTTP Exchanges [[SXG]] as conceptual influences on the layered design.