Skip to content

정리: 사람이 정하는 비밀번호에 공통 규칙과 SASLprep 정규화를 걸고, 로그인 본문을 64 KiB로 제한한다 - #52

Merged
mack-erel merged 2 commits into
mainfrom
password-policy
Oct 19, 2026
Merged

mack-erel merged 2 commits into
mainfrom
password-policy

Conversation

@mack-erel

Copy link
Copy Markdown
Contributor

요약

사람이 정하는 비밀번호에 회원 공통 규칙을 걸고, 해시 전 정규화(SASLprep)를 넣고, 로그인 요청 본문을 64 KiB로 제한합니다. 회원 공통 수치 반영 계획의 P2입니다. 마이그레이션은 없습니다.

왜

  • 비밀번호 규칙은 셀프서비스의 임시 "10자 이상"뿐이었습니다. 계정 생성, 관리자 생성, REST·CLI 비밀번호 변경에는 규칙이 아예 없었습니다.
  • 정규화가 없었습니다. 그래서 같은 비밀번호라도 다른 유니코드 표기로 넣으면 로그인 결과가 갈렸습니다.

바뀐 것

  • 규칙(core/password-policy.ts, 공통 수치 결정 1)
    • 8자 이상이고, 영소문자·영대문자·숫자·그 밖의 문자(공백·한글 포함) 4종 가운데 3종 이상이어야 합니다. 최대 길이는 없습니다.
    • 적용 명령: account-create, account-password-set(셀프서비스도 이 명령을 거칩니다), admin-user-create, admin-user-password-set. REST·CLI·콘솔 세 표면이 모두 이 명령을 부릅니다.
    • 기존 비밀번호는 막지 않습니다. 서버가 만드는 앱 비밀번호·토큰에는 적용하지 않습니다.
  • 정규화(core/saslprep.ts, RFC 4013, 공통 수치 결정 2)
    • 다른 서비스는 NFC를 씁니다. ionosphere만 SCRAM-SHA-256 규약 때문에 SASLprep을 쓰는 제품 예외입니다.
    • 해시와 SCRAM 키를 만들기 전에 정규화합니다. 정규화의 정본은 saslprep.ts 하나이고, 예전에 scram.ts에서 하던 NFKC 근사는 지웠습니다.
    • 정규화 전 원문으로 만든 기존 해시도 로그인됩니다. 로그인할 때 원문으로도 확인하고, 맞으면 정규화한 값으로 다시 저장합니다. 메일 프로토콜·셀프서비스·관리 콘솔 세 경로 모두 조건부 UPDATE로 처리합니다.
    • 근사 두 가지를 썼고 주석에 적었습니다. 미할당 판정은 지금 엔진의 \p{Cn}을 쓰고, 양방향 규칙의 L 문자는 \p{L}로 봅니다. ASCII 비밀번호는 영향을 받지 않습니다.
  • 본문 상한: 로그인·MFA·셀프서비스 비밀번호 변경 요청은 64 KiB(MAX_LOGIN_BODY_BYTES)까지만 받습니다. 나머지 관리 API는 1 MiB 그대로입니다.
  • 린터: 리터럴 제어문자 규칙을 넓혀 공백류·서식(Cf)·방향 제어·이형 선택자 문자도 잡습니다. 리뷰에서 정규식에 이런 문자가 그대로 들어간 것이 걸렸기 때문입니다.

검증

  • npm run verify 통과: 테스트 2744개.
  • RFC 4013 §3의 예시를 테스트로 넣었습니다.
  • 다시 저장한 뒤의 SCRAM 키가 정규화한 값에서 나왔는지, 코드와 따로 RFC 5802 계산으로 대조했습니다.
  • 관리자·셀프서비스 재저장과 64 KiB 초과 시 413도 테스트로 확인했습니다.
  • 독립 리뷰에서 CRITICAL·HIGH는 없었고, MEDIUM 2건과 LOW 6건을 반영했습니다.
  • 명령으로 설정하던 테스트 비밀번호("pw" 등)는 규칙에 맞게 바꿨습니다. 동작은 바뀌지 않습니다.

배포 영향

  • 마이그레이션은 없습니다.
  • ⚠ 롤백 주의: 배포 뒤에 새로 정하거나 다시 저장된 해시는 정규화한 값으로 만들어집니다. 그래서 이전 릴리즈로 되돌리면, 정규화로 모양이 바뀌는 비밀번호(전각 문자·조합형 한글 등)를 쓰는 사용자는 다시 배포할 때까지 비밀번호 로그인이 막힙니다. ASCII 비밀번호는 영향이 없습니다. 같은 내용을 OPERATIONS에도 적었습니다.
  • 정리: 로그인 시도 제한을 서버들이 공유 DB로 함께 세고, 공통 수치로 맞춘다 #51(공유 스로틀)과 문서 표가 겹칩니다. 먼저 머지되는 쪽에 맞춰 다른 쪽을 리베이스하겠습니다.

🤖 Generated with Claude Code

mack-erel and others added 2 commits October 19, 2026 10:38
…로 제한한다

왜(회원 공통 수치 2026-10-18 결정 1·2): 비밀번호 규칙이 셀프서비스의 임시 "10자 이상"뿐이었고, 계정 생성·
관리자 생성·REST/CLI 비밀번호 변경에는 아무 규칙이 없었다. 정규화도 없어서 같은 비밀번호를 다른 유니코드
표기로 넣으면 로그인이 갈렸다.

- core `checkPasswordPolicy`(정본): 8자 이상, 영소문자·영대문자·숫자·그 밖의 문자(공백·한글 포함) 4종 가운데
  3종 이상. 최대 길이 없음. 계정 생성·`account-password-set`·셀프서비스 변경·관리자 생성/변경이 모두 본다.
  기존 비밀번호는 막지 않는다. 서버가 만드는 앱 비밀번호·토큰에는 쓰지 않는다.
- core `saslprep`(RFC 4013): 다른 서비스는 NFC지만 SCRAM-SHA-256이 SASLprep을 요구해 ionosphere만 예외다(결정 2).
  해시와 SCRAM 키를 만들기 전에 정규화한다. 정규화 전 원문으로 만든 기존 해시는 로그인할 때 원문으로도 확인하고,
  맞으면 정규화한 값으로 다시 저장한다(메일 프로토콜·셀프서비스·관리 콘솔 세 경로, 조건부 UPDATE).
  근사 두 가지(미할당 판정은 현재 엔진의 \p{Cn}, 양방향의 L은 \p{L} 기준)는 주석에 적었다 — ASCII는 영향 없음.
- 로그인·MFA·비밀번호 변경 요청 본문 상한 64 KiB(`MAX_LOGIN_BODY_BYTES`). 비밀번호 길이 제한 대신
  해시 부담 공격을 막는다. 나머지 관리 API는 1 MiB 그대로.
- 테스트 비밀번호 중 명령으로 설정하던 약한 값들을 규칙에 맞게 바꿨다(동작 변화 없음).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…칙 리뷰 반영)

왜(리뷰 MEDIUM 2건):
- saslprep.ts의 정규식 세 개에 NBSP·ZWSP·RLM·이형 선택자·히브리·아랍 문자가 리터럴로 들어가 있었다(파일을
  쓰는 과정에서 \u escape가 실제 문자로 바뀌었다). diff·터미널에서는 빈칸으로 보이거나 범위가 뒤집혀 보여 리뷰어가
  무엇이 들어 있는지 확인할 수 없다. 모두 ASCII escape로 바꿨다. 같은 사고가 테스트 3개에도 있어 함께 고쳤다.
  린터 4번 규칙(리터럴 제어문자)을 공백류·서식(Cf)·방향 제어·이형 선택자까지 넓혔다. 기존 소스에 걸린 것은
  주석 이모지 뒤의 U+FE0F 셋과 주석 속 U+200B 하나였다(지움 / `*\/`로 바꿈).
- core/scram.ts가 따로 NFKC로 "SASLprep 근사"를 하고 있어 정규화 소유자가 둘이었다. deriveScramKeys가
  saslprep을 쓰게 하고 normalizePassword export와 낡은 경고 주석을 지웠다.

LOW: README 예시 비밀번호를 규칙에 맞게 고침, App.createUser가 규칙을 보지 않는 테스트용 지름길임을 주석에 명시,
본문 상한 주석의 근거를 바로잡음(scrypt 비용은 길이에 거의 비례하지 않는다 — 막는 것은 정규화·파싱 부담),
비ASCII 오답은 비교가 한 번 더 든다는 것을 동시 로그인 상한 주석에, 롤백 시 정규화된 해시를 옛 코드가 못 읽는다는
주의를 OPERATIONS에 적음. 테스트: 재저장 뒤 SCRAM 키가 정규화한 값에서 나왔는지 RFC 5802 계산으로 대조,
관리자·셀프서비스 재저장, 64 KiB 초과 로그인 본문 413.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mack-erel
mack-erel merged commit 49bb374 into main Oct 19, 2026
9 checks passed
@mack-erel
mack-erel deleted the password-policy branch October 19, 2026 01:43
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