Conversation
개인정보처리방침 "이용 과정에서 자동 생성" 항목은 단서 없이 이렇게 적고 있다:
IP 주소(SHA-256 해시 후 저장)
app.auth_logs 는 그 약속을 지켰는데(record_auth_log 가 sha256Hex 로 넣는다)
레이트리밋 버킷만 평문이었다:
login:ip:139.178.129.10:29787996 ← app.rate_limits.bucket
한쪽에서 가리는 값을 다른 쪽에서 그대로 쓰면 가린 의미가 없다. 실제로 이 키를
같은 시각의 `login:user:<아이디>` 버킷과 맞춰 특정인의 접속 IP 를 복원할 수
있었다 — 2026-08-22 로그인 조사에서 그렇게 찾았고, 그 과정에서 이 문제가 드러났다.
## 무엇을 바꿨나
clientIp() → clientIpKey() 로 바꾸고 **함수가 해싱해서 내보낸다.**
원본을 돌려주고 "호출부가 알아서 해싱" 으로 두지 않은 이유는, 그러면 다음에
추가되는 호출부가 같은 실수를 반복하기 때문이다. 이 파일이 고쳐 온 오류들이
전부 그 모양이었다(폴백 잔존·주석 오판). 원본을 만질 수 있는 자리를 없앤다.
평문이 새던 곳 전수(7):
login:ip · refresh:ip · pwreset:ip · sms:ip · lite:ip · otpverify:ip (버킷 6)
send-phone-code 의 `console.error(…, { purpose, ip })` (로그 1)
마지막 것은 이번에 처음 봤다 — 버킷만 보다가 놓칠 뻔했다. **로그도 저장이다.**
해시 앞 12자만 남긴다(같은 출처인지 대조하기엔 충분하다).
auth_logs 와 **같은 sha256Hex** 를 쓴다. 덕분에:
· record_auth_log 는 이 값을 그대로 넘긴다 — 해싱이 한 번으로 준다
· 기존 auth_logs 행과 값이 이어진다(마이그레이션 불필요)
· 두 곳을 맞대 볼 수 있다
## 하지 않은 것
record_client_error 는 이미 `'cerr:ip:' || md5(v_ip)` 로 해싱하고 있다 — 평문이
아니므로 건드리지 않았다. md5 라 방침의 'SHA-256' 과 알고리즘이 다르긴 한데,
그건 표기 정합 문제지 평문 노출이 아니고 고치려면 운영 DDL + 스냅샷 재생성이
따라온다. 이 PR 은 **DB 변경 0** 으로 두는 게 검토·되돌리기에 낫다. 별건으로 남긴다.
## 곁가지 — deno check 래칫이 0 이 됐다
새 auth_test.ts 가 auth.ts 를 임포트하면서 **기존 타입 오류 1건이 테스트 단계로
딸려 들어왔다**(지금까지는 어느 테스트도 auth.ts 를 쓰지 않아 드러나지 않았다).
b64urlToBytes 의 반환 타입을 Uint8Array<ArrayBuffer> 로 좁혀 해소했다 —
new Uint8Array(n) 은 실제로 항상 ArrayBuffer 라 런타임 동작은 그대로다.
그래서 db-tests.yml 의 MAX 를 1 → 0 으로 내린다. 0 은 래칫의 종착점이라
이제부터는 새 오류가 곧 실패다(0032 §7.8 이 지적한 "1건을 0으로 세던" 결함과
같은 자리를 아예 없앤다).
## 검증
deno check functions/*/index.ts 오류 0건 (종전 1건)
deno test _shared/*_test.ts 21 passed (auth 신규 9 포함)
auth_test.ts 가 못 박은 것 — 반환값에 원본 IP 가 없을 것, auth_logs 와 같은
해시일 것, 결정적일 것, 헤더 없으면 null(폴백 금지), XFF/x-real-ip 무시.
## 영향
버킷 키 문자열이 바뀌므로 **기존 카운터가 리셋된다.** 창이 60초~1시간이고
app.rate_limits 는 현재 0행이라 실질 영향은 없다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
개인정보처리방침 "이용 과정에서 자동 생성" 항목은 단서 없이 이렇게 적고 있습니다:
app.auth_logs는 그 약속을 지켰는데(record_auth_log가sha256Hex로 넣습니다) 레이트리밋 버킷만 평문이었습니다:한쪽에서 가리는 값을 다른 쪽에서 그대로 쓰면 가린 의미가 없습니다. 실제로 이 키를 같은 시각의
login:user:<아이디>버킷과 맞춰 특정인의 접속 IP를 복원할 수 있었습니다 — 2026-08-22 로그인 조사에서 그렇게 찾았고, 그 과정에서 이 문제가 드러났습니다.법적 판단이 아니라 자기 정합성 문제로 봅니다. IP를 레이트리밋에 쓰는 것 자체는 통상적이고, 문제는 "안 한다고 적어 놓고 했다" 쪽입니다.
바꾼 것
clientIp()→clientIpKey(). 함수가 해싱해서 내보냅니다.원본을 돌려주고 "호출부가 알아서 해싱"으로 두지 않은 이유는, 그러면 다음에 추가되는 호출부가 같은 실수를 반복하기 때문입니다. 이 파일이 고쳐 온 오류들이 전부 그 모양이었습니다(폴백 잔존 · 주석 오판). 원본을 만질 수 있는 자리를 없앱니다.
평문이 새던 곳 전수 7곳:
login:ip·refresh:ip·pwreset:ip·sms:ip·lite:ip·otpverify:ipsend-phone-code의console.error(…, { purpose, ip })마지막 것은 이번에 처음 봤습니다 — 버킷만 보다가 놓칠 뻔했습니다. 로그도 저장입니다. 해시 앞 12자만 남깁니다.
auth_logs와 같은sha256Hex를 씁니다. 덕분에:record_auth_log는 이 값을 그대로 넘깁니다 → 해싱이 한 번으로 줍니다auth_logs행과 값이 이어집니다 → 마이그레이션 불필요하지 않은 것
record_client_error는 이미'cerr:ip:' || md5(v_ip)로 해싱하고 있습니다 — 평문이 아니라 건드리지 않았습니다.md5라 방침의 'SHA-256'과 알고리즘이 다르긴 한데, 그건 표기 정합 문제지 평문 노출이 아니고 고치려면 운영 DDL + 스냅샷 재생성이 따라옵니다. 이 PR은 DB 변경 0으로 두는 게 검토·되돌리기에 낫다고 봤습니다. 별건으로 남깁니다.
곁가지 —
deno check래칫이 0이 됐습니다새
auth_test.ts가auth.ts를 임포트하면서 기존 타입 오류 1건이 테스트 단계로 딸려 들어왔습니다(지금까지는 어느 테스트도auth.ts를 쓰지 않아 드러나지 않았습니다).b64urlToBytes의 반환 타입을Uint8Array<ArrayBuffer>로 좁혀 해소했습니다 —new Uint8Array(n)은 실제로 항상ArrayBuffer라 런타임 동작은 그대로입니다.그래서
db-tests.yml의MAX를 1 → 0 으로 내립니다. 0은 래칫의 종착점이라 이제부터는 새 오류가 곧 실패입니다(0032 §7.8이 지적한 "1건을 0으로 세던" 결함과 같은 자리를 아예 없앱니다).검증
auth_test.ts가 못 박은 것:auth_logs와 같은 해시일 것null(폴백 금지 — 폴백이 곧 우회로)x-forwarded-for·x-real-ip는 쳐다보지 않을 것영향
버킷 키 문자열이 바뀌므로 기존 카운터가 리셋됩니다. 창이 60초~1시간이고
app.rate_limits는 현재 0행이라 실질 영향은 없습니다.한계 (정직하게)
이건 익명화가 아니라 가명화입니다. IPv4는 43억 개뿐이라 무염 해시는 전수 대입으로 되돌릴 수 있습니다. 목적은 방침이 약속한 수준(SHA-256)에 맞추는 것이고, 더 센 보장이 필요하면 방침과 함께 바꿔야 합니다(솔트·절단 등). 주석에 그대로 적어 뒀습니다.
백업(
scripts/backup.sh가public·app전체를 덤프)에 이미 들어간 과거 평문은 이 PR로 지워지지 않습니다 — 백업 보관기간이 지나야 사라집니다.🤖 Generated with Claude Code