Every AI decision. Sealed. Verified. Forever.

Tamper-evident HMAC audit chain for LLM applications. Cryptographically link every span. Detect tampering. Prove provenance to regulators.

pip install bijotel
spans in demo chain
days of history
providers
models
686
unit tests passing
46
production tests · 0 fail

What makes it forensic

HMAC-SHA256 over a JCS-canonicalized body of each gen_ai span. Each entry's hmac depends on the previous. Mutating any byte of any entry breaks the chain at that exact seq.

🔒

SEALED

Every span: SHA-256(canonical body) + HMAC(prev_hash + canonical_hash, secret). RFC 8785 JCS canonicalization. WAL + BEGIN IMMEDIATE for multi-writer correctness.

VERIFIED

686 unit tests + 46 production-tests across 3 rounds. Zero failures post-v2.0.5. Validated under 16-way concurrent writers, SIGKILL recovery, and direct DB tampering.

🌍

PORTABLE

Exported archives verify bit-identically across x86_64 ↔ aarch64. HMAC + JCS are architecture-independent by construction. Live on 2 independent production systems.

Verify this demo chain yourself

200 spans across 14 days. Real HMAC chain produced by bijotel 2.0.5. Run three commands. Get cryptographic proof in <1 second.

1
Install BIJOTEL
pip install bijotel
2
Download the demo chain
curl -O https://bijotel.whiteandpoint.com/demo_chain.json
200 spans, 302KB, bijotel-chain-v1 format
3
Verify integrity
bijotel verify-export demo_chain.json \
  --secret-hex bd1ed00aded0bd1ed00aded0bd1ed00aded0bd1ed00aded0bd1ed00aded00000
Public demo secret. Used only for this demo chain.
Expected output: Export VALID: demo_chain.json

Now try a tampered version

We pre-built a variant with exactly one byte flipped in entry seq=100. The HMAC chain links are still correct — but the canonical_body integrity check (added in v2.0.3) catches it.

curl -O https://bijotel.whiteandpoint.com/demo_chain_tampered.json
bijotel verify-export demo_chain_tampered.json \
  --secret-hex bd1ed00aded0bd1ed00aded0bd1ed00aded0bd1ed00aded0bd1ed00aded00000
Expected output: Export INVALID: canonical_body tampered at seq=100: body hashes to c1711163… but canonical_hash claims 273564cf…

Browse the chain

Same data your bijotel verify-export call processes. Filter, paginate, click any row for full canonical body.

seqTime (UTC)ProviderModelTokens (in/out)Prompt excerpt

How BIJOTEL fits your stack

Already using Langfuse / LangSmith / Helicone? Keep them. BIJOTEL adds what observability tools don't try to do.

Your observability stack

  • Developer experience
  • Trace UI, debugging
  • Prompt evaluation
  • Cost dashboards
  • A/B testing

BIJOTEL

  • Cryptographic proof of integrity
  • Regulator-ready audit trail
  • Tampering detection
  • JCS-canonical span body
  • HMAC-chained record-keeping

Wire BIJOTEL alongside in 3 lines. Same OTel spans — observed by both, sealed only by BIJOTEL.

Honest scope

What BIJOTEL is — and what it is not.

BIJOTEL is NOT

  • A prompt evaluator (use Langfuse Eval, Braintrust)
  • A complete observability solution (use it alongside Langfuse, LangSmith, Helicone)
  • An error tracker (use Sentry)
  • Certified by any compliance body — yet
  • A replacement for legal review of your AI governance
It IS: a cryptographic forensic layer for audit traceability and tampering detection. Designed to support EU AI Act Article 12 record-keeping requirements.

Quickstart

# Install
pip install "bijotel[anthropic]"

# 3 lines in your code (lifespan / startup)
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
from bijotel.processors import HmacChainSpanProcessor

provider = TracerProvider()
provider.add_span_processor(HmacChainSpanProcessor(
    db_path="chain.db",
    secret_key=bytes.fromhex(os.environ["BIJOTEL_HMAC_SECRET"]),
))
trace.set_tracer_provider(provider)
AnthropicInstrumentor().instrument()

# Every messages.create() is now sealed in chain.db
# Verify any time:
bijotel verify --db chain.db