Audit Xcode Security Settings

SkillSecurity

Audit and enable security-oriented Xcode build settings. Progressively enables compiler warnings, static analyzer checkers, and Enhanced Security features. Use when: user wants to secure their Xcode project, audit security settings, enable hardening, review security posture of build configuration, set up security-focused static analysis, enable static analysis, improve warning coverage, harden diagnostics, or catch more bugs at compile time in C/C++/Objective-C/Swift. SKIP: network security (TLS/ATS), code signing, privacy APIs.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Audit Xcode Security Settings skill

What this skill tells your AI

The instructions your AI receives, as published by artemnovichkov/xcode-skills in skills/audit-xcode-security-settings/SKILL.md and read by ahel’s review.

Assess an Xcode project's security posture and progressively enable security build settings and entitlements — from broadly applicable warnings through Enhanced Security hardening.

Tool Preferences

When XcodeGlob, XcodeGrep, XcodeRead, XcodeLS, and XcodeUpdate tools are available, ALWAYS use them. Do not fall back to Bash filesystem tools (ls, find, cat, grep) to learn about the project. They trigger extra permission prompts and bypass project scoping.

Tool names may carry an MCP server prefix. These tools are hosted by an MCP server whose name varies by environment (xcode-mcp, xcode-tools, xcode, etc.), so their fully qualified names look like mcp__<server>__XcodeGlob. Some harnesses register short aliases (just XcodeGlob); others only expose the prefixed form. Do not hardcode a specific server name. On the first call, use whichever form the available-tool registry advertises — look up the prefix once, then reuse it for the rest of the session. If a short-name call fails with an unknown-tool error, do not guess at the prefix: look it up in the registry and retry with the full name.

  • XcodeGlob for file discovery — find is forbidden for files inside the project.
  • XcodeGrep for content search — grep/rg is forbidden for files inside the project.
  • XcodeRead for file contents — cat/Read is forbidden for files registered in the project.
  • XcodeLS for directory listing — ls is forbidden for any path inside the project.
  • XcodeUpdate for in-place edits of project-registered text files (xcconfig files, source files) — same filePath / oldString / newString (+ optional replaceAll) signature as the built-in Edit tool, but accepts Xcode workspace-relative paths. Edit is forbidden for files registered in the project. Do not use XcodeUpdate / Edit / plutil to add or update .entitlements keys — use AddEntitlement.
  • AddEntitlement for adding or updating a target's entitlements — pass targetName, entitlementKey, entitlementValueType (bool / string / int / stringArray / dictionary), and the value. Always prefer it for entitlement changes; it adds or updates only and cannot remove keys.
  • XcodeListTargets for enumerating targets — do not parse project.pbxproj manually. Returns each target's PRODUCT_TYPE_IDENTIFIER and role flags (IS_AGGREGATE, IS_TEST_TARGET, IS_APP_EXTENSION, SUPPORTS_HOSTING_TESTS) directly.

Project root and name are already in the system prompt context. Do NOT run ls to "verify" the project layout before starting. The system prompt already tells you the working directory and the project structure.

Empty XcodeGlob results are not a failure. The .xcodeproj and .xcworkspace are not indexed as files inside the Xcode workspace — XcodeGlob "**/*.xcodeproj" correctly returns 0 matches. Use the project name from system-prompt context instead. Do not fall back to filesystem ls/find.

All Xcode* tools take Xcode workspace-relative paths. XcodeGlob, XcodeGrep, XcodeRead, XcodeLS, XcodeUpdate, XcodeWrite, and XcodeRM interpret their path arguments — and return paths — relative to the Xcode workspace root (what you see at the top of the Project Navigator). Not the git repository root; not the .xcodeproj bundle. Anything the user sees in Xcode (entitlements, xcconfig, plan and decision documents, source files) is reachable via its workspace-relative path; pass that path through these tools as-is, and don't construct absolute filesystem paths for it.

To read or edit a specific file:

  • Prefer XcodeRead / XcodeUpdate with the workspace-relative path. XcodeRead reads .entitlements plists too — they're project-registered files, navigable just like any source file — so read them this way. To add or update an entitlement, use AddEntitlement, not XcodeUpdate.

