Repository navigation
verify-gguf.md presents the unkeyed .sha256 sidecar as a signature and promises a tampered GGUF 'never reaches' the runtime (CR-A09-007) #446
Description
Wave 1 · theme D1-8: Docs describing features that do not exist (doc side of code findings) · highest severity High · 1 finding(s) · effort medium
Summary
A hub publisher or a writer to the shared export directory modifies the GGUF and writes a matching .sha256. The CI gate 'verify-gguf ... | jq -e .valid' passes with exit 0, and the page told the operator a tampered GGUF would never reach the runtime and that the sidecar is what the sender 'signed'.
Primary finding
CR-A09-007 — verify-gguf.md presents the unkeyed .sha256 sidecar as a signature and promises a tampered GGUF 'never reaches' the runtime
severity High · reachability default · intent contract-violation · confidence confirmed · independently confirmed · reproduced end to end · already tracked: KNOWN-OPEN TRUST-04 — docs/roadmap/phase-16-trust-surface-hardening.md:423-424 (S5 open)
What happens. verify-gguf.md:8 (TR:8) says to use verify-gguf as the final pre-serve gate 'so a corrupted or tampered GGUF never reaches llama.cpp, Ollama, vLLM, or LM Studio'. Line 14 (TR:14, 'imzalanan') tells operators who receive a GGUF from a third-party trainer or hub that recomputing the layers detects 'drift between what they sent and what they signed'. The sidecar is a plain, unkeyed SHA-256 read from <path>.sha256 next to the file (_gguf.py:104-151). There is no HMAC or signature, unlike the audit log's FORGELM_AUDIT_SECRET. Anyone who can write the GGUF can rewrite the sidecar, and a third party that sends both files controls both. The verifier therefore detects transit or disk corruption only, not adversarial tampering by the artefact producer (IN-10, the producer is the adversary). The page admits the regenerate-sidecar bypass at line 164 but keeps the stronger claims at lines 8, 14 and 43 ('exit 6 (post-export tamper)'). This is a doc overclaim about a verify-* gate. The no-sidecar-by-default half is the already-reported 'export never writes the sidecar' finding and is not repeated here.
Where.
docs/usermanuals/en/deployment/verify-gguf.md:14docs/usermanuals/en/deployment/verify-gguf.md:51docs/usermanuals/tr/deployment/verify-gguf.md:14forgelm/verify/_gguf.py:104-151docs/usermanuals/en/deployment/verify-gguf.md:112(also claims 'nothing was tampered with' when the sidecar matches)docs/usermanuals/en/deployment/verify-gguf.md:164forgelm/verify/_gguf.py:17docs/usermanuals/en/deployment/verify-gguf.md:141forgelm/verify/_gguf.py:175-181forgelm/cli/subcommands/_verify_gguf.py:80
Evidence.
- I flipped one byte in the tensor data of a valid GGUF (flip.gguf), regenerated the sidecar with
shasum -a 256 flip.gguf > flip.gguf.sha256, and ranfpy -m forgelm verify-gguf flip.gguf. Result: 'OK: flip.gguf / GGUF magic OK, metadata parsed, SHA-256 sidecar match', and valid=True maps to exit 0 (_verify_gguf.py:125). With the sidecar removed, the same file and a file with 1000 appended bytes also give valid=True.
Recommended fix. Reword lines 8, 14 and 43 (EN+TR): the sidecar detects accidental corruption, not tampering by anyone who can write the sidecar, and it is not a signature. Optionally add a keyed digest (HMAC with FORGELM_AUDIT_SECRET), as the audit chain has.
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
Labels
Type
Projects
- StatusShow more project fieldsTodo
Activity