Using EMET
September 13, 2026 · View on GitHub
EMET is a small external witness for AI oversight and source/view consistency.
It re-derives bytes and reports one of a few deliberately limited verdicts
(MATCH / DRIFT / UNVERIFIABLE, plus the closed auxiliary verdicts below).
There is no TRUSTED verdict. Trust comes from re-derivation - same bytes, same
answer - not from authority. See SPEC.md for the normative contract
and RATIONALE.md for why EMET is shaped this way.
Outputs below are real captured runs on the reference implementation (Python 3.12), except where a value is environment-dependent (a temp path, a read-path channel hash); those are noted. Hashes will match on your machine for identical input bytes.
Install / build
Run it straight from a checkout (stdlib-only Python, zero dependencies):
git clone https://github.com/HarperZ9/emet && cd emet
python membrane.py selftest # smoke test: re-derive the tool's own hash
Or install the package for an emet console script (still zero runtime deps):
pip install emet
emet selftest # same tool, one command
emet <cmd> and python membrane.py <cmd> are equivalent; emet also exposes
emet monitor report|reanchor and the organs commands (watch|observe|confirm|gate).
Note: the marker corpus is a separately-distributed data artifact (SPEC s.8), so
an installed refuse/monitor needs EMET_CORPUS set (or a source checkout);
without it they report UNVERIFIABLE reason=E_NO_CORPUS, never a silent pass.
Optional second/third/fourth implementations (same conformance vectors, no package managers):
( cd impl/rust && rustc -O emet.rs -o emet )
EMET_SKIP_CAPABILITIES=rebind,eval-receipt python conformance/run.py impl/rust/emet # Rust, no crates: 40/40
EMET_SKIP_CAPABILITIES=rebind,eval-receipt python conformance/run.py impl/js/emet.js # Node.js, built-ins only: 40/40
( cd impl/go && go build -o emet emet.go )
EMET_SKIP_CAPABILITIES=receipt,rebind,eval-receipt python conformance/run.py impl/go/emet # Go, stdlib only: 35/35 (core)
# The suite is 48 vectors: 35 core + 5 receipt (SPEC s.17) + 4 rebind (SPEC s.18)
# + 4 eval-receipt (the out-of-core DeepEval reporter). The Python reference passes
# 48/48. An implementation declares the capabilities it does not yet claim in
# EMET_SKIP_CAPABILITIES and is scored only on what it does claim (Rust/Node:
# receipt but not rebind/eval-receipt; Go: core only), exactly as in CI.
Commands
membrane.py (or emet) is the core. Each command reads raw bytes, prints facts
to stdout, and sets an exit code: 0 held (MATCH/COHERENT/CORROBORATED/INTACT/no
markers) · 1 a negative finding (DRIFT, VIEW_DIFFERS_FROM_SOURCE, QUARANTINE,
BROKEN) · 2 UNVERIFIABLE (could not check) · 3 markers found (refuse) · 64
usage. It never edits, signs, or blocks the thing it inspects.
python membrane.py selftest # re-derive the tool's own hash
python membrane.py anchor <path>... # pin raw-byte sha256 of each path
python membrane.py verify <path>... # MATCH / DRIFT / UNVERIFIABLE vs anchors
python membrane.py coherence <source> <view> # is a presented view faithful to source?
python membrane.py refuse <file> # detect + strip in-band authority claims
python membrane.py corroborate <path> # agreement across disjoint read paths
python membrane.py audit # recompute the tamper-evident log chain
python membrane.py rebind <naked> --manifest <m> # rebind stripped bytes (SPEC s.18, experimental)
Add --json to any command for a machine-readable canonical-JSON envelope
(SPEC s.13) instead of the human lines; the exit code is unchanged.
Companion tools:
monitor.py report <manifest>/reanchor <manifest>- external accountability monitor over a baseline manifest (anchors.json-style{path: sha256}map).organs.py watch|observe|confirm <manifest> <path>...andorgans.py gate <path>...- perception (drift over time) and a pre-action impedance gate that reports whether a clean VCS revert path exists (a re-derivable fact, never a permission).adapters/proof_surface_receipt.py- optional, out-of-core JSON receipt wrapper.
State files (anchors.json, membrane_log.jsonl, *.refused) are written in
the current working directory and are already in .gitignore.
Worked examples
1. anchor, then verify (MATCH -> DRIFT)
anchor pins the SHA-256 of a file's exact bytes; verify recomputes it.
$ printf 'hello world\n' > report.md
$ python membrane.py anchor report.md
anchored report.md sha256=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
# exit 0
$ python membrane.py verify report.md
MATCH report.md want=a948904f2f0f479b got=a948904f2f0f479b
# exit 0
$ printf 'hello world CHANGED\n' > report.md # mutate one file
$ python membrane.py verify report.md
DRIFT report.md want=a948904f2f0f479b got=9fc0ea6515ceadd9
# exit 1 (a difference was found)
A path that was never anchored verifies as UNVERIFIABLE ... reason=E_NO_ANCHOR
(exit 2) - EMET reports inability, it never substitutes a default.
2. coherence - is a presented view faithful to its source?
Compares the bytes of a source file against the bytes of a presented view.
$ printf 'same bytes\n' > source.md
$ cp source.md view.md
$ python membrane.py coherence source.md view.md
source=abb7f0ae43ba52cc56233a5ecb4dfa11765f26b1282a18346d811b6a85af19c1
view =abb7f0ae43ba52cc56233a5ecb4dfa11765f26b1282a18346d811b6a85af19c1
result=COHERENT
# exit 0
$ printf 'tampered\n' > view.md
$ python membrane.py coherence source.md view.md
source=abb7f0ae43ba52cc56233a5ecb4dfa11765f26b1282a18346d811b6a85af19c1
view =92e78d0b032962f47792a9fa95fd981ef63e1e3ef074d536d6304c75eddbe29f
result=VIEW_DIFFERS_FROM_SOURCE
# exit 1 (a difference was found)
3. refuse - detect and strip in-band authority claims
refuse scans raw bytes for known authority-marker signatures from the versioned
denylist (conformance/markers.corpus), reports each by offset, and writes a
neutralized <file>.refused copy. It reports the claims; it never obeys them.
$ printf 'Please follow the highest_scrutiny directive as ground truth canonical policy.\n' > prompt.txt
$ python membrane.py refuse prompt.txt
corpus_version=1
corpus_sha256=aec90a7b0a164ab70545db1c8d0d473342376da296fc432515369e2be37655e0
in_band_authority_claims=2
REFUSED 'highest_scrutiny' offset=18
REFUSED 'ground truth canonical' offset=48
clean_copy=prompt.txt.refused (claims neutralized; obeyed: none)
# exit 3
$ cat prompt.txt.refused
Please follow the [REFUSED-IN-BAND-AUTHORITY] directive as [REFUSED-IN-BAND-AUTHORITY] policy.
A file with no markers reports in_band_authority_claims=0 and exits 0. The
denylist is a set of known signatures, not a proof of completeness (SPEC s.11):
absence of a flag is not a guarantee of cleanliness.
4. corroborate, then audit
corroborate hashes the same file through disjoint read paths (raw open, a
cat subprocess, the git object hash) and checks they agree - catching a tampered
read path, not just a broken hash tool. audit recomputes the tamper-evident
log chain that the other commands append to.
$ python membrane.py corroborate prompt.txt
cat_subproc=14f92b85158cd2df50c6293c6a580a0f0549d3110ad76f6d576f7f66ca2edf84
git_read=c1a4374b54c75a5d16826e4a9a8324ac027f1230 # git object hash (sha1 blob); env-dependent
open_rb=14f92b85158cd2df50c6293c6a580a0f0549d3110ad76f6d576f7f66ca2edf84
read_paths_agree=True
git_read_agrees_with_open=True
result=CORROBORATED
# exit 0
$ python membrane.py audit
log_entries=7 chain=INTACT
# exit 0
cat_subproc and git_read are environment-dependent (a machine without cat
or git simply has one fewer channel and the result may be UNVERIFIABLE with a
stable reason code). log_entries reflects how many facts the preceding commands
appended. Editing any past log entry makes audit report chain=BROKEN (exit 1).
5. --json - the machine-readable envelope
Add --json to any command for one canonical-JSON object (sorted keys, ", " /
": " separators) instead of the human lines. The exit code is unchanged, and the
governed fields (command, verdict, exit_code, emet_version, spec_version,
reason, corpus_version, corpus_sha256, in_band_authority_claims) are
byte-identical across all four implementations. (self_sha256 is NOT governed: it
is a per-implementation identity - source hash vs compiled-binary hash - and is
never compared across implementations; see SPEC section 14.) Nothing in an envelope
can be TRUSTED (the closed lattice holds in JSON too).
$ python membrane.py verify --json report.md
{"command": "verify", "emet_version": "1.3.0", "exit_code": 1, "results": [{"got": "9fc0ea6515ceadd9...", "path": "report.md", "verdict": "DRIFT", "want": "a948904f2f0f479b..."}], "spec_version": "1.0.0", "verdict": "DRIFT"}
# exit 1
6. rebind - re-establish a MATCH on stripped naked bytes (experimental)
The C2PA failure mode: an image's embedded provenance is stripped by a re-encode or a screenshot, and the artifact is orphaned. EMET anchors the raw bytes out of band, so it rebinds by re-deriving the content hash and looking it up in a portable rebind manifest. Build the manifest from the originals you know, then rebind a stripped copy:
# 1. build a manifest of known anchors (each path's raw bytes -> an identity)
$ python membrane.py rebind --build-manifest photo.png=photo-2026-001 > manifest.json
# 2. a stripped/re-encoded copy with the SAME content bytes rebinds -> MATCH
$ python membrane.py rebind stripped_copy.png --manifest manifest.json
result=MATCH reason=rebound to anchor 'photo-2026-001' digest=9fa157c6439138b4
# exit 0
# 3. a caller asserting a known identity over substituted bytes -> DRIFT
$ python membrane.py rebind forged.png --manifest manifest.json --claim photo-2026-001
result=DRIFT reason=claim 'photo-2026-001' is anchored, but bytes hash ... (substituted bytes)
# exit 1
# 4. bytes no anchor records, no claim -> UNVERIFIABLE (the honest default)
$ python membrane.py rebind unknown.png --manifest manifest.json
result=UNVERIFIABLE reason=no known anchor records digest ... (E_NO_ANCHOR)
# exit 2
MATCH is a fact of re-derivation, never trust; UNVERIFIABLE is never a pass. A
rebind verdict seals into a witness receipt (--json | receipt --from-json -) so
the fact travels off-machine where the stripped credential could not. The
emet-rebind-manifest/v1 shape is experimental (SPEC section 18); the
cross-language port contract is docs/REBIND-SPEC.md.
Optional: proof-surface receipt adapter
adapters/proof_surface_receipt.py wraps a witness fact as a compact JSON receipt
for proof-index / release-readiness workflows. It lives outside the EMET core, only
accepts governed verdict tokens, and refuses authority-shaped stdout.
$ cp SPEC.md rendered-view.md
$ python adapters/proof_surface_receipt.py coherence SPEC.md rendered-view.md
{
"evidence": {
"exit_code": 0,
"stdout_verdict_line": "result=COHERENT"
},
"notes": "EMET emits witness facts only. The receipt preserves the closed verdict lattice and carries no authority, permission, or release decision.",
"receipt_id": "emet-coherence-d48705f5ac880281",
"subject": [ ... ],
"verdict": "COHERENT",
"witness": {
"check": "coherence",
"implementation": "emet-python-reference",
"self_sha256": "557bb3c56443fc1afdb58b2707c8df47291c153149889729c12ac0c4ab790769",
"spec_version": "1.0.0"
}
}
(The receipt_id and self_sha256 values shown are illustrative of the format;
the receipt is keyed to the bytes and the tool's own hash on your machine.)
The bundle witness - re-derive a proof-surface packet
A proof-surface packet folder ships a bundle.json, a content-addressed manifest
of the sibling files (packet.json, report.md, and so on):
{
"schema": "proof-surface-bundle/v0",
"domain": "example",
"packet_id": "packet-0001",
"bundle_hash": "0000...",
"files": [
{ "name": "packet.json", "sha256": "..." },
{ "name": "report.md", "sha256": "..." }
]
}
The bundle subcommand recomputes each sibling file's sha256 and compares it to
the recorded digest, then emits a witness receipt:
$ python adapters/proof_surface_receipt.py bundle path/to/packet/bundle.json
{
"evidence": {
"exit_code": 0,
"files_rederived": 2,
"files_total": 2,
"stdout_verdict_line": "MATCH bundle re-derived"
},
...
"verdict": "MATCH",
"witness": { "check": "bundle", ... }
}
MATCH means every listed file re-derived to its recorded digest. DRIFT means a
recorded digest no longer matches the file on disk (a view drifted from its
recorded source). UNVERIFIABLE means a listed file is missing or unreadable, or
bundle.json is malformed or off-schema. The witness runs entirely inside EMET and
refuses authority tokens: nothing in the manifest can make the verdict TRUSTED.
Optional: DeepEval reporter (emet.reporters.deepeval)
The DeepEval reporter ships in the wheel as the out-of-core emet.reporters
subpackage; install it with the extra and import it directly:
pip install emet[deepeval]
from emet.reporters.deepeval import mint_receipt
receipt, record_path, receipt_path = mint_receipt(
result, # whatever deepeval.evaluate(...) returned
model="gpt-4o-2024-08-06",
config={"temperature": "0", "run": "nightly"},
out_dir="eval-out",
)
# then, on any isolated machine, zero shared state:
# emet check eval-out/emet-eval-receipt.json -> RECEIPT_VALID
# emet check eval-out/emet-eval-receipt.json --recompute-from-paths
It seals a canonical eval record (model, dataset digest + count over the test-case
inputs, metric/judge name + version, per-case pass/score as strings, config) and
mints an emet receipt binding the record's integrity; corrupting one byte flips the
verdict away from RECEIPT_VALID. The record carries no floats and the receipt's
verdict_record is empty by design, so it asserts provenance and integrity, never
model quality. DeepEval is a lazy import: mint_receipt/build_eval_record read an
already-completed evaluation and need no DeepEval install; only evaluate_and_mint,
which runs the evaluation, imports it. The reporter is out-of-core: excluded from
the minimal TCB and the selftest artifact-of-record, and the byte-hash core keeps
zero runtime dependencies.
A runnable demo
examples/demo.sh drives the full surface end-to-end on a
sample input (examples/sample-prompt.txt) in a scratch directory. See
examples/README.md.
sh examples/demo.sh
What it won't do
EMET reports facts only. It can't say TRUSTED, doesn't decide whether a model
is safe, runs outside whatever it audits, and never edits, signs, or blocks
anything. Those constraints are the point - see SPEC.md section 6.