1P by GN⁺ 22시간전 | ★ favorite | 댓글 1개
  • RFC 9839는 소프트웨어 개발 시 텍스트 필드에 포함될 수 있는 문제적 유니코드 문자를 명확히 정의함
  • 이 RFC는 서로 다른 언어와 라이브러리에서 해당 문자 처리 일관성 부족에 따른 문제점을 다룸
  • 9839에서는 덜 문제적인 세 가지 하위 집합을 제안하여 선택적으로 활용 가능함
  • 기존의 PRECIS 프레임워크에 비해 적용이 더 쉽고 단순함
  • RFC 9839용 Go 언어 라이브러리도 함께 공개되어 실제 활용에 도움을 줌

배경 및 RFC 9839 개요

  • 유니코드는 거의 모든 텍스트 데이터 처리에서 표준으로 사용됨
  • 하지만 실제 데이터 구조나 프로토콜 설계 시 모든 유니코드 문자를 허용하면 문제가 발생함
  • Paul Hoffman과 필자는 계속 반복되는 유니코드 문제에 대한 명확한 기준을 제시하기 위해 IETF에 개별 초안을 제출함
  • 2년간의 논의 끝에 공식 표준으로 채택되어 RFC 9839로 발표됨
  • 이 문서는 문제적 문자 유형, 왜 문제가 되는지(기술적, 표준적 이유), 사용자가 골라 쓸 수 있는 하위 집합 세 개를 상세히 설명함

RFC 9839의 주요 내용

  • 소프트웨어와 네트워크 환경에서 텍스트 필드 설계 시 필수적으로 참고해야 할 문서임
  • RFC 9839는 10페이지 분량으로, IETF 표준 문서 중 간결한 편임
  • 주로 소프트웨어 개발자와 네트워크 엔지니어를 대상으로 쉽게 설명되어 있음

문제적 유니코드 문자 사례

  • 예시로 JSON의 username 필드에 다음과 같은 문자열이 올 수 있음
    {  
        "username": "\u0000\u0089\uDEAD\uD9BF\uDFFF"  
    }  
    
  • 각 코드 포인트가 지닌 문제점
    • U+0000 : 의미 없는 NULL 문자로 일부 프로그래밍 언어의 동작을 방해함
    • U+0089 : C1 제어 코드(CHARACTER TABULATION WITH JUSTIFICATION) 로, 그 동작이 복잡하고 일관성 없음
    • U+DEAD : 쌍을 이루지 않은 서러게이트 문자로, UTF-16의 한계에서 비롯되는 문제임. 이상적이지 않은 데이터가 발생함
    • \uD9BF\uDFFF (실제 U+7FFFF) : Noncharacter로, 표준상 교환이 금지됨
  • 위와 같은 코드 포인트는 데이터 구조와 프로토콜 내에서 일관적 처리 불가 및 예기치 않은 오류를 유발함
  • RFC 9839는 이런 문제 문자를 공식적으로 정의하고, 배제할 유형을 명확히 제시

JSON의 설계와 한계

  • JSON 창시자인 Doug Crockford의 책임은 아님
  • 유니코드가 충분히 성숙하지 않은 시기에 설계되어, 문자 집합을 엄격하게 제한하지 못했음
  • 이제 표준을 변경할 수 없으므로, 문제 문자를 경험적으로 배제하는 방식이 필요함

IETF의 PRECIS 프레임워크와의 차이

  • 2025년의 RFC 9839 이전에도, IETF에서는 RFC 8264(PRECIS Framework) 등 다양한 표준을 제공함
    • 이 프레임워크는 국제화 문자열의 정제, 적용, 비교 방법을 상세히 다룸
    • 43페이지 분량으로 배경 설명과 해결책이 포괄적
  • PRECIS는 유니코드 버전에 강하게 의존하며, 복잡하고 적용이 어려운 단점이 있음
  • RFC 9839는 간결하고 실용성에 중점을 두었으며, 새로운 프로토콜 정의 시 신속한 채택이 용이

