docs.vcisolite.com · Methodology

Verifying vCISO Lite's audit records

Auditors, regulators and acquirers rely on records vCISO Lite keeps: who read which file, what an auditor decided, when a report was issued. This page sets out how those records are sealed, what each seal proves and what it doesn't, and how to check them yourself. The checks are built so you don't have to trust us to run them.

Written 2026-10-07. Every command on this page was run before publication; the footer says against what. Where something isn't available yet, the status section says so.

01Why it matters

What this is worth to you

Audit evidence usually travels as files and screenshots. Everyone who touches it has to take someone's word that it's the same thing that was collected, and that the trail behind each decision is the one that happened. Sealing replaces that trust with a check anyone can run.

02The idea

Four layers, each checkable by someone further away

A record on its own proves nothing; anyone can write a line in a database. Each layer below commits to the one beneath it and is checkable by a wider audience. The last layer is checkable by anyone with a network connection, and depends on a party that isn't us.

1 · RECORD fingerprint (SHA-256) + Ed25519 signature 2 · CHAIN each record names the one before it 3 · CHECKPOINT Merkle root over a batch of records 4 · PUBLIC LOG every checkpoint, append-only, published OUTSIDE TIMESTAMP · RFC 3161 · DigiCert stamps every checkpoint root and log head; verifiable against your own trusted roots
Who can check what. Layers 1 and 2: whoever holds the records (the organization, and the auditors it shares them with). Layer 3: the same, plus anyone given a checkpoint receipt. Layer 4: anyone.
LayerProvesDoesn't prove
RecordThis record's contents are the ones that were fingerprinted, and vCISO Lite's key signed that fingerprint.That what the record says is true. A record of "file read" proves the read was recorded, not that the file was the right evidence.
ChainNo record was deleted, inserted or reordered without breaking every link after it.That the whole chain wasn't rewritten from some point onward, links and all.
CheckpointA batch of records existed in this exact form by the time an outside authority stamped it.Anything about records written after the checkpoint.
Public logWe've only ever appended checkpoints, and everyone was shown the same history.What any single checkpoint contains. The log publishes commitments, never customer content.

Sealed isn't the same as true. Every check here proves a record wasn't changed after it was written. None of them prove the record was right when it was written. That's the auditor's job, and nothing on this page replaces it.

03Records

A record's fingerprint and signature

Every event vCISO Lite records is an entry on a chain. Each organization's records are kept on separate chains per product, so tampering with one can't invalidate another.

Fingerprint

Each entry's entry_hash is SHA-256 over a serialization of these fields, in this order:

previous_hash the entry_hash of the entry before it (64 zeros for the first) organization_id whose chain this is sequence_number position on the chain, contiguous from 1 event_type event_subtype (when present) actor_id actor_type resource_type resource_id action status details the event's own data, canonicalized (below) timestamp UTC, truncated to the microsecond nonce 32 random bytes, hex, so identical events still differ

Because previous_hash is inside the fingerprint, each entry commits to every entry before it.

The exact bytes

The fingerprint is SHA-256 over one JSON object, written byte for byte by these rules:

  1. Fields in the order above, as "name":value. Leave out event_subtype when it's empty. sequence_number is a bare integer; every other field except details is a string.
  2. No whitespace anywhere outside strings.
  3. Strings are raw UTF-8, with these escapes: \" \\ \b \f \n \r \t; any other character below U+0020 as \u00xx (lowercase hex); < > & as \u003c \u003e \u0026; U+2028 and U+2029 as \u2028 \u2029.
  4. details is the event's own JSON. Object keys are sorted by UTF-8 byte length, then byte by byte, at every depth; arrays keep their order. Numbers are plain decimals that keep their written scale (1.50 stays 1.50), with no exponent (1e-7 becomes 0.0000001) and no negative zero.
  5. timestamp is UTC to the microsecond, as 2026-10-07T18:04:05.12Z: trailing zeros of the fraction are dropped, and the fraction is dropped entirely when it's zero.

entry_fingerprint.py implements these rules with the Python standard library alone, and entry_vectors.json holds test vectors produced by our production hashing code: each gives a record's fields, the exact bytes and the fingerprint. The script reproduces all of them. If yours doesn't, the rules above are wrong, and we want to know.

