Skip to content

fix(python): sync 응답의 기한을 소켓의 원문 읽기마다 — 헤더·청크 틀·트레일러·TLS 레코드까지, 응답마다 바이트 예산 (response-read-deadline-divergent · response-framing-unbounded) - #893

Merged
xzawed merged 7 commits into
mainfrom
py-socket-deadline
Oct 29, 2026

Conversation

@xzawed

@xzawed xzawed commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

무엇

#889(응답 기한)가 남긴 python sync 칸과 response-framing-unbounded 의 python F3a 칸을 닫는다. 판정 2026-10-29 의 기제(읽기마다의 벽시계 검사 — 감시자·스레드·타이머 없음)를 본문 고리에서 소켓의 원문 읽기 하나하나로 내렸다(ruby ReadDeadline · JVM auth DeadlineSocket 과 같은 자리). 같은 자리에 응답마다 바이트 예산(본문 상한 + 틀 몫 65,536)을 걸었다.

어디에 거는가 — python/src/keycloak_sdk/_internal/wire.py

  • 응답의 읽기는 전부 http.client 응답 버퍼 아래 원문 readinto 한 자리를 지난다. 상태 줄 · 헤더 · 1xx · 청크 크기 줄 · 트레일러 · 본문 · 프록시 CONNECT 응답 · TLS 평문이 다 여기다.
  • SDK 세션의 인스턴스에만 건다(전역 패치 없음). 사슬은 이렇다:
    • 어댑터의 풀 관리자 connection_from_pool_key(프록시면 proxy_manager_for 가 내준 관리자의 것)
    • → 풀 _get_conn
    • → 연결 response_class
    • → 응답 버퍼의 raw.readinto
  • 붙는 자리는 auth 세션(auth.py·aio/auth.py 생성 때 bound_reads)과 admin 의 sync 세션 둘(요청마다 admin_guard 가 다시 본다)이다. 마감·예산을 거는 곳은 token_cap.py · admin_body.py(성공 본문은 상태를 본 뒤 예산을 푼다) · jwks_fetch.py 다.
  • 원문 읽기마다 하는 일:
    • 마감이 지났으면 읽지 않는다.
    • 소켓 타임아웃을 min(읽기 타임아웃, 남은 시간) 으로 두고, finally 에서 되돌린다.
    • 마감 뒤에 끝난 읽기의 바이트는 쓰지 않는다.
    • 받은 바이트를 예산에 올린다. 읽기 하나는 예산 + 1 을 넘게 청하지 않는다.
  • 공개 실패는 지금과 같은 KeycloakTransportError 이고, 메시지에 응답 바이트를 싣지 않는다. 마감은 TimeoutError → urllib3 read timeout, 예산은 HTTPException → urllib3 protocol error 로 간다.
  • 자리를 못 찾으면 KeycloakConfigError 로 거부한다(retries.py 와 같은 꼴). HTTPAdapter 가 아닌 어댑터(소비자가 mount 한 전송)는 건너뛴다. 그때도 fix(java,kotlin,python,ruby,dotnet): SDK 가 읽는 응답을 설정한 타임아웃 안에 전부 읽는다 — 흘리는 서버가 호출 하나를 ≈ 상한 × 읽기 타임아웃 붙잡던 것 (response-read-deadline-divergent 판정) #889 의 본문 고리 마감은 남는다(같은 시계).
  • 예산 = 본문 상한 + 65,536. 65,536 은 ruby MAX_FRAMING_BYTES · .NET 헤더+트레일러 한도 · Go 머리 블록 한도와 같은 값이다. JVM 의 8× 배수는 8 MiB 트레일러를 통과시켜 쓰지 않았다. 종류별 값은 이렇다:
    • 토큰·introspection·logout·admin 그랜트·admin 오류: 1,114,112
    • JWKS: 116,736
    • admin 성공 본문: 없음 — 바이트로 묶지 않는 것이 정책이고, 시간은 기한이 묶는다.

RED → GREEN (읽기 1 s · 마지막 10 B 를 700 ms 마다 · 한계 3 s — 레그)

