Repository navigation
Releases: bigblue-r4/kiss-protocol
Release list
v3.3.3
Security
witness auditcould pass a rewritten history. When the transparency mirror was behind the local log, audit compared only sizes and reported "eventual consistency lag". Someone holding the machine's keys could change a past record, re-sign the head, append one more record, and audit would exit 0. Audit now checks that the local log, cut to the mirror's size, still has exactly the root the mirror holds, and fails (exit 1) if not. Affected: every release with a mirror before 3.3.3. Action: upgrade, then runwitness auditonce against your mirror; a failure on a log you haven't changed means its history before the mirror's head was altered. Found by the Proof-of-Control C07 self-assessment (attack class described in C7.3.5).- An interrupted write could leave the log permanently unopenable. A record's length and body were written separately, and a kill or power loss between them (or between a record and its signed head) left a partial or uncommitted record at the end. Later records then sat behind it unreadable, and the daemon refused to start with a root mismatch, so a crash looked like tampering and stopped all recording. Each record is now one write, and on open a true crash tail (a partial record and/or exactly one record the head doesn't yet cover) is moved to
witness.log.crash-tail-<time>beside the log, never deleted and never signed. The daemon records WARNlog_crash_tail_recovered, andwitness verifyprints a note. Anything else beyond the head still fails as tampering, and a refused log is never modified. Affected: all earlier releases. Action: upgrade. A log already stuck this way opens again on 3.3.3, with the stuck bytes kept in the quarantine file.
Added
store.Recovered()andstore.RootAt(size).
Verification
Verify any artifact with cosign:
cosign verify-blob \
--certificate-identity-regexp "https://github.com/bigblue-r4/kiss-protocol/.github/workflows/release.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature witness_linux_amd64.sig \
--certificate witness_linux_amd64.pem \
witness_linux_amd64Replace witness with enforcer and linux_amd64 with your platform (linux_arm64, darwin_amd64, darwin_arm64).
sha256sum -c witness_linux_amd64.sha256v3.3.2
Security
- A tree head re-signed with someone else's key passed verification. The head's ed25519 signature was checked against the
signer_keywritten in the head file itself, so anyone holding the machine key (for the MAC) could rewrite the log, sign the head with a key of their own and passstore.Open. When the store is opened with a signer (the daemon), the head must now be signed by that signer or by a key in the trust allowlist (store.OpenTrusting); otherwise it is rejected withErrUnexpectedSigner. The allowlist keeps key rotation working: the old key is already listed, and the daemon's first entry re-signs the head with the new one. - Stripping the signature is recorded. An unsigned head still opens (logs from before signing was configured must), but the daemon now records WARN
tree_head_unsignedand signs the next head. Seen once when signing is first turned on; any other time, someone removed the signature. - Unchanged limit: commands that open the log without a signer (
witness verify,audit,prove) have no key to pin to. Someone with full control of the machine can still use its own signer; the transparency mirror (witness audit) catches that.
Verification
Verify any artifact with cosign:
cosign verify-blob \
--certificate-identity-regexp "https://github.com/bigblue-r4/kiss-protocol/.github/workflows/release.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature witness_linux_amd64.sig \
--certificate witness_linux_amd64.pem \
witness_linux_amd64Replace witness with enforcer and linux_amd64 with your platform (linux_arm64, darwin_amd64, darwin_arm64).
sha256sum -c witness_linux_amd64.sha256v3.3.1
Security
- A truncated log verified cleanly if the signed head file was deleted too.
verifyTreeHeadtreated a missingtree-head.jsonas a fresh store, so cutting the end offwitness.logand removing the head passed bothstore.Openandwitness verify. A log with entries and no head is now reported as tampering (ErrMissingTreeHead); an empty store still opens. No log in this format has ever existed without a head (heads arrived with the Merkle log itself, 2026-05-24), so there is nothing to migrate. Remaining limit, unchanged: the head is signed on the same machine, so someone with full control of it can rewrite and re-sign — the transparency mirror (witness audit) catches that.
Fixed
- The silent-source alarm flagged event-driven sources. A door reader or the records office reports only when something happens, so after the threshold it was wrongly flagged as silent. A farm source is now watched only once it sends a
heartbeator areading(the kinds that arrive on a schedule); any report from a watched source counts as a sign of life.
Added
- Poultry house demo (
examples/poultry-demo): a self-contained 30-second run of the witness against a simulated poultry operation — normal logging, a witness outage and catch-up, a house controller going quiet, and three tampering attempts that verification catches. It also prints its limits. Its fourth tampering attempt (cut the log and delete the signed head) is caught since the fix above. farm.Bridge.SetCheckEveryto change how often silence is checked.
Verification
Verify any artifact with cosign:
cosign verify-blob \
--certificate-identity-regexp "https://github.com/bigblue-r4/kiss-protocol/.github/workflows/release.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature witness_linux_amd64.sig \
--certificate witness_linux_amd64.pem \
witness_linux_amd64Replace witness with enforcer and linux_amd64 with your platform (linux_arm64, darwin_amd64, darwin_arm64).
sha256sum -c witness_linux_amd64.sha256v3.3.0
Fixed
- Events written while the witness was down were never recorded. The NDJSON tailer seeked to the end of an existing file on every start, so a witness restart silently dropped everything a feed logged in the meantime — a gap in the record nobody could see. Tailers now save their read position under
<primary>/tail-state/and resume from it (Pipelock audit log, and all three split-brain-harness logs). The position advances only past entries the store accepted, so a crash re-reads rather than skips: delivery is at-least-once, and a repeated entry is visible where a missing one would not be. - Each (re)open is now recorded:
tail_fresh,tail_resumed(withcatch_up_bytes),tail_rotated, and WARNtail_restartedwhen a feed's file was replaced or truncated while the witness was down — whatever was unread in the old file is gone, and the record now says so. - Half-written lines were dropped. A line the tailer caught mid-write was discarded and its remainder failed to parse; the tailer now rewinds and reads it once it is complete.
- Lines at the start of a rotated file were skipped. After rotation the tailer reopened the new file at its end; it now reads it from the start. Rotation is also detected by file identity, not only by the file shrinking.
Added
- Farm automation feed and a "silent house" alarm (
internal/farm, docs/farm-events.md). Pointfarm_events_path(orFARM_EVENTS_PATH) at an NDJSON feed of readings, alarms, setting changes, door access and health records, and each event is recorded in the Merkle log as sourcefarm: alarms as WARNfarm_alarm:<alarm>, setting changes with who made them, unknown kinds still recorded asfarm_event. Every source that has reported is watched: quiet longer thanfarm_silence_minutes(default 10) gives one WARNfarm_source_silent, andfarm_source_resumedwhen it reports again. Silence is measured from each event's own timestamp, so events caught up after a restart cannot mask an outage. The feed resumes across witness restarts like the others. It detects and records; it does not control anything. - Every split-brain-harness verdict is now witnessed, not only forge runs. The SBH bridge tailed only
SBH_AUDIT_PATH, the forge (tool-generation) log, so the harness's actual decisions never reached the Merkle log. Two more SBH logs are now tailed into it as sourcesbh:SBH_DECISION_LOG(split-brain-harness ≥ 1.6.0): one line per requestsbh serveanalysed. Eventsbh_decision:stop_and_askorsbh_decision:pass; WARN when the gate stopped, the risk was high, or the turn escalated.SBH_SESSION_LOG: multi-turn slow-boil escalations, as WARNsbh_escalation.
Configure withsbh_decision_log_path/sbh_session_log_pathin config.json or the SBH env vars. No SBH log carries raw input, so neither does the witness record.
Tests
- The SBH bridge had no tests. Fixtures for all three logs are now generated by split-brain-harness's own serializers (
tests/witness_fixture.rsthere) and driven through the real tailer and store: classification, payload integrity and post-ingestionVerifyIntegrity. Readiness is probed rather than slept on, since the tailer seeks to the end of an existing file.
Verification
Verify any artifact with cosign:
cosign verify-blob \
--certificate-identity-regexp "https://github.com/bigblue-r4/kiss-protocol/.github/workflows/release.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature witness_linux_amd64.sig \
--certificate witness_linux_amd64.pem \
witness_linux_amd64Replace witness with enforcer and linux_amd64 with your platform (linux_arm64, darwin_amd64, darwin_arm64).
sha256sum -c witness_linux_amd64.sha256v3.2.4
Fixed
- Live Pipelock decision verdicts not elevated (#14, #15): a live Pipelock v3.1.0 proxy dual-emits every decision, and the v2
evidence_receipt(event_kind: proxy_decision,record_type: evidence_receipt_v2) carries its verdict atdetail.payload.verdict, which the bridge did not read. A blocked live decision now raises the witness entry to WARN. Verdict precedence:detail.action_record.verdict→detail.payload.verdict→detail.verdict→verdict.session_controllifecycle classification is confirmed correct against live v3.1.0 output. Thanks to @luckyPipewrench for the signed capture. - Installer could source the wrong
_core.sh(#18): both installer entry points ran an uncheckedcd "$(dirname "$0")"beforesource ./_core.sh. If thecdfailed, the installer kept running in the current directory and sourced whatever_core.shwas there. Finder starts the.commandlauncher in the user's home directory, exactly where a stray file would be. Both now resolve the script directory once, fail loudly if it or_core.shcannot be found, and source by absolute path. Checksum verification (VERIFY.txt, payload only) is unaffected.
Tests
- Real signed Pipelock v3.1.0 flight-recorder capture (17 receipts: session lifecycle and proxy decisions) committed as a fixture, with a classification test over every row (#15).
- HIFAS fraud verdicts driven through the real tailer: entries classify as
hifas_verdictwith payloads intact, and the HIFAS BLAKE3 hash chain is shown to survive ingestion from genesis (#17).
Docs
- README release/Go badges and a table of contents (#16); v3.2.3 changelog date corrected to its publish date.
Verification
Verify any artifact with cosign:
cosign verify-blob \
--certificate-identity-regexp "https://github.com/bigblue-r4/kiss-protocol/.github/workflows/release.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature witness_linux_amd64.sig \
--certificate witness_linux_amd64.pem \
witness_linux_amd64Replace witness with enforcer and linux_amd64 with your platform (linux_arm64, darwin_amd64, darwin_arm64).
sha256sum -c witness_linux_amd64.sha256v3.2.3
Fixes from @luckyPipewrench's live interoperability test (kiss-protocol v3.2.1 ↔ Pipelock v3.0.0) on issue #10.
Fixed
- Flight-recorder receipt classification:
classifyReceiptchecked the generic top-leveltypebeforeevent_kind, so a realtype: action_receipt, event_kind: writereceipt was logged aspipelock_receipt:action_receipt— losing the read/write distinction. Classification now follows the Pipelock v3 recorder envelope (verified against the v3.0.0 source), most specific first:detail.action_record.session_control.kindfor lifecycle receipts (forward-compat — not emitted by the v3.0.0 tag), then the canonical top-levelevent_kind(action verb onaction_receiptrows,proxy_decisionondecisionrows,checkpointon checkpoints), then legacydetail.action_record.action_type, thentype. A blocking/warning verdict now elevates an INFO row to WARN, read fromdetail.action_record.verdict(action receipts) ordetail.verdict(decision rows). - Clean-shutdown records lost: the bridge stopped its log/evidence tailers before stopping Pipelock, so Pipelock's final
transcript_rootand shutdown checkpoint were never read into the witness log.Bridge.Stopnow stops Pipelock first, and the tailers drain to EOF (with a bounded deadline) before exiting; the forwarder flushes any buffered events on the way out. - Genesis flagged tampered when agents present at init:
AgentsAtGenesiswas assigned after the snapshot hash was computed, but the hash covers that field — so a genesis taken with agents present recomputed a different hash onwitness startand was rejected as tampered.genesis.Takenow takes the agent list and folds it in before hashing. - No evidence emitted without a signing key: Pipelock's
flight_recorderis inert withoutsigning_key_pathand writes nothing, so the evidence leg silently produced no receipts. Witness now provisions a Pipelock-format Ed25519 signing key by default (under<primary>/pipelock-keys/, generated natively — seeEnsureSigningKey— and overridable viaPIPELOCK_SIGNING_KEY), so evidence is always emitted and signed. Corrected the earlier "unsigned evidence is still hash-chained and ingested" wording.
Tests
- Added a schema-faithful Pipelock v3.0.0 evidence fixture (
testdata/pipelock_v3_evidence.jsonl, matching the realrecorder.Entry/ receipt / decision / checkpoint shapes) and drove it throughclassifyReceipt, replacing the earlier hand-authoreddecisionenvelopes flagged in the live test. - Added an end-to-end clean-shutdown ingestion test: the final checkpoint written just before stop must reach the witness log via the real
EvidenceTailer → forward → storepath (regression guard for the shutdown-drain fix). - Added drain-on-stop and final-scan tests for both tailers, genesis round-trip-with-agents, and signing-key format/idempotency/default-provisioning tests.
Open item for @luckyPipewrench: the v3.0.0 tag source has no
session_controlfield (the lifecyclekindpath is kept as forward-compat per your guidance but is currently a no-op), and decision-row verdicts live atdetail.verdictrather than underaction_record. Confirmation of the canonical field mapping against a live v3 emitter would let us lock these down.
Verification
Verify any artifact with cosign:
cosign verify-blob \
--certificate-identity-regexp "https://github.com/bigblue-r4/kiss-protocol/.github/workflows/release.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature witness_linux_amd64.sig \
--certificate witness_linux_amd64.pem \
witness_linux_amd64Replace witness with enforcer and linux_amd64 with your platform (linux_arm64, darwin_amd64, darwin_arm64).
sha256sum -c witness_linux_amd64.sha256v3.2.2
Changed
- CI workflow (
ci.yml) modernized to Node 24-native action majors (actions/checkoutv5,actions/setup-gov6), dropping theFORCE_JAVASCRIPT_ACTIONS_TO_NODE24workaround and clearing the Node 20 deprecation warning. The release workflow also bumps checkout/setup-go to v5/v6 but intentionally keepssigstore/cosign-installerv3 andsoftprops/action-gh-releasev2 — their next majors are breaking (cosign v3 changedsign-blobto a bundle format), so they retain the force-node24 shim. No functional change to thewitness/enforcerbinaries.
Verification
Verify any artifact with cosign:
cosign verify-blob \
--certificate-identity-regexp "https://github.com/bigblue-r4/kiss-protocol/.github/workflows/release.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature witness_linux_amd64.sig \
--certificate witness_linux_amd64.pem \
witness_linux_amd64Replace witness with enforcer and linux_amd64 with your platform (linux_arm64, darwin_amd64, darwin_arm64).
sha256sum -c witness_linux_amd64.sha256v3.2.1
Added
- Flight-recorder receipts folded into the witness log (issue #10 follow-up): the Pipelock bridge now tails the
flight_recorderevidence directory and forwards every signed, hash-chained decision receipt into the witness Merkle log aspipelock_receiptevents, alongside the raw NDJSON audit stream. Newpipelock.EvidenceTailerdiscovers rotatingevidence-*.jsonlfiles (following pre-existing files from the end, new files from the start). Receipt signing is opt-in viaPIPELOCK_SIGNING_KEY/Config.SigningKeyPath; without a key the evidence is still hash-chained and ingested, just unsigned.
Fixed
- Pipelock config schema (issue #10): the generated Pipelock config used invented top-level keys (
proxy,audit,policy,response_scan,behavioral,mcp) that Pipelock rejects at startup with unknown-field errors, so the process exited immediately and the network audit leg silently no-op'd. Rewrote the template against the Pipelock v3 schema (mode: audit,fetch_proxy/forward_proxy,logging,mcp_input_scanning/mcp_tool_scanning,behavioral_baseline,flight_recorder) and added a regression test that runspipelock checkagainst the generated config when the binary is present. Thanks to @luckyPipewrench (Pipelock author) for the report and corrected config. - Silent Pipelock startup failure:
Runner.Startnow health-checks the proxy port before reporting success, and captures the subprocess's stderr into the witness log (aspipelock_stderrevents) so a config rejection or crash is recorded instead of masked by a false "proxy running" message.
Verification
Verify any artifact with cosign:
cosign verify-blob \
--certificate-identity-regexp "https://github.com/bigblue-r4/kiss-protocol/.github/workflows/release.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature witness_linux_amd64.sig \
--certificate witness_linux_amd64.pem \
witness_linux_amd64Replace witness with enforcer and linux_amd64 with your platform (linux_arm64, darwin_amd64, darwin_arm64).
sha256sum -c witness_linux_amd64.sha256v3.2.0 — Prior Art Reference
Added
docs/prior-art.md— curated architectural prior art for Sentinel/Witness/Bigblue: agent-governance-toolkit (policy gate), OpenFang (Merkle chain diff target), AgentSeal (MCP audit adapter), LlamaFirewall (SBIR citation baseline)
Verification
Verify any artifact with cosign:
cosign verify-blob \
--certificate-identity-regexp "https://github.com/bigblue-r4/kiss-protocol/.github/workflows/release.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature witness_linux_amd64.sig \
--certificate witness_linux_amd64.pem \
witness_linux_amd64Replace witness with enforcer and linux_amd64 with your platform (linux_arm64, darwin_amd64, darwin_arm64).
sha256sum -c witness_linux_amd64.sha256v3.1.0
Added
- SBH forge audit integration — split-brain-harness forge events are now recorded as signed Merkle leaves in the witness log
Fixed
store.Append: sanitize invalid UTF-8 bytes before hashing to prevent Merkle hash drift between platforms
Verification
Verify any artifact with cosign:
cosign verify-blob \
--certificate-identity-regexp "https://github.com/bigblue-r4/kiss-protocol/.github/workflows/release.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature witness_linux_amd64.sig \
--certificate witness_linux_amd64.pem \
witness_linux_amd64Replace witness with enforcer and linux_amd64 with your platform (linux_arm64, darwin_amd64, darwin_arm64).
sha256sum -c witness_linux_amd64.sha256