Skip to content

fix(responses): notice an unreadable MESSAGE reply, not just NEW_TASK - #3116

Merged
lidge-jun merged 1 commit into
devfrom
codex/3021-message-envelope-detection
Sep 1, 2026
Merged

fix(responses): notice an unreadable MESSAGE reply, not just NEW_TASK#3116
lidge-jun merged 1 commit into
devfrom
codex/3021-message-envelope-detection

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #3021. After an adapter_eof, a delegated subagent's MESSAGE reply reached the parent conversation as a raw gAAAA... payload instead of plaintext or a structured error.

hasUnreadableEncryptedAgentTask decides "unreadable" by stripping the routing envelope and asking whether any plaintext survives. AGENT_MESSAGE_ROUTING_ENVELOPE matched only NEW_TASK, so a MESSAGE header was never stripped and counted as surviving text — a reply whose entire body was one Fernet token measured as readable and was forwarded verbatim.

Measured on dev with a structurally valid Fernet token, before the change:

NEW_TASK -> true   (detected as unreadable)
MESSAGE  -> false  (#3021's case: forwarded)

and after:

NEW_TASK -> true
MESSAGE  -> true

Detection, not recovery

This widens the envelope pattern used by the unreadability check. recoverEncryptedAgentTask stays NEW_TASK-only, and that boundary is the point.

Recovery decrypts. Decrypting a MESSAGE on the parent's behalf would build a plaintext oracle out of a payload the parent's session may have no entitlement to read — the reporter's own expected behavior says decryption should happen "only in the owning account/session context". Letting the proxy notice that what it is about to forward is unreadable ciphertext is a different and much smaller thing, and it is the half the report actually asks for: fail closed with a structured error rather than paste the token.

The reporter withheld the ciphertext, correctly. None was needed — structurallyValidFernetTokens already existed, so the defect was reproducible from the wire shape alone.

Verification

bun test tests/v2-agent-message-failfast.test.ts tests/agent-task-recovery.test.ts
  -> 42 pass / 0 fail / 125 expect()
bun x tsc --noEmit -> exit 0

Mutation: reverting the pattern to NEW_TASK-only gives 21 pass / 2 failblocks a MESSAGE reply envelope followed only by a Fernet payload and blocks a MESSAGE envelope carried inside the encrypted slot itself. Restored to 23/0.

The third new test is the control: a MESSAGE reply that carries real text alongside a token stays readable in both directions. Widening the envelope must not turn every agent reply into a blocked one, only the ones with nothing left after the header comes off.

tests/agent-task-recovery.test.ts is included to show the recovery path is unchanged.

Checklist

  • Focused tests for the changed subsystem pass
  • Recovery-path tests included and unchanged
  • bun x tsc --noEmit clean
  • Regression tests present and mutation-verified, with a control
  • No docs-site change needed (internal payload classification)

Triaged in the 2026-08-31 non-priority-70 bug round.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of encrypted agent messages with MESSAGE routing headers.
    • Unreadable encrypted content is now detected consistently in delegated-agent replies, including when embedded within message content.
    • Valid text replies remain readable and are no longer incorrectly treated as unreadable payloads.
  • Tests

    • Added coverage for encrypted message replies, embedded routing envelopes, and readable text responses.

#3021: after an adapter_eof, a delegated subagent's MESSAGE reply reached the
parent conversation as a raw gAAAA... payload instead of plaintext or a
structured error.

hasUnreadableEncryptedAgentTask decides "unreadable" by stripping the routing
envelope and asking whether any plaintext survives. AGENT_MESSAGE_ROUTING_ENVELOPE
matched only NEW_TASK, so a MESSAGE header was never stripped and counted as
surviving text -- a reply whose entire body was one Fernet token measured as
READABLE and was forwarded verbatim.

Measured on dev before the change, with a structurally valid Fernet token:

  NEW_TASK -> true   (detected)
  MESSAGE  -> false  (#3021's case, forwarded)

and after:

  NEW_TASK -> true
  MESSAGE  -> true

This is the DETECTION half only. Recovery stays NEW_TASK-only, deliberately:
recoverEncryptedAgentTask decrypts, and decrypting a MESSAGE on the parent's
behalf would build a plaintext oracle out of a payload the parent's session may
have no entitlement to read. Widening the strip only lets the proxy notice that
what it is about to forward is unreadable ciphertext, which is what the report
asks for -- fail closed rather than paste the token.

Mutation-checked: reverting the pattern to NEW_TASK-only fails exactly the two
new MESSAGE tests (21 pass / 2 fail). The control test -- a MESSAGE reply that
carries real text alongside a token -- stays readable in both directions, so
this does not turn every agent reply into a blocked one.

Closes #3021.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 31, 2026 19:44
@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 31, 2026
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 00568a12-313b-488d-a80c-14c0fdd63d67

📥 Commits

Reviewing files that changed from the base of the PR and between 42ad9c4 and 2c33031.

📒 Files selected for processing (2)
  • src/server/responses/encrypted-payload.ts
  • tests/v2-agent-message-failfast.test.ts

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


📝 Walkthrough

Walkthrough

The unreadable encrypted-task detector now recognizes MESSAGE routing envelopes in addition to NEW_TASK. Tests cover separate and concatenated ciphertext, plus readable MESSAGE replies.

Changes

Encrypted MESSAGE detection

Layer / File(s) Summary
Broaden routing-envelope matching
src/server/responses/encrypted-payload.ts:178-195
AGENT_MESSAGE_ROUTING_ENVELOPE now matches NEW_TASK and MESSAGE headers when detecting unreadable encrypted payloads. Recovery behavior remains limited to NEW_TASK.
Validate MESSAGE reply handling
tests/v2-agent-message-failfast.test.ts:35-38, tests/v2-agent-message-failfast.test.ts:142-180
Adds a MESSAGE envelope fixture and tests that block separate or concatenated Fernet ciphertext while allowing a MESSAGE envelope followed by readable text.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 2c330

This localized change prevents ciphertext-only MESSAGE replies from being forwarded as raw payloads while preserving readable replies and existing recovery behavior; no actionable merge-blocking risk remains.

Suggested reviewers: ingwannu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. 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 describes the primary change: unreadable MESSAGE replies are detected in addition to NEW_TASK replies. It is concise and specific.
Linked Issues check ✅ Passed The changes address issue #3021 by detecting MESSAGE envelopes that contain only unreadable Fernet ciphertext and failing closed instead of forwarding raw gAAAA... payloads. The change preserves NEW_T…
Out of Scope Changes check ✅ Passed The changes are limited to encrypted-payload detection in src/server/responses/encrypted-payload.ts and regression tests in tests/v2-agent-message-failfast.test.ts. No unrelated production behavior or…
Full details: Linked Issues check

Explanation

The changes address issue #3021 by detecting MESSAGE envelopes that contain only unreadable Fernet ciphertext and failing closed instead of forwarding raw gAAAA... payloads. The change preserves NEW_TASK-only recovery, which supports the requirement to decrypt only within the owning account/session context. The new tests cover ciphertext-only MESSAGE replies, nested MESSAGE envelopes, and readable plaintext replies.

Full details: Out of Scope Changes check

Explanation

The changes are limited to encrypted-payload detection in src/server/responses/encrypted-payload.ts and regression tests in tests/v2-agent-message-failfast.test.ts. No unrelated production behavior or unrelated files are included.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/3021-message-envelope-detection

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

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-31T19:47:17.252340Z 2c33031 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.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 68 / 80

설명

이 PR은 이슈 #3021을 지금 dev HEAD 42ad9c44d에서 고칩니다. 패키지는 2.39.0이고, 바로 앞에 들어간 것은 #3110(가득 찬 5시간 burst 창을 소진으로 보기)입니다. round-2 prio≥70에서 남은 것은 #3008과 #3019입니다. 이 PR은 그 열차 밖입니다. 8월 31일 비우선70 버그 라운드에서 잡은 응답 경로 구멍입니다. 기여자 PR은 없고, 작성자는 메인테이너 본인입니다. types.ts/config.ts 분할과 무관하고, 같은 주제로 열린 다른 PR도 없습니다.

#3021이 말한 장면은 이렇습니다. Codex App이 로컬 프록시를 타고 네이티브 OpenAI Responses로 부모 작업을 돌리다가, 위임한 자식 에이전트가 있습니다. 부모 턴이 adapter_eof로 끊깁니다. 그 뒤에 같은 자식이 보낸 MESSAGE 답이 부모 대화에 그대로 올라오는데, 내용이 읽을 수 있는 글이 아니라 gAAAA...로 시작하는 Fernet 암호문입니다. 머리글(Message Type, Task name, Sender, Payload)은 멀쩡하고, 몸통만 암호문입니다. 제보자는 암호문을 붙이지 않았습니다. 그건 맞습니다. 토큰 모양이 구조적으로 유효한지만 보면 재현됩니다.

지금 체크아웃의 hasUnreadableEncryptedAgentTask(src/server/responses/encrypted-payload.ts 186–235행)는 “읽을 수 없다”를 이렇게 정합니다. 맨 끝 agent_message에서 Fernet 덩어리를 걷어 내고, CXC 제어 문단과 라우팅 봉투를 지운 다음, 남는 글자가 없으면 읽을 수 없다고 봅니다. 봉투를 지우는 정규식이 AGENT_MESSAGE_ROUTING_ENVELOPE(지금 HEAD 178행)인데, 여기가 NEW_TASK만 봅니다. MESSAGE 머리글은 지워지지 않고 “살아 남은 글”로 잡힙니다. 몸통이 Fernet 하나뿐인 답도 읽을 수 있다고 나오고, 프록시는 그걸 그대로 넘깁니다. 본문이 측정한 값입니다. 고치기 전 NEW_TASK → true, MESSAGE → false. 고친 뒤 둘 다 true.

고치는 범위는 그 정규식 하나입니다. NEW_TASK|MESSAGE를 같이 지웁니다. 복호화는 건드리지 않습니다. 복호화는 src/server/responses/agent-task-recovery.ts 71행의 ROUTING_HEADER가 따로 하고, 여기는 여전히 NEW_TASK만입니다. findEnvelopeMESSAGE를 못 찾으면 admittedRecovery가 null을 주고, 업스트림 복호화 요청도 안 나갑니다. 부모 세션이 읽을 자격이 없는 답을 프록시가 풀어 주면, 그 자체가 평문 창구가 됩니다. 제보자의 기대도 “소유한 계정/세션에서만 복호화”입니다. 이 PR은 넘기기 직전에 “이건 암호문이다”만 알아채서, 라우트된 공급자면 구조화 오류(unreadable_encrypted_agent_task, core.ts 1654–1664행, 3047–3048행)로 닫습니다. 네이티브 ChatGPT 경로에는 암호 작업을 그대로 보냅니다. 그건 예전부터 NEW_TASK와 같은 규칙입니다.

테스트는 tests/v2-agent-message-failfast.test.ts에 세 개가 더해집니다. 분리된 칸의 MESSAGE+Fernet 차단, 한 칸에 봉투와 토큰이 붙은 모양 차단, 그리고 진짜 글이 있으면 막지 않는 대조입니다. 본문은 패턴을 NEW_TASK만으로 되돌리면 앞의 두 개가 빨개진다고 적었습니다. 복호화 쪽 tests/agent-task-recovery.test.ts는 실행만 했고 파일은 안 바꿉니다. 점수는 68입니다. 부모 대화에 암호문이 보이는 실제 버그이고, 가드(#2850/#2851)의 구멍이며, 복호화 경계를 넓히지 않은 선택이 맞습니다. 다만 adapter_eof 자체는 안 고치고, 네이티브 경로의 전달은 그대로이며, HTTP 400과 복호화 거절을 잠그는 테스트가 없어서 70 근처로 올리지 않았습니다.

라인 tests/v2-agent-message-failfast.test.ts 138-167 - CXC 테스트만 handleResponses로 400과 본문에 gAAAA가 없음을 확인합니다. 새로 넣은 MESSAGE 세 테스트는 hasUnreadableEncryptedAgentTask만 부릅니다. #3021이 원하는 계약은 “토큰을 붙여 넣지 마라”입니다. MESSAGE에도 그 post() 쌍이 있으면 사용자에게 보이는 쪽이 잠깁니다
경로 tests/agent-task-recovery.test.ts - 본문은 복호화가 그대로라고 이 파일을 돌렸습니다. MESSAGE 봉투를 넣었을 때 recoverEncryptedAgentTask가 false이고 업스트림을 안 치는 테스트는 없습니다. 나중에 두 정규식을 하나로 합치면 MESSAGE가 평문 창구가 됩니다. 거절 테스트가 그 경계를 잠급니다
라인 src/server/responses/core.ts:3045-3048 - 최종 라우트가 네이티브 OpenAI가 아닐 때만 400을 줍니다. 네이티브는 읽을 수 없는 작업도 ChatGPT로 보냅니다. #3021 환경은 openai-responses, forward, pool입니다. 산 제보가 라우트 재시도가 아니라 네이티브가 암호문 MESSAGE를 그대로 돌려준 경우라면, 이 PR은 그 출력 쪽을 못 막습니다. 입력 분류 구멍은 닫습니다
경로 src/server/responses/agent-task-recovery.ts:71 ROUTING_HEADER - 탐지 정규식과 복호화 정규식이 이제 서로 다른 타입 집합을 봅니다. 의도입니다. 이름이 비슷해서 나중에 공유하기 쉽습니다. 주석이 탐지 쪽에만 있고 복호화 쪽 상수에는 없습니다
경로 src/server/responses/encrypted-payload.ts AGENT_MESSAGE_ROUTING_ENVELOPE - 지금 HEAD 178행은 NEW_TASK만 봅니다. 이 PR이 주석과 함께 MESSAGE를 넣습니다. 탐지와 복호화를 나눈 설명이 주석에 있어서, 한 줄 패치치고는 이유가 분명합니다

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

  • MESSAGE의 handleResponses 400 테스트와 복호화 거절 테스트를 이 PR에 넣을지, 후속으로 둘지
  • #3021을 이 랜딩으로 닫을지. 입력 분류는 닫히고, 네이티브가 eof 뒤에 암호문 MESSAGE를 돌려주는 출력 쪽은 남을 수 있습니다
  • 네이티브에서도 MESSAGE+Fernet만 있는 답을 400으로 닫을지. 지금은 NEW_TASK와 같이 네이티브로 보냅니다. ChatGPT가 부모 세션에서 MESSAGE를 못 풀면 예전 유출이 남습니다
  • round-2 남은 [Bug][Windows]: dashboard update aborts after proxy stops when history restore exits non-zero #3008 / #3019보다 이 패치를 먼저 넣을지. 파일은 안 겹칩니다

너의 추천
머지 쪽으로 진행하는 것을 추천합니다. 탐지와 복호화를 나눈 선택이 맞고, 정규식 한 줄로 가드 구멍을 닫습니다. HTTP 400과 복호화 거절 테스트는 있으면 더 좋고, 없어도 지금 단위 테스트와 core.ts 분기가 같습니다. 블로커로 보지 않습니다. Protect dev 리뷰 후 넣고, 머지 커밋이 정해지면 #3021을 닫되, 네이티브 출력 유출이 남으면 후속 이슈로 적으면 됩니다. 라벨은 바꾸지 않습니다.

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

@lidge-jun

Copy link
Copy Markdown
Owner Author

Administrator bypass and security-review record\n\nOwner authorization was explicit in Codex session 01a05a34-1e3a-73f2-8607-15e517cbec11 on 2026-09-01 KST. Exact head: 2c33031. Current exact-head check rollup: 0 failing, 0 pending. Independent security review PASS: detection widens fail-closed handling to MESSAGE while decryption/recovery remains NEW_TASK-only, so it does not create a decryption oracle. Using the maintain/admin pull_request bypass documented in MAINTAINERS.md because the author cannot approve their own PR. The bypass is recorded; it does not waive CI or security review.

@lidge-jun
lidge-jun merged commit 5c0c131 into dev Sep 1, 2026
27 checks passed
@lidge-jun
lidge-jun deleted the codex/3021-message-envelope-detection branch September 1, 2026 00:10
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.

1 participant