Skip to content

fix: give the remediation search a route when the runtime exposes no search tool - #244

Closed
topij wants to merge 1 commit into
mainfrom
fix/remediation-search-route
Closed

fix: give the remediation search a route when the runtime exposes no search tool#244
topij wants to merge 1 commit into
mainfrom
fix/remediation-search-route

Conversation

@topij

@topij topij commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Found by running the remediation check for real — on cs-toolkit, converting its
forked /session-start adapter to a pointer at this workflow — rather than by
reading it.

The defect

The step said to search the archives using "your runtime's own file-search
facility, passing the subject as a parameter."

A session with no such tool exposed — only MCP search for unrelated systems —
has no route at all. Its preferred route is unavailable and its stated fallback is
to read #241 and treat building a shell command as its own problem. That is a
description of a hazard, not an instruction, and an agent in that position is
stuck with a step it cannot perform.

What the withdrawal got wrong

#241 was right about the shell and wrong about its scope. It treated every
subject as equally dangerous. The risk is actually a property of where the subject
came from
:

subject source shell search
cron job name, branch, path, scope your own repo / config fine — operator-controlled
issue title, PR title a tracker that may accept issues from outside not fine — this is the case #241 is about

All three recipes #241 records were defeated by title inputs: an apostrophe, a
bare quote, a heredoc delimiter line. None of those arrives in a job name you wrote
into your own automations.yaml.

So the step now splits on provenance, which restores a usable route for most
candidates without reopening anything.

For the tracker case it also says to match on the candidate's id rather than
its title where possible. An id is the part whose shape you control, and it is what
actually identifies the item.

This is measured, not reasoned

The live run on cs-toolkit exercised three different branches of the step:

candidate outcome
nightly-fixer dropped — a dated diagnosis at friction-log-archive.md:199, plus "DISABLED 2026-07-19 (monthly spend cap)" in its own status line
weekly-slides stands — archive hits were about a JSON handoff and a headline follow-up, not the skip; the generic-subject case the step warns about
CUS-1107 replaced by CUS-1110 — the residual-follow-up rule firing

Every subject that worked was an id or a config-derived name. Not one was a
free-text title. That is the evidence behind the split above, and it is also why
the id-first advice is in the step rather than in a comment.

Without this step, nightly-fixer would have rendered as a fresh 🔴 on a job
deliberately disabled two weeks earlier.

Verification

make test → 721 passed. python scripts/kit_doctor.py → 32 unchanged, 0 differ,
0 missing.

Neither executes this prose. The cs-toolkit run is the only thing that did, and
it is what produced the finding — which is the argument for that step of the
sequence existing at all.

#241 stays open: nothing here makes a shell recipe for tracker titles safe, and its
constraints are unchanged.

…search tool

Found by running the step for real, on cs-toolkit, rather than by reading it.

The step said to search the archives with "your runtime's own file-search
facility, passing the subject as a parameter". A session with no such tool exposed
— only MCP search for unrelated systems — has no route at all: the preferred one
is unavailable and the stated fallback is to read #241 and treat building a shell
command as its own problem. That is a description of a hazard, not an instruction.

The withdrawal in #241 was right about the shell and wrong about its scope. It
treated every subject as equally dangerous. In practice the risk is a property of
WHERE THE SUBJECT CAME FROM:

- a cron job name, a branch, a path, a scope — from your own repo or config,
  operator-controlled, and an ordinary shell search is fine
- an issue or PR title — from a tracker that may accept issues from outside, which
  is the attacker-influenceable case #241 is actually about

That distinction restores a usable route for the majority of candidates without
reopening anything. The three recipes #241 records were all defeated by *title*
inputs — an apostrophe, a quote, a delimiter line — and none of those arrive in a
job name you wrote in your own automations.yaml.

For the tracker case the step now also says to match on the candidate's ID rather
than its title where possible. An id is the part whose shape you control, and it
is what actually identifies the item: the live run that found this defect matched
`CUS-1107` and `nightly-fixer`, never a free-text title.