RFC 9839의 하위 집합 및 활용 예시

  • 9839는 세 가지 현실적인 하위 집합(scalars, XML, assignables)을 제시함
  • 각 하위 집합은 배제할 문제적 문자 범위를 조금씩 달리함
  • 다음은 주요 데이터 포맷과 RFC 9839의 하위 집합이 문제적 문자를 어떻게 처리하는지에 대한 표 요약임
    • CBOR, TOML, XML, YAML 등 일부 포맷은 서러게이트 또는 컨트롤 문자를 부분적으로 배제함
    • I-JSON은 서러게이트와 noncharacter를 배제함
    • 일반 JSON, Protobufs는 배제하지 않음
    • XML, YAML은 Charset 특성상 부분적으로만 noncharacter/컨트롤 코드를 제외함
      • 참고: XML과 YAML은 Basic Multilingual Pane 이외의 noncharacter는 제외하지 않음

Go 언어용 RFC 9839 라이브러리

  • RFC 9839의 세 하위 집합에 대한 문자 검증을 지원하는 작은 Go 라이브러리가 공개됨
  • 충분히 테스트가 이루어졌으며 최적화는 아직 진행 중임
  • 실제 현업에서 테스트 및 피드백을 환영함

RFC 9839의 의의와 작업 과정

  • RFC 9839는 공동필자들과 여러 차례 피드백을 거쳐 15개 이상의 초안 수정 후 공식 발표됨
  • 많은 커뮤니티 전문가들의 논의와 기여를 통해 초기안보다 훨씬 완성도 있는 문서로 발전함
  • “Acknowledgements” 섹션에 기여자 명시함

RFC 개별 제출의 경험

  • RFC 9839는 개별 제출(individual submission) 로 진행되었음
  • Working Group을 통한 전통적 방식보다 노력과 절차의 부담이 큼
  • Working Group 참여 경험과 비교 시, 전통적 방식이 더 효율적이고 추천할 만함
