Skip to main content

Forensic-grade tamper-evident audit chain for LLM applications. HMAC-SHA256 chain, content-addressable storage, pre-call policy gate, regression detection.

Project description

BIJOTEL

PyPI Python License: MIT Tests Coverage

Forensic-grade tamper-evident audit chain for LLM applications.

BIJOTEL turns the spans your OpenTelemetry GenAI instrumentation already emits into a HMAC-sealed chain on disk, content-addressable storage with semantic dedup, and a pre-call policy gate that audits before it blocks. It's a plug-in to whatever tracer you have (OpenLLMetry, AnthropicInstrumentor, custom wrappers) — it does not replace your tracer; it extends it.

Status: v1.1.0 on PyPI. Production-validated through 13 consecutive days on the GENA agent ecosystem: 4,952 chain entries, 8 wheel upgrades, 0 chain breaks. API surface frozen for the v1.x line.

Install

pip install bijotel

Optional extras:

pip install "bijotel[anthropic]"     # Anthropic SDK + instrumentation
pip install "bijotel[openai]"        # OpenAI SDK
pip install "bijotel[api]"           # FastAPI + uvicorn → `bijotel serve`
pip install "bijotel[fingerprint]"   # sentence-transformers (semantic dedup)
pip install "bijotel[ast]"           # tree-sitter (Bash AST code safety)
pip install "bijotel[all]"           # everything above

Quickstart

import os
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor

from bijotel.processors import HmacChainSpanProcessor, CasSpanProcessor

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

# Now every anthropic.chat call is sealed in the chain with full canonical
# body, prev_hash linkage, HMAC, and CAS-deduped body storage.

Generate a fresh secret:

export BIJOTEL_HMAC_SECRET=$(python -c "import secrets; print(secrets.token_hex(32))")

Verify integrity later:

bijotel verify --db chain.db

CLI

After install, the bijotel command exposes 8 subcommands:

bijotel verify --db chain.db                          # full HMAC re-verification
bijotel inspect --db chain.db 4952                     # one entry's canonical body
bijotel stats --db chain.db                           # chain + CAS + policy stats
bijotel list --db chain.db --since 2026-05-20         # filterable browsing
bijotel export --db chain.db --output out.json        # signed portable JSON
bijotel verify-export out.json                         # auditor-side verification
bijotel regression --db chain.db --window 100         # z-score + IQR drift
bijotel serve --port 8080 --db chain.db               # REST API only (Swagger at /docs)
bijotel serve --port 8080 --db chain.db --dashboard   # API at /api/* + React dashboard at /

--since uses UTC calendar dates (YYYY-MM-DD, lower bound inclusive at 00:00:00Z), consistent across all subcommands.

REST API (bijotel serve)

bijotel serve exposes 18 endpoints. Full OpenAPI 3.1 spec at /openapi.json, interactive Swagger UI at /docs.

Method Path Description
GET /health Liveness + version + db_exists
GET /version Package version metadata
GET /docs Swagger UI
GET /redoc ReDoc UI
GET /openapi.json OpenAPI 3.1 spec
GET /chain Paginated chain rows, since/until filters
GET /chain/stats Aggregate counters (entries/CAS/dedup/age)
GET /chain/{seq} One entry with full canonical body
POST /chain/verify Smoke (default) or full=true canonical
GET /policy/rules Active rules + closure introspection
POST /policy/evaluate Dry-run a request through the engine
GET /layers 14-layer bijuterii manifest
GET /regression/latest Most recent persisted run
GET /regression/history Paginated timeline
POST /regression/run Execute fresh run (optionally persist)
POST /export Download a signed JSON snapshot
POST /export/verify Upload + return validity + reason

Optional Bearer auth

Set BIJOTEL_API_KEY on the serve process and all endpoints (except /health, /version, /docs, /redoc, /openapi.json) require Authorization: Bearer <key>. Constant-time comparison (hmac.compare_digest). Empty / unset env = no auth (dev mode).

Dashboard

A React/Vite dashboard ships in src/bijotel/dashboard/:

Page URL What it does
Chain Explorer /chain 4 stats cards + paginated table + click-row → side panel with full canonical body; Verify + Export buttons
Policy Decisions /policy Active-rules grid + live Evaluate form (dry-run a prompt) + layers grid
Regression Monitor /regression Status cards + recharts timeline (24h/7d/30d/all) + per-dimension breakdown + Run-Now panel
System Status /system Full 14-layer manifest table

Bundle stays under 100 KB gzip on initial load thanks to per-route code splitting; the heavy recharts chunk lazy-loads only when /regression is visited.

Dev:

cd src/bijotel/dashboard
npm install
npm run dev   # http://localhost:5173 with /api proxied to :8080

Production build → dashboard_dist/ at project root. Day 12 polish wires bijotel serve --dashboard to mount it as static.

14 AI safety bijuterii covered

Each layer maps to a catalog pattern. status reflects the live GET /layers response on a healthy production install (chain populated, policy engine configured). active = runtime evidence present (e.g. chain rows > 0); available = code ships, host can opt in; planned = v1.5+.

On a fresh pip install against an empty chain.db, only otel_genai and provider_protocol start as active — the rest report available until they have data to point at. This is intentional (M2 — reality > docs).

