Static analysis reads the bundle on disk. Nothing executes, so this is the part you can safely run against something you don’t trust at all. It is also where most of the report comes from. Roughly forty detectors run over a bundle; this page groups them by the question they answer.

Identity — is this what it says it is?

Full chain validation: Developer ID, the signing Team ID, hardened-runtime flags, and the spctl assessment. The 10-character Team ID is expanded to the developer’s registered name, so A1B2C3D4E5 becomes a company you can recognise.
A deep dive rather than a yes/no: whether the ticket is stapled, what spctl says about it, and the SHA-256 of the bundle. Every relaxation is reported with the entitlement or flag responsible.
Where the bundle came from — quarantine attributes, Mac App Store receipt if present, and the update mechanism the app carries (Sparkle, its own updater, or none).

Capability — what is it allowed to do?

  • Entitlements — the full list, with the sandbox and hardened-runtime exceptions called out rather than buried.
  • TCC — which privacy-protected resources the app declares an interest in, and the usage-description strings it will show you when it asks.
  • Privacy Manifest — Apple’s declared manifest, cross-checked against what the binary actually links. A manifest that omits an API the binary calls is more interesting than either fact alone.
  • App extensions and helpers — every embedded extension, XPC service, and helper binary, each analysed rather than counted.

Requested, granted, used

Declaring an interest is only the first of three questions, and the Static tab crosses all three in one table — Permissions: requested vs granted vs used. The gaps are the point. A capability requested and granted but never touched across a run you drove properly is worth a question; one macOS has granted that the app never declared a reason for is worth a larger one.
The granted column needs Full Disk Access — privacycommand’s own, not the inspected app’s. macOS protects the TCC databases, so a copy of privacycommand without FDA can’t read them. It doesn’t guess: the column reads unknown and the table shows a card with a button into the right System Settings pane. Grant it and reopen the app.This is a TCC grant, not root — the helper is a separate thing and won’t help here. Four things go missing without FDA, all of them in this table: the granted column; any row that exists only because macOS recorded a grant — including the granted-but-never-declared case above, which has no row at all when the grant can’t be read; the System access granted list below the table, which is where the inspected app’s own Full Disk Access, Accessibility, Screen Recording or Input Monitoring grants would appear; and the Verdict column, which collapses to Grant unknown on every row.That last one costs more than it looks. Used in binary, not declared — a warning that needs no TCC data at all, since it comes from the binary — is computed after the unknown-grant check and never reached, so the warning and the row ordering that surfaces it both disappear. The rest of the static report is unaffected.

Content — what does it ship?

  • SDK fingerprints — analytics, advertising, and attribution SDKs matched against a fingerprint database. See Telemetry.
  • Hard-coded domains and URLs — hosts sitting in the binary and in bundled resources.
  • URL schemes and document types — what the app registers to handle, which is also what other apps can use to poke it.
  • Frameworks and rpaths — what it links, where it looks, and whether the search paths widen the dylib hijacking surface.
  • Embedded assets and resources — including scripts and databases shipped inside the bundle.
  • Secrets and licence keys — names of credential-shaped strings. It reports that a key-shaped thing exists and what it appears to be for; it does not print the value.
  • Feature flags and trial stateisPro, isTrial, subscription_status, experiment_id, and the platform-specific switches behind them (LaunchDarkly, Optimizely, Firebase Remote Config, PostHog, Statsig, Unleash).

Risk — what should worry you?

Anti-analysis signals. Checks a binary can make to notice it’s being inspected — debugger detection, VM detection, integrity checks. Present in plenty of legitimate DRM, so this is a signal rather than a verdict. Dylib hijacking surface. Where a writable or under-specified library search path would let an attacker land code inside the app’s process. Behaviour analysis. The detectors above produce signals; a behaviour pass turns combinations of them into findings with a risk tier, which is what drives the summary and the batch scan triage table.

Every finding is explained

privacycommand ships a Knowledge Base entry alongside each detector. When a finding says the app has com.apple.security.cs.disable-library-validation, you get a plain-English explanation of what that entitlement relaxes and why an app might legitimately want it — not just the raw key. This is a deliberate contract: a new detector is not complete without its Knowledge Base entry. If you’re adding one, see Contributing.

Showing your work

Fourteen of the risk contributors carry a show source button — the magnifying glass next to the row on the risk-score breakdown. It opens a popover naming where that evidence actually came from. For the signing-related ones that’s a command you can re-run yourself:
For the rest it’s a location rather than a command — Contents/Info.plist, Contents/MacOS/<executable>, or the Files or Network tab for anything a monitored run produced. The popover text is selectable, and where there’s a file behind it, Reveal in Finder takes you there. Contributors outside those fourteen have no button — for those, the Knowledge Base entry above is the explanation. The Executive Summary rows have a one-click copy, but what it copies is a Markdown summary of the finding — severity, message, evidence, Knowledge Base id — not a command.

Next

The binary summary

Network call sites, and decompiling them.

Privacy labels

Developer claims against binary contents.