# App Store 가이드라인 2.3.1: 숨겨진 기능과 OTA 업데이트

> 가이드라인 2.3.1에 따라 Apple은 숨겨졌거나 휴면 상태이거나 심사 노트에 없는 기능을 거절해요. 원격으로 바꿀 수 있는 것과 새 빌드가 필요한 것을 정리했어요.

원본 페이지: [App Store 가이드라인 2.3.1: 숨겨진 기능과 OTA 업데이트](https://devoria.fr/ko/blog/app-store-guideline-2-3-1-hidden-features-ota-updates)
작성자: [Mehdi Azizi](https://devoria.fr/ko/mehdi-azizi)
게시일: 2026-11-04
최종 내용 수정: 2026-11-04


가이드라인 2.3.1은 숨겨진 기능, 휴면 기능, 문서화되지 않은 기능을 금지해요. 앱이 하는 모든 일은 사용자와 심사자에게 보여야 해요. 새로운 기능은 하나하나 심사 노트에 구체적으로 적어야 하고요. **원격 업데이트는 Apple이 본 것을 고칠 수는 있어도, Apple이 보지 못한 것을 추가할 수는 없어요.**

저는 Expo로 iOS 앱을 출시하고, 모든 앱에 첫 빌드부터 EAS Update를 넣어서 수정 사항을 배포해요. 제 규칙은 이거예요: 버전이 심사 중일 때는 원격 업데이트를 절대 하지 않는다. 이 가이드는 Apple의 원문, 라이선스, Expo 문서, 공개된 거절 사례를 바탕으로 해요.

## 가이드라인 2.3.1은 무엇을 말하나요?

2.3.1은 [App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/#2.3.1)의 2번 섹션 "Performance" 안에서 2.3 "Accurate Metadata" 파트를 여는 조항이에요. 원문은 이렇게 시작해요: "Don't include any hidden, dormant, or undocumented features in your app; your app's functionality should be clear to end users and App Review." 쉽게 말해 숨긴 것도, 잠들어 있는 것도, 문서화되지 않은 것도 없어야 한다는 뜻이에요.

이어지는 문장은 모든 제출에 적용돼요: "All new features, functionality, and product changes must be described with specificity in the Notes for Review section of App Store Connect (generic descriptions will be rejected) and accessible for review." **새로운 기능은 모두 심사 노트에 구체적으로 설명되어야 하고, 심사자가 접근할 수 있어야 해요.** 그러니 "버그 수정 및 개선" 같은 문구로는 부족해요.

같은 항목은 오해를 부르는 마케팅도 겨냥해요. App Store든 다른 곳이든, 없는 콘텐츠나 서비스를 약속하거나 가짜 가격을 보여주는 것이죠.

이 부분은 Nolera에서 도움이 됐어요. 출시 전에 스토어 페이지를 다시 읽다가 검증할 수 없는 문장 두 개를 발견했어요. 더 이상 사실이 아닌 "프랑스어 인터페이스만 제공"과, 한 번도 출시된 적 없는 앱에 붙은 "가장 많이 선택된 팩"이었어요. 심사자에게 남긴 노트도 이미 지난 제품 상태를 설명하고 있었고요. 이 부분이 겨냥하는 게 바로 이런 거예요. 반면 [2.3.10](https://devoria.fr/ko/blog/guideline-2-3-10-accurate-metadata-rejection)은 주로 스토어 페이지를 다뤄요.

## 2.3.1 거절 메시지는 무슨 뜻인가요?

거절 제목은 "Guideline 2.3.1 - Performance"이고, 가끔 뒤에 "Hidden Features"가 붙어요. React Native 개발자들이 공개한 메시지 두 개를 볼게요.

- **2024년 3월**, [CodePush GitHub 이슈](https://github.com/microsoft/react-native-code-push/issues/2680): "The app contains hidden features."라는 말만 있고 다른 설명은 없었어요. 개발자가 자세한 설명을 요청하자 2.5.2 조항으로 추가 답변을 받았어요: "Your app, extension, or linked framework appears to contain code designed explicitly with the capability to change the app's behavior or functionality after App Review approval." 이후 앱이 통과했는지는 이슈에 나와 있지 않아요.
- **2023년 2월**, [다른 이슈](https://github.com/microsoft/react-native-code-push/issues/2434)에서 Apple은 바이너리의 "CodePush-related remote code downloading/updating support"를 지적했어요.

제 해석은 이래요: **심사자는 업데이트 내용을 보는 게 아니라, 업데이트를 받을 수 있는 코드를 봐요.** 두 사례 모두 CodePush 관련이에요. EAS Update를 겨냥한 최근 공개 거절 사례는 찾지 못했지만, 그렇다고 해서 아무것도 증명되는 건 아니에요.

## 원격 업데이트: 2.5.2와 라이선스가 허용하는 것

[2.5.2 규칙](https://developer.apple.com/app-store/review/guidelines/#2.5.2)은 앱이 "download, install, or execute code which introduces or changes features or functionality of the app"하는 것을 금지해요. 이 규칙은 [백그라운드 모드](https://devoria.fr/ko/blog/guideline-2-5-4-background-mode-rejection) 가이드에서도 다뤘어요.

[Apple Developer Program License Agreement](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/#b331)의 3.3.1(B) 조항은 다운로드한 실행 코드를 금지하지만, 이렇게 덧붙여요: "Interpreted code may be downloaded to an Application". 조건은 세 가지예요. 그 코드가 앱의 주요 목적을 바꾸지 않을 것, 서명이나 샌드박스, 시스템 보안을 우회하지 않을 것, 다른 앱을 파는 스토어를 만들지 않을 것.

Expo 앱의 JavaScript는 인터프리트되는 코드예요. 제가 보기에 EAS Update는 이 범위 안에 들어가요. EAS Update [문서](https://docs.expo.dev/eas-update/introduction/)도 업데이트가 App Store 규칙을 따라야 하며 "changes to your app's behavior need to be reviewed"라고 짚어요. 치명적인 버그라면 Expo는 원격으로 먼저 고치고, 그 수정을 담은 버전을 다시 제출하는 방식을 제안해요.

**제 기준: 이미 있는 것을 고치고 조정하는 건 OK. 앱의 정체성이나 판매하는 것을 바꾸는 건 안 돼요.**

## 원격으로 바꿀 수 있는 것, 새 빌드가 필요한 것

여기서 인용한 Apple과 Expo 페이지를 바탕으로 제가 쓰는 기준표예요. "무료에서 유료로 전환" 행과 마지막 두 행은 제 해석이에요.

| 변경 사항                    | 방법              | 이유                           |
| ---------------------------- | ----------------- | ------------------------------ |
| JavaScript 버그              | 원격 업데이트     | Expo가 의도한 용도             |
| 텍스트, 번역, 스타일         | 원격 업데이트     | Expo가 의도한 용도             |
| 구독 가격                    | App Store Connect | 코드 변경 없음                 |
| 프로모션 텍스트              | App Store Connect | 새 제출 불필요                 |
| 이미 승인된 유형의 인앱 구매 | 해당 구매만 심사  | App Store Connect 규칙         |
| 해당 유형의 첫 인앱 구매     | 새 버전           | App Store Connect 규칙         |
| 새 기능                      | 새 버전           | 2.3.1 및 2.5.2                 |
| 무료에서 유료로 전환         | 새 버전           | 스토어 페이지와 노트 수정 필요 |
| 권한, Info.plist             | 새 빌드           | 바이너리 안의 파일             |
| 네이티브 모듈, Expo SDK      | 새 빌드           | 네이티브 코드 변경             |
| 기능을 끄는 스위치           | 원격 설정         | 일시적이라면 허용              |
| 기능을 드러내는 스위치       | 금지              | 한 번도 심사받지 않은 기능     |

Apple의 [버전 정보](https://developer.apple.com/help/app-store-connect/reference/app-information/platform-version-information) 페이지에 따르면 프로모션 텍스트는 "without requiring an updated submission" 바꿀 수 있어요. [인앱 구매 제출](https://developer.apple.com/help/app-store-connect/manage-submissions-to-app-review/submit-an-in-app-purchase) 도움말에 따르면 각 유형의 첫 인앱 구매는 새 버전과 함께 제출하고, 그다음부터는 단독으로 제출해요. Expo는 네이티브 코드, 권한, SDK 버전을 새 바이너리가 필요한 항목으로 분류해요. [ITMS-90683](https://devoria.fr/ko/blog/itms-90683-missing-purpose-string-expo)과 같은 경우죠. 고장 난 기능을 끄는 스위치는 새로운 걸 추가하지 않아요. 단, 구독자가 돈을 내고 산 것을 빼앗지 않는 한에서요.

## 거절당하지 않는 원격 스위치

원격 스위치, 즉 feature flag는 그 자체로 금지된 게 아니에요. 무엇을 숨기느냐가 관건이에요. 제 규칙은 이래요.

- **스위치로 보여줄 수 있는 모든 것은 심사자에게 존재하고 켜져 있어야 하며**, 심사 노트에 이름이 적혀 있어야 해요.
- A/B 테스트는 심사자가 볼 수 있는 두 변형을 비교해요. 심사받은 변형과 처음 보는 변형을 비교하면 안 돼요.
- **심사 중인지 감지해서 다르게 동작하는 코드는 절대 넣지 않아요.** 그게 바로 숨겨진 기능의 정의예요.
- 원격으로 제어하는 페이월은 승인된 상품만 보여주고, 모든 변형이 [3.1.2 규칙](https://devoria.fr/ko/blog/guideline-3-1-2-subscription-app-rejection)을 지켜야 해요.

## 데모 모드와 테스트 계정

[가이드라인 2.1](https://devoria.fr/ko/blog/guideline-2-1-app-store-rejection-fix)은 앱에 로그인이 있다면 데모 계정을 요구하고, 서버도 켜져 있어야 해요. 법적 또는 보안상의 이유로 계정을 줄 수 없다면 앱 내장 데모 모드로 대신할 수 있지만, "with prior approval by Apple"일 때만 가능하고 모든 기능을 보여줘야 해요. Apple의 버전 정보 페이지는 여기에 덧붙여요: 계정은 만료되면 안 되고, 추가 계정은 노트에 적는데 노트는 4,000바이트로 제한돼요.

2.3.1과의 연결 고리는 이거예요: **다른 방법으로는 보이지 않는 화면을 여는 데모 모드나 테스트 계정은 반드시 설명해야 해요.** 알리지 않은 비밀 제스처, 코드, 특수 계정은 숨겨진 기능이에요. 특수 접근 방법마다 노트에 이름을 적고, 무엇을 여는지 함께 써두세요.

## 제 규칙: 심사 중에는 원격 업데이트 금지

제 모든 앱에 EAS Update가 들어간 이유가 있어요. 제 알람 앱 Solys에서, 효과음이 한 번이라도 실패하면 앱이 소리를 아예 못 내는 JavaScript 버그가 있었어요. 수정은 세 줄이면 됐지만, 배포하려면 전체 빌드와 심사가 필요했어요. 그 뒤로 모든 앱에 첫 빌드부터 expo-updates를 넣어요. 네이티브 모듈이라서, 이게 없이 컴파일된 바이너리는 아무것도 받지 못하거든요. `app.json`의 제 설정이에요.

```json
{
  "expo": {
    "runtimeVersion": { "policy": "fingerprint" },
    "updates": { "fallbackToCacheTimeout": 0, "checkAutomatically": "ON_LOAD" }
  }
}
```

`fingerprint`를 쓰면 업데이트는 네이티브 코드가 일치하는 바이너리에만 도달해요([런타임 버전](https://docs.expo.dev/eas-update/runtime-versions/)). 하지만 버전이 심사를 기다리거나 심사 중일 때는 아무것도 배포하지 않아요. 이유는 두 가지예요.

1. **심사자는 수정 사항을 보지 못해요.** `fallbackToCacheTimeout`이 0이면 새로 설치한 앱은 내장된 코드로 시작하고, 다음 실행 때 업데이트를 적용해요([expo-updates 문서](https://docs.expo.dev/versions/latest/sdk/updates/)). 심사자는 설치하고 한 번 실행해서 바이너리를 테스트해요. Nolera는 이미 원격으로 수정을 배포한 상태에서 거절당했어요. 이 이야기는 2.1 가이드에 적어 두었어요.
2. **심사자가 테스트하는 것과 사용자가 받는 것은 같아야 해요.** 심사 중에 동작을 바꾸면 심사받은 버전과 배포된 버전 사이에 차이가 생겨요.

## 구체적인 심사 노트

2.3.1은 일반적인 설명을 거절해요. 제가 쓸 템플릿이에요. 영어로 써요.

```text
What's new in this version:
- [Feature]: [where to find it, how to test it].

Remote configuration:
- "[flag]" controls [feature]. It is ON for review.

Over-the-air updates:
- The app uses expo-updates for bug fixes only.
  No update will be published during this review.

Demo account: see Sign-in information. It never expires.
```

## 2.3.1 거절에는 어떻게 답하나요?

App Store Connect에서 답하면 돼요. 여기서 [App Review 메시지에 답장](https://developer.apple.com/help/app-store-connect/manage-submissions-to-app-review/reply-to-app-review-messages/)할 수 있어요.

- **메시지가 모호하다면**: 2024년 이슈의 개발자처럼 어떤 기능을 말하는지 물어보세요.
- **실제 기능인데 설명이 부족했다면**: 정확한 경로와 짧은 영상을 제공하고 노트를 보완하세요.
- **스위치가 처음 보는 기능을 드러낸다면**: 스위치를 없애거나, 켜고 설명한 뒤 다시 빌드하세요.
- **업데이트 코드가 문제라면**: 3.3.1(B) 조항 범위 안에서 수정용으로만 쓴다고 설명하세요. 2023년 이슈에서는 Apple이 그 코드를 제거하라고 요구했어요.

## 제출 전 2.3.1 체크리스트

- 이번 버전의 모든 새 기능을 경로와 함께 노트에 설명했다.
- 모든 원격 스위치를 심사용으로 켜두고 노트에 이름을 적었다.
- 심사자에게만 다르게 동작하는 코드는 없다.
- 특수 접근 방법을 설명했다: 데모 모드, 테스트 계정, 제스처.
- 스토어 페이지와 페이월이 빌드에 없는 것을 약속하지 않는다.
- 심사 중에는 원격 업데이트를 배포하지 않는다.

## 제출 전에 서류 점검하기

[Review Kit](https://devoria.fr/ko/kits/app-review)에는 제 제출 전 체크리스트, 심사자용 노트 템플릿, 프로젝트를 검토해 주는 Claude Code skill이 들어 있어요. 사람의 눈으로 보길 원한다면 [저와 한 시간 예약하세요](https://devoria.fr/ko/coaching). 화상으로 심사 노트와 업데이트 설정을 함께 검토해요. 전체 과정은 [Apple에 거절된 앱](https://devoria.fr/ko/expertise/app-rejected-by-apple) 페이지에 있어요.

첫 출시를 준비하고 있나요? [무료 진단을 받아보세요](https://devoria.fr/ko/diagnostic). 단계를 순서대로 정리해 주고, 제 Apple 거절 방지 체크리스트를 보내드려요.

## 자주 묻는 질문

### "Guideline 2.3.1 - Performance - Hidden Features"는 무슨 뜻인가요?

Apple이 앱에 숨겨진 기능, 휴면 기능, 또는 심사 노트에 없는 기능이 있다고 판단했다는 뜻이에요. 심사 후에 앱을 바꿀 수 있는 코드를 지적하는 경우도 있고, 이때 2.5.2 거절이 함께 오기도 해요.

### Apple은 EAS Update를 허용하나요?

라이선스는 앱의 주요 목적을 바꾸지 않는 한 다운로드한 인터프리트 코드를 허용해요. 수정은 OK. 심사받지 않은 기능 추가는 안 돼요.

### iOS 앱에서 feature flag를 써도 되나요?

네, 스위치로 보여줄 수 있는 모든 것이 심사를 받았다면요. 심사용으로 모든 스위치를 켜두고 노트에 설명하세요.

### 데모 모드를 심사자에게 알려야 하나요?

네. 앱 내장 데모 모드는 Apple의 사전 승인이 필요하고, 모든 기능을 보여줘야 해요.

### 심사 중에 원격 업데이트를 배포해도 되나요?

명시적으로 금지하는 규정은 없어요. 하지만 심사자는 첫 실행에서 그 업데이트를 보지 못하고, 심사받은 버전과 차이가 생겨요. 저는 항상 하지 않아요.


## 내 프로젝트

[Review Kit](https://devoria.fr/ko/kits/app-review): 제 App Store 심사 체크리스트, 수정해서 통과한 Apple 리젝 사례, Google Play 체크리스트, Claude Code 스킬 3개를 담았어요. ₩73,000.

[iOS & Android Kit](https://devoria.fr/ko/kits/ios): Claude Code로 iPhone·Android 앱을 만들어 보세요. 제 앱들의 시작 코드, 스토어 출시까지의 과정, 모든 키트를 담았어요. ₩148,000.

[진단: 내 아이폰 앱까지 남은 거리](https://devoria.fr/ko/diagnostic): 7개 질문, 60초. 아이폰 앱 출시를 진짜 가로막는 게 뭔지, 4단계 로드맵, 나에게 맞는 키트까지 알려드려요.
