배포와 인프라11 분 읽기

제 스토어 라벨은 건강 데이터가 당신과 연결되지 않는다고 말하고 있었습니다

앱 스토어 개인정보 라벨에서 건강·이메일·User ID가 전부 "연결되지 않음"으로 신고돼 있었습니다. 스키마에는 모든 로그 테이블에 계정 외래키가 걸려 있습니다. 결정적 증거는 Apple 자신의 마법사 문구였습니다.

#app-store#privacy#ios#gotchas#verification
왼쪽: 라벨이 신고한 '연결되지 않음' 목록. 오른쪽: 같은 데이터가 계정 외래키와 행수준 보안으로 묶여 있는 스키마.
라벨은 선언이고 스키마는 사실입니다. 둘이 다르면 틀린 쪽은 라벨입니다.
왼쪽은 라벨이 신고한 연결되지 않음 목록, 오른쪽은 같은 데이터가 계정 외래키로 묶여 있는 스키마

당신 앱의 스토어 개인정보 라벨을 마지막으로 본 게 언제입니까? 그리고 그게 당신 데이터베이스 스키마와 일치합니까?

저는 출시할 때 채워 넣고 그 뒤로 열어본 적이 없었습니다. 넉 달 동안요.

지적은 남이 했고, 확인은 스키마가 했습니다

익명 제보 하나가 제 앱들의 EU 규제 문제를 조목조목 적어 보냈습니다(그 이야기). 열한 개 항목 중 하나가 이랬습니다.

스토어 개인정보 라벨은 건강 데이터·이메일·User ID를 "수집하지만 사용자와 연결되지 않음"으로 표시하고 있습니다. 그런데 정책은 모든 로그가 인증된 계정을 통해 저장·동기화된다고 말합니다.

이건 확인이 쉬운 종류입니다. 마이그레이션 파일을 열면 됩니다.

user_id UUID NOT NULL REFERENCES auth.users ON DELETE CASCADE

모든 로그 테이블이 이랬습니다. 행 수준 보안 정책도 하나였습니다.

CREATE POLICY "users_own_data" ON %s FOR ALL TO authenticated
  USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid())

연결을 끊는 장치가 어디에도 없습니다. 애초에 연결이 기능입니다 — 계정으로 로그인해서 자기 기록을 보는 앱이니까요.

그러니 라벨이 틀렸습니다.

실제로 어떻게 되어 있었나

콘솔을 열어 봤습니다. 예상보다 나빴습니다.

앱 A (피부 기록): Health, Fitness, Email Address, User ID, Purchases, Other Financial Info가 전부 "Data Not Linked to You". 일곱 개 중 Crash Data 하나만 Linked였습니다.

앱 B (PCOS 추적): 일곱 개 카테고리 전부 Not Linked. Linked 칼럼이 비어 있었습니다.

앱 C (폐경 추적): 여섯 개 전부 Not Linked. 게다가 증상을 추적하는 앱인데 Health 데이터 유형 자체가 선언돼 있지 않았습니다. User Content로만 잡혀 있었습니다.

앱 D (갱년기 추적): 아홉 개 유형 전부 Not Linked.

왜 이렇게 됐는지는 짐작이 갑니다. 저 마법사의 질문은 "이 데이터가 사용자 신원과 연결됩니까?"인데, 출시 직전에 그 질문을 읽으면 "우리가 이걸로 사용자를 추적합니까?"로 읽힙니다. 광고 SDK도 없고 추적할 의도도 없으니 "아니오"가 자연스럽게 나옵니다. 질문은 의도를 묻지 않는데 저는 의도로 답했습니다.

결정적 증거는 Apple이 써 놓았습니다

라벨을 고치려고 다이얼로그를 열었더니, 두 번째 단계 본문에 이렇게 적혀 있었습니다.

Data collected from an app is usually linked to the user's identity via these means, unless specific privacy protections are put in place before collection to de-identify or anonymize it (...)

Note: "Personal Information" and "Personal Data", as defined under relevant privacy laws, are considered linked to the user.

제보가 한 주장과 같은 말입니다. 그리고 이건 제3자의 해석이 아니라 플랫폼 자신의 정의입니다. 수집 전에 연결을 끊는 조치를 하지 않았다면 연결된 것으로 본다 — 저는 그 조치를 한 적이 없습니다.

여기서 반박할 여지가 사라졌습니다.

API에는 없습니다

고치려고 API를 먼저 찾았습니다. 44개 앱이라서요.

