Skip to content

fix(responses): stop a namespaced MCP exec from authorizing bare shell aliases - #2966

Merged
lidge-jun merged 4 commits into
devfrom
codex/carry-2953-namespaced-exec
Aug 30, 2026
Merged

fix(responses): stop a namespaced MCP exec from authorizing bare shell aliases#2966
lidge-jun merged 4 commits into
devfrom
codex/carry-2953-namespaced-exec

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

Carries #2953 by @luvs01, plus one repair commit. A namespaced MCP tool named exec could lend its
normalization authority to the bare shell aliases apply_patch, exec_command and shell_command,
which the undeclared-tool guard is supposed to reject.

Opened as a maintainer PR because pushing the repair to the contributor branch reset its
review-readiness checklist and returned it to draft; that attestation is the author's. The cherry-pick
preserves @luvs01's authorship across all three original commits.

Carries #2953. Close it as carried once this lands.

Review boundary

This is an authorization boundary, so it wants a security review rather than a routine pass. The
audited properties, each probed directly:

  • Bare exec stays out of the normalization-authority set, and bridge maps cannot restore it.
  • Case, Unicode, separator and nested-namespace variations do not produce a bypass.
  • apply_patch, exec_command and shell_command remain fail-closed with 502.

The repair

The original also rejected a legitimate case: when a namespaced tool is selected through a bare
tool_choice, the bridge creates an unambiguous bare exec alias for it. That alias was discarded
with everything else, so an upstream response calling bare exec returned 502 despite having invoked
the sole declared MCP tool.

An authorized bare selector is now restored to its namespaced identity before the guard runs, using
the request-bounded toolBridgeMaps.toolNsMap. Only an unambiguous single-candidate mapping is
restored; ambiguity still fails closed, and bare exec still holds no normalization authority.

Verification

Based on dev@dca16949b.

  • bun test tests/responses-undeclared-tool-guard.test.ts tests/namespace-tool-compat.test.ts tests/core-lab-boundary.test.ts132 pass, 0 fail
  • bun x tsc --noEmit → clean

The new end-to-end case selects the sole namespaced exec by bare tool_choice and asserts the
upstream bare exec succeeds, alongside assertions that the three shell aliases still return 502.
tests/core-lab-boundary.test.ts is in the gate because src/server/responses/core.ts is a core-path
file that must not reach src/lab.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

luvs01 and others added 4 commits August 30, 2026 11:24
…l aliases

A namespaced tool named `exec` was aliased into the declared-name set under its
bare name as well as its flattened wire name. That bare entry is not just a name:
`normalizeDeclaredToolName` treats `declared.has("exec")` as the switch that maps
an emitted `exec_command`, `shell_command`, or `apply_patch` onto the code-mode
shell tool. An MCP server advertising its own `exec` therefore handed the turn a
bare shell tool the request never declared, and the undeclared-tool guard accepted
all three helper names.

Withhold only that one bare alias when `exec` is declared inside a namespace. The
flattened name still authorizes the tool, a namespaced call still matches by its
full wire name, and every other inner name keeps the bare alias it had before. A
request that separately declares a top-level `exec` is unchanged.
…c alias

`buildToolBridgeMaps` also aliases a namespaced tool under its bare name when the
caller's `tool_choice` selected it unambiguously, which the bridge needs to route
the call back. `refreshUndeclaredToolGuard` merges that map into the same declared
set, so a request whose only `exec` is an MCP tool selected by a bare
`tool_choice` re-entered the normalization switch and the three helper names were
authorized again.

Admit that one alias only when the caller's own catalog declared a bare `exec`.
Selecting an MCP `exec` is not a declaration of the code-mode shell tool. Bridge
routing is untouched, so the namespaced call still returns under its bare selector.
Codex groups ordinary top-level tools under the reserved `functions` namespace.
The namespace parser deliberately lowers those children without an MCP namespace,
but the undeclared-tool collector treated `functions.exec` like an MCP tool and
withheld its bare name.