Hacker News 의견
  • 어떤 문자가 문제를 일으키는 건 분명하다고 생각하지만, 데이터 구조나 프로토콜 설계자가 모든 종류의 문자(심지어 적절히 이스케이프된 것조차)를 임의로 허용하지 않으려는 경향을 가지는 게 최악의 시나리오라는 느낌임. 예를 들어, 사용자명 유효성 검사는 다른 레이어에서 처리해야 한다고 봄. 사용자명을 60자 미만, 이모지나 zalgo 문자 금지, 널 바이트 금지 등으로 체크하고 API에서 적절한 에러를 반환하는 작업임. JSON 파싱 단계에서 사전 유효성 검증 대신 이런 문제로 실패하길 원치 않음. 물론 사용자명에는 특정 문자 클래스가 분명히 부적절함. 하지만 탭 문자 등이 실제로 사용되는 텍스트 파일을 전송한다면 내 언어의 utf8 "string" 타입에서 처리 가능한 것은 인코딩 가능하길 기대함. 특히 널 바이트의 용례가 많으며, 실제로 JSON에서 종종 보임. 하지만 제한된 "정상" 유니코드 집합만 써야 한다면 표준이 존재하는 것이 각자 미니 표준 만드는 것보다는 낫다고 생각함. 결론적으로 아이디어 자체는 좋아 보이나, 블로그 글에서 든 논리는 잘 납득이 안됨

    • 2025년 기준, 로우 레벨 와이어 프로토콜에서 쓸 문자열 표현 방식은 아래 중 하나만 실질적으로 옹호 가능하다는 생각임

      • "Unicode Scalars"(잘 구성된 UTF-16, 파이썬 문자열 타입)
      • "잠재적으로 잘못된 UTF-16"(WTF-8, 자바스크립트 문자열 타입)
      • "잠재적으로 잘못된 UTF-8"(바이트 배열, Go 문자열 타입)
      • 위 방식 중 하나에 "U+0000 없음" 옵션 추가(버퍼 오버플로우 취약점 이전에 설계된 언어/라이브러리 연동 시)
    • 진지하게 말하면, 프레인 텍스트 파일에서 C0(줄바꿈과, 마지못해 HT 제외) 및 C1 문자를 사용하지 않았으면 함. ANSI 컬러 마크업 같은 걸 저장하고 싶어하는 건 이해하지만, 이런 경우 실제로는 플레인 텍스트가 아니라 일종의 텍스트 마크업 포맷임. 마크다운과 유사하지만, C0 범위의 인코딩을 쓴다는 점만 다름. 데이터가 'cat' 명령어 등으로 예쁘게 보인다는 이유만으로 플레인 텍스트라고 말할 수는 없음. 프레인 텍스트로 인코딩된 마크업 포맷이 상호 운영성 때문에 많다는 점은 인지함

    • 데이터 구조와 프로토콜에서 임의 문자군을 금지하기 시작하는 게 최악이라는 의견 자체가 현실과 동떨어진 사고방식이라고 봄. 진짜 최악은 파서 등 소프트웨어 결함으로 보안 침해가 발생하는 것임

    • 사용자명에 UTF-8을 허용하는 시스템이 있는지 의문임. 프로그래밍적으로 조작되거나 평가되는 모든 식별자(로그인 사용자명, 비밀번호 등)는 반드시 ASCII여야 한다는 게 당연함. ISO-8859-1도 아니고, 오직 ASCII만 사용해야 함. 유니코드로는 이런 용도에 맞지 않음. 사용자명을 띄우는 경우 등에서는 상관없겠지만, 시스템 로그인의 식별자로서는 비-ASCII 인코딩은 무조건 금지임. 키보드 소프트웨어조차 ASCII를 벗어나면 시각적 표현에 대해 UTF-8의 일관성을 자장 담보할 수 없으며, 운영체제와 설정에 따라 더 혼란스러움. 앞으로 남겨질 바이너리와 유니코드 해석 AI가 일치한다는 보장도 없음. 또한 일관성 관련해서, IVS 상황이나 정규화(NFC/NFD/NFKC/NFKD) 문제를 명확하게 범위 내/외로 다루는지 RFC 9839도 기사도 명확하지 않음. 목적 섹션이 아예 빠져 있는 것 같음. 대략적으로 "비문자 코드 포인트"가 있다는 식의 모호한 언급만 있음

    • 사용자명에서 왜 이모지를 금지해야 하는지 궁금함

  • IETF가 2025년까지 Bad Unicode 지원을 기다린 게 아니란 점을 말하고 싶음. 이미 예전부터 RFC 8264: PRECIS Framework에서 다양한 Bad Unicode 문제를 광범위하게 다룸. RFC 8265(링크), 8266(링크) 등 관련 RFC도 참고하면 도움됨. 일반적으로, 텍스트 방향을 바꾸거나 입력 기기마다 다르게 인코딩될 수 있는 비밀번호 등은 사용자명/패스워드에 쓰이면 안됨. 이러한 RFC 프로필을 통해 안전하게 대응 가능함. 이런 목적에서는 "failing closed"(더 엄격하게 차단)가 더 안전함. 새로운 이모지가 나온다고 해도, 사용자명에서 허용해서 모든 페이지에 영향을 주는 것보다는 차라리 금지하고 보수적으로 가는 걸 선호함

    • 그래도 너무 닫힌 상태로 두면 20년 뒤에도 20년 전 이모지가 아직도 미지원 상태로 남게 되어 결국 사용자 불만만 커짐
  • Unicode는 "좋은" 부분이 분명 있지만, 예외적으로 제외해야 할 문자가 있음을 알아야 한다는 점이 실망 스러움. 언어 기록 방식을 포괄적으로 받아들이려다 너무 복잡해진 결과임. 어느 문자가 특별 취급되어야 한다는 점을 항상 생각해야 해서 피곤함. 그래서 Unicode 문자열은 독자적인 데이터 단위라고 생각해서 다룸. 입력 받고, 저장하고, 렌더링, 데이터 동등성 비교는 해도, 내용에 대해 해석하려 들지는 않음. 심지어 문자열을 이어붙이거나 다루는 것도 불안함

    • Unicode는 끝없는 트리비아와 나쁜 결정의 심연 같음. 예를 들어, 관련 RFC에서 구식 ASCII 제어 문자(표시 혼동 우려) 경고는 있지만, Explicit Directional Overrides 같은 치명적인 보안 문제가 있는 방향 전환 문자는 아무런 언급이 없음

    • 간단한 예시로, 첫 번째 문자열이 고아 이모지 수정자로 끝나고 두 번째가 수정 가능한 이모지로 시작하면 이미 문제 발생임. 더 복잡한 케이스가 늘어날수록 문제만 커짐

    • 복잡성이 크긴 하지만, 이런 것 중 서러게이트와 제어 코드는 언어 기록 목적이 아니라 이상한 설계가 과거를 위해 남겨진 결과임

    • Unicode가 불편하지만, 기존 다른 인코딩 표준보다는 덜 불편하다고 생각함

  • 대부분의 문제는 유효하지 않은 UTF-8 바이트 시퀀스를 거부하거나 전체적으로 에러 반환하도록 처리하면 된다고 생각함. 예를 들어, 서러게이트 등은 원래 UTF-8에서 불법이기에, utf-8을 쓰는 언어라면 이런 시퀀스에 대해 에러를 반환해야 함. 실제로 문제가 되는 건 "코드 포인트"로 문제적인(non-printing, 등) 것들이라고 봄. 이건 불법 바이트 시퀀스와는 분명히 별도의 개념으로 다뤄야 더 유용함

    • 충분히 합리적이라고 생각함. 이런 선택은 애플리케이션 구현자 몫이지, 범용 라이브러리에서 정할 사안이 아니라고 봄. 사용자명만 다루는 JSON 파서 같은 건 본 적이 없음
  • Unicode는 이미 각 코드 포인트의 범주(General Category)를 정의해서 이상한 문자 종류를 분류함. 관련 위키백과 문서를 참고할 수 있음. 예로, 파이썬에서 unicodedata.category(chr(0))은 "Cc"(control), unicodedata.category(chr(0xdead))은 "Cs"(surrogate)를 리턴함

  • 모든 "legacy control" 문자를 리터럴 뿐만 아니라 이스케이프 문자열(e.g., "\u0027")까지 제외하는 건 과하다고 생각함. C1은 잘 안 쓰이니 괜찮지만, C0 문자 일부는 실사용 예제가 있음. escape, EOF, NUL 등은 명확한 쓰임새가 남아 있다고 봄

    • 좀 특이한 C0 문자(U+001E Record Separator 등)는 데이터 스트림에서 쓸모가 많다고 생각함. 문서엔 막더라도, 스트림 데이터엔 유용함

    • 프로그램 소스 코드에서 form feed(U+000C) 문자 쓰인 걸 봄. Emacs는 페이지 단위 내비게이션용으로 원래 지원하니, 이런 게 포함되곤 함

  • Unicode가 좋다고 생각하지 않음. 문자 집합이 무엇이든 실제로 쓸 문자종류(제어 문자, 그래픽 문자, 최대 길이 등)는 결국 각 어플리케이션에 맞게 결정해야 함. JSON 등에서 포함/제외를 시도해도 별 효과가 없음. 유니코드든 ASCII든 다른 문자셋이든 특정 서브셋(혹은 superset)에 이름 붙이는 게 때론 유용할 수는 있지만, 모두에게 좋은 선택이라 착각하면 안 됨. RFC 9839는 몇몇 유니코드 서브셋에 이름 붙이긴 하지만, 내가 만들 서비스에 무조건 옳다는 보장은 없음. 내 결론은 유니코드를 아예 쓰지 않거나 강제하지 않는 것도 고려해봐야 함

    • 실제 문제는 조합 문자(combining character)임. 이 때문에 유니코드는 문자 집합에서 문자 묘사용 DSL로 바뀌었음
  • 입력을 제어할지, 아니면 신뢰할 수 없는 입력에 대해 안전하게 출력하는 데이터타입(web+log+debug용)으로 감쌀지 고민임

  • 한 그래픽 유닛에 들어갈 수 있는 유니코드 스칼라 값 개수에 대한 제한이 표준에 있었으면 함. 마지막으로 봤을 때(몇 년 전이지만) 표준에는 그런 제한이 없었고, 대신 스트리밍 애플리케이션에서는 그래픽 유닛을 128바이트로 제한하라는 권고만 있었음. 이런 한계를 표준에 명확히 두면 구현이 훨씬 쉬워지고 불필요한 제약도 없으리라 생각함

  • 실제로 "제어 문자가 없디"라는 전제만으로 프로그램을 짜서 깨지는 케이스 겪었음(폼 피드는 페이지 구분 등, escape 문자는 터미널 용도 등으로 흔히 쓰임). "전부 UTF-8임"이라는 가정도 깨지곤 함(오래된 데이터 파일, 로그 등). 텍스트로 유의미한 처리를 안 한다면, 그냥 바이트 시퀀스로 내용 변경 없이 넘기는 게 제일 좋음. 하지만 Microsoft Windows로 인해 가끔 char16_t 시퀀스를 넘겨야만 할 때도 있음. UTF-16은 UTF-8과 입출력이 근본적으로 다름. 변환할 때는 외부 데이터 → 내부 형태 전환시 WTF-8(UTF-16), 서러게이트 이스케이프(UTF-8) 방식을 각각 써야 함. 두 방식 혼합은 불가함