UAT: a newline in a verifier field value silently flips a failed item to pass (phase-gate bypass) #35
Labels
No labels
already-shipped
bug
documentation
duplicate
enhancement
good first issue
help wanted
in progress
invalid
needs-decision
proposal
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: crenshawdev/cadence#35
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: high · Area: uat · surfaced in the post-v1.2.0 cross-model review sweep (reproduced with a fixture)
What happens
A phase's UAT checklist stores each item as plain
field: valuelines, including astatus:line. When the verifier subagent reports results, the free-text it wrote (evidence,reason) is written into the file verbatim. If that text contains a line break followed bystatus: pass, the next parse reads it as a secondstatus:line for that item, and last-assignment-wins overwrites the real verdict. A reported failure silently becomes a pass, and once every item reads pass the phase gate opens and the phase is marked complete.The verdict text comes from a model, and models emit multi-line output routinely, so this needs no adversary: a plausible evidence string is enough to defeat the verification gate the whole method rests on.
Mechanism (verified against source)
parseUat(cadence-core/bin/lib/planning-files.mjs:272-275) walks each item's lines and assignsitem[field] = valuewith no dedup — the laststatus:line in a block wins.renderUat(cadence-core/bin/lib/planning-files.mjs:293-296) writes each value as${k}: ${it[k]}with no newline sanitization, so a multi-line value becomes multiple physical lines.uat merge(cadence-core/bin/planning.mjs:350, 361) copies the verifier'sevidence/reasoninto the item verbatim, thenwriteUat→renderUatpersists it.Reproduction
A verifier gap payload such as:
sets item 1 to
fail, but writes anevidence: saw an errorline followed by a barestatus: passline. On the next read item 1 parses aspass, anduat statusreports the phase complete.Related corruption from the same unsanitized write
###splits into a phantom item.##truncates the item's remaining fields (parseUat:273breaks on##).Fix direction
Sanitize field values on write in
renderUat— strip or escape newlines (and neutralize a leading##/###) so a value can never introduce a new physical line or heading. Optionally hardenparseUatto treat a repeatedstatus:within one item as a defect rather than silently taking the last.