칸 바탕 22af932(#889 머리) 이 PR
헤더 / 청크 크기 줄 / 트레일러 × 여섯 레인(auth · introspect · logout · JWKS · admin 그랜트 · admin) 7,066–7,129 ms 뒤에야 실패 1,001–1,030 ms
TLS — 흘린 바이트마다 레코드 하나(레인 셋) 7,105–7,129 ms 1,005–1,019 ms
gzip admin 성공 본문의 꼬리 흘리기(등록부의 미측정 칸) 7,117 ms 1,011–1,015 ms
본문 흘리기 sync 1,413–1,432 ms 1,001–1,018 ms
HTTP 프록시 머리 · CONNECT 응답 흘리기 미측정 1,002 · 1,007 ms
F3a(트레일러 16 MiB) × sync 다섯 레인 16,777,357–16,777,724 B 를 읽고 수락 1,114,113 B 에서 거부(JWKS 116,737)

TLS 레코드 하나 안을 흘리는 칸은 RED 가 아니었다(1,017–1,025 ms). CPython _ssl 의 읽기는 소켓 타임아웃을 호출 하나의 총량으로 쓰기 때문이고, ruby·JVM 과 다르다. 가드로 남겼다.

대조군:

  • 멈춤: 1,006–1,021 ms(그대로).
  • 상한 바로 아래 정상 응답 네 조각: Content-Length 479–518 ms · 4 KiB 청크 + 트레일러 467–506 ms 수락.
  • 큰 admin 목록 2,408,890 B 청크: 507 ms 수락.
  • admin 성공 + F3a: 수락. 예산이 없는 것을 일부러 고정했다.

변이(커밋 위에 걸고 되돌림):

변이 결과
M1 읽기마다 검사 삭제 24 실패
M2 원문 읽기 대신 버퍼 채우기마다 25 실패(5,644–7,115 ms)
M3 예산 삭제 8 실패
M4 읽기를 남은 시간으로 묶지 않음 단위 시험 둘만 잡는다 — 흘리기는 1,415–1,445 ms 로 3 s 여유 안

게이트(레그)

전체 1,529 통과(바탕 1,460) · 31 선택 해제 · 커버리지 100.00%(문장 1,449 · 가지 270) · ruff · ruff format · mypy strict(41 파일) 깨끗 · 파사드 걷기 · 적대 경로 행렬 통과. aio 의 F3a 는 h11 이 131,211–131,578 B 에서 거부(바탕과 같다).

독립 검토(Grok — wire.py 한 파일, 닫힌 질문 넷)

  1. 감싼 raw.readinto 를 지나지 않고 응답 바이트를 읽는 길이 있는가 → 없다. BufferedReader.read/read1/readline/peek/readinto 는 raw.readinto 로만 채우고, RawIOBase.read/readall 은 readinto 를 부른다. 감싸는 때는 makefile 직후, 버퍼가 비어 있을 때다.
  2. 소켓 타임아웃이 늘 되돌려지는가(읽기가 던져도) → 예(finally).
  3. 한 교환의 계량이 다른 요청의 응답에 걸릴 수 있는가 → 아니다. 계량은 응답을 만들 때 ContextVar 에서 잡고, 쉬던 연결의 다음 응답은 다시 묻는다. 스레드는 문맥이 따로다.
  4. 교환 안의 SDK 요청이 조용히 계량되지 않는 길이 있는가 → 예, 둘. HTTPAdapter 가 아닌 어댑터, 그리고 교환 밖에서 만든 응답이다. 둘 다 모듈 머리말이 적은 설계 경계이고(본문 고리 마감은 남는다), 위 표의 여섯 레인이 실제 SDK 세션에서 붙는 것을 잰다.

urllib3 권고(검색이 찾은 CVE-2026-97689 — 레그가 확인)

  • 판: 로컬 venv 는 urllib3 2.7.0 이다. CI 는 2.8.0 으로 푼다(매트릭스 다섯 · integration), CI 의 pip-audit 은 「No known vulnerabilities」.
  • 로컬 pip-audit(2.7.0)은 PYSEC-2026-4177 = CVE-2026-97689 와 형제 둘(97688 · 97687 — 미측정)을 낸다.
  • 노출: 그 결함은 urllib3 의 stream()/read_chunked() 길이다. SDK 는 read1() → http.client 의 상한 있는 파서를 지나 16 MiB 청크 확장을 65,536 에서 거부한다. 2.7.0 에서 urllib3 자체 파서를 강제해도 이 훅이 1,114,113 B 에서 끊는다.
  • 의존 하한은 건드리지 않았다(별 판정).

CI 에서 드러난 판 차이 둘(PM — 대조군을 판이 아니라 동작으로 가름)

첫 CI 에서 python 다섯 판이 대조군 둘에 실패했다. 둘 다 SDK 결함이 아니라 하위 판이 이미 고친 것에 기댄 대조였다.

  1. urllib3 2.8.0(CI)이 CVE-2026-97689 를 고쳐, 자체 청크 파서가 크기 줄을 65,536 에서 스스로 끊는다(_MAX_CHUNK_LINE_LENGTH). 로컬 venv 는 2.7.0 이다.
    • 「훅 없이는 16 MiB 를 끝까지 읽는다」 대조군이 거짓이 됐다.
    • 이제 2.8.0 이상에서는 「예산이 아니라 urllib3 가 끊었다」를 본다.
    • 2.7.0 · 2.8.0(따로 받은 휠) 둘 다 통과.
  2. 최신 CPython 보안 패치 판(CI 의 3.10.22 · 3.11.17 · 3.12.15 · 3.13.17 · 3.14.9)은 http.client 가 트레일러 줄 수를 100 에서 묶는다(HTTPException: got more than 100 trailers). 로컬 3.13.11 · WSL 3.12.3 은 묶지 않는다.
    • 「admin 성공 본문은 16 MiB 트레일러도 받는다」 대조군이 새 판에서 거짓이 됐다. CI 는 「Can't connect to server」였다.
    • 원인은 대조군이 python-keycloak 이 감싸기 전의 예외를 싣게 해서 CI 에서 읽었다. SDK 경계는 하위 사슬을 일부러 뗀다.
    • 이제 시험 모듈이 http.client 에 트레일러 101 개를 먹여 동작으로 묻는다. 묶는 판에서는 「끊은 것이 http.client 다」(그 문구 · SDK 예산 문구 아님 · 예산보다 적게 읽음)를 본다.
    • 실제 3.13.11 과, 새 판 동작을 흉내 낸 임시 플러그인 양쪽에서 통과했다(418,056 B — CI 의 419,080 B 와 같은 꼴).
    • wire.py 머리말과 CHANGELOG 의 「트레일러 줄 수를 세지 않는다 · 16 MiB 를 받았다」에 「판에 따라」를 붙였다.

⚠️ 두 하위 수정 모두 판에 기대는 방어다. 소비자의 CPython·urllib3 판은 SDK 가 고르지 않으므로, 이 PR 의 예산이 판과 무관한 바닥이다.

남은 칸(등록부에 적음)

  • HTTPS 프록시 URL(TLS 안의 TLS) · pyOpenSSL 소켓: 읽기 하나가 바깥 소켓을 여러 번 돌아(urllib3/util/ssltransport.py:266) 안쪽 레코드 흘리기가 기한을 넘길 수 있다. 늦은 바이트는 거부한다 — 코드 판독이고 미측정이다.
  • admin 성공 본문은 바이트 예산이 없다(정책). 시간은 기한이 묶는다.

문서(PM)

🤖 Generated with Claude Code

https://claude.ai/code/session_01R4jYS5GwFA8V5oAqjw8pMp

@xzawed
xzawed force-pushed the py-socket-deadline branch from 12acf61 to 9382823 Compare October 29, 2026 08:14
xzawed and others added 7 commits October 29, 2026 17:26
`tls_cert` 픽스처와 서버 문맥(`_server_tls` → `server_tls`)이 test_no_automatic_retry.py 안에 있어,
다른 시험 모듈이 쓰려면 픽스처를 모듈 사이로 import 해야 했다(ruff F811 · 픽스처 이름이 별칭을 따른다).
다른 공유 도우미처럼 conftest 에 두고 `from tests.unit.conftest import server_tls` 로 쓴다. 인증서를 만드는
코드는 그대로이고 범위만 module → session(한 실행에 한 번)이다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4jYS5GwFA8V5oAqjw8pMp
… 응답마다 본문 상한 + 틀 몫 65,536 의 바이트 예산 (response-read-deadline-divergent · response-framing-unbounded F3a)

#889 의 마감은 SDK 의 본문 고리(read1 앞뒤)에 있어 http.client 호출 하나 안의 읽기를 못 봤다 — 헤더
블록(`send` 안)·청크 크기 줄·트레일러를 흘리는 서버 앞에서 그 호출이 끝난 뒤에야 실패했다. 그리고
http.client 는 줄 하나를 65,536 에서 끊지만 트레일러 줄 수는 세지 않아 4 KiB 줄 4,096 개(16 MiB)를
끝까지 읽고 받아들였다.

자리(`_internal/wire.py`): 응답의 읽기는 전부 http.client 응답 버퍼 아래 원문 `readinto` 한 자리를
지난다. SDK 세션의 HTTPAdapter 마다 풀 관리자의 `connection_from_pool_key`(프록시면
`proxy_manager_for` 가 내준 관리자의 것) → 풀의 `_get_conn` → 연결의 `response_class` → 응답 버퍼의
`raw.readinto` 로 닿는다(전역 패치 없음). 요청 직전에 `exchange` 가 마감·예산을 문맥 변수에 걸고, 원문
읽기마다 앞뒤로 마감을 보고 읽기 하나를 `min(읽기 타임아웃, 남은 시간)` 으로 묶으며(끝나면 되돌린다)
받은 바이트를 예산에 올린다(읽기 하나는 예산 + 1 까지만 청한다). 거는 곳: auth 세션은 생성 때
(`bound_reads` — sync·aio 의 쓰지 않는 sync 세션 둘 다, `forbid_retries` 옆), admin 의 두 sync 세션은
요청마다(`admin_guard._plan_reads`). 실패는 지금과 같은 꼴 — 마감은 소켓 타임아웃의 `TimeoutError`
(urllib3 ReadTimeoutError), 예산은 http.client 틀 한도와 같은 `HTTPException`(urllib3 ProtocolError) →
공개 API 에서 `KeycloakTransportError`, 본문 없음. #889 의 본문 고리 검사는 같은 마감으로 남긴다(버퍼에
와 있던 바이트 · HTTPAdapter 가 아닌 어댑터).