For entitlements files, never derive the path by hand. Each target's authoritative entitlements path is the evaluated value of its CODE_SIGN_ENTITLEMENTS build setting — get it from GetTargetBuildSettings and use it as-is. Do not parse project.pbxproj to reconstruct the path, and do not glob **/*.entitlements: orphaned .entitlements files may exist on disk that aren't referenced by any target. One entitlements file can be referenced by multiple targets.

Fall back to Bash only for operations the Xcode tools cannot do (e.g., git operations).

Bundled Reference Documents

All reference material lives under references/ next to this file.

  • references/security-settings-reference.md — the canonical list of security build settings and entitlements this skill tracks, with hardened values, CLI flags, and language scope.
  • references/reading-build-settings.mdGetTargetBuildSettings schema, the filter script recipe, the audit-table construction, and the "already hardened" / "deliberately disabled" predicates.
  • references/enhanced-security.md — the Enhanced Security capability: build settings, entitlements, supported product types.
  • references/pointer-authentication.md — arm64e pointer signing: supported platforms, consumer-side compatibility notes.
  • references/universal-binaries-for-libraries.md — universal-binary recipe for library/framework targets (ONLY_ACTIVE_ARCH = NO; pointer authentication adds the arm64e slice automatically), qualifying product types, XCFramework guidance.
  • references/security-compiler-warnings.md — the security-focused compiler warnings and settings enabled by Enhanced Security.
  • references/cpp-hardening.md — C++ stdlib hardening (CLANG_CXX_STANDARD_LIBRARY_HARDENING) and bounds-safe buffers (ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS).
  • references/typed-allocators.md — type-aware allocator support and the hardened-heap sub-option.
  • references/stack-zero-init.md — automatic stack-variable zero-initialization at runtime.
  • references/readonly-platform-memory.md — read-only protection of dyld state.
  • references/runtime-restrictions.md — dylib and Mach-message platform restrictions.
  • references/hardware-memory-tagging.md — MTE entitlements and supported hardware.
  • references/additional-settings.md — opt-in diagnostic settings beyond the defaults (may have more false positives).
  • references/adoption-strategy.md — recommended ordering for validating Enhanced Security features (lowest-risk to highest-effort).
  • references/decision-document.md — how to maintain the persistent xcode-security-settings.md decision document.

The skill ships one helper script:

  • scripts/filter_build_settings.py — filters GetTargetBuildSettings JSON to the macros tracked in security-settings-reference.md. See references/reading-build-settings.md for usage.

Common Failure Modes

