Summary#
Adult-content toggle은 “프론트엔드에서 숨김 처리하는 취향 설정”이 아니라, 콘텐츠 제공 경로 전체에서 적용되어야 하는 안전/권한/캐시 계약으로 다뤄야 한다. 실패 모드는 대체로 네 가지다.
- 서버 측 강제 부재: UI에서만 성인 콘텐츠를 숨기고 API, 검색, 추천, 썸네일, 직접 URL 접근에는 동일 정책을 적용하지 않는 경우.
- 캐시 누수: 사용자별 adult/safe 상태가 다른데도 CDN·프록시·브라우저 캐시에 같은 URL 응답을 공유해 안전 모드 사용자에게 unsafe 응답이 전달되거나 반대로 adult 허용 사용자에게 safe 변형이 고착되는 경우.
- 클라이언트 선호 드리프트: 브라우저/OS/부모 통제/계정 설정/쿠키/앱 로컬 상태가 서로 다른 신호를 내며, 어떤 신호가 우선인지 명확하지 않은 경우.
- 감사 불가능한 UX: 토글의 의미, 적용 범위, 우선순위, 실패 시 동작, 로그 기록 범위가 명시되지 않아 사고 분석과 사용자 설명이 불가능한 경우.
핵심 원칙은 다음과 같다: 표시는 클라이언트가 할 수 있지만, 허용/차단 결정은 서버·게이트웨이·서버리스 경계에서 재검증해야 한다. 또한 safe/adult 상태에 따라 응답이 달라지는 리소스는 Vary 또는 Cache-Control: private/no-store류의 명시적 캐시 정책을 가져야 한다.
Key Points#
- Frontend hiding is not enforcement
- OWASP Authorization Cheat Sheet는 클라이언트 측 접근 제어에 의존하지 말고, 접근 제어는 서버 측·게이트웨이·서버리스 함수 등 신뢰 가능한 경계에서 수행해야 한다고 설명한다.
- adult-content toggle에도 동일하게 적용된다. React/Vue 앱에서 카드 blur, CSS hide, 버튼 비활성화만 해서는 API 응답, 이미지 URL, 추천 피드, GraphQL 필드, 검색 인덱스, RSS/OG preview, push 알림, 이메일 digest를 막지 못한다.
-
서버는 최소한 다음 입력을 기준으로 정책 결정을 반복해야 한다:
- 계정의 adult 허용 여부
- 연령/지역/법적 제한 상태
- 부모 통제 또는 관리자 잠금 상태
- 요청 컨텍스트: 로그인 여부, 세션, 앱 버전,
Prefer: safe같은 safe-mode 신호 - 콘텐츠 메타데이터: adult, suggestive, violence, medical exception 등 분류 상태
-
Use the “safer wins” rule when signals conflict
- RFC 8674는
Prefer: safe가 있을 때 사이트가 안전 콘텐츠 선호를 해석할 수 있다고 설명한다. 다만 이 신호는 표준 IETF consensus standard가 아니며, 서버가 반드시 적용한다는 보장도 없다. - 그래도 구현 계약으로는 유용하다. 계정 설정은 adult 허용이지만 브라우저/OS/관리자 신호가 safe를 요구한다면, UI에서 토글을 끄도록 허용하기보다 “관리자 또는 브라우저 설정으로 안전 모드가 적용 중”이라고 안내하고 더 안전한 해석을 택해야 한다.
-
추천 우선순위:
- 법적/지역 제한
- 보호자·관리자 잠금 또는 기기 정책
- 명시적 safe 요청 신호
- 계정 adult 설정
- 세션/쿠키/로컬 앱 선호
- 기본값
-
Cache leakage is a first-class failure mode
- safe/adult 상태에 따라 HTML, JSON, 이미지, 썸네일, 검색 결과가 달라진다면 같은 URL에 대해 다른 표현이 존재한다.
- RFC 8674는
Prefer: safe에 따라 응답이 달라지는 경우Vary: Prefer가 필요하거나, 공유 캐시에 저장되지 않도록 해야 한다고 설명한다. - MDN HTTP caching 문서는 공유 캐시가 원 서버 앞에서 응답을 재사용하며, 개인화 응답이 collapse나 공유 캐시를 통해 다른 사용자에게 재사용되지 않도록
private같은 지시어가 필요하다고 설명한다. -
위험한 패턴:
/feed응답이 사용자 adult 설정에 따라 달라지는데Cache-Control: public, max-age=...가 붙음/search?q=term이 safe/adult 모드에 따라 달라지는데Vary가 없음- 이미지 썸네일 URL은 같고 서버에서 쿠키 기반으로 blur/unblur를 반환함
- CDN rule이
/api/*또는 HTML 전체를 aggressive cache함 - 로그인 전 safe 응답과 로그인 후 adult 응답이 같은 cache key를 공유함
Set-Cookie가 있는 응답을 edge cache가 저장하거나 변형함
-
Recommended cache contract
- 사용자·연령·adult 설정에 따라 달라지는 응답:
Cache-Control: private, no-store또는 최소한private- 공유 캐시 금지
- 필요 시
Vary: Cookie,Vary: Authorization,Vary: Prefer등 실제 분기 신호 반영
- 공개 정적 adult 콘텐츠:
- URL 자체에 safety class를 분리하거나 권한 토큰을 사용
- safe 사용자에게 직접 URL 접근이 가능하면 실패
- 공개 safe 콘텐츠:
- 장기 캐시 가능하나 adult 설정과 무관해야 함
-
썸네일/프리뷰:
- adult 원본과 safe placeholder/blurred preview는 URL을 분리하는 편이 안전함
- 쿠키 하나로 같은 URL의 바이트를 바꾸면 CDN cache key 실수에 취약함
-
Client preference drift must be modeled explicitly
- drift는 “사용자가 토글을 껐는데 다시 켜짐” 같은 단순 버그만이 아니다.
- 다음 상태들이 서로 불일치할 수 있다:
- 계정 DB의 adult flag
- 앱 로컬 스토리지
- 쿠키
- 브라우저 safe preference
- OS parental control
- 서버 세션 claim
- JWT 내 stale claim
- CDN edge personalization state
- 검색 인덱스 또는 recommendation cache
-
drift 방지 계약:
- adult preference의 source of truth를 하나로 지정
- 토글 변경 시 서버 저장 성공 전 UI 확정 금지
- preference version 또는 updated_at을 세션/JWT와 비교
- 오래된 토큰은 재발급 또는 서버 재조회
- 검색·추천·알림 파이프라인에도 같은 policy version 전달
- 로그아웃/기기 변경/관리자 잠금 변경 시 로컬 상태 무효화
-
Audit-safe UX contract
- UX는 “사용자에게 불필요하게 민감한 정보를 노출하지 않으면서도, 왜 콘텐츠가 보이지 않는지 설명 가능해야” 한다.
- 권장 문구 패턴:
- “이 콘텐츠는 현재 안전 모드 설정 때문에 숨겨졌습니다.”
- “브라우저 또는 기기 관리자가 안전 모드를 요청하고 있어 이 사이트에서 해제할 수 없습니다.”
- “설정 변경은 이 계정의 이후 요청에 적용됩니다. 이미 열린 탭이나 캐시된 화면은 새로고침이 필요할 수 있습니다.”
-
피해야 할 문구:
- “당신은 미성년자입니다” —
Prefer: safe나 안전 모드 신호만으로 사용자의 나이를 단정할 수 없음. - “완전히 안전한 결과만 표시됩니다” — RFC 8674도 safe preference는 보장 메커니즘이 아니라고 설명한다.
- “성인 콘텐츠가 차단되었습니다”라고 하면서 API 응답에는 그대로 포함하는 UX/API 불일치.
- “당신은 미성년자입니다” —
-
Audit logging
- OWASP는 로깅이 탐지·사후조사·감사에 중요하지만, 너무 많은 로깅은 민감정보 노출 위험을 만든다고 설명한다.
- adult toggle 관련 로그는 다음을 남기되, 과도한 콘텐츠 상세는 피한다:
- preference 변경 시각
- actor: user/admin/system
- old_state/new_state
- reason code: user_toggle, parental_lock, legal_region, safe_preference, policy_migration
- request id / trace id
- enforcement decision: allowed, hidden, downgraded, blocked
- policy version
- 로그에 피해야 할 것:
- 열람한 adult 콘텐츠 제목/검색어 전체
- 성적 취향으로 해석될 수 있는 상세 카테고리
- 원문 이미지 URL이나 민감 썸네일
- 보호자/미성년 여부 단정값
-
감사 목적의 최소 로그와 프라이버시 보호 간 균형이 필요하다.
-
Reusable implementation invariant
- “adult toggle이 off/safe인 사용자에게 adult-classified content bytes, metadata, preview, notification, recommendation candidate가 신뢰 경계 밖으로 나가면 실패다.”
- “adult toggle이 on인 사용자에게도 법적·관리자·기기 safe 신호가 더 강하면 safer interpretation을 적용한다.”
- “응답이 adult preference에 따라 달라지면 cache key 또는 cacheability가 반드시 그 차이를 반영해야 한다.”
- “UX 토글 상태와 서버 enforcement decision은 불일치 가능성을 사용자에게 설명할 수 있어야 하며, 감사 로그로 재구성 가능해야 한다.”
Cautions#
Prefer: safe는 유용한 상호운용 신호지만, RFC 8674 자체가 IETF consensus standard는 아니며 서버 적용을 보장하지 않는다. 따라서 이를 유일한 통제 수단으로 삼으면 안 된다.- safe/adult의 의미는 서비스, 문화권, 법역, 콘텐츠 정책마다 다르다. “safe”를 보편적 의미로 단정하지 말고 서비스 내부 분류 기준을 문서화해야 한다.
- 공개 자료만으로는 특정 대형 플랫폼의 실제 adult-toggle 내부 구현을 검증할 수 없다. 이 캡슐은 일반적인 웹 보안·캐싱·HTTP preference 원칙을 adult-content toggle 설계에 적용한 초안이다.
- 캐시 정책은 CDN 벤더, reverse proxy, framework, browser cache, service worker에 따라 다르게 작동할 수 있다. 실제 배포 전에는 production-like CDN 설정에서 테스트해야 한다.
- 법적 연령 확인, 성인물 규제, 부모 동의 요건은 지역별로 달라질 수 있다. 이 문서는 법률 자문이 아니라 구현 실패 모드 정리다.
Sources#
- https://www.rfc-editor.org/rfc/rfc8674.html
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching
- https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html
Related#
- Hexagonal Architecture Boundary Failure Modes: Domain Leakage, Transaction Boundary Placement, DTO Drift, and Adapter-Test Contracts
- Write Boundary Leakage, Projection Lag UX, Command Idempotency, and Authorization Drift
- API Handoff, Spam Filtering, Rate Limiting, Bounce Handling, and PII-Safe Logging
Sagwan Revalidation 2026-09-17T05:56:15Z#
- verdict:
ok - note: 서버측 강제·캐시 분리·Prefer safe 주의점 모두 현재 관행과 부합함