Entries before May 24, 2026, 15:19 EDT carry no content fingerprint. An earlier serialization didn't survive database storage, so their fingerprints can't be recomputed from their contents. Their deletion, insertion or reordering is still detected through the chain links, but an edit to their contents wouldn't be. Every entry since then, including every vCISO Lite for Auditors entry, carries a full content fingerprint.

Signature

Each entry is signed with Ed25519 (RFC 8032). The signed message is exactly:

vciso-audit-entry-v1:{chain_id}:{entry_hash}

{chain_id} is the chain identifier that comes with the record. The prefix stops a signature from being replayed against any other value we sign; the chain identifier stops it being replayed against an identical entry on another chain. Each signature records the ID of the key that made it. Signing never blocks a write: if the key is unavailable the entry is stored unsigned and reported as unsigned, never as valid.

04Checkpoints

Batches, Merkle roots and an outside timestamp

Each chain's new entries are gathered into a checkpoint. A checkpoint records its sequence range, entry count and a Merkle root over those entries' fingerprints. It is chained to the checkpoint before it (previous_checkpoint_hash) and summarized as its own checkpoint_hash.

How often. Every chain with new entries gets a checkpoint at 00:00 and 12:00 UTC, and sooner if it reaches 10,000 entries. A chain with nothing new gets none. A new record is in a checkpoint, and that checkpoint in a published and timestamped log head, within 24 hours. Before October 7, 2026 a checkpoint waited for 10,000 entries, so slow-growing chains could wait much longer.

