internal/auditpush at 441da802c8, reviewed by claude-code, verified by codex.
PushLedgerDir and LoadDir publish a ledger directory with no chain verification, so an entry whose bytes were edited after signing is loaded into the view and pushed into the operator's warehouse as the record — carrying its tampered numbers and its stale entry_hash — while AppendLedgerEntry and WriteCheckpoint refuse the very same directory.
#!/bin/sh
# Reproduces: a ledger directory that VerifyLedgerDir reports as broken, and
# that AppendLedgerEntry refuses to extend, is still loaded by LoadDir and
# still pushed to a warehouse by PushLedgerDir.
cat > internal/auditpush/zz_repro_unverified_push_test.go <<'GOEOF'
package auditpush
import (
"bytes"
"compress/gzip"
"database/sql"
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
)
func TestReproTamperedLedgerDirIsPublishedAsTheRecord(t *testing.T) {
dir := t.TempDir()
rate := 0.42
b := Bundle{
Scan: ScanRow{Repo: "o/r", Commit: "c0ffee", Host: "box", CorralVersion: "v1", Substrate: "workspace"},
Files: []Row{{Repo: "o/r", Commit: "c0ffee", Path: "a.go", KillRate: &rate, Disposition: "audited"}},
}
if _, err := PushBundle(dir, b); err != nil {
t.Fatalf("PushBundle: %v", err)
}
names, err := ledgerFileNames(dir)
if err != nil || len(names) != 1 {
t.Fatalf("setup: ledgerFileNames = %v, %v", names, err)
}
p := filepath.Join(dir, ScansSubdir, names[0])
raw, err := readMaybeGzip(p)
if err != nil {
t.Fatal(err)
}
var tree map[string]any
if err := json.Unmarshal(raw, &tree); err != nil {
t.Fatal(err)
}
bundle, ok := tree["bundle"].(map[string]any)
if !ok {
t.Fatalf("setup: no bundle in the entry: %v", tree)
}
bundle["Scan"].(map[string]any)["Commit"] = "deadbeef"
bundle["Files"].([]any)[0].(map[string]any)["KillRate"] = 0.99
edited, err := json.MarshalIndent(tree, "", " ")
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
zw := gzip.NewWriter(&buf)
if _, err := zw.Write(edited); err != nil {
t.Fatal(err)
}
if err := zw.Close(); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, buf.Bytes(), 0o644); err != nil {
t.Fatal(err)
}
checks, err := VerifyLedgerDir(dir, nil)
if err != nil {
t.Fatal(err)
}
if len(checks) != 1 || checks[0].Problem == "" {
t.Fatalf("setup: the tampered entry verifies clean, so this proves nothing: %+v", checks)
}
fmt.Printf("VerifyLedgerDir: %s: %s\n", checks[0].File, checks[0].Problem)
if _, aerr := AppendLedgerEntry(dir, LedgerEntry{Kind: KindRetract, Retracts: "deadbeef", Reason: "x"}, nil); aerr == nil {
t.Fatal("setup: AppendLedgerEntry accepted the broken chain")
} else {
fmt.Printf("AppendLedgerEntry refuses the SAME directory: %v\n", aerr)
}
// Door 1: the view (corral verify --db <dir>, corral seal --db <dir>).
vdb, err := LoadDir(dir)
if err != nil {
t.Fatalf("DEFECT ABSENT: LoadDir refused the broken chain: %v", err)
}
defer vdb.Close()
var viewRate float64
if err := vdb.QueryRow(`SELECT kill_rate FROM corral_audits`).Scan(&viewRate); err != nil {
t.Fatalf("LoadDir read back: %v", err)
}
fmt.Printf("LoadDir loaded it anyway: kill_rate=%v\n", viewRate)
// Door 2: corral ledger push.
target := filepath.Join(t.TempDir(), "w.duckdb")
plan, err := PushLedgerDir(dir, target, false, false)
if err != nil {
t.Fatalf("DEFECT ABSENT: PushLedgerDir refused the broken chain: %v", err)
}
db, err := sql.Open("duckdb", target)
if err != nil {
t.Fatal(err)
}
defer db.Close()
var commit, entryHash string
var whRate float64
if err := db.QueryRow(`SELECT s.commit_sha, s.entry_hash, a.kill_rate FROM corral_scans s, corral_audits a`).Scan(&commit, &entryHash, &whRate); err != nil {
t.Fatalf("warehouse read back: %v", err)
}
fmt.Printf("PushLedgerDir published it: plan=%+v\n", plan)
fmt.Printf("warehouse holds commit_sha=%q kill_rate=%v entry_hash=%.12s\n", commit, whRate, entryHash)
fmt.Printf("the entry that was actually written said commit c0ffee / kill_rate 0.42\n")
if viewRate != 0.99 || whRate != 0.99 || commit != "deadbeef" {
t.Fatalf("DEFECT ABSENT: view=%v warehouse=%v commit=%q", viewRate, whRate, commit)
}
}
GOEOF
go test -count=1 -run TestReproTamperedLedgerDirIsPublishedAsTheRecord -v ./internal/auditpush/ > zz_repro_a.out 2>&1
rc=$?
cat zz_repro_a.out
exit $rc=== RUN TestReproTamperedLedgerDirIsPublishedAsTheRecord
VerifyLedgerDir: 20260908T185834Z-c0ffee-f9ead8a778cf.json.gz: entry bytes do not match its hash — edited after it was written
AppendLedgerEntry refuses the SAME directory: auditpush: /tmp/TestReproTamperedLedgerDirIsPublishedAsTheRecord3835533581/001 does not verify (20260908T185834Z-c0ffee-f9ead8a778cf.json.gz: entry bytes do not match its hash — edited after it was written) — refusing to append, which would sign this chain as the new entry's history
LoadDir loaded it anyway: kill_rate=0.99
PushLedgerDir published it: plan={Scans:1 Reviews:0 Adjudications:0 Skipped:0 Retracted:0 NotPushable:0 Files:1 Findings:0}
warehouse holds commit_sha="deadbeef" kill_rate=0.99 entry_hash=a7166438fcc9
the entry that was actually written said commit c0ffee / kill_rate 0.42
--- PASS: TestReproTamperedLedgerDirIsPublishedAsTheRecord (0.08s)
PASS
ok github.com/pdbethke/corralai/internal/auditpush 0.092sI traced PushLedgerDir through ReadLedgerDir and LiveEntries, and LoadDir through its replay path. Neither calls VerifyLedgerDir or otherwise rejects a ChainCheck. The reproduced script uses the exact tampered entry named by the claim and demonstrates that VerifyLedgerDir and AppendLedgerEntry reject its integrity while both publication paths consume its edited values. I found no narrower safeguard that refutes this.
ref 2a793fdf90c9742e91276ae5211ef7b8bcef5f60e632b1da2670a02cc6b55c2f#R1
A retraction never reaches the warehouse: rows pushed before the retraction are neither removed nor marked, and the schema has no retraction column at all, so `corral_seal` over the ledger directory and `corral_seal` in the warehouse pushed from that same directory give two different answers for the same file.
#!/bin/sh
# Reproduces: push a scan entry to a warehouse, then retract the entry, then
# push again. LoadDir's corral_seal drops the file; the warehouse's
# corral_seal still reports the retracted verdict as the file's current one.
cat > internal/auditpush/zz_repro_retract_warehouse_test.go <<'GOEOF'
package auditpush
import (
"database/sql"
"fmt"
"path/filepath"
"testing"
)
func TestReproRetractionNeverReachesTheWarehouse(t *testing.T) {
dir := t.TempDir()
rate := 0.42
b := Bundle{
Scan: ScanRow{Repo: "o/r", Commit: "c0ffee", Host: "box", CorralVersion: "v1", Substrate: "workspace"},
Files: []Row{{Repo: "o/r", Commit: "c0ffee", Path: "a.go", KillRate: &rate, Disposition: "audited"}},
}
if _, err := PushBundle(dir, b); err != nil {
t.Fatalf("PushBundle: %v", err)
}
target := filepath.Join(t.TempDir(), "w.duckdb")
if _, err := PushLedgerDir(dir, target, false, false); err != nil {
t.Fatalf("first PushLedgerDir: %v", err)
}
entries, err := ReadLedgerDir(dir)
if err != nil || len(entries) != 1 {
t.Fatalf("setup: %d entries, %v", len(entries), err)
}
if _, err := WriteRetraction(dir, entries[0].Hash, "the run was wrong", nil); err != nil {
t.Fatalf("WriteRetraction: %v", err)
}
fmt.Printf("retracted entry %.12s\n", entries[0].Hash)
plan, err := PushLedgerDir(dir, target, false, false)
if err != nil {
t.Fatalf("second PushLedgerDir: %v", err)
}
fmt.Printf("push after the retraction: %+v\n", plan)
vdb, err := LoadDir(dir)
if err != nil {
t.Fatal(err)
}
defer vdb.Close()
var viewRows int
if err := vdb.QueryRow(`SELECT count(*) FROM corral_seal WHERE repo='o/r' AND path='a.go'`).Scan(&viewRows); err != nil {
t.Fatalf("view: %v", err)
}
db, err := sql.Open("duckdb", target)
if err != nil {
t.Fatal(err)
}
defer db.Close()
var whRows int
var kr sql.NullFloat64
if err := db.QueryRow(`SELECT count(*), max(kill_rate) FROM corral_seal WHERE repo='o/r' AND path='a.go'`).Scan(&whRows, &kr); err != nil {
t.Fatalf("warehouse: %v", err)
}
var cols int
if err := db.QueryRow(`SELECT count(*) FROM duckdb_columns() WHERE column_name LIKE '%retract%'`).Scan(&cols); err != nil {
t.Fatalf("columns: %v", err)
}
fmt.Printf("corral_seal over the LEDGER DIRECTORY : %d row(s)\n", viewRows)
fmt.Printf("corral_seal in the WAREHOUSE pushed from it: %d row(s), kill_rate=%v\n", whRows, kr.Float64)
fmt.Printf("columns anywhere in the warehouse naming a retraction: %d\n", cols)
if viewRows != 0 || whRows != 1 || cols != 0 {
t.Fatalf("DEFECT ABSENT: view=%d warehouse=%d retraction-columns=%d", viewRows, whRows, cols)
}
}
GOEOF
go test -count=1 -run TestReproRetractionNeverReachesTheWarehouse -v ./internal/auditpush/ > zz_repro_b.out 2>&1
rc=$?
cat zz_repro_b.out
exit $rc=== RUN TestReproRetractionNeverReachesTheWarehouse
retracted entry 3000c919a48b
push after the retraction: {Scans:0 Reviews:0 Adjudications:0 Skipped:1 Retracted:0 NotPushable:1 Files:0 Findings:0}
corral_seal over the LEDGER DIRECTORY : 0 row(s)
corral_seal in the WAREHOUSE pushed from it: 1 row(s), kill_rate=0.42
columns anywhere in the warehouse naming a retraction: 0
--- PASS: TestReproRetractionNeverReachesTheWarehouse (0.13s)
PASS
ok github.com/pdbethke/corralai/internal/auditpush 0.134sI checked both the push ordering and schemas. PushLedgerDir builds the warehouse's existing-hash set before live-entry filtering, skips an already-pushed hash before considering whether it is now retracted, and treats retraction entries as non-pushable chain facts. The warehouse schemas contain no retraction state. The script consequently proves the stated directory/warehouse divergence after a second push.
ref 2a793fdf90c9742e91276ae5211ef7b8bcef5f60e632b1da2670a02cc6b55c2f#R2
VerifyLedgerDir sets no Problem for an UNSIGNED entry even when a verifying key is supplied and every other entry in the chain is signed, so WriteCheckpoint — the one verb that deletes evidence, and whose guard exists precisely to stop laundering — prunes the signed history and enshrines the unsigned entry as the checkpoint's Head, after which the whole record verifies clean.
#!/bin/sh
# Reproduces: an entry nobody signed, appended to an otherwise-signed chain,
# draws no Problem from VerifyLedgerDir even with the public key in hand; and
# WriteCheckpoint then deletes the signed history and names that unsigned
# entry as the head the checkpoint stands in for.
cat > internal/auditpush/zz_repro_unsigned_head_test.go <<'GOEOF'
package auditpush
import (
"crypto/ed25519"
"fmt"
"testing"
)
func TestReproUnsignedEntryBecomesTheCheckpointedHead(t *testing.T) {
dir := t.TempDir()
pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
signer := Ed25519LedgerSigner{KeyID: "corral-certify", Key: priv}
rate := 0.42
for _, c := range []string{"c1", "c2"} {
b := Bundle{
Scan: ScanRow{Repo: "o/r", Commit: c, Host: "box", CorralVersion: "v1", Substrate: "workspace"},
Files: []Row{{Repo: "o/r", Commit: c, Path: "a.go", KillRate: &rate, Disposition: "audited"}},
}
if _, err := AppendLedgerEntry(dir, LedgerEntry{Bundle: b}, signer); err != nil {
t.Fatalf("setup append: %v", err)
}
}
// Anyone who can write to the branch appends an entry they cannot sign.
perfect := 1.0
forged := Bundle{
Scan: ScanRow{Repo: "o/r", Commit: "forged", Host: "box", CorralVersion: "v1", Substrate: "workspace"},
Files: []Row{{Repo: "o/r", Commit: "forged", Path: "a.go", KillRate: &perfect, Disposition: "audited"}},
}
if _, err := AppendLedgerEntry(dir, LedgerEntry{Bundle: forged}, nil); err != nil {
t.Fatalf("setup forged append: %v", err)
}
checks, err := VerifyLedgerDir(dir, pub)
if err != nil {
t.Fatal(err)
}
for _, c := range checks {
fmt.Printf("%-52s signed=%-5v sigOK=%-5v problem=%q\n", c.File, c.Signed, c.SigOK, c.Problem)
}
last := checks[len(checks)-1]
if last.Signed {
t.Fatalf("setup: the last entry is signed")
}
if last.Problem != "" {
t.Fatalf("DEFECT ABSENT: the unsigned entry drew a problem: %q", last.Problem)
}
forgedHash := last.Hash
fmt.Printf("unsigned entry %.12s draws NO problem under the operator's own public key\n", forgedHash)
name, pruned, err := WriteCheckpoint(dir, signer)
if err != nil {
t.Fatalf("DEFECT ABSENT: WriteCheckpoint refused: %v", err)
}
fmt.Printf("WriteCheckpoint deleted %d entries (2 of them signed) into %s\n", pruned, name)
after, err := ReadLedgerDir(dir)
if err != nil || len(after) != 1 || after[0].Checkpoint == nil {
t.Fatalf("after checkpoint: %d entries, %v", len(after), err)
}
acks, err := VerifyLedgerDir(dir, pub)
if err != nil {
t.Fatal(err)
}
fmt.Printf("the record now: head=%.12s entries=%d problem=%q note=%q\n",
after[0].Checkpoint.Head, after[0].Checkpoint.Entries, acks[0].Problem, acks[0].Note)
if after[0].Checkpoint.Head != forgedHash || acks[0].Problem != "" {
t.Fatalf("DEFECT ABSENT: head=%q problem=%q", after[0].Checkpoint.Head, acks[0].Problem)
}
fmt.Printf("the signed history is gone and the attested head is an entry nobody signed\n")
}
GOEOF
go test -count=1 -run TestReproUnsignedEntryBecomesTheCheckpointedHead -v ./internal/auditpush/ > zz_repro_c.out 2>&1
rc=$?
cat zz_repro_c.out
exit $rc=== RUN TestReproUnsignedEntryBecomesTheCheckpointedHead 20260908T185838Z-c1-6e31c0b0589a.json.gz signed=true sigOK=true problem="" 20260908T185838Z-c2-67f39449f584.json.gz signed=true sigOK=true problem="" 20260908T185838Z-forged-bfab9fc3f339.json.gz signed=false sigOK=false problem="" unsigned entry c9f989af483c draws NO problem under the operator's own public key WriteCheckpoint deleted 3 entries (2 of them signed) into 20260908T185838Z-checkpoint-c9f989af483c.json.gz the record now: head=c9f989af483c entries=3 problem="" note="chain begins at a checkpoint: 3 earlier entries (through 2026-09-08, head c9f989af483c) are not present" the signed history is gone and the attested head is an entry nobody signed --- PASS: TestReproUnsignedEntryBecomesTheCheckpointedHead (0.01s) PASS ok github.com/pdbethke/corralai/internal/auditpush 0.014s
VerifyLedgerDir deliberately records unsigned state in Signed but assigns Problem only for an invalid signature when a signature exists. WriteCheckpoint calls VerifyLedgerDir with a nil key and gates solely on Problem, then places a signed checkpoint whose Head is the stored hash of the unsigned final entry before deleting the old entries. The fact that unsigned entries are legal and visibly marked does not refute the narrower claim that this destructive gate accepts and checkpoints one.
ref 2a793fdf90c9742e91276ae5211ef7b8bcef5f60e632b1da2670a02cc6b55c2f#R3
EntryHash for a corral-ledger-2 entry throws away the file's own bytes (e.Raw) and re-marshals this binary's structs, so any key present in the file that the current LedgerEntry/Bundle structs do not model is silently dropped by json.Unmarshal before hashing and cannot break the entry's hash or its signature.
EntryHash has an explicit corral-ledger-2 branch that clears fields and calls CanonicalSparseJSON on the decoded LedgerEntry struct instead of canonicalizing e.Raw. Therefore unknown JSON members discarded during unmarshal cannot participate in the recomputed v2 hash. The compatibility note about sparse hashing documents the behavior but does not refute it.
ref 2a793fdf90c9742e91276ae5211ef7b8bcef5f60e632b1da2670a02cc6b55c2f#R4
IsLedgerDir treats ANY existing directory as a ledger directory, so a mistyped `--db /home/me` or `--db .` is not refused but silently converted into one — writeLedgerFile MkdirAll's a `scans/` subdirectory inside it and starts a new chain there — with no check that the path was ever a ledger directory.
IsLedgerDir returns true for any existing directory without checking for a scans subdirectory, ledger entries, or a marker. PushBundle selects the ledger writer from that result, and placeEntry creates scans with MkdirAll. I checked the surrounding call path for a separate initialization or confirmation guard and found none; thus an arbitrary existing directory is accepted and initialized as claimed.
ref 2a793fdf90c9742e91276ae5211ef7b8bcef5f60e632b1da2670a02cc6b55c2f#R5
PushLedgerDir gates source custody for scan entries on what the entry actually carries (`withSource && e.Bundle.SourcePushed`, the R4 fix) but passes `withSource` straight through for review entries, so insertReviewEntry writes `corral_reviews.source_pushed = true` for a review whose findings carry no scripts or output at all — the same unearned custody claim R4 names, at the door R4 did not close.
The scan branch intersects the requested withSource value with e.Bundle.SourcePushed, but the review branch passes withSource directly to pushReviewEntryTx. insertReviewEntry then writes that value unchanged into corral_reviews.source_pushed even if every script and output string is empty and therefore inserts no source bytes. No review-entry custody discriminator equivalent to Bundle.SourcePushed exists in this path.
ref 2a793fdf90c9742e91276ae5211ef7b8bcef5f60e632b1da2670a02cc6b55c2f#R6
PushPlan.Retracted is incremented for retracted entries of every kind, but `corral ledger push` prints it as "N retracted scan(s) left out", so a retracted review or adjudication is reported to the operator as a retracted scan.
PushLedgerDir increments PushPlan.Retracted before its kind switch, so the count includes retracted scans, reviews, and adjudications. runLedgerPush renders the entire count as "retracted scan(s) left out." The nearby comment explicitly says entries of every kind are filtered, confirming rather than narrowing the mismatch.
ref 2a793fdf90c9742e91276ae5211ef7b8bcef5f60e632b1da2670a02cc6b55c2f#R7
Recorded so that "found nothing here" can be told apart from "never looked here".
This package is unusually well-defended at the doors it has decided to defend, and that is exactly where its failures live: the same integrity rule is enforced at two doors and skipped at two others. `AppendLedgerEntry` (ledgerdir.go:387) and `WriteCheckpoint` (ledgerdir.go:781) both refuse to touch a ledger directory that does not verify, and each carries a comment explaining why the alternative is laundering. `PushLedgerDir` and `LoadDir` — the two verbs that turn a directory into something a stranger reads (`corral ledger push`, `corral verify --db <dir>`, `corral seal --db <dir>`, the MotherDuck share) — run no chain check at all, so an entry whose bytes no longer match its hash is published as the record and stamped onto rows as `entry_hash` (finding 1, reproduced: the warehouse ends up holding a kill rate of 0.99 for an entry that was signed at 0.42, while `VerifyLedgerDir` on the same directory says "entry bytes do not match its hash"). Finding 2 is the same asymmetry one level up: retraction is honoured by every in-package reader and by `LoadDir`, and is invisible in the warehouse — after a retraction the same `corral_seal` query returns 0 rows over the directory and 1 row over the warehouse pushed from it, and there is no column anywhere in the schema that could be used to exclude it. Finding 3 is about what `Problem` does not say: an unsigned entry in an otherwise-signed chain sets no `Problem`, so the two gates that consume `Problem` — including the one verb that deletes evidence — treat it as sound, and `WriteCheckpoint` will prune a signed history and enshrine the unsigned entry as the checkpoint's `Head`. Unsigned entries are documented as legal; the defect is that the gates cannot tell an operator that the head they are about to make permanent is one. The rest (4–7) are code-read observations, the sharpest of which is that `EntryHash` for corral-ledger-2 ignores the file's bytes entirely and re-marshals this binary's structs. What I could not break is listed under sound: the column/placeholder alignment across all five grains, the migration lists, the corral-ledger-4 keyid binding and its downgrade, the retraction reverse-pass at depth 4, and the slice-cloning in the three bundle transforms all held up under close reading.
The review survives adversarial verification. R1–R3 are directly demonstrated by scripts matching their stated inputs and outcomes. R4–R7 accurately describe the current code; comments explain some design intent but do not negate the claimed behavior.