Reference
Security & signing
clockworktz can download timezone data at runtime. Bad timezone data can silently shift every time an app shows, so updates are signed, hashed and version-checked before they're used. This page describes exactly what's verified.
Reporting a vulnerability
Please email hello@adamrichardturner.dev rather than opening a public issue. You'll get a reply within a few days.
What gets published
Each release of the @adamrichardturner/clockworktz-data package contains:
tzdb.json: the compiled datamanifest.json:{ version, releaseDate, dataUrl, sha256, signature, keyId }
{
"version": "2026f",
"releaseDate": "2026-11-01T08:15:18.006Z",
"dataUrl": "https://cdn.jsdelivr.net/npm/@adamrichardturner/clockworktz-data@1.2026.6/tzdb.json",
"sha256": "4ce4303530d44f85a22fc801f9289d91a358005523e678cf3bc6e7ffa5e7c29c",
"signature": "Txd4kbmGHdec9E/cCAIADYc7j48YYmO7X1VpVZk+VePCWfPCdvyIIyXzV/tjQ9K/I2oEohfIstN+8Uwk3fJ9Dg==",
"keyId": "63afe89a3ee676d5"
}
Package versions are 1.<year>.<release>, so tzdb 2026f is 1.2026.6. The library fetches https://cdn.jsdelivr.net/npm/@adamrichardturner/clockworktz-data@1/manifest.json (exported as DEFAULT_MANIFEST_URL).
What's signed
signature is an Ed25519 signature over the UTF-8 bytes of:
${version}\n${sha256}
where sha256 is the lowercase hex SHA-256 of tzdb.json. The library verifies it with WebCrypto against DEFAULT_PUBLIC_KEY (a raw key, base64):
DLvL/miUAfXKI8Sae27IuV/o5l3vbg5OIp5QMkeXLkU=
When data is rejected
Data is rejected, and the current data kept, when:
- the signature doesn't verify, or WebCrypto or Ed25519 is unavailable
- the downloaded bytes don't match
sha256 - the data's own
versiondoesn't match the manifest - the version isn't newer than the loaded data (no downgrades)
Version order follows tzdb naming (2026a < 2026f < 2027a) and is exposed as compareTzdbVersions(a, b).
Where the data comes from
The data package is generated in the clockworktz repository by a scheduled workflow:
- It downloads the IANA release and verifies IANA's own signatures on the downloads.
- It builds
zicand compiles the data. - CI checks that the committed data rebuilds byte for byte.
- It signs the manifest with the project key and opens a pull request.
Key handling
- The private key is stored only as the
TZDB_SIGNING_KEYGitHub Actions secret and is used by the TZDB Update workflow. - To rotate it: generate a new key, update
DEFAULT_PUBLIC_KEYin the library, release the library, then replace the secret. Clients on older versions keep verifying against the old key until they upgrade.
Self-hosting
To serve data from your own infrastructure, publish your own manifest.json and tzdb.json, sign the manifest with your own Ed25519 key in the format above, and pass the public key:
await initClockworkTZ({
engine: 'embedded-tzdb',
data: tzdb,
manifestURL: 'https://cdn.example.com/tzdb/manifest.json',
publicKey: 'BASE64_RAW_ED25519_PUBLIC_KEY',
})
dataUrl may be absolute or relative to the manifest URL. Caches are keyed by manifest URL and public key, so data verified with one key is never served for another.
Verifying a manifest yourself
verifyManifestSignature(manifest, publicKey?) checks a manifest's signature and resolves true or false. ensureFreshTzdb(manifestURL?, options?) runs the whole fetch-verify-cache flow and resolves { version, data }, or null, without changing the active engine. See the API reference.
Without the network
If you never pass manifestURL, clockworktz makes no network requests at all. The system-intl engine never does.