Skip to content
Free tool

Verify an action log without trusting the people who wrote it

An audit log is only evidence if somebody other than its author can check it. Paste an export here and your browser recomputes every hash, follows the chain and checks the seal's signature with WebCrypto. The code is the same Apache-2.0 verifier the product ships.

Paste the JSON from an audit export, or drop the file. The check runs locally; the network panel of your browser will show no request.

This tool runs in your browser and needs JavaScript.

Observations, not verdicts. What you enter is processed for this result and written to a usage journal described in the privacy policy; nothing else is stored.

Verifying an export

  1. Fetch the export

    GET /v1/audit/export with your API key, or bb.audit.export() in the SDK. The bundle holds the entries, the seals that cover them and a howToVerify section naming the algorithms.

  2. Paste it here

    The verifier parses the bundle, re-encodes every entry canonically and recomputes its SHA-256. A hash that does not match the stored one, or a prev that does not point at the entry before, is a chain problem with the index and the reason.

  3. Read the seal verdict

    For each seal, the range it covers, its algorithm, and whether the signature over the head hash verifies against the public key in the export. An unsealed tail is reported as unsealed, not as broken.

  4. Pin the key

    A signature that verifies against a key contained in the same file proves the file is self-consistent. Compare the publicKey with the one you fetched from /v1/audit/seals earlier to prove it is the key you expect.

Note · Two checks, because each is blind to something

The chain catches an altered or removed entry but not a truncated tail: cut the log after entry 400 and the chain up to 400 is still perfect. The seal catches a truncated tail but not an entry edited and rehashed. verifyLog runs both; the exported verifyChain and verifySeal are for tooling and neither is a check on its own.

What is verified, byte for byte

Every entry in a Browserberg action log carries the hash of the entry before it and its own hash, SHA-256 over a canonical encoding of the entry's fields. The canonical encoding is hand-written and length-prefixed: each field is written as its byte length followed by its bytes, in a fixed order. That sounds fussy until you consider the alternative. JSON.stringify orders keys by insertion and escapes characters differently between languages, so a verifier written in Python or Go would disagree with ours about the bytes and report every entry as tampered. A length-prefixed encoding can be reimplemented from a one-page description and produce the same bytes everywhere.

The seal

A seal covers a range of entries, from to to, and signs the head hash of that range together with the hash the range started from. Two algorithms exist. ed25519 is a local key, the opt-in for a customer self-hosting the whole stack, who is attesting to themselves. ecdsa-p256-sha256 is the hosted service's algorithm: the private key lives in OVHcloud KMS and never touches the host that writes the log, and the signature is stored as the raw r||s pair, which happens to be the form WebCrypto verifies natively, so no DER conversion is needed. Every seal names its algorithm and the verifier dispatches on it; an algorithm it does not know is refused, not skipped.

Why it runs in the browser

A verifier hosted by the party that wrote the log is worth little. This one is the audit package's own code, Apache-2.0, bundled for the browser and using WebCrypto for SHA-256 and the signature check. You can read it, run it offline, or port it. The API also offers POST /v1/audit/verify as a support convenience, and the export's howToVerify says plainly that a check you ran yourself is the one that counts.

What verification does not tell you

A log that verifies is a log that has not changed since it was sealed. It is not a log that is true. The entries record what the platform did on a customer's behalf: the observe, the click, the extraction, with their hashes of inputs and outputs. Whether the page the agent saw was the page the customer thinks it saw is a question about the run, not about the log. The verifier also cannot tell you the key is ours: a forger with a fresh key pair can produce a self-consistent bundle. Key pinning against a seal you fetched at a different time, through a different channel, closes that gap, and the tool says so instead of showing a green tick that implies more.

Entries after the last seal are chain-checked only. That is normal for a live log, and the verdict says unsealed rather than invalid. If you need a range closed, POST /v1/audit/seal seals it now.

Questions about audit log integrity

What is a hash-chained audit log?

A log where each entry contains the hash of the previous one, so changing or removing an entry breaks every hash after it. It is the structure behind tamper-evident logs and, in a different setting, blockchains.

Why is a hash chain not enough on its own?

Because the chain has no notion of where the log ends. Delete the last hundred entries and what remains is a perfectly valid chain. A signed seal over a range fixes the end, which is why both are checked.

Does this tool send my log anywhere?

No. Parsing, hashing and signature verification run in your browser with WebCrypto. You can confirm it in the network panel or by loading the page and disconnecting.

What does a failed chain check look like?

A list of problems, each with the index of the entry and a reason: hash mismatch, prev does not match, or a malformed entry. The first problem is usually the one that matters; everything after it fails as a consequence.

Can I verify the log in my own code?

Yes. The verifier is Apache-2.0 and depends on nothing else in the product, and the canonical encoding is documented so it can be reimplemented in another language and produce the same bytes.

Produce a log worth verifying

Five browser hours, no card. Run a task, export the log, paste it here.