npx claudepluginhub charleswiltgen/axiom --plugin axiomThis skill uses the workspace's default tool permissions.
Certificate management, provisioning profiles, entitlements configuration, CI/CD signing setup, and distribution build preparation for iOS/macOS apps.
Provides Ktor server patterns for routing DSL, plugins (auth, CORS, serialization), Koin DI, WebSockets, services, and testApplication testing.
Conducts multi-source web research with firecrawl and exa MCPs: searches, scrapes pages, synthesizes cited reports. For deep dives, competitive analysis, tech evaluations, or due diligence.
Provides demand forecasting, safety stock optimization, replenishment planning, and promotional lift estimation for multi-location retailers managing 300-800 SKUs.
Certificate management, provisioning profiles, entitlements configuration, CI/CD signing setup, and distribution build preparation for iOS/macOS apps.
Use when you need to:
"How do I set up code signing for my app?" "My build fails with 'No signing certificate found'" "How do I set up fastlane match for my team?" "errSecInternalComponent in GitHub Actions" "ITMS-90035 when uploading to App Store" "How do I add push notification entitlements?" "Multiple certificates match — ambiguous identity" "Code signing works locally but fails in CI" "How do I sign for App Store distribution?" "My provisioning profile expired, what do I do?"
Signs you're making this harder than it needs to be:
CODE_SIGN_IDENTITY = "" to suppress errors — Defers the problem to archive/export where it's harder to debug.Before configuring or debugging any code signing issue:
security find-identity -v -p codesigning
This tells you what certificates are installed, valid, and available for signing.
# Find profile embedded in most recent build
find ~/Library/Developer/Xcode/DerivedData -name "embedded.mobileprovision" -newer . 2>/dev/null | head -3
# Decode it
security cms -D -i path/to/embedded.mobileprovision
Check: expiration, embedded certificates, entitlements, device list.
codesign -d --entitlements - /path/to/MyApp.app
Compare these against the profile's entitlements and your .entitlements file. All three must agree.
# Get SHA-1 from keychain
security find-identity -v -p codesigning | grep "Apple Distribution"
# Output: 1) ABCDEF123... "Apple Distribution: Company (TEAMID)"
# Get SHA-1 from profile
security cms -D -i embedded.mobileprovision | plutil -extract DeveloperCertificates xml1 -o - -
echo "<base64 data>" | base64 -d | openssl x509 -inform DER -noout -fingerprint -sha1
The SHA-1 hashes must match. If they don't, the profile was generated with a different certificate than the one in your keychain.
digraph signing_decision {
"New project or\nsmall team?" [shape=diamond];
"CI/CD pipeline?" [shape=diamond];
"Multiple targets\nwith different profiles?" [shape=diamond];
"Team > 3 developers?" [shape=diamond];
"Automatic Signing" [shape=box, label="Use Automatic Signing\nXcode manages everything"];
"Manual + match" [shape=box, label="Use Manual Signing\n+ fastlane match"];
"Manual Signing" [shape=box, label="Use Manual Signing\nspecify profiles explicitly"];
"Automatic + overrides" [shape=box, label="Use Automatic Signing\nwith xcodebuild overrides\nfor CI"];
"New project or\nsmall team?" -> "CI/CD pipeline?" [label="no, larger team"];
"New project or\nsmall team?" -> "Automatic Signing" [label="yes, solo/2-3 devs"];
"CI/CD pipeline?" -> "Team > 3 developers?" [label="yes, have CI"];
"CI/CD pipeline?" -> "Multiple targets\nwith different profiles?" [label="no CI"];
"Team > 3 developers?" -> "Manual + match" [label="yes"];
"Team > 3 developers?" -> "Automatic + overrides" [label="no, small team + CI"];
"Multiple targets\nwith different profiles?" -> "Manual Signing" [label="yes"];
"Multiple targets\nwith different profiles?" -> "Automatic Signing" [label="no"];
}
Best for: Solo developers, small teams, projects without CI.
Xcode manages certificates and provisioning profiles automatically. You just select a team.
Xcode → Target → Signing & Capabilities → ✓ Automatically manage signing → Select Team
How it works:
Limitations:
xcodebuild overrides or Xcode CloudBest for: Teams, CI/CD pipelines, apps with multiple targets/extensions.
You explicitly specify which certificate and profile to use.
Xcode → Target → Signing & Capabilities → ✗ Automatically manage signing
→ Select Provisioning Profile for each configuration (Debug/Release)
How it works:
Required build settings:
CODE_SIGN_STYLE = Manual
CODE_SIGN_IDENTITY = Apple Distribution: Company Name (TEAMID)
PROVISIONING_PROFILE_SPECIFIER = MyApp App Store Profile
DEVELOPMENT_TEAM = YOURTEAMID
| Scenario | Certificate Type | Notes |
|---|---|---|
| Debug build on device | Apple Development | Auto-created by Xcode |
| TestFlight / App Store | Apple Distribution | 3 max per account |
| Ad Hoc distribution | Apple Distribution | Same cert, different profile type |
| macOS outside App Store | Developer ID Application | 5-year validity |
Certificates expire after 1 year (5 years for Developer ID). When a cert expires:
fastlane match nuke [type] then fastlane match [type]Team coordination: Notify the team before regenerating. Distribution certs are shared — regenerating one affects everyone.
For debug builds on registered devices:
For testing on specific devices without TestFlight:
For TestFlight and App Store submission:
xcodebuild -exportArchiveFor in-house distribution (Apple Developer Enterprise Program only):
If using automatic signing, Xcode handles steps 1 and 3 automatically.
| Capability | Entitlement Key | Profile Requirement |
|---|---|---|
| Push Notifications | aps-environment | Must be in profile |
| App Groups | com.apple.security.application-groups | Must be in profile |
| Associated Domains | com.apple.developer.associated-domains | Must be in profile |
| Sign in with Apple | com.apple.developer.applesignin | Must be in profile |
| HealthKit | com.apple.developer.healthkit | Must be in profile |
| iCloud | com.apple.developer.icloud-* | Must be in profile |
| In-App Purchase | Automatic | No profile change needed |
| Background Modes | UIBackgroundModes (Info.plist) | No profile change needed |
| Keychain Sharing | keychain-access-groups | Must be in profile |
Each target (app, widget, extension, etc.) needs its own:
All targets must share the same Team ID. App Groups enable data sharing between targets.
com.example.app → MyApp.entitlements
com.example.app.widget → Widget/Widget.entitlements
com.example.app.NotificationService → NotificationService/NotificationService.entitlements
See axiom-code-signing-ref for complete CI keychain scripts. The critical steps:
default-keychain -s as it breaks access to login keychain credentials~/Library/MobileDevice/Provisioning Profiles/fastlane match manages certificates and profiles in a shared git repo (or cloud storage), encrypted with a passphrase.
Initial setup (run once by a team admin):
fastlane match init # Choose storage (git, google_cloud, s3)
fastlane match development # Generate dev certs + profiles
fastlane match appstore # Generate distribution certs + profiles
CI usage (readonly — never generate in CI):
# Fastfile
lane :release do
setup_ci # Creates temporary keychain
match(type: "appstore", readonly: true)
build_app(scheme: "MyApp", export_method: "app-store")
end
Key rules:
readonly: true — never generate certificates from CIMATCH_PASSWORD as a CI secret (encrypts/decrypts the cert repo)setup_ci to create a temporary keychain (handles all keychain setup)app_identifier in MatchfileXcode Cloud handles signing automatically:
ci_post_clone.sh for custom setup (SPM auth, certificates from custom sources)Wrong:
Slack: "Hey, here's the distribution cert 📎 cert.p12, password is Company123"
Right: Use fastlane match (encrypted git repo) or Xcode's automatic signing with a shared team account.
Why it matters: .p12 files shared in chat create security risks (credentials in chat history), version confusion (which cert is current?), and single-point-of-failure (if the sender's cert expires or is revoked). Time cost: 1-2 hours per team member when the shared cert breaks.
Wrong:
git add certificates/distribution.p12
git add profiles/AppStore.mobileprovision
git commit -m "Add signing files"
Right: Use CI secrets management:
# GitHub Actions
echo "$P12_BASE64" | base64 --decode > certificate.p12 # From secrets
echo "$PROFILE_BASE64" | base64 --decode > profile.mobileprovision
Why it matters: Certificates and profiles are secrets — they allow anyone to sign apps as your team. Even in private repos, git history is permanent. Once committed, the credential is in every clone forever.
If already committed: Scrub history with git filter-repo --path path/to/cert.p12 --invert-paths, then rotate the compromised certificate (revoke in portal, create new, update CI secrets). Every existing clone still has the old cert — treat it as compromised.
Wrong:
CODE_SIGN_IDENTITY = ""
CODE_SIGNING_REQUIRED = NO
CODE_SIGNING_ALLOWED = NO
Right: Diagnose the actual signing error with the mandatory diagnostic steps.
Why it matters: Disabling code signing "works" for Simulator builds but fails for device builds, archives, and distribution. The problem is deferred to a point where it's harder to debug and closer to a deadline. Time cost: the original issue plus 30+ minutes of export/upload debugging.
Wrong:
# CI pipeline uses one developer's personal certificate
security import ~/charles-personal.p12 ...
CODE_SIGN_IDENTITY = "Apple Distribution: Charles Personal (ABC123)"
Right: Use a dedicated team certificate managed via fastlane match or a shared Apple Developer account:
match(type: "appstore", readonly: true)
Why it matters: When that developer leaves, changes machines, or their cert expires, CI and all team distribution breaks. One person's personal cert should never be a shared infrastructure dependency. Time cost: 2-4 hours of emergency cert rotation when the person is unavailable.
Context: Build deadline approaching, code signing error blocking the archive.
Pressure: "We don't need signing for testing, just disable it and we'll fix it after release."
Reality: You can't submit to TestFlight or the App Store without valid code signing. Disabling it now defers a blocking issue to the most time-pressured moment — the actual submission. The diagnostic steps take 5-10 minutes and will identify the root cause.
Correct action: Run Steps 1-4 from Mandatory First Steps. Most signing issues are a single expired or mismatched component.
Push-back template: "We can't submit to TestFlight or the App Store without valid signing. The diagnostic takes 5 minutes and will tell us exactly what's wrong. Disabling it now means we'll hit the same blocker during submission with even less time."
Context: CI is broken because the distribution certificate expired. A team member offers their personal cert as a quick fix.
Pressure: "I have a working cert, just use mine for now."
Reality: Using a personal certificate creates a single point of failure. When that person is unavailable, on vacation, or leaves the company, signing breaks with no path to recovery without their cooperation. The proper fix (renewing the team cert) takes the same amount of time.
Correct action: Renew the team's distribution certificate in Apple Developer Portal. Update CI secrets. Regenerate provisioning profiles with the new cert.
Push-back template: "Your cert would work short-term, but it makes you a single point of failure for all builds. Renewing the team cert takes the same 10 minutes and doesn't create a dependency on one person."
Context: Setting up CI/CD for the first time. Developer wants to commit the certificate to git for convenience.
Pressure: "It's a private repo, nobody else can see it."
Reality: Git history is permanent. Even in private repos, the .p12 (which contains the private key) lives in every clone, every fork, and every backup forever. If the repo is ever made public, open-sourced, or accessed by a contractor, the signing key is exposed. CI secrets management exists specifically for this.
Correct action: Base64-encode the .p12 and store as a CI secret (GitHub Secrets, GitLab CI Variables, etc.). Decode at build time.
Push-back template: "Git history is permanent — once committed, the certificate is in every clone forever. CI secrets management (GitHub Secrets) takes the same effort to set up and is designed for exactly this. Let me set it up properly."
Before archiving for distribution:
Certificates:
security find-identity -v -p codesigning shows the expected identityProvisioning Profiles:
Entitlements:
Build Settings:
CODE_SIGN_STYLE matches intent (Automatic or Manual)CODE_SIGN_IDENTITY correct for build type (Development vs Distribution)PROVISIONING_PROFILE_SPECIFIER set for manual signingDEVELOPMENT_TEAM set to correct Team IDCI/CD (if applicable):
~/Library/MobileDevice/Provisioning Profiles/if: always())WWDC: 2021-10204, 2022-110353
Docs: /security, /bundleresources/entitlements, /xcode/distributing-your-app
Skills: axiom-code-signing-ref, axiom-code-signing-diag