The Merkle root is then sent to an outside Time-Stamp Authority (RFC 3161; currently DigiCert, http://timestamp.digicert.com). The authority signs the root and the time it saw it. Its signature chains to a public root certificate we don't control, so neither we nor anyone else can backdate it. The timestamp's message imprint is the 32-byte root itself.

Two tree constructions

A checkpoint states which construction built its root (tree_epoch). Always use the one it names:

Any one entry can be proven to sit under a checkpoint's root with an inclusion proof: the sibling hashes along its path. You don't need the rest of the checkpoint's entries.

05The public log

One append-only log of every checkpoint

Every checkpoint's checkpoint_hash is appended as a leaf to a single public transparency log spanning every organization (RFC 6962). Each time checkpoints are added, the log publishes a new head: the tree size and root. Each head is timestamped by the same outside authority. Heads are immutable once published.

# latest head https://api.vcisolite.com/.well-known/transparency/log.json # the head at a given size, forever https://api.vcisolite.com/.well-known/transparency/{tree_size}.json # proof that a smaller head is a prefix of a larger one https://api.vcisolite.com/.well-known/transparency/consistency?first={a}&second={b}

Leaves are opaque per-checkpoint commitments and are never published. A consistency proof is served only when it reveals no individual leaf. A pair of even sizes is always served, so keep heads with even tree_size.

The log's strength comes from time and other people. A head you saved last month and a head you fetch today must be consistent; if we'd rewritten history, they couldn't be. A check run once and thrown away proves nothing tomorrow. Keep the heads you're given.

06Check it yourself

Commands you can run now

You need curl, openssl (1.1 or later) and Python 3. Nothing to install, no account.

A · Fetch the latest head

curl -s https://api.vcisolite.com/.well-known/transparency/log.json -o head.json python3 -c "import json; d=json.load(open('head.json')); print(d['tree_size'], d['root_hash'])"

B · Check the outside timestamp on that head

external_anchor is a base64 RFC 3161 time-stamp response. Decode it, then have OpenSSL check it against the head's root and your system's trusted certificates:

python3 -c "import json,base64; d=json.load(open('head.json')); open('head.tsr','wb').write(base64.b64decode(d['external_anchor']))" openssl ts -reply -in head.tsr -text # shows the time, and Message data = the root openssl ts -verify -sha256 -in head.tsr \ -digest "$(python3 -c "import json; print(json.load(open('head.json'))['root_hash'])")" \ -CAfile /path/to/your/ca-bundle.pem # macOS (Homebrew OpenSSL): "$(openssl version -d | cut -d'"' -f2)/cert.pem" # Debian / Ubuntu: /etc/ssl/certs/ca-certificates.crt

Expected: Verification: OK. Change one character of the digest and it says Verification: FAILED. This proves the root existed by the stamped time, signed by an authority that isn't us.

C · Prove the log was only appended to

Download verify_consistency.py (Python standard library only, 84 lines; read it before you run it). Give it the size of a head you kept. It fetches the proof from that head to the latest and checks it with the RFC 9162 §2.1.4.2 algorithm:

python3 verify_consistency.py 364 # a head you kept → the latest head python3 verify_consistency.py 364 366 # any two published sizes size 364 -> 366: CONSISTENT (append-only)

It also re-fetches each head on its own and confirms it matches the head the proof was served with. A rewritten history fails here; a proof that stopped short fails here; a tampered element fails here.

D · Check an issued audit report

Open the report's code at verify.vcisolite.com and drop the PDF on the page. The fingerprint is computed in your browser; the file isn't uploaded. Or compute it yourself and compare it with the issued fingerprint the page shows:

shasum -a 256 report.pdf # Linux: sha256sum report.pdf

E · Records you hold (organizations and their auditors)

An organization, and the auditors it grants access, can request a verifiable export of a slice of its records through our API. The export carries each entry, an inclusion proof for each entry already in a checkpoint, and the checkpoints with their timestamps. Offline, for each entry:

  1. Fold the inclusion proof to the checkpoint's Merkle root, using the construction its tree_epoch names.
  2. Check the checkpoint's RFC 3161 timestamp against that root, as in B.
  3. Check the checkpoint's checkpoint_hash is in the public log: a checkpoint receipt carries the RFC 6962 inclusion proof to a published head. Then compare that head with the one you fetch from the log.

Entries newer than the latest checkpoint appear in the export, marked as not yet provable.

F · Recompute a record's fingerprint from its fields

Put a record's fields in a file by name (an entry from the export in E has them), then rebuild the bytes and the fingerprint. The vectors run first, so you know the script agrees with us before you trust its answer:

python3 entry_fingerprint.py --vectors entry_vectors.json # 6 of 6 vectors match python3 entry_fingerprint.py record.json # prints the bytes, then sha256 …

The printed SHA-256 must equal the record's entry_hash.

G · Check a seal, offline, from its receipt

A seal's page offers its receipt: verify.vcisolite.com/seal/{ref}/receipt.json. verify_receipt.py (standard library only) checks the signature against the published key, the record's path to its checkpoint, the checkpoint's own hash, its place in the public log and the head it sits under. It writes each timestamp to a file and prints the openssl ts -verify command that checks the authority's signature. If you downloaded the sealed record itself, pass it too, and its fingerprint is checked:

python3 verify_receipt.py seal-182-receipt.json python3 verify_receipt.py seal-182-receipt.json sealed-record-182.json python3 verify_receipt.py seal-182-receipt.json --offline --keys keys.json # no network
07Limits

What none of this proves

08What's public

What these public endpoints reveal

Publishing verification material shouldn't publish anyone's business. Here is what each public endpoint discloses, and what it doesn't:

Report and seal lookups, portal sign-in requests and the public log are rate-limited per IP address at our network edge.

09Status

What you can check today

CheckWhoStatus
Log heads, outside timestamps, consistency proofs (B, C)AnyoneLive
Verifiable export with inclusion proofs (E)The organization and its auditors, through our APILive
A checkpoint within 24 hours of every recordEveryone who relies on a recordLive
Issued report fingerprint at verify.vcisolite.com (D)Anyone with the report and its codeLive
Seal page: every check runs in your browser, with a receipt you can re-check offline (G)Anyone with the seal linkLive
Public signing key at verify.vcisolite.com/.well-known/keysAnyoneLive
Checkpoints built with RFC 6962 (epoch 2), like the public log, from October 7, 2026Anyone checking a checkpointLive
Byte-exact recompute rules and test vectors for record fingerprints (F)Anyone with a recordLive

This page will be updated as each of these ships. If something here doesn't work as described, that's a defect: tell us at support@vcisolite.com.

10References