예산 = 본문 상한 + 틀 몫 65,536(ruby `MAX_FRAMING_BYTES` · .NET 헤더+트레일러 · Go 머리 블록과 같은 값):
토큰·introspection·logout·admin 자체 그랜트·admin 오류 응답 1,114,112 · JWKS 116,736. admin 성공 본문은
상태를 본 뒤 예산을 푼다(목록은 정당하게 크다 — 틀만 따로 세어도 같은 바이트를 본문으로 보내면 그만이다).

실측(Windows · Python 3.13.11 · requests 2.34.2 · urllib3 2.7.0 · 읽기 타임아웃 1 초 · 꼬리 10 B 를 0.7 초마다):
- 수정 전(22af932): 틀 흘리기 sync 열여섯 칸 7,066~7,129 ms 뒤에야 실패 · TLS 레코드마다 한 바이트
  7,105~7,129 ms · 레코드 하나 안의 흘리기는 이미 1,017~1,025 ms(CPython `_ssl` 읽기는 소켓 타임아웃을
  호출 하나의 총량으로 쓴다) · F3a sync 다섯 레인 16,777,357~16,777,724 B 를 다 읽고 수락(admin 오류는 404).
- 수정 뒤: 흘리기 전부 1,001~1,030 ms `KeycloakTransportError`(본문 흘리기도 1.4 s → 1.0 s) · F3a 는 예산
  + 1 B(1,114,113 · JWKS 116,737)에서 거부 · HTTP 프록시 머리 흘리기 1,002 ms · CONNECT 응답 흘리기 1,007 ms.
