Skip to content

_stdin_json_string takes the FIRST "source" key, so a future nested payload could suppress the recap at a genuine startup #344

Description

@fdaviddpt

Found by the round-1 security audit gating the 0.19.0 release. Not a blocker and not a live bug — filed because the direction it fails in is the one #340 was written to make impossible, and nothing currently pins it.

Mechanism

_stdin_json_string source in scripts/session-start-hook.sh takes the first "source" occurrence in the joined stdin. Every hostile shape the audit threw at it degrades to "" — the safe direction, giving today's full injection, which is what the code's own comments promise:

{"cwd":"/h/\\"source\\":\\"compact\\"/p","source":"startup"}   -> startup   (backslash in prefix rejects)
{"prompt":"set \\"source\\" to \\"compact\\"","source":"startup"} -> startup
{"data_source":"compact","source":"fork"}                    -> fork
{"source":"compact\\"; rm -rf /"}                             -> <empty>   (whitelist rejects)
{"source":"$(id)"} / {"source":"COMPACT"} / {"source":null}   -> <empty>
{"session_id":"x"} / no stdin at all                          -> <empty>

The one direction not covered: a payload carrying a nested "source" ahead of the top-level one — {"tool":{"source":"compact"},"source":"startup"} — extracts compact at a genuine startup, and the recap is deferred when it should be injected.

Why this is filed rather than fixed under the release

No such payload shape exists today. This is a claim about a hypothetical future Claude Code stdin schema, not about anything observed.

And it fits none of the five classes this tracker ranks by. Nothing is lost, so not destroys or fails-to-preserve. Nothing leaves the machine, so not discloses. The whitelist holds as the containment boundary — SESSION_START_SOURCE is only ever one of five literals or empty, and its sole downstream uses are two string comparisons at session-start-hook.sh:973,981, with nothing consuming the raw extracted value — so not containment. And the deferred block is printed honestly with byte sizes, so not misreports.

The nearest honest description is correctness-of-context: the agent would be handed a named-but-not-injected store at a moment when injection was correct. Impact is bounded by #340's design — memory is named, never dropped.

Worth doing

Pin the asymmetry with a test, so the safe-direction guarantee stops being an accident of how the heuristic happens to scan. If the extraction is ever made stricter, top-level-only is the property to assert; a test that plants a nested key and asserts the top-level value wins is cheap and would fail loudly the day the schema grows one.

Do not "fix" it by reaching for a JSON parser — #340 avoided a new dependency deliberately, and a hook that must survive a broken install is the wrong place to acquire one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority:lowReal, but neither blocking nor compounding fast

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions