Repository navigation
Description
aggregator: yes/no survey answers are never validated — any string (or even an array) is accepted as a yes/no answer
Summary
The Needs-you survey's POST-boundary check (checkActionAgainstItem) promises that survey answers are checked against the published question's options — and it works for single and multi questions. For yesno questions it never fires: any value is accepted and stored as the answer to a yes/no question, including nonsense strings and even an array of both options. The card then renders that value as "✓ answered: banana".
Evidence
Three pieces line up into the hole:
- The sanitizer only attaches
optionstosingle/multiquestions —sanitizeQuestionsinpm-stack/aggregator/src/tier-items.ts:117-134. Ayesnoquestion's options are dropped even when the publisher supplies them (verified: a yesno question published withoptions: [{id:"yes"},{id:"no"}]sanitizes to a question with nooptionsfield). - The boundary check validates a yesno answer only when options exist —
checkActionAgainstItem,tier-items.ts:347:if ((q.type === "single" || q.type === "yesno") && q.options) { ... }. Since yesno questions never carry options after sanitization, the guard is dead code for them. - The renderer knows the real option set —
questionFieldHtmlinpm-stack/aggregator/src/render.ts:963-968synthesizes[{id:"yes",label:"Yes"},{id:"no",label:"No"}]whenq.optionsis absent. The UI can only ever submityes/no; the API accepts everything else anyway.
Verified this run on main b27dbe8e with the verbatim tier-items.ts (sha256 cdb1fd21…, identical on sandbox and laptop) under Bun on the laptop (~/git/fleet/probe-2026-10-27-laneB-yesno; log hidden_files/probe-2026-10-27-laneB-yesno/out.log):
control single bogus value : REJECTED: answer for "audience" must be one of its options
control single valid value : ACCEPTED (null)
control multi string (not array) : REJECTED: answers for "tags" must be chosen from its options
control multi bogus entry : REJECTED: answers for "tags" must be chosen from its options
yesno valid 'yes' : ACCEPTED (null)
yesno BOGUS 'banana' : ACCEPTED (null) <-- bug
yesno BOGUS array ['yes','no'] : ACCEPTED (null) <-- bug
yesno-with-opts BOGUS 'banana' : ACCEPTED (null) <-- bug (sanitizer stripped the options)
text BOGUS array ['a','b'] : ACCEPTED (null) <-- same shape: no per-type value-shape check for text either
Downstream, the bogus value is merged into the card state verbatim (card-state.ts answered map, latest answer wins) and printed as the answered value (render.ts question state line), and it is what the consumer of the answer action receives — a yes/no answer it cannot interpret.
Why it matters
The boundary check is the contract the consumers rely on (#1291/#1293 design: "option and survey membership … checks"). Every other question type is enforced; yes/no is the most common triage question shape ("Go ahead?"), and its answers silently escape validation, so a malformed or hand-crafted POST can record an unanswerable "answer" that marks a required question as answered on the card.
Acceptance criteria
- A
yesnoanswer whose value is not exactly"yes"or"no"(or one of the question's published option ids, if the sanitizer is changed to keep publisher-supplied yesno options) is rejected bycheckActionAgainstItemwith a legible error, in the same style as thesinglecase. - A non-string value (e.g. an array) for a
yesnoorsinglequestion is rejected; an array value for atextquestion is rejected (per-type value shape:single/yesno/text→ string,multi→ string array). - Valid answers still pass:
yes/nofor yesno, a published option for single, a string for text, an option-id array for multi; partial submits (feat(dashboard): multi-question cards — render every question, submit one answers array #1451) are unaffected. - Regression tests in
pm-stack/aggregator/test/needs-user.test.tscover the yesno bogus-value and array cases and the text array case, alongside the existing single/multi controls.
Activity
developing-today commentedon Oct 27, 2026
CODE-557
dezren39 commentedon Oct 27, 2026
🤖 Agent note · filed
7 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up (small_fix)
dezren39 commentedon Nov 1, 2026
🤖 Agent note · handed off (PR opened)
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done (PR opened)
pr_opened.