npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

c2pa-html

v0.2.0

Published

C2PA manifest embedding, referencing, and hard binding for HTML documents

Readme

Overview

Implements the Embedding Manifests into HTML section of the C2PA Technical Specification: a C2PA Manifest Store carried inline as the Base64 content of a <script type="application/c2pa"> element, or referenced externally by a <link rel="c2pa-manifest"> element, both in the document head.

<head>
    <script type="application/c2pa">...Base64 Manifest Store...</script>
</head>
[dependencies]
c2pa-html = "0.1"

HTML has its own embedding clause, so the structured-text method explicitly does not apply to it — that method covers text/* formats "not already covered by a format-specific embedding section".

This crate owns two things:

  1. The association — discover, embed, reference, and remove the script or link element.
  2. The hard binding — the exact c2pa.hash.data coverage for HTML, with compute and verify.

Signature verification, certificate trust, assertion validation, and resolution of an external manifest URI are not reimplemented here.

Not certified or conformance-tested by the C2PA. It implements the embedding and hard binding as specified.

What it does

| | | |---|---| | document | discover, embed, reference, and remove a manifest association | | hardbinding | the exact c2pa.hash.data coverage, with compute and verify |

Signature verification, certificate trust, assertion validation, and resolution of an external manifest URI are out of scope.

Reference an external manifest

The specification prefers this form.

use c2pa_html::document;

let page = b"<html>\n<head>\n    <title>Example</title>\n</head>\n<body></body>\n</html>";
let out = document::embed_reference(page, "https://fabrikam.example/m.c2pa")?;

let manifest = document::extract(&out)?;
assert_eq!(manifest.href(), Some("https://fabrikam.example/m.c2pa"));
# Ok::<(), c2pa_html::Error>(())

Carry the manifest inline

use c2pa_html::document;

let page = b"<html>\n<head>\n    <title>Example</title>\n</head>\n<body></body>\n</html>";
let out = document::embed(page, b"manifest-store-bytes")?;

assert_eq!(
    document::extract(&out)?.store(),
    Some(&b"manifest-store-bytes"[..])
);
// Removing the manifest restores the document byte for byte.
assert_eq!(document::remove(&out)?, page);
# Ok::<(), c2pa_html::Error>(())

Bind the document

use c2pa_html::document;
use c2pa_html::hardbinding::{compute_data_hash, verify_data_hash, Algorithm, Sha2};

let page = b"<html>\n<head>\n    <title>Example</title>\n</head>\n<body></body>\n</html>";
let out = document::embed(page, b"manifest-store-bytes")?;

let binding = compute_data_hash(&out, Algorithm::Sha256, &Sha2)?;
assert!(verify_data_hash(&out, &binding, &Sha2).is_ok());
# Ok::<(), c2pa_html::Error>(())

Binding coverage

  • Inline — one exclusion range covering the entire script element, <script through </script> inclusive. The hash is over the document with that range removed.
  • External — no exclusion range at all. The hash is over the entire document, link element included, so the manifest URI cannot be swapped without breaking the binding.

The hash is over the bytes of the document as stored, with no normalization. Anything that re-serializes the HTML — a CMS, a CDN, a formatter that rewrites quote styles or collapses whitespace — shifts byte offsets and invalidates the binding. That is by design: re-serialization is a content modification. Embed after the final serialization step, or use an external manifest.

The inline hash can be computed before the manifest exists

Because the exclusion covers the entire script element, the covered bytes are the document with the element cut out — and embed adds no bytes outside the element, so that is the original document. Signing an inline HTML manifest needs no placeholder-reserve-then-fill dance: hash, sign, embed.

use c2pa_html::hardbinding::inline_hash_before_embed;

An external manifest has no exclusion, so the link element is inside the hash and the order reverses: insert the link first, then hash.

Discovery

Scoped to the document head, byte-oriented, and tolerant of how HTML is actually written:

  • all three attribute quoting forms — rel="x", rel='x', rel=x
  • ASCII case-insensitive tag names, attribute names, type, and rel
  • rel as a space-separated token list, per HTML
  • link matched on rel alone; type="application/c2pa" is optional
  • comments, doctypes, and the contents of other script and style elements are never mistaken for markup
  • an implied head (both tags omitted) is still searched

A document shall carry at most one association. Two script elements, two link elements, or one of each are all rejected with the specified manifest.html.multipleManifests failure — the manifests were located, and the document is rejected for carrying too many.

Finding nothing carries no status code (it is simply an unsigned document), and neither does an element that matches but yields no Manifest Store — the specification names no code for that, and the fail-safe reading is that nothing was obtained. Error::is_no_manifest_located() draws the line a caller actually needs: "carries no provenance" versus "carried provenance that was rejected".

Bytes, not text

The specification directs a validator to treat the document "as a series of bytes (vs. text)", so every entry point takes &[u8] and returns Vec<u8>. A document in a legacy ASCII-compatible encoding scans correctly, and byte offsets mean what the hard binding says they mean.

Zero dependencies, no features

Discovery, embedding, Base64, SHA-2, and the binding algorithm are all in-crate. There is nothing to enable and nothing to pull in — the dependency list is empty in every configuration.

SHA-256/384/512 are implemented against FIPS 180-4 and tested against the NIST vectors. A hash is the one primitive where writing it yourself is uncontroversial: fully specified, no key, public input, no timing side channel, and published vectors that pin every path.

What that gives up is speed — no SHA-NI, no NEON. Immaterial for an HTML document; it would not be for a multi-gigabyte asset, which is why hashing goes through the Hasher trait. A caller with that problem injects an accelerated implementation and never touches the built-in one.

Related Crates

Part of a family of single-purpose crates, one per C2PA embedding method. Each is standalone and independently versioned.

| Crate | Description | |---|---| | c2pa-structured-text | Structured text: ASCII-armoured manifest in a comment or front matter | | c2pa-unstructured-text | Unstructured text: invisible Unicode variation-selector run | | c2pa-http | HTTP: the c2pa-manifest Link header, with a Tower middleware | | c2pa-text-binding | Soft binding and content fingerprinting for text assets | | c2pa-vtt | WebVTT caption and subtitle embedding | | c2pa-zip | ZIP-based documents: EPUB, DOCX, ODT, OXPS | | c2pa-warc | WARC web archive embedding (ISO 28500) | | c2pa-fonts | OpenType/TrueType (SFNT) font embedding | | c2pa-ml | ML model containers: GGUF, SafeTensors, ONNX | | c2pa | Official C2PA SDK |

Security

Found a vulnerability? Please report it privately — see SECURITY.md.

License

Licensed under either of Apache License, Version 2.0 or MIT License at your option.

Built by WritersLogic