- 대조군: 멈춤 1,006~1,021 ms(그대로) · 상한 바로 아래 건강 응답 467~522 ms 수락(길이·4 KiB 청크 +
  트레일러) · 2,408,890 B admin 목록 507 ms 수락 · admin 성공 본문 + F3a 는 16,777,482 B 를 읽고 수락(정책) ·
  aio 는 그대로(머리 흘리기 1,004~1,019 ms · F3a 131,211~131,578 B 에서 h11 이 거부).

스위트(같은 소스 · #889 의 `reader` 뒤 검사 단위 시험을 test_wire.py 로 되살리기 전): 1,528 passed ·
31 deselected · 커버리지 100.00%(1,449 문 · 270 가지 — wire.py 132 · 30) · ruff · ruff format · mypy strict
깨끗. 바탕(22af932 의 시험)은 1,460 passed.

⚠️ 동작 변화(소비자에게 보인다 — CHANGELOG 는 PM): sync 응답 하나가 소켓에서 예산을 넘으면
`KeycloakTransportError` 다(16 MiB 트레일러 토큰 응답 · 압축 스트림 뒤 꼬리가 예산을 넘는 토큰 응답은
예전에 수락, 틀이 예산을 넘는 admin 오류는 예전에 `KeycloakNotFoundError` 등). 흘리는 머리·틀은 이제 마감에
실패한다. JWKS 메시지에는 `HTTP response exceeds 116736 bytes on the wire` 가 실린다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4jYS5GwFA8V5oAqjw8pMp
…없이 다시 읽는 길 (response-read-deadline-divergent)

등록부 남은 칸의 「gzip 성공 본문이 빈 값으로 풀리는 동안 urllib3 가 계속 읽는 길도 같은 부류(미측정)」. admin
성공 본문은 urllib3 가 푼다(`read1(decode_content=True)`) — 풀린 것이 없으면 그 호출 안에서 소켓을 다시 읽어,
본문 고리의 검사는 그 호출이 끝난 뒤에야 본다. gzip 본문의 마지막 10 바이트(deflate 끝 + CRC·길이)를 0.7 초마다.

실측(Windows · Python 3.13.11 · urllib3 2.7.0 · 읽기 타임아웃 1 초): 수정 전(22af932 의 src) 7,117 ms 뒤에야
`KeycloakTransportError`(시험 실패 — 한계 3 s) · 수정 뒤(59d44e15) 1,015 ms · 1,011 ms(두 번). 이 칸도 원문 읽기마다
보는 마감(`_internal/wire.py`)이 닫는다 — 소스 변경 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4jYS5GwFA8V5oAqjw8pMp
…록부 두 칸을 닫음

- CHANGELOG [Unreleased]: #889 의 Security 줄 끝 「남은 칸: python sync 와 JVM admin」에서 python 을 뺐다(이 PR 로 거짓이
  된다 — 출시 전이라 고친다). python 줄 둘을 더했다 — Security(상태 줄·헤더·청크·트레일러·TLS 레코드 7 → 1.0 초) ·
  Changed(⚠️ 응답마다 바이트 예산을 넘으면 KeycloakTransportError — 지금까지 받던 경우 셋).
