Releases: octavuntila-prog/BIJOTEL
Release list
v2.16.0 — append_event: seal non-span events into the chain
Adds bijotel.append_event(db_path, secret_key, event, ...) — a public primitive that seals an arbitrary JSON-canonicalizable dict into the same tamper-evident HMAC chain HmacChainSpanProcessor writes, without an OpenTelemetry span. Non-span consumers (e.g. an external audit framework with its own event model) can now back their chain with bijotel and inherit verify_chain, signed Ed25519 export, Rekor anchoring, and federation for free.
Parity is structural, not just tested. The row-sealing critical section and schema init are now single shared module functions (_seal_canonical, _init_chain_db) called by BOTH on_end (span path) and append_event (event path) — one write path, so a span row and an event row are byte-identical chain links. Proven by the mixed-chain test: interleaved span + event rows -> verify_chain VALID + chain-linked across the source boundary (runs on all platforms).
994 existing tests unchanged (the span path is behaviorally identical) + 6 new. Released end-to-end by CI (test -> PyPI -> ghcr multi-tag), no manual steps.
v2.15.1 — Release pipeline proven end-to-end (no runtime changes)
Test/CI-only patch — no library or runtime changes; production on 2.15.0 needs no redeploy.
Fixed
- Stale test contract that blocked the v2.15.0 release run:
test_existing_chain_db_perms_preservedstill asserted the pre-2.15.0 contract (existing chain.db perms preserved), while ISSUE-15 deliberately changed it to idempotentchmod 0o600on every open. The test now asserts the new contract explicitly (644 → open → 600), renamedtest_existing_chain_db_perms_tightened_to_0600.
CI
- GitHub Actions bumped ahead of the Node 24 forcing (2026-06-16):
actions/checkoutv4→v5,actions/setup-pythonv5→v6. - This release is cut end-to-end by the tag-triggered workflow (test gate → PyPI), no manual upload — closing the process gap behind audit M1/M2.
v2.15.0 — Security hardening (audit remediation)
Remediation of the 2026-06-08 technical audit (shipped-library P0/P1 findings). Every fix carries a regression test that failed before the change. 994 tests pass; ruff clean. HMAC chain core, JCS canonicalization, Ed25519/ECDSA crypto and multi-writer concurrency were audited and found sound.
- ISSUE-3: cross-anchor receipt verify no longer fail-open — requires an out-of-band federation key; CLI
federation verify --federation-keynow required. - ISSUE-5: AST python checker fails closed on SyntaxError (emits unparseable_python warning).
- ISSUE-4: ReDoS removed in override/reverse jailbreak regexes + 16KB scan cap.
- ISSUE-8: library verify_export requires a trust anchor (HMAC secret or Ed25519 pubkey).
- SP-1: new IsolatingSpanProcessor — a third-party SpanProcessor can no longer abort the dispatch and skip the HMAC seal.
- ISSUE-15: idempotent 0600 chmod-on-open for the chain DB.
- ISSUE-13/14: python-multipart>=0.0.18 (CVE-2024-24762 / CVE-2024-53981); lock header.
PyPI: https://pypi.org/project/bijotel/2.15.0/
Full notes: CHANGELOG.md
v2.14.1 — fix: EnergySpanProcessor broke chain sealing on a live TracerProvider
Fixed: EnergySpanProcessor now subclasses opentelemetry.sdk.trace.SpanProcessor. It was duck-typed and lacked _on_ending (called by OTel SDK ≥1.42 on span end) — registering it on a live TracerProvider raised AttributeError and broke chain sealing (2026-06-05 incident). Subclassing inherits the full protocol incl. future hooks.
New tests/test_span_processor_protocol.py: real TracerProvider start→end over ALL processors (red-first validated). Suite 983 passed.
v2.14.0 — cross-view REST + idempotent Rekor 409 + energy live-cutover guard
Added: POST /cross-view (bijotel cross-view CLI over serve; per-ecosystem stats + structural integrity; not federation). bijotel energy mark-live + DB-stored live_cutover_seq so backfill never double-counts live rows.
Fixed: Rekor HTTP 409 (entry already exists) is now an idempotent success in anchor publish (typed RekorEntryExistsError; non-409 still fails hard).
Suite 979 passed, ruff clean. Full notes: CHANGELOG.md.
v2.13.3 — MCP invocations sealed by the default processor
MCP invocations now sealed by the default processor
The default HmacChainSpanProcessor filter kept only gen_ai.* spans, so MCP
tool invocations (bijotel.mcp.* attributes emitted by MCPInstrumentor) were
silently dropped — the v2.12 "sealed by the existing processor" claim only
held if the host passed a custom filter_fn. The 18 mocked MCP unit tests never
verified a real sealed call, so this never surfaced (same class of gap as the
2.13.2 Rekor fix).
Fixed
_default_filternow also keepsbijotel.mcp.*spans → MCP invocations seal out-of-the-box.
Added
- Real (non-mocked) e2e test: in-memory MCP server + client, real
call_tool, sealed by the default processor. _default_filterregression-guard unit test.
No impact on existing GENA/ARA deployments (neither instruments an MCP client).
v2.13.2 — Rekor anchoring live-interop fix (ECDSA P-256)
Fixes Rekor anchoring against the live Sigstore Rekor. Anchors were signed with pure Ed25519, but Rekor verifies Ed25519 hashedrekord entries via Ed25519ph (unsupported by Python's cryptography) — so every live submission failed with 'ed25519: invalid signature' (the 15 unit tests mocked Rekor and missed it). Anchoring now signs with ECDSA P-256 over a SHA-256 digest (Rekor's canonical hashedrekord path). Adds bijotel.crypto.ecdsa_p256, 'bijotel keygen --type ecdsa', and a non-mocked live Rekor test (BIJOTEL_REKOR_LIVE=1). Verified end-to-end against rekor.sigstore.dev. Ed25519 signed-exports are unchanged.
v2.13.1 — verify_chain reports last_seq
[2.13.1] — 2026-06-02 — verify_chain reports last_seq
Fixed
verify_chain(...)now reports the last verified seq in its
success tuple(True, last_seq, None). Previously the success path
always returned(True, None, None)regardless of how many rows were
verified — confusing when callers wanted to know how far the verify
reached. Thevalidflag was always authoritative and is unchanged;
this only populates the previously-Nonemiddle element.- Full-chain verify:
last_seq= seq of the last row in the chain. - Range /
last_nverify:last_seq= last seq in the window. - Empty chain:
last_seqstaysNone(nothing verified) — documented. - Surfaced during the stabilization freeze (ARA Day-4 check showed
(valid=True, last_seq=None)on a full-chain verify).
- Full-chain verify:
Notes
- Behavior change is limited to the middle element of the return tuple
on the success path. Failure path(False, seq, reason)unchanged.
Tests updated to assert the real last seq; 1 new empty-chain test.
v2.13.0 — Cross-ecosystem view
[2.13.0] — 2026-05-27 — Cross-ecosystem view
For operators with more than one BIJOTEL-instrumented ecosystem
(GENA + ARA + Gen4, or production + staging), the new
CrossEcosystemView aggregates totals, provider union, and
per-chain integrity in one read-only view. No chain merging —
each chain keeps its own HMAC secret, signing key, and Rekor
anchor.
Added
bijotel.cross_viewmodule:CrossEcosystemViewclass — add N chains by local DB path or
pre-exported JSON, get unifiedsummary()dict and
integrity_report().ChainStatsdataclass — per-chain snapshot (entries, providers,
models, first/last timestamps).load_chain_stats_from_db(name, db_path)— read SQLite chain.db.load_chain_stats_from_export(name, export_path)— read v2 export
JSON.
bijotel cross-viewCLI subcommand:--chain NAME=PATH
(repeatable, mix DB and export files),--json,--integrity.- 20 new tests in
tests/test_cross_view.pycovering loaders,
summary, integrity report, CLI human + JSON paths, edge cases
(empty, single chain, duplicate names, mixed sources). - Public API:
CrossEcosystemView,ChainStatspromoted to
top-levelbijotel.*namespace. - Docs:
docs/guides/cross-ecosystem.md(usage + CLI + what it
proves / does not prove).
Honest scope
- The view is read-only and observational. It does not validate
cross-chain causality, does not merge chains, does not propose
trust beyond what each chain establishes individually. - The structural integrity check (when no HMAC secret is provided)
is weaker than a fullbijotel verify— it only confirms the
chain is non-empty and parseable. For real HMAC integrity, pass
the secret viaintegrity_report({"name": secret_bytes}). - Mixed DB + export sources work, but exports lose the HMAC chain
(the export format strips rawprev_hashlinkage by design);
integrity for export-sourced chains is structural only.
v2.12.0 — MCP invocation sealing
[2.12.0] — 2026-05-27 — MCP invocation sealing
First non-LLM seal target: Model Context Protocol (MCP) tool invocations
now land in the same HMAC chain alongside LLM calls. Motivated by 40+
CVEs filed against MCP servers in Jan-Apr 2026 and NSA CSI (May 2026)
recommending audit logging for MCP — bijotel-mcp adds the cryptographic
integrity layer NSA stops short of specifying.
Added
bijotel.mcpmodule:MCPInstrumentor— monkey-patchesmcp.ClientSession.call_toolso
every async tool invocation emits an OTel span carrying
bijotel.mcp.*attributes. Idempotent; raises a clean
ImportErrorif the MCP SDK isn't installed.mcp_invocation_context(...)— public helper that builds the
attribute dict for callers who emit MCP spans manually (e.g. from
a non-stdlib MCP client).MCP_ATTRS— authoritative attribute vocabulary (10 keys covering
server identity, tool, input/output hashes, status, transport,
timing).
- MCP attribute vocabulary under the
bijotel.mcp.*namespace:
server_name, server_version, tool_name, tool_input_hash (SHA-256 hex),
tool_output_hash, caller, duration_ms, status, error_type, transport. - Hash-only content capture: tool inputs/outputs are SHA-256 hashed
before sealing, never stored raw. Preserves forensic value (verify
against a known-good blob later) without exposing potentially
sensitive content (file paths, credentials, prompts) in the chain. - Optional dependency:
pip install bijotel[mcp]pulls in the MCP
SDK. The base install does NOT require MCP —bijotel.mcpmodule
imports cleanly, only.instrument()needs the SDK. - Design doc:
docs/design/bijotel-mcp.mdcovers attribute
vocabulary, 3 integration patterns (in-process, proxy, Go collector),
threat model, and explicit out-of-scope items. - 18 new tests in
tests/test_mcp_invocation.py: hash determinism,
attribute completeness, instrumentor idempotency, success/error path
span emission. Uses an in-treemcpmodule stub so tests are hermetic.
Honest scope
- v2.12.0 covers integration pattern A (in-process Python
instrumentor) only. Pattern B (bijotel-mcp-proxyfor non-Python
servers) and pattern C (bijotel-collectorextension) are documented
but not implemented. - bijotel-mcp seals invocations; it does not gate them. Policy
enforcement on MCP tool calls (e.g. "denywrite_filefor user X")
is separate PolicyEngine work. - No production MCP traffic was sealed at release time — pattern A is
built and tested against a stub, real deployment validation
follows once an MCP-consuming agent is running on GENA or ARA.
Trust hierarchy after v2.12.0
| Layer | Mechanism | Scope |
|---|---|---|
| L1 | HMAC chain | LLM calls (existing) |
| L2 | + MCP attrs | LLM + MCP tool invocations (new) |
| L3 | Ed25519 export | external auditors |
| L4 | Rekor anchor | public transparency log |
| L5 | TEE attestation | producing software |
| L6 | Federation | cross-org co-signing |