URL %2520 버그 = iOS 17.0/17.1 WebKit pasteboard가 복사 시 URL을 재인코딩 (WebKit Bug 261936).

현상

  • 공백이 %2520으로 깨짐, 한글은 %EC%88%98 정상 → 부분 재인코딩
  • 복사-붙여넣기 경로만 영향, 클릭(JS redirect)은 정상
  • iOS 집중 + referrer 누락 패턴과 일치

원인

%2520 = %25(= %) + 원래 있던 20. 공백(%20)이 한 번 더 인코딩된 것.

공백 → %20 → (% 만 재인코딩) → %2520

URL 인코딩은 멱등(idempotent)이 아니다 — encodeURIComponent를 두 번 하면 값이 달라진다.

진범은 WebKit Bug 261936:

  • iOS 16↓: NSURL URLWithString:이 invalid char에 nil
  • iOS 17.0/17.1: invalid char를 자동 percent-encode (regression)
  • iOS 17.2: 수정

pasteboard가 NSURL을 만들 때 %20은 invalid로 잘못 판단해 재인코딩, 한글 percent-encoding(%EA%B0%80)은 valid UTF-8로 통과 → 비대칭의 원인.

해결

원인이 외부(OS 버그)면 추적보다 방어가 ROI 높음.

  • 화이트리스트 라우트 패턴에서만 디코딩
  • 디코딩 후 위험 패턴(.., //, \) 차단
  • segment별 encodeURIComponent 후 301 redirect

무조건 이중 디코딩 금지 — ..%252f..%252f../../ path traversal 우회 벡터.

교훈

  • URL은 디코딩된 원본으로 보관, 인코딩은 출력 직전 한 번 (single source of truth)
  • “방어적으로 한 번 더”가 함정 — 멱등이 아닌 연산엔 통하지 않는다

참고

#539

에이전트 코딩 환경 — 워크트리·포트·자격증명

병렬 에이전트를 돌릴 때 워크트리 위치·포트·gitignore 파일·토큰 범위를 결정적으로 고정하는 레시피.

언제워크트리를 여럿 띄웠는데 서로의 서버를 죽이거나, 의존성 검증이 거짓 통과하거나, 새 워크트리에서 첫 명령이 실패할 때.

#583

GenUI 도입 — 모델이 정하는 게 인자인가 배치인가 코드인가

Static/Declarative/Open-ended 판별부터 카탈로그 설계·신뢰 경계·회귀 전략까지.

언제누군가 "우리도 GenUI 해보자"고 말했을 때. 회의에서 그 단어가 나오면 셋 중 뭔지부터 확정한다.

#587

TOTP 2FA 구현 (otpauth)

등록·확정·검증 세 단계와, 라이브러리가 안 해주는 것들. OTP 검증 서버 같은 건 없다.

언제앱 기반 2FA(TOTP, RFC 6238)를 직접 붙일 때. 먼저 직접 구현할 일인지부터 가른다.

#592