hdk-audit — Hedera Detection Key
A Python library, not an ML model (no weights).
Model card summary
| What this repository is | A Python library for tamper-evident audit trails of language-model calls. Not an ML model; no weights. |
| Developed by | Jakub Ćwirlej |
| Origin | Spun off from the author's AI newsroom editor |
| Requirements | Python 3.10+; no GPU; optional anchoring on Hedera Consensus Service |
| Status | Alpha. It ran for a while in a demo application, anchoring its records on Hedera; the application did not go to production, so HDK is not used in production today |
| Intended use | Keeping a verifiable record of prompts, answers and other events of an LLM-based application (e.g. for the record-keeping duties of the EU AI Act) |
| Out of scope | Commercial use (licence); it does not filter, block or change prompts or answers; it is not a security product on its own |
| Licence | CC BY-NC 4.0 from version 0.1.3 (versions 0.1.0–0.1.2 remain under Apache-2.0) |
In plain words
When an application uses a language model, its logs of what was asked and answered sit in a database the operator controls: they can be edited or deleted and nobody outside can tell. HDK records each event together with a cryptographic fingerprint that also covers the events before it, so editing, deleting or inserting an entry later shows up when the record is verified. Optionally, it publishes a fingerprint of the whole record to the public Hedera ledger, so even the operator cannot rewrite history without it being noticed. Only hashes leave your system, never the content.
Status
Alpha. HDK was used for a while in a demo application, anchoring its records on Hedera; that application did not go to production, so HDK is not used in production today. An offline reference implementation, not an audited security product.
What it does
The distribution is hdk-audit; the import name is hdk. The package called hdk on PyPI is a different, unrelated project (by Zama), so do not pip install hdk for this.
- Each event becomes a node in a hash DAG stored in SQLite:
self_hash= H(content hash ‖ parent node hashes) andnode_hash= H(canonical JSON of all stored fields).verify()recomputes both, checks that every parent exists and recomputes each anchor's Merkle root (RFC 6962: leaf = H(0x00 ‖ data), node = H(0x01 ‖ left ‖ right)). Editing any field of a node is reported. - Canary: a counter and a commitment SHA-256(hex(secret) ‖ counter) advance with every node. The secret is kept outside the database:
canary_key_file(created with mode 0600 if missing),HDK_CANARY_KEY(64 hex chars) orHDK_CANARY_KEY_FILE. Without a key the canary is disabled andverify()says so. With a key, an edited or reset counter, or a rollback below the last anchored checkpoint, is reported. - Keyed node hashes (since 0.1.2): with a canary key, every
node_hashis an HMAC-SHA256 under a key derived from it, so a node cannot be rewritten and re-hashed without the key. The canary state also records how many nodes it wove and the last generation, sealed by a MAC: deleting the newest event (before it is anchored), deleting any other event or inserting one is reported. - Hedera anchoring (optional):
await hdk.anchor_now()records an anchor — the Merkle root of the un-anchored nodes, a canary checkpoint and a link to the previous anchor — and submits it to the Hedera Consensus Service when credentials and thehederaextra are present; otherwise it stays local andanchor.statussays why. - Provider wrappers:
hdk.complete(provider="openai" | "anthropic" | "google", model=..., messages=..., api_key=...)makes the call and records prompt and response as two linked nodes. - CLI
hdk-audit:verify,anchor,stats,tree,inspect,export,certificate,version.
Requirements
Python 3.10+; httpx. Optional extras: hedera (hiero-sdk-python>=0.3,<1), openai, anthropic, google (google-generativeai). No GPU.
Install
git clone https://huggingface.co/JakCwi/hdk-audit
pip install ./hdk-audit # core: hash DAG, canary, CLI
pip install "./hdk-audit[hedera]" # + Hedera anchoring
pip install "./hdk-audit[openai]" # + a provider wrapper (also: anthropic, google)
The same package is downloadable as hdk-audit-0.1.3.zip from https://jakubcwirlej.com (unzip, then pip install ./hdk-audit-0.1.3).
Quick start
from hdk import HDK, HDKConfig
from hdk.models import EventType
hdk = HDK(HDKConfig(db_path="audit.sqlite", canary_key_file="canary.key")) # keep the key away from the DB
hdk.record_event(EventType.PASTE_EXTERNAL, "pasted text", session_id="doc-1")
hdk.record_event(EventType.PUBLISH, "final text", session_id="doc-1")
report = hdk.verify()
print(report["dag_valid"], report["canary_valid"], report["anchor_mode"])
hdk.close()
hdk-audit verify --db audit.sqlite --canary-key-file canary.key
hdk-audit anchor --now --db audit.sqlite # Hedera via HDK_HEDERA_OPERATOR_ID, _OPERATOR_KEY, _TOPIC_ID
Parameters and how to choose them
| setting | default | how to choose |
|---|---|---|
HDKConfig.db_path |
~/.hdk/dag.sqlite |
one database per application or audited workspace; ":memory:" for tests. |
HDKConfig.canary_key_file |
none (then HDK_CANARY_KEY / HDK_CANARY_KEY_FILE) |
set it: without a key, un-anchored entries are protected by anchoring only. Store it on a different machine/volume than the database; back it up — losing it means keyed nodes can no longer be checked. |
HDKConfig.canary_enabled |
True | leave on. |
HederaConfig.network |
testnet |
testnet for trials, mainnet for real anchors (HDK_HEDERA_NETWORK). |
operator_id, operator_key, topic_id |
none | all three are needed for submission (HDK_HEDERA_OPERATOR_ID, _OPERATOR_KEY, _TOPIC_ID); creating the topic is up to you. |
| how often to anchor | — | anchoring is explicit: call anchor_now() (or the CLI) on your own schedule. More often = shorter window in which un-anchored entries can be rewritten by someone holding the key; each submission costs a small HCS fee. |
Pitfalls and limitations
- Without a canary key, entries not yet anchored are protected by anchoring only: anyone who can write the database can change such an entry and recompute its hashes, and
verify()cannot tell (itsunanchored_protectionfield says so). Use a key, or anchor often. - Someone who can rewrite the whole database and holds the canary key can rebuild a consistent history; so can someone who restores a complete older copy of the database (until it is compared with an anchor). Only anchors confirmed on Hedera are out of their reach.
- The canary works at the application level: copying the database file at the OS level leaves no trace (it cannot forge history, though).
HDKConfig.auto_anchorandHederaConfig.anchor_intervalexist but are not used to trigger anchoring in this version: anchor explicitly.- Databases from 0.1.0 do not verify under 0.1.1+ (hashes are computed differently); 0.1.0 also kept the canary secret in plain text in the database, later versions never read it.
- A 0.1.1 database opened by 0.1.2+ with its key is adopted once as it is; every node written afterwards is keyed.
rotate_secret()keys later nodes under the new key; earlier nodes are then checked by anchoring only (verify()["keyed_unchecked"]counts them). Opened without the key, keyed nodes are counted there too instead of being reported as altered. - The
googlewrapper uses thegoogle-generativeaiSDK (configured globally per call). - Guarantees are computational (SHA-256, HMAC, external anchoring), not absolute. Alpha software, not audited.
Changes in 0.1.3
- Licence: CC BY-NC 4.0 (versions up to 0.1.2 were released under Apache-2.0 and remain under it).
- Certificates: the hashscan.io link follows the configured Hedera network (it was always
testnet), and a local-only anchor no longer gets a link.
How to test
pip install pytest pytest-asyncio
pytest # never touches a network (the Hedera client is mocked)
How it works in detail: TECHNICAL.md.
Licence
Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0), from version 0.1.3 — free for non-commercial use with attribution. Full text in LICENSE. Earlier versions (0.1.0–0.1.2) were released under Apache-2.0 and remain available under it.
Citation and contact
Jakub Ćwirlej, HDK (Hedera Detection Key): tamper-evident audit trails for LLM calls, 2026, https://huggingface.co/JakCwi/hdk-audit; technical note: https://www.researchgate.net/publication/400762996. Contact: https://jakubcwirlej.com.