Skip to content

fix(responses): enforce native injection tool authorization - #5470

Closed
luvs01 wants to merge 3 commits into
lidge-jun:devfrom
luvs01:codex/fix-native-injection-tool-authorization-bypass
Closed

luvs01 wants to merge 3 commits into
lidge-jun:devfrom
luvs01:codex/fix-native-injection-tool-authorization-bypass

Conversation

@luvs01

@luvs01 luvs01 commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Prevent native multi-response delivery from bypassing the request-local undeclared-tool guard, so a model cannot relay or advertise a client-executed tool call the request never declared.
  • Reuse the existing declared-wire catalog logic so non-forward native routes enforce the same request-local tool boundary as the ordinary SSE path.

Changes

  • Add configureToolAuthorization to NativeResponseControl and wire it into passthrough dispatch so native owners receive the resolved request-local declared tool sets.
  • Enforce the request-local catalog in NativeInjectionChannel by validating response.output_item.added, advertised completed calls, and terminal snapshots against the declared tool set before advertising or relaying them.
  • Add a regression test that exercises the public-API native-injection path and verifies a function omitted from the request catalog is rejected.
  • Document the authorization boundary update in the streaming-health structure note.

Testing

  • bun test tests/responses/ws-native-injection.test.ts tests/responses/ws-native-result-continuations.test.ts — 94 pass, 0 fail.

Summary by CodeRabbit

  • Bug Fixes

    • Native tool calls are now checked against the request’s declared tool catalog before being forwarded.
    • Undeclared tool calls are rejected consistently across native response events, preventing invalid events from being relayed.
    • Upstream errors now preserve structured error codes when available, improving error reporting over WebSocket connections.
  • Documentation

    • Clarified native function-result injection behavior and fail-closed validation rules.

@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 5faff35a-bcef-47b0-a602-42da9484c946

📥 Commits

Reviewing files that changed from the base of the PR and between 39143fd and b24bfed.

📒 Files selected for processing (7)
  • src/server/responses/native-injection.ts
  • src/server/responses/native-response-control.ts
  • src/server/responses/passthrough-dispatch.ts
  • src/server/ws-bridge.ts
  • structure/transports/streaming-health.md
  • tests/responses/ws-native-injection.test.ts
  • tests/responses/ws-native-result-continuations.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Native injection now receives the request tool catalog, rejects undeclared native tool calls, and reports structured authorization errors through WebSocket frames. Tests cover rejected and declared calls, and continuation fixtures now provide tool declarations.

Changes

Native tool authorization

Layer / File(s) Summary
Authorization snapshot propagation
src/server/responses/native-response-control.ts, src/server/responses/passthrough-dispatch.ts
The native response control accepts authorization data. Passthrough dispatch forwards the current declared tool and call-type sets.
Native event authorization
src/server/responses/native-injection.ts
Native injection stores the authorization snapshot and rejects undeclared calls in added, advertised, and completed output items.
Structured error reporting and validation
src/server/ws-bridge.ts, tests/responses/ws-native-injection.test.ts, tests/responses/ws-native-result-continuations.test.ts, structure/transports/streaming-health.md
The WebSocket bridge preserves structured error codes. Tests cover rejected and declared calls. Continuation fixtures and documentation include the tool catalog behavior.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Suggested reviewers: lidge-j

Merge Risk: ⚪ Minimal · up to b24bf

Native responses now enforce declared tool catalogs and preserve structured authorization errors; undeclared calls are rejected while declared calls remain supported. The supplied regression coverage passes, so this is mergeable with normal checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 6 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: enforcing tool authorization for native response injection. This matches the authorization logic added in src/server/responses/native-injecti…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 6 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Sep 21, 2026
@github-actions

github-actions Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

✅ READY

  • all PR quality gates passed.

Hygiene

Deterministic PR hygiene checks passed.

@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 73 / 80

평소 SSE 길에서는, 요청에 적어 두지 않은 클라이언트 도구 이름을 모델이 부르면 막습니다. 그런데 네이티브 멀티응답(인젝션) 길은 그 검사를 안 타고 이벤트를 그대로 넘겼습니다. 그래서 카탈로그에 없는 함수 호출을 광고하거나, 클라이언트에게 중계할 수 있었습니다.

이 PR은 그 구멍을 막습니다. 패스스루가 이미 갖고 있던 “이번 요청이 선언한 도구 집합”을 configureToolAuthorization으로 네이티브 채널에 넘깁니다. 채널은 output_item.added, 광고(done), 끝 스냅샷의 output을 같은 가드로 검사하고, 없으면 에러를 던집니다. codex-ws-exchangeobserve가 실패하면 그 프레임을 클라이언트에 넣기 전에 끊습니다. 공개 API 픽스처로 “카탈로그에 없는 함수는 거절” 테스트가 하나 들어갔고, 이어쓰기 테스트는 가드가 켜진 API 경로에서 깨지지 않게 tools를 명시했습니다. base는 dev입니다. tip보다 1커밋 뒤입니다. draft이고 review-ready 라벨이 있습니다. types.ts/config.ts를 나누는 변경은 아닙니다.