SymptomCauseCorrect Response
Tool call fails with "unknown tool" / "tool not found" for XcodeGlob etc.The harness registers these tools only under their full MCP-prefixed name (mcp__<server>__XcodeGlob) in this environmentLook up the prefix in the available-tool registry, retry once with the full name, then use the full name for the rest of the session.
XcodeGlob "**/*.xcodeproj" returns 0 matchesThe .xcodeproj itself isn't a project-indexed fileUse the project name from system context; do not fall back to find or ls
XcodeRead <workspace-relative-path> fails for a file truly inside the .xcodeproj / .xcworkspace bundle (e.g. WorkspaceSettings.xcsettings)That file isn't a project-navigator memberTranslate to filesystem absolute path using the project root from system context, then use Read / Edit. (Does not apply to .entitlements files — those are navigable.)
Read on an entitlements path you derived by hand returns File does not existThe path was reconstructed from project.pbxproj group nesting or guessed by globbing **/*.entitlements. Xcode's authoritative path for a target's entitlements is the evaluated value of CODE_SIGN_ENTITLEMENTS, not whatever the navigator shows.Look up CODE_SIGN_ENTITLEMENTS for the target via GetTargetBuildSettings (or read it from the audit table) and use its evaluated value as the path.

Workflow

Phase 1: Briefing

Before doing any work, tell the user — in two or three sentences — what this skill is, what it will do, and roughly how much of their time and attention to expect:

  • What it is. An audit of the project's Xcode security build settings and entitlements (compiler warnings, Enhanced Security entitlements, pointer authentication, universal binaries for libraries, etc.).
  • What happens. The skill runs in two parts of roughly equal length. First, planning: I analyze the project and write an editable plan file at the project root for you to review. Then, execution: once you pick Run, I apply only the changes you approved. Nothing is modified until you pick Run.
  • Time commitment. Planning is a few minutes of my analysis (longer on projects with many targets — I'll narrate progress) plus your review of the plan file, which can be quick or thorough — your call. Execution takes about as long: applying the approved changes, with two things that can pause for your input — the inquiry step (if there are deliberately-disabled settings whose rationale isn't documented), and a final yes/no on whether to keep the plan file in your project as a record. This all usually takes about 15-30 minutes, split roughly evenly between the two parts, depending on the number of build targets and how long it takes for you to review and approve the plan.

Keep it tight — the user already invoked the skill knowing they wanted an audit. The briefing exists so they have realistic expectations.

Then check for source control. The project has source control if either:

  • The Environment block's Is a git repository field is true, or
  • A single filesystem check at the project root finds any of .git, .hg, .svn, .bzr, .fslckout, _FOSSIL_, CVS.

Otherwise the project has no source control. Record this state — Phase 4 Step 3 uses it to decide whether to include the ⚠️ blockquote in the plan file.

After delivering the briefing, pause via AskUserQuestion. If the project has source control:

  • Begin audit — proceed to Phase 2.
  • Cancel — exit with "Cancelled — no changes applied."

If the project has no source control, tell the user first: "It is strongly recommended setting up source control before continuing. This skill modifies build settings and entitlements; without something like Git, rollback requires manual undo and you won't have a clean way to review the differences. Xcode has built-in support for Source control management" Then ask:

  • Set up source control first (Recommended) — exit with "Set up source control and re-run the skill."
  • Proceed without source control — proceed to Phase 2; Phase 4 Step 3 will surface the no-source-control reminder again in the plan file.
  • Cancel — exit with "Cancelled — no changes applied."

The pause exists so the briefing stays on screen long enough to read; Discovery and Analysis output would otherwise scroll it away. Failing early when there's no source control avoids spending minutes on discovery and analysis only for the user to bail at plan-approval time.

Phase 2: Discovery

Read the Environment block in the system prompt. Relevant fields:

  • Primary working directory — the project root (the project name is the basename).
  • Is a git repository — whether the project is git-tracked (used by the source-control check in Phase 1).

Track Progress

Every per-target / per-setting action that needs to happen must have its own task for transparency.

  • Phase 1 (Briefing) is one task that completes when the user picks Begin audit / Cancel.
  • Phase 3 creates one task per target (Audit <target>); the task closes once Phase 3 has produced both the per-target audit-table rows and (for supported product types) the Enhanced-Security category for that target. Phase 3 stores all per-target state in the task's description field (see Phase 3 Step 4 for the format) so later phases can read it back via TaskGet. Phases 4–7 read these task descriptions.
  • Phase 4 (Plan & Approve) is one task that completes when the user picks Run/Cancel.
  • On Run, Phase 4 step 5 parses the plan and creates fine-grained tasks. For each apply task it embeds that target's delta (extracted from the corresponding Audit <target> task's description) into the apply task's own description so Phase 5 doesn't have to look it up again.
    • For each Enhanced Security sub-item that's checked:
      • Enable Enhanced Security: Enable Enhanced Security at project level (one task). On pbxproj-only projects, this task encapsulates the guide-and-verify flow described in Phase 5 Step 1a.
      • Update entitlements: one Apply Enhanced Security entitlements to <target> per target needing changes.
      • Hardware memory tagging: Apply Hardware Memory Tagging (one task; walks supported targets internally).
    • For each Warnings sub-item that's checked:
      • Apply Compiler Warnings if that sub-item is checked.
      • Apply Static Analyzer Warnings if that sub-item is checked.
      • Apply Clang-Tidy Warnings if that sub-item is checked.
    • Apply Additional Diagnostic Settings if checked.
    • Emit Bounds Safety Adoption guidance if checked.
    • One Inquire about <MACRO> on <target> per Phase-6 candidate (only if "Inquire about disabled settings" is checked).
    • Report and update decision document.
    • Prompt to remove plan file — always last; also fires on error paths.

When entering each phase or sub-step:

  • Print one line naming the phase or sub-step in plain English — never the phase number. Use the phase's name (e.g., "▶ Briefing", "▶ Analyzing project", "▶ Plan & Approve", "▶ Applying settings"); for sub-steps, name what's being done (e.g., "▶ Detecting languages", "▶ Building the audit table").
  • Update the task to in_progress.

When finishing each phase or sub-step:

  • Print one line: "✓ " with a brief outcome if applicable (e.g., "✓ Detecting languages: C and Swift found.").
  • Update the task to completed.

Phase 3: Analyze Project and Settings

No user interaction. Gather facts in the background.

Step 1: Locate the existing decision document

XcodeGlob '**/xcode-security-settings.md'. If found, XcodeRead it and extract languages + prior setting decisions with their statuses and rationale. This informs subsequent phases.

