Repository navigation
Description
What
`core/src/host/turn-origin.js` promises a fail-closed default: line 18 says
The caller treats "none" as \x27other\x27 (fail closed).
but `originOfSource()` returns `undefined` (i.e. "none") from two very different places:
- line 28-29 area: the two documented "carries no one\x27s request" cases (a `mention` host notice, a handoff note);
- line 43: `default:` — any source `kind` this version does not recognise.
#616 turns "none in the owner\x27s own chat" into "the owner" for the purpose of standing approvals (`approvals.js`, `standingCovers` → `settled(rt, sessionId) === OWNER`, plus the new own-chat clause). So an unrecognised source kind arriving in a Bot\x27s own chat now inherits a standing approval instead of failing closed.
Why this is a real hazard, and not reachable today
Every `kind` the host can currently emit is handled explicitly above the `default:`, so nothing today lands there. The hazard is latent: the next host/DSH source kind (a new notice shape, a relay, a plugin-injected event) is "unknown" by definition on the day it ships, and the one path where `undefined` is now permissive is exactly the own-chat one. The header comment then no longer describes the code, which is how this kind of hole survives review later.
Evidence
- `core/src/host/turn-origin.js:18` — the fail-closed promise.
- `core/src/host/turn-origin.js:43` — `default: return undefined`.
- `core/src/host/approvals.js` — `standingCovers` requires `settled(...) === OWNER`; fix: honour a standing approval on a turn with no verdict (#608) #616 adds the verdict-less own-chat turn as a way to reach OWNER.
- Constructed check: `originOfSource({kind:"some-future-kind"}, bot)` → `undefined` → `settled()` → `undefined` → fix: honour a standing approval on a turn with no verdict (#608) #616\x27s own-chat clause → grant applies, card does not appear.
Options
- Docs only: rewrite the `turn-origin.js:18` header to say what the code now does. Cheapest, but leaves the permissive default.
- Narrow the clause (recommended): in `approvals.js`, apply the own-chat widening only to a turn that took in no message at all (DSH\x27s retry turn), not to a turn whose messages produced "no verdict" for an unrecognised reason. Unknown kinds then stay fail-closed.
- Strictest: make `originOfSource()` return `OTHER` for unrecognised kinds, and keep `undefined` only for the two documented no-request cases. This matches the header exactly.
Acceptance criteria
- A test in `core/test/approvals.test.js` (or `turn-origin.test.js`) feeds a fabricated source kind into a Bot\x27s own chat with a standing grant present: the card is shown, the grant is not applied.
- No regression in the Always allow does not persist: the same permission card comes back after approval #608 case: DSH\x27s retry turn (no message at all) still honours the grant, 17/17.
- `turn-origin.js` header and code agree; if option 2 is chosen, the header states the narrower rule.
Out of scope
#616 itself is correct for the reproduced defect; this is the residual hole its own header promises is closed. Not a blocker for that PR.
Activity
Birfy commentedon Oct 21, 2026
Triage: agent-ready. Security-relevant fail-open, small and testable.
Scope: in
core/src/host/turn-origin.jsan unrecognised sourcekindfalls intodefault:and yieldsundefined— indistinguishable from the two documented "carries no one's request" cases. After #616,undefinedin the Bot's own chat resolves to the owner for standing approvals, so a future source kind would silently inherit a standing grant instead of failing closed.Fix: return
OTHERfromdefault:(an unknown source is not the owner), keepundefinedonly for the documented no-message cases, and make the header comment match the code. Tests: an unrecognisedkindin the Bot's own chat is not covered by a standing grant (rejection case), and the #616 own-chat behaviour (a retry turn with no message at all) still covers it. Not a documentation-only change.Birfy commentedon Oct 21, 2026
Claiming. Branch
fix/620-turn-origin-fail-closed, frommain3995d9b.Option 3 from the issue:
originOfSource()returnsotherfor an unrecognised source kind, andundefinedstays only for the documented no-request cases (DSH's notice kinds listed explicitly, a mention receipt, a handoff note). After #616 that is the difference between "show the card" and "inherit the owner's standing approval".Tests: an unknown kind is
otherinturn-origin.test.js, and the acceptance case ingroup-status.test.js— with analways-botgrant, a turn in the Bot's own chat carrying an unknown source kind still shows the card. Red onmain(19/21), green here (21/21); no regression on the #608 retry turn. Separate PR from #616, which is merged.Cover the context kinds the host and DSH inject in ordinary owner turns
Birfy commentedon Oct 22, 2026
Triaged to
agent-ready— the defect, the option and the file are all settled in the text above; the earlier claim comment is stale (no branch and no PR exist for it), so whoever takes it claims again with a branch name.Acceptance:
originOfSource()incore/src/host/turn-origin.jsreturns'other'for anykindthat falls todefault:;undefinedstays only for the documented no-request cases (amentionhost notice, a mention receipt, a handoff note) — the comment at line 18 ("The caller treats "none" as 'other' (fail closed)") must become true for thedefault:path too.standingCovers(core/src/host/approvals.js), i.e. it must behave likeother, not like the owner. This is the case fix: honour a standing approval on a turn with no verdict (#608) #616 made permissive, so it is the only one worth a regression test.mention/ receipt / handoff cases as "none".core/testgreen at the tip,ds-bot-patches.mjs generatethencheckgreen (this touchescore/, so the patches mirror is part of done).Red evidence to put in the PR body: revert the
default:change and the new own-chat test fails; the other turn-origin tests still pass, which shows the change is narrow.Written-by: Triage
Birfy commentedon Oct 22, 2026
Closing as fixed — the fix is already on main and landed before the 04:04 triage that moved this to
agent-ready; that label is removed with the close. Myagent-readywas wrong: I marked it from the issue text without checking main first, which is exactly the rule the owner re-stated today.Evidence on main
7223a1a1:core/src/host/turn-origin.jsdefault:now readsif (NOTICE_KINDS.has(source.kind)) return undefinedand thenreturn OTHER— an unrecognised kind isother, not "nobody's request".NOTICE_KINDSholds the documented DSH notice kinds (tool,system-prompt,plan-mode,user-approval, the clock/context injections,subagent-settled,compact-checkpoint, …).core/test/group-status.test.js:377(#620: an unrecognised source kind in the Bot's own chat still asks), andcore/test/turn-origin.test.js:30still pins the no-request kinds asundefined.02049004,merged_at 2026-10-21T23:42:54Z.Dev2's local check reproduces the direction: revert
default:toreturn undefined→ 2 of 27 red, both of them this issue's cases.Written-by: Triage
fix(turn-origin): fail closed on a source kind it does not recognise (T…
Restore the ThinkFlowLab#574 and ThinkFlowLab#613 paragraphs the Thin…
Restore the ThinkFlowLab#608 'no verdict at all' paragraph the ThinkF…