GET /v1/apps/{id}/appDataUsages           -> 404
GET /v1/apps/{id}/appDataUsagesPublishState -> 404

앱 리소스의 relationships 목록을 전부 뽑아 봤습니다. accessibilityDeclarations, appTags, webhooks, searchKeywords까지 있는데 개인정보·데이터 사용 관련 항목은 하나도 없습니다. 스크립트로 일괄 처리할 방법이 없다는 뜻입니다. 콘솔 UI 전용입니다.

당신이라면 어떻게 하겠습니까? 44개 앱을 클릭으로 고치겠습니까, 심사에서 걸릴 때까지 두겠습니까, 아니면 제일 민감한 것만 고치겠습니까?

여기 좋은 소식이 하나 있습니다

라벨 수정은 버전 제출과 무관하게 즉시 발행됩니다. 심사 대기 중인 앱이어도 됩니다. 바이너리를 올릴 필요도, 심사를 기다릴 필요도 없습니다. Publish를 누르면 그 순간 스토어 페이지가 바뀝니다.

이게 중요한 이유는, 잘못된 라벨은 "다음 릴리스에 묻어 보내는" 종류의 부채가 아니라는 것입니다. 오늘 고칠 수 있습니다. 그래서 미룰 명분이 없습니다.

데이터 유형 하나당 다섯 단계입니다.

  1. 사용 목적 (App Functionality 유지)
  2. "사용자 신원과 연결됩니까?" → 예
  3. 추적 정의 (읽기만)
  4. 추적 예시 (읽기만)
  5. "추적 목적으로 씁니까?" → 아니오 (광고 SDK 없음) → Publish

세 개 앱을 이렇게 고쳤습니다. 끝나면 "Data Not Linked to You" 칼럼이 통째로 사라지고 전부 Linked로 옮겨갑니다.

자동화에서 물린 두 가지

브라우저 자동화로 돌렸고, 두 번 물렸습니다.

하나. 한 배치에 두 유형을 이어 붙이면 실패합니다. Publish를 누르면 페이지가 다시 렌더되고, 그 전에 받아둔 요소 참조가 다른 것을 가리킵니다. 네 번째 앱에서 이걸 밟았습니다 — 다이얼로그가 아예 열리지 않았고, 다행히 반영도 되지 않아 중간 상태 없이 원래대로였습니다. 유형 하나 끝날 때마다 참조를 새로 받아야 합니다.

둘. Publish 버튼의 x좌표가 움직입니다. 다이얼로그 폭이 제목 길이에 따라 달라집니다. "Health"는 1006, "Purchase History"는 1045, "User ID"는 995. 좌표를 하드코딩하면 조용히 빈 곳을 누릅니다.

두 번째가 더 무섭습니다. 실패했다는 신호가 없기 때문입니다.

솔직한 부분

열여섯 개쯤 되는 건강 데이터 앱 중 세 개만 고쳤습니다. 앱당 20~30번 클릭이라 하루에 다 못 했습니다. 나머지는 여전히 틀린 라벨로 나가 있습니다.

그리고 전부를 일괄로 뒤집으면 안 됩니다. 계정이 없는 게임이나 온디바이스 도구는 실제로 "연결되지 않음"이 맞습니다. 라벨은 앱마다 판단해야 하고, 이건 스크립트로 못 하는 이유가 UI 제약만이 아닙니다.

폐경 추적 앱의 Health 유형 미선언은 아직 안 고쳤습니다. 유형을 새로 추가하는 것은 "이 앱이 실제로 무엇을 수집하는가"에 대한 판단이고, 라벨의 연결 여부를 뒤집는 것과는 다른 종류의 결정입니다.

3분 자가진단

  1. 앱의 개인정보 라벨을 열어 "Data Not Linked to You" 목록을 읽으세요. 거기 있는 항목이 당신 DB에서 사용자 외래키가 걸린 테이블에 저장됩니까?
  2. 마법사 2단계 본문을 실제로 읽으세요. "수집 전에 연결을 끊는 조치"를 한 적이 있습니까? 없으면 답은 정해져 있습니다.
  3. 라벨과 웹 정책과 앱 내 문구와 스토어 설명이 같은 말을 하고 있습니까? 네 곳입니다.

1번은 스키마 한 번 보면 끝납니다. 저는 그걸 남이 지적해 주고 나서야 했습니다.

혹시 지금 확인해 보셨다면, 라벨과 스키마가 어긋난 항목이 몇 개였는지 알려주세요. 저는 일곱 개 중 여섯 개였습니다.

관련 글