Stephen Hemminger <[email protected]> writes:

> On Tue,  9 Jun 2026 14:26:47 -0400
> Aaron Conole <[email protected]> wrote:
>
>> Add an iterative tool-use loop to review-patch.py for the Anthropic
>> and OpenAI providers. The reviewer can now look up additional context
>> from the DPDK source tree when the patch alone is insufficient,
>> rather than having to guess at surrounding code, API contracts, or
>> function signatures.
>> 
>> Tool calling is enabled by default with a limit of 10 rounds. Pass
>> '--tool-rounds 0' to disable it and restore the previous single-shot
>> behavior.  The round limit prevents runaway cost on large patches
>> that when reached will force the model to deliver a final judgement.
>> 
>> Initial tool set:
>>   - grep           Searches for regex across the file system with
>>                    optional path restrictions and case-insensitive
>>                    matches.
>>   - file_read      Line range read of a specific path.
>> 
>> Both tools are limited to the repository root to prevent path
>> traversal.  Path outputs are relative to the repo root.
>> 
>> The system prompt is extended when tool calling is active to
>> encourage the model to use tools only when genuinely needed,
>> keeping unnecessary round trips and token costs under control
>> and to a minimum.
>> 
>> Internally, _common.py gains send_request_raw() (returning the
>> raw response dict) so the tool-calling loops can inspect
>> stop_reason / finish_reason before extracting text.
>> 
>> Signed-off-by: Aaron Conole <[email protected]>
>> ---
>
> Well AI saw the security bypass here...
>
> Error
> =====
>
> devtools/ai/review-patch.py: path containment check is bypassable
> (_tool_grep and _tool_file_read)
>
>       repo_resolved = Path(repo_root).resolve()
>       search_path = (repo_resolved / rel_path).resolve()
>       if not str(search_path).startswith(str(repo_resolved)):
>               return "Error: path is outside the repository"
>
> str.startswith() on the resolved path string is a prefix match, not a
> path-component match. If repo_root resolves to /home/me/dpdk, then
> /home/me/dpdk-private/secrets passes the check, since the string starts
> with "/home/me/dpdk". The commit message states both tools are "limited
> to the repository root to prevent path traversal" -- that property does
> not hold for sibling directories sharing the name prefix. Same bug in
> both tool helpers.
>
>       # is_relative_to (3.9+), raises nothing, no string games
>       if not search_path.is_relative_to(repo_resolved):
>               return "Error: path is outside the repository"
>
> (Absolute rel_path and ../ escapes are already caught by resolve() +
> this fix; only the prefix case is currently open.)
>
>
> Warning
> =======
>
> devtools/ai/_common.py: unrelated reformatting mixed into a feature
> patch. The docstring re-wrapping in print_token_summary(),
> _extract_text(), _print_verbose_usage(), and the classify_review()
> regex reflow in review-patch.py are cosmetic churn unrelated to tool
> calling. Drop them or split into a separate cleanup; they inflate the
> diff and obscure the actual change.
>
> devtools/ai/review-patch.py: grep --include list excludes the files the
> tool prompt points the model at. _tool_grep restricts to
> *.[ch]/*.py/*.rst/*.ini, but TOOL_PROMPT_EXTENSION explicitly directs
> the model to check ".ci/" scripts, meson.build, and MAINTAINERS. Those
> are extensionless or shell, so grep silently returns "No matches found"
> rather than surfacing the omission. Either widen the include set or
> note the restriction in the tool description so the model doesn't draw
> false conclusions from empty results.
>
>
> Info
> ====
>
> xai is OpenAI wire-compatible and already shares build_openai_request()
> elsewhere, but tool calling is gated to provider == "openai" in
> call_api() and the verbose banner. xai (and any future
> OpenAI-compatible provider) falls back to single-shot with no notice.
> Reusing call_api_with_tools_openai() for xai looks free.
>
> send_request_raw() bypasses _extract_text(), which is where the
> "error" in result check lived. The tool loops read stop_reason /
> choices directly, so a provider error returned with HTTP 200 yields an
> empty string and a misleading "clean" review instead of a hard failure.
> Worth a guard on api_result.get("error") at the top of each loop body.
>
> Default-on at 10 rounds is a behavior and cost change for every existing
> caller, and grants repo-wide read to the model by default. Reasonable
> for an RFC to propose, but worth calling out explicitly for the list --
> some CI users may want opt-in rather than opt-out.

I think I've addressed many of the above issues.  Posting the 'PATCH'
version.

Reply via email to