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 . 

For CI, I prefer strict mode:

npx --yes @accessverdict/cli@0.2.3 scan . --strict 

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 

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 

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 

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 . 

But CI should normally be reproducible.

Pinning:

@accessverdict/cli@0.2.3 

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 

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 

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 

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 

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

Why archive the report?

There is an important boundary between:

repository evidence 

and:

the final built application 

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 

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 

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 

and compare it with:

npx --yes @accessverdict/cli@0.2.3 scan ./cases/05-clean 

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: 

Only on main

Good when you want less CI noise.

on: push: branches: - main 

Only before release

You can also trigger the workflow manually:

on: workflow_dispatch: 

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 

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 . 

For a stricter CI gate:

npx --yes @accessverdict/cli@0.2.3 scan . --strict 

And for automation:

npx --yes @accessverdict/cli@0.2.3 scan . --json 

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.