Skip to content
Blog

Feature Flags Under App Review Guideline 2.5.19

Apple's 16 October 2026 guideline revision turns remote config into a review surface. How to audit feature flags, and what 1.2 now asks of AI image apps.

Jorge Valbuena11 min read

On 16 October 2026 Apple published a revised set of App Review Guidelines alongside an enforcement post titled “Strengthening App Review Guidelines against nonconsensual intimate imagery and bait-and-switch apps.” Two things in it matter if you ship an iOS app with remote config: a guideline that names dormant features as grounds for account termination, and a content rule that quietly expanded to cover apps with no social features at all.

What actually changed — and what didn’t

Guideline 2.5.19 now reads, as retrieved on 19 October 2026:

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. Attempting to hide undocumented features in your app binary, such as through code obfuscation or other methods, is grounds for removal of your app from the App Store and termination of your developer account.

It reads like a new prohibition. It isn’t. Apple’s own changelog describes 2.5.19 as a “new guideline” that “clarifies long-standing policy on hidden, dormant, or undocumented features, formerly covered in Guideline 2.3.1,” and the enforcement post calls it a clarification of a “longstanding prohibition.” The text moved out of 2.3 (Accurate Metadata) and into 2.5 (Software Requirements). What’s new is the location, the salience, and a public statement of the penalty.

That is still a reason to audit, because the enforcement was never theoretical. Apple’s May 2026 fraud report, covering calendar 2025, says it evaluated more than 9.1 million submissions and rejected over 2 million — including over 22,000 rejected for hidden or undocumented features and nearly 59,000 apps removed for being modified after review with intent to commit financial fraud. Those rejections happened under the old numbering. The rule has teeth already; October just pointed a light at it.

One note on citation hygiene: the guidelines page carries no version stamp and Apple calls it a living document, so every quote here is dated to retrieval on 19 October 2026 rather than to a version.

The rule that makes feature flags possible at all is 2.5.2, untouched by this revision. It prohibits apps that “download, install, or execute code which introduces or changes features or functionality.” A JSON payload that selects between branches already compiled into the reviewed binary is data, not code. RevenueCat’s March 2026 write-up on remote testing puts the line well: you can change the data that populates a screen, but you can’t download the screen itself.

So 2.5.2 permits the mechanism, and 2.5.19 names “dormant” as a category of violation. There are two defensible readings of how those fit together, and they are genuinely in dispute.

Reading A: the text says what it says. A feature compiled into the binary and switched off by a server flag is dormant. The only safe posture is to disclose every behavior-changing flag that ships off by default.

Reading B: the harm is misrepresentation, not dormancy. Apple’s companion post defines the conduct as misrepresenting what an app does during review “and then changing after approval — for example, adding features or functionality that weren’t included in the version of the app that was submitted.” Those words appear in the news post, not in 2.5.19 itself. On this reading, a flag over code that was in the submitted build adds nothing post-approval.

As of 19 October 2026 — three days after the revision — Apple has published no clarification addressing remote config, feature flags, or A/B testing, and no 2.5.19 rejection has been reported. The only Apple-channel discussion I could find is a 2017 developer forum thread asking exactly this question, with no Apple staff reply.

The useful part: both readings prescribe the same action. Disclose and make reviewable. And the risk is asymmetric — a reviewer note costs a paragraph, while the named penalty is account termination.

The audit: inventory, then three buckets

Start with an inventory. Every flag key, who owns it, what it does, and what state App Review will observe. Then classify each one against the guideline text, not against taste.

Cosmetic — changes the presentation of functionality already described in your metadata. Colors, copy, layout order, image assets, the ordering of existing onboarding steps. Governed by 2.5.2 as data. Keep it; no disclosure needed. Watch the new 2.3.14 if the same asset also appears as a creative asset on your product page.

Operational — kill switches, circuit breakers, rate limits, endpoint selection, rollout percentages, cache TTLs, retry policy, crash-loop bailouts. These degrade or disable; they never add. Keep them, document them internally.

Behavior-changing — adds a capability, changes what data is collected, changes the monetization model, alters an AI system prompt or model routing, unlocks a surface, changes age-appropriateness, changes which third parties receive data. This is 2.5.19 plus 2.3.1(a) territory, and 5.1.1 if collection changes. Retire it, or disclose it with a reviewable path.

The boundary is not “does it change the UI.” It is: could a reviewer looking at the submitted build, in its default remote state, have seen this? Anything that fails that test is bucket three regardless of how small it looks.

Make the manifest a typed artifact so the classification can be tested rather than remembered:

enum FlagClass: String, Codable, CaseIterable {
    case cosmetic       // presentation of already-described functionality
    case operational    // degrade / disable / route; never adds capability
    case behavior       // adds or alters capability → 2.3.1(a) disclosure
}

struct Flag: Codable, Sendable {
    let key: String
    let owner: String
    let classification: FlagClass
    let defaultValue: Bool          // the state App Review will observe
    let addedInVersion: String
    let expiresAfter: Date?
    let reviewerNote: String?       // required when .behavior
    let reviewablePath: String?     // how a reviewer reaches non-default
}

extension Flag {
    var isDisclosureComplete: Bool {
        guard classification == .behavior else { return true }
        return !(reviewerNote ?? "").isEmpty && !(reviewablePath ?? "").isEmpty
    }
}

Then fail the build on an undisclosed flag:

@Test func behaviorFlagsAreDisclosed() throws {
    let registry = try FlagRegistry.loadFromBundle()
    let undisclosed = registry.flags.filter { !$0.isDisclosureComplete }
    #expect(undisclosed.isEmpty,
            "Undisclosed behavior-changing flags: \(undisclosed.map(\.key))")
}

@Test and #expect are Swift Testing, which shipped with Xcode 16 and is current in Xcode 27.1 (released 15 October 2026); on older toolchains write the XCTest equivalent. The real failure mode is manifest drift, so pair this with a release build phase that diffs flag-key string constants in the binary against the committed manifest.

The reviewer note

Guideline 2.3.1(a) is the companion rule to 2.5.19, and it is the one with the operative language: all new features “must be described with specificity in the Notes for Review section of App Store Connect (generic descriptions will be rejected) and accessible for review.” Accessible for review is the phrase that governs flags.

Apple’s App Review page adds the practical version: “If there are special configurations to set, include the specifics. If features require an environment that is hard to replicate or require specific hardware, be prepared to provide a demo video or the hardware.”

Five elements make a note that satisfies both. This structure is craft inference from the rule text, not an Apple-published template:

  1. That the app uses server-side configuration, and which system.
  2. The default state the reviewer will see.
  3. The exhaustive scope of what flags can change, plus an explicit statement that no flag enables functionality absent from this binary.
  4. A reviewable path to any non-default state — a build-time override, a debug URL scheme, a QA account pinned to a variant, or a demo video.
  5. That no flag can change data collection, monetization model, or age-appropriate content.

Two adjacent details worth having right. 2.1(b): if a configured in-app purchase can’t be found or reviewed in your app, explain why in the notes. And a built-in demo mode in place of a demo account is permitted only “with prior approval by Apple.” On timing, Apple’s current App Review page says review “typically” covers at least 50% of submissions in under 24 hours and 90% in under 48 — not the 90%-in-24-hours figure that circulates on secondary sites.

Kill switches, and the flag that edits a system prompt

No guideline addresses kill switches by name. From the text, disabling a feature is not “adding features or functionality that weren’t included in the version submitted,” so it sits outside 2.5.19’s stated conduct. The exposure is elsewhere: if the disabled feature appears in your App Store description or screenshots, a long-lived kill triggers 2.3.1(a)’s misleading-marketing clause and 2.1 completeness. A kill switch is an incident tool with a short half-life. If it has been engaged for three months, it is a product decision and the metadata should match.

There is an inversion here that’s worth knowing: for an app with user-generated or generated content, a tested kill switch over the generation pipeline is your remediation mechanism under Guideline 1.2, which expects you to remove violating content and “provide a plan to improve your compliance.” In that conversation the switch is an asset.

And this is where the two halves of the October revision meet. A flag that changes no code path but swaps an LLM system prompt, switches model routing, or moves a safety-filter threshold is behavior-changing — even though it is pure data under 2.5.2. For an image-generation app, a server-side string edit can turn a compliant build into a 1.1.4 violation, 1.1.4 being the long-standing ban on “overtly sexual or pornographic material.” Prompt and policy configuration belongs in bucket three. Treat it as reviewable surface.

Guideline 1.2 now reaches apps with no social features

The strengthened 1.2 applies to apps that “enable users to create, generate, upload, share, or otherwise contribute content,” and Apple added a sentence naming the scope: “This includes social media, user-generated content platforms, and AI content creation apps.” Content moderation requirements now explicitly cover nonconsensual intimate imagery, including imagery generated with AI tools.

Read the scope carefully. Create, generate, plus the explicit naming of AI content creation apps, means an image-generation or image-editing app with no feed, no sharing, and no other users is subject to all four of 1.2’s required mechanisms: a filtering method, a reporting mechanism with timely responses, the ability to block abusive users, and published contact information. The third is semantically odd for a single-player editor, but it is in the list. Apple also now states that failing to implement safeguards, or failing to remediate after notice, may be a Developer Code of Conduct violation.

The most useful correction I can offer here concerns the framework most teams will reach for first. SensitiveContentAnalysis cannot discharge this obligation. It is a display-time warning tool, and SCSensitivityAnalyzer.analysisPolicy is .disabled unless the user has turned on Sensitive Content Warning or Communication Safety in Screen Time. Apple’s documentation is explicit that detection works only when the policy is something other than .disabled. A per-user opt-in cannot cover a platform duty that runs to all content.

import SensitiveContentAnalysis

// Display-time warning ONLY. Not a moderation gate: analysisPolicy is
// .disabled unless the user opted in, so this may return nothing at all.
func shouldBlurBeforeDisplay(imageAt url: URL) async throws -> Bool {
    let analyzer = SCSensitivityAnalyzer()
    guard analyzer.analysisPolicy != .disabled else { return false }
    let analysis = try await analyzer.analyzeImage(at: url)
    return analysis.isSensitive && analysis.shouldIndicateSensitivity
}

The framework requires the com.apple.developer.sensitivecontentanalysis.client entitlement and is available from iOS 17. Apple does ship a QR code and configuration profile for exercising the intervention flow, documented under “Testing your app’s response to sensitive media” — teams often assume this is untestable.

The deeper reason classifiers don’t close this gap: NCII is a consent problem, not a nudity problem. No on-device classifier can tell whether the depicted person consented or is even real. The working mechanisms are hash matching against victim-submitted banks, provenance and identity constraints at generation time, and a fast human-reviewed takedown path. StopNCII.org (run by SWGfL) offers participation tiers and uses PDQ/PhotoDNA for photos and MD5 for videos — note that MD5 means a re-encode evades the match. NCMEC’s Take It Down covers imagery of people under 18 at the time of capture, so a general-purpose app needs both banks. Fees, onboarding steps and API specs for StopNCII are not published; contact is required, and I won’t guess at them. For balance, CDT’s April 2026 brief “Digital Fingerprints, Human Stakes” documents hash matching’s governance problems — no adjudication, false positives, no appeal — and Ofcom pairs its hash-matching recommendation with human-review safeguards.

The harder deadline isn’t Apple’s

Apple’s 1.2 says proactively prevent and remove. The US TAKE IT DOWN Act is more specific and more measurable, and it has been live longer. The FTC began enforcing the platform provisions on 19 May 2026 — five months before Apple’s revision.

Covered platforms must provide a notice-and-removal process and, on a valid request, remove the content and known identical copies within 48 hours. Requesters don’t have to find duplicates; the platform must make reasonable efforts. Per FTC business guidance, the process needs a plain-language explanation, clear and conspicuous notice, an easy submission route (the FTC suggests an option inside the photo or video itself), and people without an account must be able to submit requests. Digital forgeries made with software or AI are covered alongside authentic imagery, and coverage is not limited to large platforms. The penalty on the FTC’s guidance page is $53,088 per violation — an inflation-adjusted figure that changes annually, so date it when you cite it. In May 2026 the FTC sent compliance warning letters to fifteen companies, Apple among them.

For UK storefronts, Ofcom set a 30 September 2026 deadline for measures against sharing of illegal intimate images including AI deepfakes, opened an enforcement programme on 9 September 2026, and has stakeholder responses due 6 November 2026. I could not read Ofcom’s own announcement page — it returned HTTP 403 — so that detail comes via SWGfL’s summary and secondary coverage, and should be re-verified against Ofcom before you act on it. Reported dates for the new UK deepfake offences (February and June 2026) come from a law-firm guide rather than primary legislation. On EU DSA duties specific to NCII I found nothing, which is a gap rather than an absence.

Two adjacent clocks the flag audit will surface. Age ratings were restructured into 4+, 9+, 13+, 16+ and 18+ tiers, with a new questionnaire required for all apps since 31 January 2026 — and Apple explicitly tells developers to consider how AI assistants and chatbot functionality affect how often sensitive content appears. A flag that changes content exposure can invalidate those answers. Sources conflict on whether UGC alone raises a rating, so check App Store Connect Help rather than a blog. Separately, the Developer Program License Agreement was updated on 13 October 2026 for unrelated reasons; an unaccepted agreement blocks submissions, so that’s a second clock running this month.

Everything above was verified as of 19 October 2026, against iOS 27.0.1 and Xcode 27.1. The claim most likely to go stale first is that Apple has issued no guidance on remote config under 2.5.19. If that changes, the audit still holds — because both readings of the rule ask for the same thing.