- 등록부 `response-read-deadline-divergent`: python sync 칸 닫힘 · 남은 python 칸(HTTPS 프록시 — TLS 안의 TLS, 코드 판독)을
  적었다. `response-framing-unbounded`: python 트레일러 칸을 바이트 예산이 닫았다 — 남은 것은 node(상류 보고, 사람 몫).
  적재 174781 이하 · 원본 199954 앵커 아래 46B · 모양 28/0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4jYS5GwFA8V5oAqjw8pMp
…사슬 전부를 싣는다

- test_wire `test_urllib3s_own_chunk_parser_is_bounded_by_the_wire_budget[False]`: 대조군이 「훅 없이는 urllib3 자체 청크
  파서가 16 MiB 크기 줄을 끝까지 읽는다」(CVE-2026-97689 의 취약 동작)를 단언해, 그것을 고친 urllib3 2.8.0(CI 가 푸는 판)
  에서 실패했다(65,587 B 에서 urllib3 의 ProtocolError). 2.8.0 이상에서는 「예산이 아니라 urllib3 가 끊었다」(예산보다 적게
  읽고 ProtocolError)를 본다 — 판정은 어느 판이든 hooked 칸이다. 2.7.0(로컬 venv)·2.8.0(따로 받은 판) 둘 다 통과.
- test_response_framing_budget: admin 성공 대조군이 CI 에서만 「Can't connect to server」(5 ms · 419 KB)로 실패했다 —
  Windows·WSL Ubuntu(urllib3 2.8.0 · requests 2.34.2 · 커버리지 포함·전체 스위트)에서는 재현되지 않는다. python-keycloak
  은 get 에서 난 예외를 무엇이든 그 문구로 감싸므로 단언 메시지가 원인 사슬 전부를 싣게 했다(진단 — 다음 CI 에서 읽는다).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4jYS5GwFA8V5oAqjw8pMp