# Bijuterie Layer Status
11 Forensic-First HMAC-SHA256 chain (HmacChainSpanProcessor) active
2 Content-Addressable Storage CAS unique-body table active
2 Merkle DAG dag_nodes + dag_refs reference graph available
10 Compliance-as-Code PolicyEngine + 8 rule factories active
16 Regression Detection z-score + IQR over input_tokens/output_tokens/cost active
19 OTel GenAI Semconv Compatible with OpenLLMetry, Anthropic/OpenAI instrumentors active
7 Provider Protocol AnthropicAdapter, OpenAIAdapter active
7 Deterministic + Semantic Fingerprinting SHA-256 + sentence-transformers available
5 AST-First Code Safety tree-sitter bash + stdlib Python ast available
15 Inference Routing Pareto cost/quality/latency + per-agent budget available
18 Misalignment Probes 29 probes across 8 attack categories available
D Containment (Combo D) Permitted + Safe + Sealed orchestrator available
3 Energy Accounting per-call kWh + carbon estimate planned
9 Consensus Voting Multi-model agreement planned

What makes BIJOTEL different

  • HMAC-SHA256 tamper-evident chain. Each span carries prev_hash || canonical_hash re-hashed with a server secret. Any mutation — even reordering — breaks verification. The bijotel-chain-v1 export schema lets external auditors verify with the secret alone, no SQLite access.
  • Content-addressable storage with semantic dedup. Identical request bodies share storage; the dedup factor surfaces as a metric (/chain/stats field). The Merkle DAG layer (#2) enables reference-graph queries.
  • Pre-call policy gate with audit trail. Eight rule factories (prompt_pattern_deny, pii_detection, output_length_limit, model_allowlist, model_version_pin, cost_per_call_max, daily_token_budget, rate_limit_calls_per_minute) compose into a PolicyEngine. Decisions: allow / warn / deny. Warnings attach to the span via bijotel.policy.warning. Denies emit a synthetic chain entry with bijotel.blocked=true.
  • Statistical regression detection on the chain itself. No separate metrics pipeline. RegressionDetector reads from chain.db, computes baseline + flags drift on input_tokens / output_tokens / cost using z-score AND IQR (default BOTH mode minimizes false positives).
  • Composable with upstream OTel instrumentors. BIJOTEL adds SpanProcessors on top of your existing opentelemetry-instrumentation-anthropic / opentelemetry-instrumentation-openai chain. It never wraps the SDK call itself, so there's no provider-specific glue to maintain.

Production validated

The v1.1.0 release passed a Day-10 integration test against GENA's production agent ecosystem (Aisophical):

  • 13 days continuous operation, 4 wheel upgrades (v0.5.0 → v0.6.0 → v0.6.1 → v1.1.0).
  • 4,952 chain entries, POST /chain/verify with full=true returns valid:true — cross-version HMAC continuity.
  • 0 chain breaks across the upgrade window; the chain processor's BEGIN IMMEDIATE critical section + WAL mode survived all concurrent-writer scenarios that came up.
  • First production regression baseline persisted: cost $0.0033 ± $0.0008 per call (24% relative stdev); ~396 spans/day.
  • 18/18 endpoints responded correctly against the live chain.
  • POST /export produced a 48.1 MB signed snapshot; POST /export/verify confirmed validity.

Full report in INTEGRATION_TEST_20260523.md.

Known issues

  • Dashboard bijotel serve --dashboard not wired yet (planned v1.3+). Today's pattern: run bijotel serve on the backend, run Vite dev server (or any static host) on the dashboard side, point the proxy at :8080.
  • Vite dev server binds IPv6-only on some Windows installs. curl 127.0.0.1:5173 returns nothing; use curl localhost:5173 (DNS resolves to ::1) or curl '[::1]:5173'. Browsers are fine.
  • GENA-style deploys that install the wheel without extras must add python-multipart to their requirements.txt if they want POST /export/verify to register (FastAPI's UploadFile requires it). The [api] extra carries it transitively.
  • GitHub source repository remains private during the v1.x.x development window — the URLs in PyPI metadata (Documentation, Issues, Changelog, Source) currently 404 for external visitors. This is intentional and will flip when the repo goes public.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bijotel-1.5.0.tar.gz (422.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bijotel-1.5.0-py3-none-any.whl (356.1 kB view details)

Uploaded Python 3

File details

Details for the file bijotel-1.5.0.tar.gz.

File metadata

  • Download URL: bijotel-1.5.0.tar.gz
  • Upload date:
  • Size: 422.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for bijotel-1.5.0.tar.gz
Algorithm Hash digest
SHA256 15c963f542a44f70bc5bfc3b6bf069affd66a6b16729740829f17c0f26d7f8e2
MD5 527ccaca71c7754f8abbb674c6ed3a5f
BLAKE2b-256 5e52b45fa311fc1750915e2b05cc51fbee2f5320b68c0cc2dac696499e3012b2

See more details on using hashes here.

File details

Details for the file bijotel-1.5.0-py3-none-any.whl.

File metadata

  • Download URL: bijotel-1.5.0-py3-none-any.whl
  • Upload date:
  • Size: 356.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for bijotel-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 91098384f9942f826224663074f04c911cf0f003c3f81a6f224630a5ea83d7d3
MD5 1b346c99fbbc4e6f27ce0187aeb784ca
BLAKE2b-256 347107e7baa0b0e6efd24a4c4e745176ee87bb0f317612d2417546e382e99ca1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page