MEASURED, not assumed. Running the step on cs-toolkit exercised three different
branches of it: `nightly-fixer` (dropped — a dated diagnosis in the archive plus
a "DISABLED … monthly spend cap" note in its own status line), `weekly-slides`
(candidate stands — the archive hits were about something else, which is the
generic-subject case the step warns about), and `CUS-1107` (replaced by CUS-1110,
the residual follow-up rule firing). Every subject that worked was an id or a
config-derived name.

VERIFIED: `make test` 721 passed; `python scripts/kit_doctor.py` 32 unchanged, 0
differ, 0 missing. Neither executes this prose; the run on cs-toolkit is the only
thing that did, and it is what produced the finding.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@topij, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6cda0079-8973-42f4-ab17-69370c6c6455

📥 Commits

Reviewing files that changed from the base of the PR and between e49ddf3 and 1c0b924.

📒 Files selected for processing (2)
  • docs/agentic-dev-kit/workflows/session-start.md
  • kit-manifest.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@topij

topij commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Closing unmerged, under a bound declared before the second lens reported

Both panel lenses returned HIGH/Major on the same paragraph, independently.

1 · The "operator-controlled ⇒ shell is fine" bucket is not safe, disproven on its own example. A lens ran git check-ref-format --branch against six crafted names: git accepts ;, &&, |, ", ', backtick and $() in a branch name. A maintainer checking out a fork's PR branch gets that attacker-chosen name — and git branch --show-current is in this workflow's own gather step. The carve-out put an attacker-controlled string in the bucket that needs no guidance.

The other lens reached the same class from the opposite end, splicing backup-$(touch INJECTED_FILE) as a job name into command text and executing it. Its statement of the flaw is the durable part:

"Operator-controlled answers who typed the string; it says nothing about what characters it contains, and the shell only cares about the latter."

My live-run evidence — nightly-fixer, weekly-slides, CUS-1107 — was three happy-path slugs. It never tested the metacharacter classes #241 explicitly required, and I cited it as though it had.

2 · It contradicted unedited text three lines below. The doc still says the subject is "candidate-derived text — a ticket title, a PR title, a job name". Both lenses flagged it, and both judged the result worse than the pre-PR state, which was one unambiguous rule. I edited the paragraph stating the new rule and not the one stating the old one.

3 · The fallback-within-a-fallback had no exit for a tracker subject with no parameter tool — it points at a tool the branch has already said doesn't exist.

4 · This repo's doctrine predicted it. safety-critical-changes.md rule 1: "treat 'we tightened the matcher' as a stopgap, not a fix." A provenance classifier over "build a shell command from arbitrary text" is that shape.

Why closed rather than fixed

This is the fourth round on one paragraph across two PRs — three recipes withdrawn in #241, then this classifier. My obvious next move was a fifth version testing characters instead of origin, which is a tightened matcher too.

I declared the bound before the adversarial lens reported: another Major on this paragraph ⇒ close unmerged and refile rather than write a fifth version. Two arrived. #230 set the precedent last session and the reason holds — a bound honoured only when convenient is not a bound.

What survives

The defect is real and stays open. A runtime with no search tool still has no route through this step. Refiled as #245 with every finding, the constraints any next attempt must meet, and the observation that fail-closed is probably cheapest: "no parameter tool ⇒ skip this candidate's archive check and say so in the briefing" needs no shell guidance at all, and is the only option that adds no rule for a later round to hole.

Also recorded there: a lens mutated the classification in an isolated copy and make mutation-test reported 720 passed, 1 deselected — the mutation survived. Nothing pins this property; the panel is the only check it gets.

Separately, CodeRabbit found a genuine fail-open in the same file on cs-toolkit's copy — a failed gh pr list is indistinguishable from an empty list, so the briefing renders an all-clear PR bucket off a command that never ran. Unrelated to the search route, and it blocks a downstream consumer, so it goes in its own PR.

Process note: the first copy of this comment landed on the wrong repository — my working directory was cs-toolkit, so gh resolved 244 against in-parallel-oy/cs-toolkit and commented on an unrelated merged PR there. Deleted (confirmed 404) and reposted here with --repo pinned. Recording it because it is the cd-drift class this repo already tracks, pointed at a forge write rather than a file write.

@topij topij closed this Aug 2, 2026
@topij
topij deleted the fix/remediation-search-route branch August 2, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant