This specification profiles [[WACZ]]. It constrains how a WACZ package is written and defines the additional members BrowserHive records inside it, so that a reader holding only the archive can tell what the capture obtained, what it did not, and under what settings it ran.
This is a working draft. It describes what BrowserHive writes today. It has no standing beyond that project and is not endorsed by the publishers of [[WACZ]].
This version adds blockUrlPatterns to settings as a
required member, and replaces the cache member with
session. A package written against 1.1.0 therefore does not
conform to 1.2.0. This is intended, and it is why conformsTo names a
version: a reader holding an older package can still tell what it was
written against. Versions 1.0.0 and 1.1.0 remain published at their own URLs
for that reason, but BrowserHive no longer produces them.
Version 1.1.0 required a cache member describing how the
capture treated the browser HTTP cache. No producer ever wrote it: the
setting it described had by then been folded into a broader one covering the
whole browser context, which is what session now names. The
member is removed rather than kept alongside, because a required member no
implementation writes makes conformance a statement about nothing.
A [[WACZ]] package says what was captured. It does not say what was missed, nor under what conditions the capture ran. Those facts are knowable only at capture time, and they are lost once the capturing process exits.
That loss matters because the archive is the thing that travels. A reader opening a package three years later has no access to the logs, the API response, or the machine that produced it. If the package cannot answer “is anything missing here?”, nothing else can.
This profile therefore requires a package to carry its own account of the capture: which bodies are absent and why, which limits were in force, which requests were declined and under what pattern, which certificates the origins presented. None of these change what [[WACZ]] means. They record what would otherwise be unrecoverable.
The conformance target of this specification is a package: a single file. Conformance is a property of files, not of the software that writes them. An implementation may write conforming and non-conforming packages at different times, and this specification says nothing about which.
A conforming package is a conforming [[WACZ]] package. This specification adds requirements. It removes none, and it relaxes none.
Where this specification uses the terms MUST, MUST NOT, SHOULD and MAY, they are to be interpreted as described in [[RFC2119]].
JSON member names are case-sensitive. Where a member is described as holding a timestamp, its value MUST be an [[RFC3339]] date-time in UTC.
browserhive:capture member
The datapackage.json of a conforming package
MUST contain a member named
browserhive:capture whose value is
a JSON object.
This member MUST NOT be nested inside
config or metadata. Those names carry meaning for
[[WACZ]] readers; see .
Members of browserhive:capture not defined here
MAY be present. Readers
MUST ignore members they do not recognise.
browserhive:capture MUST contain a
member conformsTo whose value is the URL of the version of this
specification the package claims to satisfy.
"browserhive:capture": {
"conformsTo": "https://uraitakahito.github.io/browserhive-specs/wacz-profile/1.2.0/"
}
The declaration is carried here rather than in the
profile member of datapackage.json, which
is not available for this purpose.
A reader MUST NOT infer conformance from the mere presence of browserhive:capture. Without conformsTo, the version a package was written against is unknown.
build
browserhive:capture MUST contain a
member build identifying, in machine-readable form, the
software that produced the package.
| Member | Type | Required | Description |
|---|---|---|---|
version | string | yes | Release identifier. |
revision | string | yes | Source revision the build was made from. |
buildTime | string | yes | When the software was built. |
"build": {
"version": "3.4.0",
"revision": "0776cae",
"buildTime": "2026-08-21T04:04:09.000Z"
}
The format of version is not constrained. A build made from a
release carries something like "3.4.0", while a build made
between releases may carry a value that includes its distance from the last
one, such as "3.4.0-12-gabc1234". Both say which build produced
the package. A reader should treat the value as an identifier — equal
values mean the same build — rather than parse it as a version number.
[[WACZ]] already defines software. That member is prose, and
no two producers write it alike, so nothing parses it. build
exists so the same facts can be read by machine without guessing at a
format.
browser
browserhive:capture MUST contain a
member browser describing the browser that performed the
capture. It MUST contain a member
product whose value is a string.
"browser": {
"product": "Chrome/151.0.7922.137"
}
The spelling is not defined here. The value is whatever the browser names itself as, so a package produced by a different engine carries a different shape. A reader can compare two values for equality; it cannot assume a grammar.
The value MUST NOT be a placeholder. A producer that cannot obtain it MUST NOT produce a conforming package: an archive whose recording browser is unknown is outside this profile, and omitting the member is not a way to say so.
settings
browserhive:capture MUST contain a
member settings recording the settings that were
in force for this capture.
Values MUST be the resolved values, not the values a request asked for. A request that says nothing still runs under some setting, and it is that setting the archive has to report.
| Member | Type | Required | Description |
|---|---|---|---|
signature | string | yes | Either "required" or "none": whether this
capture was permitted to be published unsigned. |
viewport | object | yes | Members width and height, both integers, in CSS pixels. |
devicePixelRatios | array of number | yes | The device pixel ratios the page was loaded at, in the order it was loaded. The number of entries is the number of loads. |
session | string | yes | Whether the capture ran in a browser context created for it and discarded afterwards, or in one the capturing process reuses across captures. It tells a reader whether the package could depend on state left by an earlier capture. |
behaviors | array of string | yes | Behaviors that actually ran, not those that were enabled. |
limits | object | yes | The body caps that were in force for this capture. Members
maxResponseBytes and maxTaskBytes, both integers, in bytes. |
blockUrlPatterns | array of string | yes | The URL patterns that were in force for this capture. A request matching one of them is recorded but its body is not fetched. See blocked requests. |
acceptLanguage | string | no | Present only when the capture set one, since it changes what origins return. |
"settings": {
"signature": "none",
"viewport": { "width": 1280, "height": 800 },
"devicePixelRatios": [1],
"session": "isolated",
"behaviors": ["autoscroll", "autofetch"],
"limits": {
"maxResponseBytes": 20971520,
"maxTaskBytes": 209715200
},
"blockUrlPatterns": ["*://*.example-analytics.com/*"]
}
In this example session records that the capture ran in a
context of its own, and the single entry in
devicePixelRatios that it loaded the page once. The two
entries under behaviors are the
behaviors that ran — not the ones that were enabled.
acceptLanguage is absent because this capture set none. The
values themselves are the producer's to choose; this profile does not
define the spellings shown here.
devicePixelRatios is a list rather than a number because a
capture may load the page more than once. Recording a single value would
tell a reader that one rendering is absent when it is present.
The values MUST appear in the order the page was loaded, so that a reader can say not only which renderings the archive holds but which one was drawn last — artefacts that carry the page's appearance follow the state left by the final load.
A capture that loads the page twice carries one value per load. A request
that set acceptLanguage shows it here as well:
"settings": {
"signature": "required",
"viewport": { "width": 1280, "height": 800 },
"devicePixelRatios": [1, 2],
"session": "isolated",
"behaviors": ["autoscroll"],
"acceptLanguage": "ja,en;q=0.9",
"limits": {
"maxResponseBytes": 20971520,
"maxTaskBytes": 209715200
},
"blockUrlPatterns": []
}
The two measure different things.
maxResponseBytes applies to the body of a single response;
maxTaskBytes applies to the running total of the bodies
written into this package.
The difference matters to a reader. maxResponseBytes weighs
one response at a time, so any number of responses under it are kept and
the size of the package cannot be inferred from it.
maxTaskBytes watches the accumulated total, so once the line
is crossed bodies are dropped regardless of size —
a small response among the dropped ones is not a contradiction.
limits MUST be present even when no
body was dropped. It is the only place the archive can say
which line a truncated body crossed — a record of a
truncation carries the fact and the actual size, but whether that size was
too much is only legible next to the line.
The truncatedUrls member of completeness does
not distinguish which of the two a body crossed, even
though the remedies differ (one response too large
versus the whole page too heavy). A reader who needs the distinction has to
look outside the package, at what the capture reported.
completeness
browserhive:capture MUST contain a
member completeness, the package's own account of the bodies it
does not hold.
| Member | Type | Required | Description |
|---|---|---|---|
bodylessUrls | array of string | yes | URLs that appeared only as 304. The body never crossed
the wire, so the archive cannot hold it. |
truncatedUrls | array of string | yes | URLs whose body the capture dropped on reaching a size limit. |
complete | boolean | yes | true if and only if both arrays are empty. |
"completeness": {
"bodylessUrls": [],
"truncatedUrls": [],
"complete": true
}
A capture that lost nothing takes this shape. complete is
derived from the two arrays, so two empty arrays alongside
false never occur.
A capture missing bodies for both reasons:
"completeness": {
"bodylessUrls": [
"https://example.com/assets/app.css"
],
"truncatedUrls": [
"https://example.com/media/hero.mp4",
"https://example.com/media/poster.png"
],
"complete": false
}
Here the stylesheet appeared only as a 304: its body never
crossed the wire, so the archive cannot hold it. The video and its poster
were dropped at a limit. A URL dropped twice still appears once.
Both arrays MUST be sorted, so that two captures of the same page produce comparable output.
The two are separate members because they call for different responses.
A 304 is the origin's doing and cannot be recovered from
here; a body dropped at a limit is this side's doing and returns if the
limit is raised.
Responses that legitimately carry no body
MUST NOT be listed: redirects, 204,
and bodies omitted by a content-type filter the requester configured.
Reporting the last of these would tell the reader only what they had
already decided.
coverage
browserhive:capture MAY contain a
member coverage describing how far down the page the capture
reached.
| Member | Type | Required | Description |
|---|---|---|---|
scrollExhausted | boolean | yes | true when scrolling stopped at a step limit rather than
at the end of the page — that is, when material below was never requested. |
scrollSteps | number | yes | Steps actually taken. |
scrolledPx | number | yes | Approximate depth reached: steps multiplied by viewport height. |
"coverage": {
"scrollExhausted": true,
"scrollSteps": 40,
"scrolledPx": 32000
}
The three values are read together. scrolledPx is
scrollSteps multiplied by the viewport height recorded in
settings — 40 × 800 here — so a reader can check
one against the other. And because scrollExhausted is
true, the 32,000 pixels are where the capture
stopped, not where the page ended: whatever lay below was never
requested, and therefore leaves no trace anywhere else in the package.
coverage MUST be absent when no
scrolling behavior reported. It MUST NOT be
written with default values: scrollExhausted: false asserts
that the page was covered, and a capture that never scrolled has not
earned that claim.
tls
browserhive:capture MAY contain a
member tls recording what the browser saw of each HTTPS
connection. If present it MUST contain
hosts and chains.
tls.hosts
An object keyed by host. Each value is either an object with the members
below, or null.
| Member | Type | Required | Description |
|---|---|---|---|
protocol | string | yes | Negotiated TLS version. |
cipher | string | yes | Negotiated cipher. |
subject | string | yes | Subject common name, as the browser reported it. |
san | array of string | yes | The host names the certificate is valid for (subjectAltName). Empty for a certificate that carries none. |
issuer | string | yes | Issuer common name. |
validFrom | string | yes | Start of the certificate's validity period. |
validTo | string | yes | End of that period. |
chainRef | string | no | Key into tls.chains. Absent when no chain could be obtained. |
A host reached over HTTPS whose connection revealed no details
MUST be recorded with the value
null. Omitting the key instead would read as
“this host was plain HTTP”, which is a different fact.
Host name matching uses san, not subject.
subject records the common name, which current readers do not
match against — the value of subject
MUST NOT be read as “this is the host name it
was accepted for”.
A missing chainRef MUST NOT be read
as “this host presented no certificate”. Chain retrieval can
fail for reasons unrelated to the host.
tls.chains
An object whose keys are the values used by chainRef and
whose values are arrays of base64-encoded DER certificates. Each array
MUST be ordered leaf first.
Hosts sharing a chain SHOULD share one entry. Certificates are commonly reused across the hosts of one page, and storing a copy per host inflates the package for no gain.
Chains are stored rather than referenced. Certificate Transparency holds the same bytes, but evidence that depends on a third-party service remaining reachable is weaker for that dependence — and a private CA's chain, which is the case worth examining, was never logged there at all.
"tls": {
"hosts": {
"example.com": {
"protocol": "TLS 1.3",
"cipher": "AES_128_GCM",
"subject": "example.com",
"san": ["example.com", "www.example.com"],
"issuer": "Example Issuing CA",
"validFrom": "2026-06-30T20:37:58.000Z",
"validTo": "2026-09-28T21:37:55.000Z",
"chainRef": "9a57318f8c89976c"
},
"cdn.example.net": {
"protocol": "QUIC",
"cipher": "AES_128_GCM",
"subject": "*.example.net",
"san": ["*.example.net", "example.net"],
"issuer": "Example Issuing CA",
"validFrom": "2026-08-05T20:42:14.000Z",
"validTo": "2026-10-28T20:42:13.000Z",
"chainRef": "c52653c92800f6fd"
},
"img.example.net": {
"protocol": "TLS 1.3",
"cipher": "AES_128_GCM",
"subject": "*.example.net",
"san": ["*.example.net", "example.net"],
"issuer": "Example Issuing CA",
"validFrom": "2026-08-05T20:42:14.000Z",
"validTo": "2026-10-28T20:42:13.000Z",
"chainRef": "c52653c92800f6fd"
},
"api.example.org": {
"protocol": "TLS 1.3",
"cipher": "AES_256_GCM",
"subject": "api.example.org",
"san": ["api.example.org"],
"issuer": "Example Issuing CA",
"validFrom": "2026-05-14T09:03:11.000Z",
"validTo": "2026-08-12T09:03:10.000Z"
},
"beacon.example.org": null
},
"chains": {
"9a57318f8c89976c": [
"MIIDbzCCAxSgAwIBAgIRALaMq73qMP3mE30B…",
"MIICjDCCAhKgAwIBAgIQf5wJDMcM1Y2ZBpFB…"
],
"c52653c92800f6fd": [
"MIIIUDCCBzigAwIBAgIQHnJSlrCYIdkSw8c/…",
"MIIFCzCCAvOgAwIBAgIQVJ5xIVMKmZ0sNHxs…"
]
}
}
The example records five hosts. Each of the rules above shows up in it.
cdn.example.net and img.example.net point at
the same chainRef — they presented
the same certificate, so the chain is stored once in
chains.api.example.org has no chainRef.
A certificate was presented — subject and the fields
below it are the evidence — only the chain could not be obtained.beacon.example.org is null. It was reached over
HTTPS but the connection revealed nothing. The key is kept because
dropping it would read as "this host was plain HTTP".cdn.example.net has protocol "QUIC".
The member is the string the browser reported, and it is not
always a TLS version.
Values in chains are base64-encoded DER. They are cut short
here for readability; a real certificate is a string of roughly
1–2 KB.
A capture may decline to fetch the body of a request whose URL matches one of
the patterns named in blockUrlPatterns. For each such request the
package MUST contain a WARC metadata
record, hereafter a record of a blocked request, so that the
archive records that the request was made.
The record's WARC-Target-URI MUST be the
request URL. Its body is application/warc-fields and
MUST contain:
| Field | Required | Description |
|---|---|---|
blocked | yes | The literal true. It distinguishes this record from
the other metadata records a package carries. |
reason | yes | Why the body is absent. block-url-pattern is the only
value this version defines. |
pattern | yes | The pattern that matched, spelled exactly as it appears in
blockUrlPatterns, so that a reader can find it there. |
method | yes | The request method. |
resourceType | no | What the page was fetching, when the capture knows it. |
WARC/1.1 WARC-Type: metadata WARC-Target-URI: https://www.example-analytics.com/collect?v=2 WARC-Date: 2026-09-06T02:14:31.882Z Content-Type: application/warc-fields Content-Length: 118 blocked: true reason: block-url-pattern pattern: *://*.example-analytics.com/* method: POST resourceType: XHR
blockUrlPatterns MUST be present even
when no request matched, and an empty array
MUST mean that no pattern was in force. Without that
rule a reader cannot separate a page that requested nothing from a capture
that declined to record what it requested, and those are different claims
about the same package.
The requirement mirrors the one on settings's limits, and
for the same reason: a setting that changes what the package contains has to
be legible from the package whether or not it happened to fire.
A record of a blocked request carries no response and no body. It asserts that the request was made and that this profile's producer chose not to record what came back — not that the request failed, and not that the origin returned nothing.
Records of blocked requests are not required to appear in the package's CDXJ index. A reader looking for them reads the WARC.
storage member
browserhive:capture MUST contain a member storage
recording what web storage the captured origins held. The member records the
presence and shape of that storage; it MUST NOT contain stored values.
Web storage is recorded because it cannot be reconstructed later. It never
crosses the network, so no WARC record holds it: a page that renders from
localStorage renders from data the archive does not contain.
Without this member a reader cannot tell such a page from one that genuinely
had nothing to show, and completeness does not help — it reports
bodies the package does not hold, and storage never became a body.
{
"browserhive:capture": {
"storage": {
"profile": "browserhive:storage/1",
"stage": "after-behaviors",
"valuesRecorded": false,
"origins": [
{
"origin": "https://www.example.co.jp",
"local": { "keys": 2, "bytes": 32, "digest": "sha256:b58f31fdc511fb94c356ae7f6d64ac71e5bf056584653c3f3ec61a1f08bfaa0f" },
"session": { "keys": 1, "bytes": 13, "digest": "sha256:8ce272dea9fc6d7902b23f10411da49a7fb2e3c954ba9f1580cc087292eaafc2" }
},
{ "origin": "https://ads.example.net", "unreadable": true }
]
}
}
}
storage MUST contain profile,
stage, valuesRecorded and origins.
profile — the rules the member follows. For this
version of this specification the value MUST be
"browserhive:storage/1". A producer that changes what it
records, or how a digest is computed, MUST change this value.
stage — the point in the capture at which storage was
read. For this version the value MUST be
"after-behaviors". The stage is normative because the
content changes: a page writes to storage as it runs, so a reading taken
before behaviours ran and one taken after describe different states.
valuesRecorded — whether this package also carries the
stored values, in the directory defined below. See
.
origins — an array with one entry per origin the
capture could observe.
Each entry MUST contain origin, the origin in the serialised
form of [[WACZ]]'s enclosing document — scheme, host and, when not
the default, port.
Web storage is partitioned by origin, and a page routinely embeds
documents from origins other than its own — an advertisement, an
analytics frame, an embedded player. Each of those has its own storage,
which the page's own origin cannot see. So this is not a record of
“the page's storage”: it is one entry per origin the capture
was able to observe, and an entry for a third party is the expected case
rather than an anomaly.
An origin that cannot hold storage at all — an opaque origin, such
as a document at about:blank or a data: URL
— has nothing to record and does not appear.
An entry MUST contain either both local and
session, or the single member unreadable with the
value true. The second form says the capture could not read
that origin's storage. It is distinct from an origin whose storage was
empty, which is written as areas with "keys": 0. Recording
them the same way would put back the ambiguity this member exists to
remove.
An entry here and a line in storage/origins.jsonl are two
halves of one record. The entry says what an origin's storage
looked like — how many keys, how many bytes, which digest
— and is always present. The line says what it
contained, and is present only when the values were asked for;
valuesRecorded states which of the two a package holds. An origin
recorded as unreadable has no line, because there is nothing
to put in one.
local and session each MUST contain:
keys — the number of keys the area held.bytes — the total size of the area, counted as the
UTF-8 encoding of every key and value. Not UTF-16 code units, which is
what the browser's own quota accounting uses.
digest — "sha256:" followed by the
lowercase hexadecimal SHA-256 of the area, computed over the keys sorted
by code point, each contributing
key U+0000 value U+0000 in UTF-8. The sort and
the separator are normative: without them the same content yields
different digests, and the digest exists to be compared across captures.
A digest MUST NOT be treated as a commitment to the value. The values are frequently low entropy — a flag, a theme name, a timestamp — and such a digest is trivially reversed by guessing.
storage directory
[[WACZ]] permits additional files and directories, provided they stay out of
archive, indexes and pages, and are
listed in the resources section of
datapackage.json. A conforming package MAY use that allowance
for a second entry.
A conforming package MAY contain an entry at
storage/origins.jsonl. When present, it MUST be declared in
resources like any other file, and it MUST be JSON Lines: one
JSON object per line, each object recording the web storage one origin held.
When the entry is present, valuesRecorded MUST be
true. When it is absent, valuesRecorded MUST be
false. A reader can therefore tell whether a package carries
stored values without opening the archive body.
{
"profile": "browserhive:storage/1",
"origin": "https://www.example.co.jp",
"takenAt": "2026-09-06T01:22:14.881Z",
"stage": "after-behaviors",
"areas": {
"local": { "feed.cache": "[{\"id\":1}]", "ui.theme": "dark" },
"session": { "nav.scroll": "820" }
}
}
Each line MUST contain profile, origin,
takenAt as an [[RFC3339]] timestamp, stage and
areas. areas MUST contain local and
session, each an object mapping key to value exactly as the area
held them. An origin recorded as unreadable in
storage MUST NOT have a line here.
[[WACZ]] permits additional files and directories, provided they stay out of
archive, indexes and pages, and are
listed in the resources section of datapackage.json.
A conforming package uses that allowance for one entry.
A conforming package MAY contain an entry at
accessibility/axtree.jsonl. When present, it
MUST be declared in resources like any
other file, and it MUST be
JSON Lines: one JSON object per line,
each object recording one snapshot of the accessibility tree the browser
computed for the captured page.
The tree is recorded because it cannot be reconstructed later. It is derived
from the DOM and the layout tree, it contains nodes with no
corresponding DOM node (CSS generated content, list markers), and
aria-owns reparents nodes so its shape differs from the document
tree. Replaying the archive against a future browser produces a different
tree. This is the same reason tls is recorded rather than
referenced.
Each line MUST contain the members
profile, url, takenAt,
stage and tree.
profile — the pruning rules the line follows. For
this version of this specification the value
MUST be
"browserhive:axtree/1". A producer that changes the
pruning rules MUST change this value, so a
reader can tell what was discarded.url — the page the snapshot describes.takenAt — when the snapshot was taken, as an
[[RFC3339]] timestamp.stage — the point in the capture at which the
snapshot was taken. For this version the value
MUST be "after-behaviors".
The stage is normative because the tree changes.
Autoscroll triggers lazy loading, so a tree taken before behaviours
ran and one taken after describe different pages.tree — an array of nodes, described below.
A line SHOULD also carry
devicePixelRatio, viewport and
nodes — respectively the pass the snapshot came from,
the viewport in effect, and the number of nodes before
pruning. The last of these lets a reader see how much was discarded.
{
"profile": "browserhive:axtree/1",
"url": "https://www.iana.org/",
"takenAt": "2026-08-22T02:55:56.759Z",
"stage": "after-behaviors",
"devicePixelRatio": 1,
"viewport": { "width": 1280, "height": 800 },
"nodes": 255,
"tree": [
{
"role": "RootWebArea",
"name": "Internet Assigned Numbers Authority",
"children": [
{ "role": "heading", "name": "Domain Names", "level": 2 },
{ "role": "image", "name": "logo",
"url": "https://www.iana.org/_img/iana-logo-header.svg" }
]
}
]
}
Shown across several lines for readability. In the file each snapshot occupies exactly one line.
Every node MUST carry role. A node
MUST NOT carry any member other than
role, name, level, url,
checked, expanded, disabled,
required, value and children.
Two of those carry more weight than the rest:
level — the heading depth. Without it a
heading node cannot be told from another, and the document
structure the tree exists to record is lost.url — the reference an image or link points at. It
is what connects a node to the response recorded in
archive/, so a reader can go from “there was a chart
here” to the bytes of that chart.
A member whose value is absent, empty or false
MUST NOT be written. Writing defaults on every
node costs more than the content it surrounds.
Nodes the browser marks as ignored MUST NOT appear
in tree. Their children MUST be kept
and attached to the nearest retained ancestor.
This is what keeps hidden text out of the record: subtrees removed by
aria-hidden or display: none are ignored nodes,
so they never reach the file. A reader can therefore treat the tree as a
description of what was visible, which is a stronger claim
than anything the WARC payload alone supports.
Nodes whose role is generic, none or
InlineTextBox MUST NOT appear in
tree either, and their children
MUST be kept in the same way. These roles carry no
meaning of their own; keeping them doubles the depth of the tree without
adding anything a reader can use.
A producer MUST NOT drop nodes for being outside the viewport. Doing so is reasonable for an agent, which can only act on what it can see, but an archive that discards the part of the page nobody scrolled to is not a record of that page.
The requirements in this section do not come from [[WACZ]]. They come from what existing readers of WACZ packages do, and a package that ignores them is unreadable in practice even though it satisfies the base specification.
profile member of datapackage.json
MUST be "data-package". Readers
switch on this value and reject values they do not know, so it is not
available to identify this profile. See
.
config and metadata in
datapackage.json MUST NOT be used to
carry values defined by this specification. Readers assign them meaning.
response record
MUST be in HTTP/1.1 form, whatever protocol
version carried the exchange.
.gz. Readers locate it by
extension and do not decompress it.
These are recorded here so a package author can satisfy them. Why each one exists is a matter of implementation history and is documented outside this specification.
tls is not proof of provenance. Certificates are public: anyone can
obtain one and present a copy. Holding these fields says nothing about who
answered a request. Two narrower things are worth the storage —
issuer changes when a connection is intercepted, and the
validity period can be checked against the time the capture claims —
and neither survives the capture unless it is written down.
completeness asserts absence, not correctness. It reports bodies this package empirically does not hold. It does not assert that the bodies it does hold are what the origin served.
A package records URLs in completeness and host names in tls. Where the captured page carried personal data in URLs, those URLs travel with the archive.
The accessibility tree carries the names the browser computed for elements,
which are drawn from the visible text of the page. That text is
already present in the WARC payload, so recording the tree
exposes nothing the package did not already hold. The pruning rules make its
exposure narrower than the payload's: ignored nodes are excluded, so text
hidden by aria-hidden or display: none does not
appear in the tree even though it appears in the response body.
storage exposes nothing the package did not already hold. It records key counts, byte counts and digests. It does not record values, and a digest is not a value — though, as noted above, a digest over low-entropy content is not a hiding place either.
A package that also contains storage/origins.jsonl
does expose more. Web storage routinely holds bearer
tokens, session identifiers and unsent drafts, none of which appear in any
WARC payload; this is the one place in this profile where a conforming
package can carry material the capture never received over the network.
Producers SHOULD leave the directory out by default. A reader can tell the
two cases apart without opening the archive body, because
valuesRecorded says which one this is.
browser names the build that did the recording, and it is required.
A producer that presents a different identity to the origin — a
substituted User-Agent, for example — does not thereby
change this member: what is sent to the server and what the package reports
are separate. Operators who treat their build version as sensitive should
know that a conforming package discloses it.