Step 2: Detect languages

One XcodeGlob per language. Empty result is not a failure — record the language as absent.

  • **/*.c → C
  • **/*.cpp, **/*.cxx, **/*.cc → C++
  • **/*.m → Objective-C
  • **/*.mm → Objective-C++
  • **/*.swift → Swift

Objective-C++ implies C++ is present. .mm files contain C++ source, so any audit gated on "C++ present" (C++ stdlib hardening, bounds-safe-buffers guidance, CLANG_ANALYZER_OSOBJECT_C_STYLE_CAST, etc.) must fire when Objective-C++ is detected, even when no .cpp/.cxx/.cc files exist.

Filename extension is not authoritative. An Xcode project can override a file's compiled language via explicitFileType / lastKnownFileType in project.pbxproj — most commonly a .m file marked sourcecode.cpp.objcpp (compiled as Objective-C++), or a .h marked sourcecode.c.h / sourcecode.cpp.h. To catch these overrides, grep -E 'sourcecode\.cpp\.[a-zA-Z0-9]+' <project-root>/<ProjectName>.xcodeproj/project.pbxproj via Bash. project.pbxproj is Xcode's project description file inside the .xcodeproj bundle; read it directly. Treat any sourcecode.cpp.objcpp match as both Objective-C++ and C++; treat any other sourcecode.cpp.* match as C++.

Step 3: Build the audit table

See references/reading-build-settings.md for column definitions, the construction recipe, and the canonical predicates ("already hardened", "at default OFF", "deliberately disabled"). At a glance:

  1. Call XcodeListTargets to enumerate targets. Skip entries with IS_AGGREGATE = true (they have no product type). Record TARGET_NAME, CONTAINING_PROJECT, and PRODUCT_TYPE_IDENTIFIER for each remaining target — Step 4 categorizes targets by PRODUCT_TYPE_IDENTIFIER directly (no inference).
  2. For each target: TaskCreate "Audit <target>", set in_progress. Call GetTargetBuildSettings, run scripts/filter_build_settings.py over the resulting JSON, and record evaluatedValue and setAtTargetLevel (yes if targetValue is present in the JSON) per tracked macro. Hold these rows ready to write into the task's description in Step 4 (along with the category). Leave the task in_progress — Step 4 closes it.
  3. Scan for explicit settings in two passes with the filter regex: XcodeGrep over *.xcconfig, and grep -nE '<filter regex>' <project-root>/<ProjectName>.xcodeproj/project.pbxproj via Bash. project.pbxproj is Xcode's project description file inside the .xcodeproj bundle; read it directly. Record per-macro numMatchesInXCConfigs, numMatchesInPbxproj, and the file:line citations.
  4. The audit table is the joined view: one row per (target, tracked macro). Phases 4, 5, and 6 all consume this table; nothing else is re-fetched.

This step scales with target count: each GetTargetBuildSettings call takes several seconds, and there is one per target. On projects with roughly ten or more targets it can take a few minutes.

Step 4: Per-target Enhanced-Security state

Route each target into one of three categories by the PRODUCT_TYPE_IDENTIFIER recorded in Step 3:

  • Entitlements-supported — product type is in the "Supported Product Types" list of references/enhanced-security.md (applications, XPC services, system extensions, driver extensions [build settings only], tools). Read the entitlements plist at the path stored in this target's CODE_SIGN_ENTITLEMENTS build setting and classify the target as Up-to-date, Partial, Off, or No-entitlements-file. Multiple targets can share the same CODE_SIGN_ENTITLEMENTS path; classify each target independently.
  • Library/framework — product type is in the qualifying set listed in references/universal-binaries-for-libraries.md (frameworks, static frameworks, static libraries, dynamic libraries). No entitlements read. Phase 5 will configure the universal-binary recipe (ONLY_ACTIVE_ARCH = NO) for these.
  • Skipped — anything else (test bundles, app extensions, etc.).

Now write everything Phase 3 has learned about this target into the Audit <target> task's description via TaskUpdate, then set it completed. The description holds the entire per-target state Phases 4–6 need to consult later. Format:

Category: <category> [/ <sub-state>]      # e.g. "Entitlements-supported / Partial", "Library/framework", "Skipped"
Entitlements path: <evaluated CODE_SIGN_ENTITLEMENTS>     # omit for Library/framework and Skipped
SDKROOT: <value>
SUPPORTED_PLATFORMS: <value>
Missing entitlements: <comma-separated short names>      # Entitlements-supported only; omit if empty
Deliberately-disabled: <MACRO>=<value> (<source>[+<source>...]), ...   # one per disabled row; sources ⊆ {target-level, xcconfig, pbxproj} joined with '+' when more than one applies; omit the line entirely if none

Audit table:
  <MACRO>=<value> setAtTargetLevel=<yes|no> numMatchesInXCConfigs=<n> numMatchesInPbxproj=<n> matchLocations=<citations>
  ...

The Category line is first so any client that surfaces a snippet shows something meaningful. The Audit-table block is the per-(target, tracked macro) rows from Step 3 in key=value form — one line per tracked macro, using the canonical column names defined in references/reading-build-settings.md. matchLocations carries the file:line citations in the same <source>:<file>:<line>[,<line>...] format used throughout. Library/framework and Skipped targets get this Category line, the platform fields, and the Audit-table block, then complete immediately (no entitlements read).

On large projects this iterates over many .entitlements plists — if Step 3 took noticeable time, this one will too.

Phase 4: Plan & Approve

This phase produces a tailored, editable plan file that the user reviews before any changes happen. Once approved, Phases 5–7 run end-to-end with no further prompts.

Step 1: Source-control state

Source control was checked in Phase 1, and the user already accepted any no-source-control state at that point. Phase 4 Step 3 uses the recorded state to decide whether to include the ⚠️ blockquote in the plan file.

Step 2: Skip if everything is already configured

TaskList the Audit <target> tasks and TaskGet each. Early-exit if all default-checked plan items are already at their target state:

  • Every Enhanced-Security category (from each task's Category: line) is Up-to-date or Skipped.
  • Every relevant Warnings setting (compiler, static analyzer, and clang-tidy) is already hardened on every applicable target (per each task's Audit-table block).
  • No task's Deliberately-disabled: line yields a row (after the Phase-6 exclusions below).

Optional follow-ups (Additional diagnostic settings, Bounds safety adoption) do not block early-exit. Report "Everything in scope is already configured" and exit; do not write a plan file.

Step 3: Write the plan file

Create xcode-security-audit-plan.md at the root of the Xcode workspace via XcodeWrite (path: xcode-security-audit-plan.md, no parent group). XcodeWrite both writes the file to disk under <project-root>/ and registers it in the project so the user can open it directly from Xcode's Project Navigator.

Include only items that apply to the project (see omission rules below). Use this template — substitute the placeholders in <…>:

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
20
Forks
1
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
audit-xcode-security-settings
Source
github.com/artemnovichkov/xcode-skills