SDK 경계(lower.py)가 하위 사슬을 일부러 떼어 지난 CI 의 진단이 LowerLibraryError 에서 멈췄다
(KeycloakConnectionError at keycloak.connection.raw_get:346). raw_get 을 엿봐 원래 예외를 메시지에 붙인다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4jYS5GwFA8V5oAqjw8pMp
… 인지 본다

CI 의 진단(원래 예외)이 원인을 말했다: `http.client.HTTPException: got more than 100 trailers`. CI 의 CPython
(3.10.22 · 3.11.17 · 3.12.15 · 3.13.17 · 3.14.9)은 트레일러 줄 수를 100 에서 묶는 보안 패치 판이고, 로컬 3.13.11 ·
WSL 3.12.3 은 묶지 않는다. 그래서 「admin 성공 본문은 16 MiB 트레일러도 끝까지 받는다」는 대조가 새 판에서 거짓이었다.

- 시험 모듈이 판 번호가 아니라 동작으로 묻는다(`HTTP_CLIENT_LIMITS_TRAILERS` — http.client 에 트레일러 101 개를
  먹여 본다). 묶는 판에서는 「끊은 것이 http.client 다(그 문구 · SDK 예산 문구 아님 · 예산보다 적게 읽음)」를 본다.
  큰 목록을 끝까지 받는 대조는 `test_a_large_admin_list_is_read_whole` 이 진다.
- 검증: 실제 3.13.11 → 묶지 않는 갈래 통과(16,777,482 B) · 새 판의 http.client 동작을 흉내 낸 임시 플러그인 →
  묶는 갈래 통과(418,056 B — CI 의 419,080 B 와 같은 꼴) · 모듈 전체도 흉내 아래 통과.
- wire.py 머리말 · CHANGELOG 의 「트레일러 줄 수를 세지 않는다 · 16 MiB 를 받았다」에 「판에 따라」를 붙였다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4jYS5GwFA8V5oAqjw8pMp
@xzawed
xzawed force-pushed the py-socket-deadline branch from fa634fe to 7b2009c Compare October 29, 2026 08:29
@sonarqubecloud

Copy link
Copy Markdown

@xzawed
xzawed merged commit 84b0bfb into main Oct 29, 2026
37 checks passed
@xzawed
xzawed deleted the py-socket-deadline branch October 29, 2026 08:50
xzawed added a commit that referenced this pull request Oct 29, 2026
…I 하위 판 함정

- 진입점 1번: 판정 수행이 끝났다 — 남은 칸은 각 항목이 적는다(응답 기한 · 번역 가드 · php (6) · framing node 상류 보고).
- 지금 상태: `[Unreleased]` 에 아홉 언어 다 변화가 있다 — 다음 물결은 사람 몫.
- 함정 3번: CI 의 하위 판(urllib3 2.8 · CPython 트레일러 상한)이 대조군을 뒤집었다(#893) — 대조군은 동작으로 가른다 ·
  충돌한 PR 에는 실행이 안 생긴다. 옛 예시 셋(jwks 600 초 경계 · node·.NET · probe.sh 정규식)은 git 이력이 소유한다.
- 판정 문단: 라운드 2(#892) 포인터.
적재 174781 이하 · 원본 199954 앵커 아래 48B · 모양 28/0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4jYS5GwFA8V5oAqjw8pMp
xzawed added a commit that referenced this pull request Oct 29, 2026
…I 하위 판 함정 (#900)

- 진입점 1번: 판정 수행이 끝났다 — 남은 칸은 각 항목이 적는다(응답 기한 · 번역 가드 · php (6) · framing node 상류 보고).
- 지금 상태: `[Unreleased]` 에 아홉 언어 다 변화가 있다 — 다음 물결은 사람 몫.
- 함정 3번: CI 의 하위 판(urllib3 2.8 · CPython 트레일러 상한)이 대조군을 뒤집었다(#893) — 대조군은 동작으로 가른다 ·
  충돌한 PR 에는 실행이 안 생긴다. 옛 예시 셋(jwks 600 초 경계 · node·.NET · probe.sh 정규식)은 git 이력이 소유한다.
- 판정 문단: 라운드 2(#892) 포인터.
적재 174781 이하 · 원본 199954 앵커 아래 48B · 모양 28/0.


Claude-Session: https://claude.ai/code/session_01R4jYS5GwFA8V5oAqjw8pMp

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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