Audit log

Give your security or procurement reviewer evidence of recorded activity. Linked records help detect changes; signed timestamp receipts can be checked offline. Review receipt availability and retention for your plan below.

What it is

Supported operations are recorded for your tenant, a separate customer workspace. A hash chain links each record to the previous record’s fingerprint. Each entry commits to the previous entry's hash, so any tampering breaks the chain at the mutated row and every subsequent row.

A background service requests signed timestamps for recorded entries using RFC 3161, a standard for timestamp receipts. A Trusted Timestamp Authority (TSA) is the external service that signs them. Check that a receipt contains a granted token before relying on that timestamp. The TSA signs a token over the entry's hash plus the TSA's clock time. We persist that token. You can extract it later as a signed receipt to prove that the entry existed at or before the timestamped moment, without needing to trust deny.sh as the witness.

The construction

For each operation we record:

entry_hash = sha256(
  prev_hash       ||  // chain link
  tenant_id       ||  // your api-key hash, not the raw key
  op_type         ||  // 'vault.store', 'encrypt', etc.
  canonical(payload) ||  // sorted-keys JSON
  created_at         // unix epoch seconds
)
prev_hash for entry 1 = sha256(tenant_id || "GENESIS")

Then a background worker sweeps unsigned entries every 60 seconds and asks one or more TSAs to sign the entry_hash. The signed token is persisted alongside the entry. Multiple TSAs are supported for redundancy (FreeTSA primary, DigiCert paid backup) so the chain remains verifiable even if one TSA later goes offline.

The receipt artefact

A receipt is the shareable record for a single entry. Fetch one over the API:

GET /v1/audit/receipt/<entry_id>        # JSON
GET /v1/audit/receipt/<entry_id>.pdf    # branded PDF
Authorization: Bearer <your api key>

The receipt is the entry's metadata plus all its TSA tokens. It deliberately does not include the operation's payload, only sha256(canonical(payload)), so you can hand a receipt to a regulator or auditor without sharing the secret content. Operation type, time and other metadata remain visible.

Tenant id is one-way redacted in the receipt: tenant_id_hash_redacted = sha256(tenant_id || "receipt-v1"). Stable per tenant, opaque to readers.

Verifying a receipt offline

The deny-sh CLI ships a verifier. It walks the embedded RFC 3161 token, recomputes the TSA's declared digest of TSTInfo (sha256, sha384, or sha512 per the SignerInfo digest algorithm), checks it matches the signed messageDigest attribute, and verifies the SignerInfo signature against the TSA signing certificate embedded in the token. No network required.

$ deny-sh verify-receipt receipt.json

  entry id           1042
  op type            vault.store
  entry hash         8e34b91d…
  created at         2026-05-18T17:33:07.000Z
  chain length       1
  TSA tokens         1

  freetsa  (granted)
    ✓ messageImprint matches entry_hash
    ✓ signedAttrs.messageDigest matches sha256(TSTInfo)
    ✓ SignerInfo signature verifies
    ✓ genTime in plausible range

  OK :  receipt verifies against at least one granted TSA token.

Exit code 0 on OK, 1 on TAMPERED, 2 on bad input. The --json flag emits a machine-readable result for piping into compliance dashboards.

What this proves, and what it does not

The chain proves whether records match their linked hashes and available signed timestamps, and the TSA timestamps prove an external party (the TSA, not deny.sh) saw each entry's hash at or before the recorded time. A receipt with a granted token from a public TSA like FreeTSA or DigiCert is third-party verifiable: the regulator does not need to trust us as the witness, they trust the TSA's published cert and signature.

What it does not prove: that the operation's payload was what you say it was. The chain commits to sha256(canonical(payload)), not the payload itself; hosted encryption does receive plaintext during processing, whereas local encryption does not. The audit record should not contain that secret content. If you need stronger evidence of the operation's contents, the customer-side application should record the canonical payload locally and prove it matches the receipt's op_payload_hash field.

Verify against trusted certificates

The CLI's bundled verifier is the convenience path. For verification against an externally trusted public key infrastructure (PKI), the deny-sh CLI can export the receipt as a bundle that openssl ts -verify reads natively:

deny-sh verify-receipt receipt.json --export-openssl ./bundle
bash ./bundle/verify.sh

The bundle contains the raw .tsr TimeStampResp bytes per TSA, the message-imprint hex, the FreeTSA CA chain (bundled with the CLI; pinned to a known root fingerprint), and a one-liner verify.sh that runs openssl ts -verify for every token. Exit 0 = standard RFC 3161 PKI validation succeeded against the bundled root. The regulator can substitute their own trusted CA bundle by setting the relevant *_CAFILE env var or replacing freetsa-cacert.pem with their own.

Limits, retention, and trust model

Retention depends on your plan. Export the evidence you need before its retention period expires. TSA tokens carry their own genTime + signed certs and remain verifiable as long as the TSA's signing certificate chain remains accepted by your local trust store. The deny-sh CLI ships with the FreeTSA root CA bundled (pinned at install time) so the --export-openssl flow works out of the box; the convenience verifier deliberately stops at "signature valid against the cert embedded in the token" and defers full PKI-root validation to the openssl path so a single trust decision (which root CAs to honour) lives in one place: the regulator's own trust store.

Signed receipts are available on Team and Enterprise. The hash chain itself is recorded for every tenant from day one; the API surface that issues downloadable receipts is gated.

Get started

Review recorded activity in your dashboard and request receipts for supported operations. To pull a receipt:

curl -H "Authorization: Bearer $DENY_API_KEY" \
  https://deny.sh/v1/audit/receipt/<entry_id> > receipt.json

deny-sh verify-receipt receipt.json

Questions, integrations, custom retention SLAs, or auditor walkthroughs: hello@deny.sh.