라인 src/server/responses/native-injection.ts authorize - 거절할 때 던지는 메시지는 “Native injection response emitted an undeclared client tool.”입니다. 보통 SSE가 쓰는 undeclared_tool_call 코드와 숫자가 다릅니다. 클라이언트·로그는 “도구 가드 거절”이 아니라 “네이티브 인젝션 실패/전송 불명”으로 보일 수 있습니다.

라인 tests/responses/ws-native-injection.test.ts - 새 테스트는 output_item.added 한 장만 봅니다. 끝 스냅샷에만 미선언 호출이 들어 있는 경우, 또는 done만 오는 경우는 코드에 advertise 경로가 있어도 회귀 테스트는 없습니다. 선언된 get_value가 가드가 켜진 공개 API에서 그대로 통과하는 긍정 검사도 이 파일의 새 케이스에는 없습니다(기존 테스트는 기본이 forward라 가드가 꺼져 있습니다).

메인테이너의 판단이 필요한 지점

네이티브 거절도 SSE와 같은 undeclared_tool_call로 맞출지, 지금처럼 인젝션 실패로 묶어도 될지 정해 주세요. forward 인증에서는 가드가 꺼지는 점은 SSE와 같습니다. 그 예외를 네이티브에도 그대로 둘지가 핵심입니다. tip 1커밋 뒤와 draft→Ready는 랜딩 때 보면 됩니다.

너의 추천

방향이 맞습니다. 가드를 재사용하고, 중계 전에 끊는 순서가 맞으며, 공개 API 회귀도 핵심을 잡습니다. 머지해도 됩니다. 가능하면 거절 코드를 SSE와 맞추고, 끝 스냅샷만 있는 미선언 호출 테스트 하나를 더 넣은 뒤 Ready로 올리세요. tip rebase는 막히는 충돌이 없으면 같이 하면 됩니다.

이 댓글은 grok-bot이 작성했습니다

…undeclared_tool_call

The authorize path now throws the same undeclared_tool_call code/message as the SSE guard, and the ws upstream pump preserves a structured error code instead of flattening every pump failure to websocket_protocol_error. Adds regression coverage for undeclared calls arriving only in the terminal snapshot or output_item.done, plus a declared-call positive case on the guarded public-API path.
@luvs01
luvs01 force-pushed the codex/fix-native-injection-tool-authorization-bypass branch from 4fd6dea to 5b955f7 Compare September 21, 2026 20:38
@luvs01

luvs01 commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Review feedback applied on 5b955f74:

  • Rejection code aligned with SSE: authorize now throws undeclared_tool_call with the shared undeclaredToolCallMessage, and the ws upstream pump preserves a structured error code (sendUpstreamError) instead of flattening every pump failure to websocket_protocol_error. Clients now see the same upstream_error/undeclared_tool_call identity as the SSE path.
  • Regression coverage added: undeclared call arriving only in the terminal snapshot (response.completed), undeclared name swapped into output_item.done after a declared added, and a declared get_value call forwarded on the guarded public-API path.

Tests: ws-native-injection + ws-native-result-continuations + ws-steering-completion + ws-steering-smoke = 161 pass. Branch is at dev tip.

@luvs01
luvs01 marked this pull request as ready for review September 22, 2026 01:23
lidge-jun added a commit that referenced this pull request Sep 22, 2026
…Lite catalogs (#5508)

* fix(responses): bound native steering routes and replay memory

Native steering and injection accepted requests on routes that were never
meant to carry them, and their replay buffers grew without an upper bound,
so a long-lived WebSocket exchange could retain an unbounded amount of
app-owned memory. Restrict steering to the routes that declare it, cap the
replay and body sizes, and release the stores when an exchange settles.
Preexisting negative route tests are retained, and the injection docs now
cross-link the steering contract.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(responses): enforce native injection tool authorization

Carry #5470 onto the bounded native-control path. Configure the resolved request-local catalog before attachment, reject undeclared added/done/terminal items before relay, and preserve structured rejection codes. Retain declared rich-result continuations and the existing size and rollback controls.

Local checks: NOT RUN by instruction. Hosted CI remains required.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(subagents): recognize Responses Lite plaintext V2 catalogs

Carry #5492. Recognize the first developer additional_tools catalog only when top-level tools is absent. Preserve opt-in, canonical destination, explicit catalog precedence, conflict checks and response restoration. Include unit and server-boundary regressions and clarify the Chinese catalog wording.

Local checks: NOT RUN by instruction. Hosted CI remains required.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(responses): reject oversized injection before enqueueing

Validate the serialized injection before reserving call identities or queue bytes. A result exceeding the configured body limit is refused synchronously even when another result awaits acknowledgement, leaving the retained socket usable for corrected input.

Add a two-call regression on subscription and public API routes covering the typed refusal, unchanged replay retention, first acknowledgement, corrected second result and final cleanup. Preserve immediate refusal and authorization coverage, and align the public and structure contracts.

Independent static review: PASS. Local checks: NOT RUN by instruction; revised-head hosted CI remains required before integration.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

---------

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@lidge-jun

Copy link
Copy Markdown
Owner

Thank you, @luvs01. This change landed on dev through the consolidation PR #5508 (squash commit 5365429), which carries your commits with a Co-authored-by trailer so the contribution stays attributed to you. Closing this PR as superseded by #5508. If anything from this branch did not make it into the landed version, please point it out and we will pick it up.

@lidge-jun lidge-jun closed this Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants