Skip to content

Fork splice: a signed suffix from a sibling continuation of the same log prefix verifies with exit 0 under --require-hmac, which forges the run's gate verdict (CR-A07-016) #464

Open

Description

@cemililik

Wave 2 · theme D2-1: Security and integrity: medium/large effort · highest severity High · 1 finding(s) · effort large

Summary

An output-dir writer holds a failed or reverted run A and a passing sibling run B that grew from a copy of the same dir under the same FORGELM_AUDIT_SECRET. They overwrite A's audit_log.jsonl with prefix + B's suffix and leave the genesis manifest untouched. forgelm verify-audit --require-hmac prints OK with hmac_verified=true and exits 0. A's directory now carries an authenticated record whose last safety event reads passed=True, beside A's artefacts.

Primary finding

CR-A07-016 — Fork splice: a signed suffix from a sibling continuation of the same log prefix verifies with exit 0 under --require-hmac, which forges the run's gate verdict

severity High · reachability default · intent contract-violation · confidence confirmed · independently confirmed · reproduced end to end

What happens. Each audit line is authenticated with a key that depends only on the secret and the line's own run_id (writer compliance.py:258, verifier compliance.py:2429). The genesis manifest pins only line 1's hash and run_id (compliance.py:443-461, checked at 2449-2520). The chain walk (2523-2578) only checks that each line's prev_hash matches its predecessor. When two runs append to copies of the same output dir (a copied checkpoint dir, or --resume-from in two copies of a pipeline dir), both continuations hang validly off the same last common line and share a byte-identical genesis manifest. Replacing run A's suffix (gate failed / model.reverted) with run B's suffix (gate passed) keeps every prev_hash, every HMAC and the genesis pin valid. This is not the same as M-A07-002 (tail truncation): truncation can only delete, while a splice appends authenticated content that contradicts what run A did. A fix for 002 does not close it either: a tail anchor kept beside the log, keyed or not, is forked and swapped together with the suffix. With the same pipeline_run_id on both forks (b2), the spliced log is indistinguishable from a genuine one even by run_id. Nothing a verifier receives says which continuation belongs to this directory, run attempt or model artefact.

Where.

Evidence.

  • repro.log, case (b1): copied output dir, fresh run_ids per process (ForgeTrainer pattern, trainer.py:354). The genesis manifests of forkA and forkB are byte-identical (True). After the splice, verify-audit --require-hmac --output-format json returns {success:true, valid:true, entries_count:6, hmac_verified:true}, exit 0, and the last event is safety.evaluation_completed passed=True. Case (b2): pipeline_run_id 'pl_20261001_fork' on both forks. Splicing B's dpo-passed suffix into A returns exit 0…

Recommended fix. Add a verifier option that pins an externally recorded expectation (run_id or attempt nonce, final head hash). Record the head hash in artefacts that leave the dir (approval event, CI output). Disclose in verify_audit.md and RTP R-04 that the chain proves internal consistency, not which continuation of a forked prefix belongs to a run. Rate this separately from M-A07-002.

Acceptance criteria

  • A regression test reproduces the failure scenario and asserts the corrected behaviour.
  • Documentation listed above is corrected in English and in its Turkish mirror in the same PR.
  • Every finding in this issue is resolved or explicitly closed with a reason.

Found by the October 2026 ForgeLM code and documentation review against commit cab2947. Code links point at that commit; line numbers may have moved since. Finding IDs (CR-…, DOC-…, TECH-…) are stable references for this backlog.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: complianceCode area: compliancebugSomething isn't workingseverity: highWrong behaviour or a false claim users rely onsource: review-2026-10Found by the October 2026 code and documentation reviewwave: 2Roadmap wave 2: high severity, larger effort; next-release tech

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions