DEV Community

이대겸
이대겸

Posted on

Run PrivacyInfo.xcprivacy Preflight in GitHub Actions Before App Store Submission

App Store privacy issues are much easier to investigate before a release than after an upload warning appears.

A useful release workflow is:

  1. scan the repository,
  2. review PrivacyInfo.xcprivacy and Required-Reason API signals,
  3. check dependency evidence,
  4. fail CI when the repository needs review,
  5. keep the result with the release evidence.

This post shows one way to add that preflight to GitHub Actions using the AccessVerdict CLI.

The goal is not to replace Apple's validation.

The goal is to catch repository-level evidence earlier.

What are we checking?

Before an App Store submission, I want CI to answer a few simple questions:

  • Does the repository contain a PrivacyInfo.xcprivacy file?
  • Are there Required-Reason API signals in the source?
  • Does the privacy manifest appear structurally consistent with those signals?
  • Are there dependencies that deserve privacy-manifest review?
  • Did anything change since the last release?

These are repository questions.

They are not the same thing as validating the final built application.

That distinction matters.

A clean repository preflight does not guarantee App Store acceptance, but it can catch obvious gaps before upload.

Run the CLI locally first

You can run the preflight without installing anything globally:

npx --yes @accessverdict/cli@0.2.3 scan .
Enter fullscreen mode Exit fullscreen mode

For CI, I prefer strict mode:

npx --yes @accessverdict/cli@0.2.3 scan . --strict
Enter fullscreen mode Exit fullscreen mode

Strict mode is useful when you want review-only findings to affect the CI result instead of being treated as advisory.

You can also request machine-readable output:

npx --yes @accessverdict/cli@0.2.3 scan . --json
Enter fullscreen mode Exit fullscreen mode

The scan is deterministic and repository-based.

Source files are scanned locally by the CLI rather than uploaded to an AI model.

Minimal GitHub Actions workflow

Create:

.github/workflows/privacy-preflight.yml
Enter fullscreen mode Exit fullscreen mode

Then add:

name: App Store Privacy Preflight

on:
  pull_request:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read

env:
  DO_NOT_TRACK: "1"

jobs:
  privacy-preflight:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24

      - name: Run privacy preflight
        run: npx --yes @accessverdict/cli@0.2.3 scan . --strict
Enter fullscreen mode Exit fullscreen mode

That is enough to turn the repository scan into a basic CI gate.

The DO_NOT_TRACK=1 environment variable disables AccessVerdict CLI telemetry for the workflow.

Why pin the CLI version?

You could use:

npx @accessverdict/cli scan .
Enter fullscreen mode Exit fullscreen mode

But CI should normally be reproducible.

Pinning:

@accessverdict/cli@0.2.3
Enter fullscreen mode Exit fullscreen mode

means a future CLI release does not unexpectedly change the behavior of an existing release workflow.

You can upgrade the version deliberately after reviewing the changes.

Understand the exit codes

The CLI uses different exit codes depending on the result.

At the time of writing:

0   Clean / no blocking finding
2   Blocking finding
3   Review-only finding when --strict is enabled
4   Scan or read error
64  CLI usage error
Enter fullscreen mode Exit fullscreen mode

This makes it possible to use the preflight as a real CI decision instead of parsing human-readable text.

For example:

npx --yes @accessverdict/cli@0.2.3 scan . --strict
Enter fullscreen mode Exit fullscreen mode

will cause the GitHub Actions step to fail when the repository contains a blocking issue or a review result that strict mode treats as actionable.

Preserve the report as a CI artifact

A useful improvement is to keep the preflight result with the workflow run.

That gives you historical evidence of what the repository looked like when a release was reviewed.

Run the CLI with JSON output:

- name: Generate privacy preflight report
  run: |
    npx --yes @accessverdict/cli@0.2.3 scan . --json --strict > privacy-preflight.json

- name: Archive privacy preflight report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: privacy-preflight-report
    path: privacy-preflight.json
Enter fullscreen mode Exit fullscreen mode

The if: always() is important.

If the scan fails the CI step, GitHub Actions can still upload the report so you can inspect the evidence that caused the failure.

A complete workflow could look like this:

name: App Store Privacy Preflight

on:
  pull_request:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read

env:
  DO_NOT_TRACK: "1"

jobs:
  privacy-preflight:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24

      - name: Generate privacy preflight report
        run: |
          npx --yes @accessverdict/cli@0.2.3 scan . --json --strict > privacy-preflight.json

      - name: Archive privacy preflight report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: privacy-preflight-report
          path: privacy-preflight.json
Enter fullscreen mode Exit fullscreen mode

Now every workflow run can leave behind a machine-readable preflight report.

Why archive the report?

There is an important boundary between:

repository evidence
Enter fullscreen mode Exit fullscreen mode

and:

the final built application
Enter fullscreen mode Exit fullscreen mode

A repository scanner cannot prove exactly what ended up inside the final App Store artifact.

But preserving the preflight report gives you useful traceability.

If an App Store warning appears later, you can go back to the exact CI run and inspect:

  • the commit,
  • the dependency lockfiles,
  • the PrivacyInfo.xcprivacy state,
  • the detected Required-Reason API evidence,
  • the dependency inventory,
  • and the preflight result that was reviewed before release.

For a production release process, that history can be more useful than simply seeing a green checkmark.

Lockfiles matter

Privacy review is not only about your own Swift or Objective-C source.

Dependencies matter too.

Depending on the project, useful evidence may include files such as:

Podfile
Podfile.lock
Package.swift
Package.resolved
pubspec.yaml
package.json
Enter fullscreen mode Exit fullscreen mode

A repository preflight can use those files to identify dependencies that deserve additional privacy review.

It should not pretend to know more than the repository can prove.

For example, identifying an SDK dependency is different from proving that the exact compiled version in the final application satisfies every current App Store requirement.

That final distinction should remain explicit.

Test the workflow before using it on a real app

I maintain a small public repository containing intentionally simplified privacy-manifest cases:

https://github.com/tzlee123/ios-privacy-manifest-preflight-cases

The examples include:

01-missing-manifest
02-missing-category
03-structure-review
04-sdk-review
05-clean
Enter fullscreen mode Exit fullscreen mode

They are intentionally small teaching fixtures rather than complete buildable iOS applications.

You can try:

npx --yes @accessverdict/cli@0.2.3 scan ./cases/01-missing-manifest
Enter fullscreen mode Exit fullscreen mode

and compare it with:

npx --yes @accessverdict/cli@0.2.3 scan ./cases/05-clean
Enter fullscreen mode Exit fullscreen mode

There is also a public GitHub Actions workflow running the CLI against these cases:

https://github.com/tzlee123/ios-privacy-manifest-preflight-cases/actions/workflows/accessverdict-cli-demo.yml

That is useful if you want to inspect a working CI example before adding the workflow to your own repository.

Where should this run?

There are several reasonable choices.

On every pull request

Good when privacy-related changes happen frequently.

on:
  pull_request:
Enter fullscreen mode Exit fullscreen mode

Only on main

Good when you want less CI noise.

on:
  push:
    branches:
      - main
Enter fullscreen mode Exit fullscreen mode

Only before release

You can also trigger the workflow manually:

on:
  workflow_dispatch:
Enter fullscreen mode Exit fullscreen mode

For a small team, I like combining all three and adjusting later based on actual workflow volume.

AI review and deterministic checks are different tools

An AI assistant can be useful for explaining a finding.

For example:

  • Why might UserDefaults require review?
  • What does this manifest key mean?
  • Which dependency introduced this SDK?

But a release gate should ideally behave the same way every time it sees the same repository state.

That is why I prefer a deterministic scanner for the CI decision and AI for the explanation layer.

The two approaches complement each other.

A practical release checklist

Before App Store submission:

[ ] PrivacyInfo.xcprivacy exists where expected

[ ] Required-Reason API signals have been reviewed

[ ] Manifest categories and reason arrays have been reviewed

[ ] Dependency lockfiles have been checked

[ ] SDK privacy requirements have been reviewed

[ ] Preflight passes in CI

[ ] JSON report is archived with the workflow run

[ ] Final built product is still reviewed separately
Enter fullscreen mode Exit fullscreen mode

The last item is important.

Repository evidence is valuable, but it should not be confused with final binary validation.

Try the repository preflight

AccessVerdict provides the same repository preflight through the web and CLI:

https://accessverdict.dev/app-store-preflight?utm_source=devto&utm_medium=referral&utm_campaign=privacy_preflight_ci

Or run it locally:

npx --yes @accessverdict/cli@0.2.3 scan .
Enter fullscreen mode Exit fullscreen mode

For a stricter CI gate:

npx --yes @accessverdict/cli@0.2.3 scan . --strict
Enter fullscreen mode Exit fullscreen mode

And for automation:

npx --yes @accessverdict/cli@0.2.3 scan . --json
Enter fullscreen mode Exit fullscreen mode

The important part is not adding another tool to the release process.

It is moving privacy-manifest review earlier, when the repository evidence is still easy to inspect and fix.


AccessVerdict is an independent developer tool and is not affiliated with Apple.

A clean preflight result does not guarantee App Store acceptance. Always review current Apple requirements and the final application before submission.

Top comments (0)