On a replay continuation, outbound and bridge-map names are intentionally not
allowed to widen the current caller snapshot. The resulting set contained only
`functions__exec`, so legitimate bare `exec` and its normalized helper calls
were refused with 502.

Treat the reserved namespace as the top-level container it is. Genuine MCP
namespaces still withhold the bare exec alias, and a focused replay regression
proves the current catalog authorizes bare exec after history expansion.
A namespaced MCP exec selected through a unique bare tool_choice was kept out of the guard's declared-name set, but its upstream bare call was then rejected with a 502. Restore only the request-bounded bare selector to its namespaced identity before guard inspection while keeping code-mode helper normalization disabled.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 30, 2026 02:25
@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 23 minutes.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fdef9b63-4daa-4950-82bf-1a8ffae00388

📥 Commits

Reviewing files that changed from the base of the PR and between dca1694 and 67f0962.

📒 Files selected for processing (5)
  • src/server/responses-undeclared-tool-guard.ts
  • src/server/responses/core.ts
  • src/types.ts
  • src/types/tools.ts
  • tests/responses-undeclared-tool-guard.test.ts

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T02:29:07.730501Z 67f0962 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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 Aug 30, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67f09620be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +3645 to +3648
? [[alias, {
namespace: identity.namespace,
name: identity.name,
kind: identity.freeform ? "custom" as const : "function" as const,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor call kinds when restoring bare namespace aliases

When a bare tool_choice selects a namespaced function and the routed provider responds with a same-named custom_tool_call (or vice versa), this alias is still restored and accepted. Although the map records kind, restoreRoutedNamespaceCalls matches only the name, after which the undeclared-tool guard sees the authorized namespace__name and relays an item type the client never declared, potentially with the wrong input/arguments shape. Make this restoration conditional on the response item type matching identity.kind, leaving mismatches for the guard to reject.

Useful? React with 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 76 / 80

설명

이 PR은 MCP로 선언된 이름공간 exec가, 요청에 선언하지 않은 맨몸(bare) 셸 별칭 apply_patch / exec_command / shell_command까지 같이 승인하던 권한 경계를 고칩니다. 지금 dev(HEAD dca16949b)의 normalizeDeclaredToolName(src/types/tools.ts)은 카탈로그에 exec가 있으면 위 헬퍼 이름을 exec로 접어 줍니다. 그리고 collectDeclaredWireToolNames / undeclared-tool 가드는 이름공간 도구의 안쪽 이름(bare)도 허용 집합에 넣었습니다. 그래서 MCP something__exec만 선언해도 bare exec가 권한 집합에 들어가고, 그 순간 코드모드 헬퍼 정규화까지 켜져 선언하지 않은 셸 별칭이 통과할 수 있었습니다. 본문이 말하듯 인가 경계라 보안 리뷰 대상입니다.

고치는 축은 두 갈래입니다. responses-undeclared-tool-guard.ts에서는 functions 네임스페이스(와이어 접두 없는 내장)와 진짜 MCP 네임스페이스를 나눕니다. MCP이고 안쪽 이름이 CODE_MODE_EXEC_TOOL_NAME("exec")이면 bare 별칭을 넣지 않고, 다른 안쪽 이름은 예전처럼 bare도 허용합니다. responses/core.ts에서는 buildToolBridgeMaps가 bare tool_choice로 유일한 이름공간 도구를 골랐을 때 만드는 bare 별칭을, 가드 직전에 toolNsMap으로 다시 이름공간 정체성으로 되돌립니다(restoreAuthorizedBareNamespaceToolCalls). bare exec를 선언 집합에 넣는 대신 복원하는 쪽이라, 헬퍼 정규화 스위치가 잘못 켜지지 않습니다. 모호하면 여전히 fail-closed이고, bare exec 자체는 정규화 권한을 갖지 않습니다.

메인테이너 수리 커밋이 기여자 #2953의 “전부 버리기”가 깨뜨린 정당한 경로를 되돌립니다. bare tool_choice로 유일한 MCP exec를 고르면 브리지가 bare exec 별칭을 만드는데, 그걸 선언 집합에서 통째로 빼면 업스트림이 bare exec를 호출해도 502가 납니다. 요청 범위의 단일 후보만 복원하고, 클라이언트가 카탈로그에 bare exec를 직접 선언한 경우에만 그 이름을 권한 집합에 남깁니다. CODE_MODE_EXEC_TOOL_NAMEtypes/tools.ts에서보낸 뒤 types.ts로 재수출해 가드·코어·정규화가 같은 문자열을 쓰게 한 점도 HEAD 카탈로그/도구 경계와 잘 맞습니다. 기반 dev@dca16949b, 테스트 132 통과·tsc 깨끗하다고 적혀 있고, E2E로 bare 선택 + bare exec 성공과 셸 별칭 502를 같이 잠갔습니다. types/config 분할과 무관한 독립 보안 픽이며 #2953을 체리픽으로 실어 옵니다.

라인 - 문제

src/server/responses/core.ts authorizedBareNamespaceToolAliases - toolNsMap에서 alias === identity.name인 항목만 복원합니다. 의도는 “bare tool_choice가 만든 단일 bare 별칭”이지만, 맵에 같은 조건을 만족하는 다른 경로가 생기면 가드 앞에 복원이 넓어질 수 있습니다. 지금 브리지 불변식이 그 조건을 보장하는지 한 번만 확인하면 좋습니다.

src/server/responses-undeclared-tool-guard.ts BUILTIN_FUNCTIONS_NAMESPACE = "functions" - Codex 내장 네임스페이스 문자열을 가드 파일에 하드코딩합니다. 다른 곳의 functions 상수와 어긋나면 내장 도구가 MCP처럼 취급될 수 있으니, 공유 상수로 묶을지 판단이 필요합니다.

src/types/tools.ts CODE_MODE_EXEC_TOOL_NAME - "exec" 문자열을 상수로 모은 것은 좋고, normalizeDeclaredToolName도 같은 상수를 씁니다. 다만 주석/문서에 남은 리터럴 "exec"와 상수 이름 대응을 리뷰어가 한눈에 보게 두면 이후 리네임 실수가 줄어듭니다.

tests/responses-undeclared-tool-guard.test.ts - 대소문자·유니코드·구분자·중첩 네임스페이스 우회와 셸 별칭 502, bare 선택 성공 케이스가 본문 감사 목록과 맞습니다. core-lab-boundary를 게이트에 넣은 이유도(responses/core.ts가 lab로 새지 않게) 타당합니다.

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

  • 인가 경계라서 보안 관점 한 번 더 볼지(본문이 요청한 감사 세 항목: bare exec 권한 집합 배제, 우회 변형, 셸 별칭 502).
  • 랜딩 후 fix(responses): stop a namespaced MCP exec from authorizing bare shell aliases #2953(현재 드래프트)을 landed-via-maintainer로 닫을지.
  • functions 네임스페이스 문자열을 공유 모듈 상수로 올릴지, 가드 로컬로 둘지.
  • bare 복원 조건을 toolNsMap 형태에만 둘지, bridge가 “이번 턴 bare 선택” 플래그를 더 명시적으로 넘길지.

너의 추천

보안 픽으로 dev에 병합하는 쪽을 추천합니다. 실패 닫힘(fail-closed)을 유지하면서 bare tool_choice 정상 경로만 되살린 수리라 기여자 #2953만 단독 병합하는 것보다 이 carry가 맞습니다. 병합 직후 #2953에 Landed via #2966 at <commit>landed-via-maintainer를 달고 닫으세요. Windows/dispatch와 무관한 독립 스택이라 쿼터·프록시 carry들과 병렬로 랜딩해도 됩니다.

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

@lidge-jun
lidge-jun merged commit de4e846 into dev Aug 30, 2026
24 checks passed
@lidge-jun
lidge-jun deleted the codex/carry-2953-namespaced-exec branch August 30, 2026 02:43
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