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.

Introduction

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.

Goals

Non-goals

Conformance

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).

The 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.

Categories and content model

Categories
Flow content. Palpable content.
Contexts in which this element can be used
Where flow content is expected.
Content model
One <meta name="signed-at"> claim and zero or more additional claim <meta> elements (see § In-band claim metadata), followed by flow content.
Tag omission
Neither tag is omissible.
DOM interface
{{HTMLSignedSectionElement}}.

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]].

Attributes

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)
The exact, case-sensitive signing-profile identifier. This report defines 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)
Either 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)
A string identifying the signer. The value is opaque to the HTML layer; user agents resolve it to a public key using one of the methods defined in [[I-D.grey-htmltrust]] (typically a Decentralized Identifier [[DID-CORE]], a direct URL to a key document, or a trust-directory reference). Implementations MUST accept at least one resolution method and SHOULD support DIDs.
signature (required)
The cryptographic signature over the canonical signing payload, encoded as canonical unpadded standard Base64 per [[I-D.grey-htmltrust]]. This is not base64url.
content-hash (required)
The hash of the canonicalized signed content of the element, including signed semantic attributes. The value MUST be prefixed with the hash algorithm identifier and a colon (for example, 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)
The signature algorithm used to produce the 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.

In-band claim metadata

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.

author
The human-readable name of the author. Its normalized value participates in the claims hash. The protocol assigns no identity semantics to the string by itself.
signed-at
A signer-asserted UTC timestamp in the exact YYYY-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:License
An SPDX license identifier or URL describing the license under which the signed content is offered. Its normalized value participates in the claims hash; the protocol does not validate the license.
claim:CanonicalURL
The URL at which this signed region is canonically published. This is descriptive signed metadata. It does not select or replace the location derived from the final response URL and signature-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.

Parsing

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.

Default rendering

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.

DOM Interface

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.

The 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.

The verify() method

The 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.

User-Agent Processing Model

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.

Layer 1: Cryptographic verification

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:

  1. Require all six v1 attributes, select htmltrust-signature-v1, and validate the exact url or origin scope. Profile selection occurs before canonicalization and has no legacy fallback.
  2. Extract the canonical signed content of the element, including signed semantic attributes, using the canonicalization rules defined in [[I-D.grey-htmltrust]]. Reject a signed URL outside htmltrust-safe-url-v1.
  3. Compute the content hash and compare it against the content-hash attribute. If the values do not match, verification fails with {{SignedSectionCryptoOutcome/valid}} = false and {{SignedSectionCryptoOutcome/reason}} = "content-hash-mismatch".
  4. Canonicalize every direct-child claim, require one strict 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.
  5. Resolve the 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".
  6. Verify the 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-only
The user agent verified an original response snapshot or source refetch and did not compare it to the current rendered DOM.
rendered-match
The user agent verified source HTML and also determined that the current rendered DOM canonicalizes to the same signed content and claim set.
stale
The source input verified, but the current rendered DOM or required attributes have changed since that source input, or no longer canonicalize to the same signed content and claim set. A stale result MUST NOT be presented as verification of the currently rendered content.

Layer 2: Trust decision

Trust 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.

Verification lifecycle

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:

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.

Errors

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.

User Interface Guidance

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.

Security and Privacy Considerations

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.

Network, CSP, and privacy model

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.

Fingerprinting

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.

Content Security Policy interactions

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.

Mutation-driven re-verification cost

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()}}.

Claim metadata and sanitizers

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.

Side channels

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.

Republication and location binding

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.

Accessibility Considerations

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.

Open Issues

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.

Conformance Summary

A conforming user agent MUST:

A conforming authoring tool MUST:

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.

Acknowledgements

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.