| name | security-audit |
| description | Automated security vulnerability scan for iOS/macOS apps. Covers secrets, storage, network, input validation, privacy manifest, and file protection. Triggers: "security audit", "check for secrets", "security scan". |
| version | 2.1.0 |
| author | Terry Nyberg |
| license | MIT |
| allowed-tools | ["Grep","Glob","Read","Write","Bash","AskUserQuestion"] |
| metadata | {"tier":"execution","category":"analysis"} |
Security Audit
Quick Ref: Automated security scan for iOS/macOS apps. Output: .agents/research/YYYY-MM-DD-security-audit.md
YOU MUST EXECUTE THIS WORKFLOW. Do not just describe it.
All findings use the Issue Rating Table format. Do not use prose severity tags.
Pre-flight: Git Safety Check
git status --short
If uncommitted changes exist:
AskUserQuestion with questions:
[
{
"question": "You have uncommitted changes. Commit before proceeding?",
"header": "Git",
"options": [
{"label": "Commit first (Recommended)", "description": "Save current work so you can revert if this skill modifies files"},
{"label": "Continue without committing", "description": "Proceed — I accept the risk"}
],
"multiSelect": false
}
]
If "Commit first": Ask for a commit message, stage changed files, and commit. Then proceed.
Step 1: Scope Selection
AskUserQuestion with questions:
[
{
"question": "What type of security audit do you want?",
"header": "Scope",
"options": [
{"label": "Full audit (Recommended)", "description": "All categories: secrets, storage, network, input validation, privacy"},
{"label": "Quick scan", "description": "Secrets + network only — highest-risk categories"},
{"label": "Focused audit", "description": "I'll specify which categories to scan"}
],
"multiSelect": false
}
]
If "Focused audit", ask which categories:
AskUserQuestion with questions:
[
{
"question": "Which security categories should I scan?",
"header": "Focus",
"options": [
{"label": "Secrets & API Keys", "description": "Hardcoded credentials, tokens, keys"},
{"label": "Data Storage", "description": "Keychain, UserDefaults, file protection"},
{"label": "Network Security", "description": "HTTPS, ATS, certificate handling"},
{"label": "Privacy & Permissions", "description": "Privacy manifest, usage descriptions"}
],
"multiSelect": true
}
]
Freshness
Base all findings on current source code only. Do not read or reference
files in .agents/, scratch/, or prior audit reports. Ignore cached
findings from auto-memory or previous sessions. Every finding must come
from scanning the actual codebase as it exists now.
Step 2: Automated Scanning
Run patterns for each enabled category. Quick scan runs only 2.1 and 2.3. Full audit runs all sections.
Every grep hit is a CANDIDATE — verify by reading the file before reporting (see Step 3).
2.1 Secrets & API Keys
Grep pattern="(api[_-]?key|apikey|secret[_-]?key|client[_-]?secret)\s*[:=]\s*[\"'][^\"']+[\"']" glob="**/*.swift" -i
Grep pattern="(api[_-]?key|apikey|secret|password|token)\s*[:=]" glob="**/*.{plist,json,xcconfig}"
Grep pattern="[Bb]earer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+" glob="**/*.swift"
Grep pattern="AKIA[0-9A-Z]{16}" glob="**/*.{swift,plist,json,xcconfig}"
Grep pattern="-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----" glob="**/*.{swift,pem,key}"
Grep pattern="AIza[0-9A-Za-z\-_]{35}" glob="**/*.{swift,plist,json}"
Grep pattern="(sk|pk)_(live|test)_[0-9a-zA-Z]{24}" glob="**/*.{swift,plist,json}"
Common false positives:
apiKey as a struct property name (no hardcoded value) → check if value is assigned inline
- Keys in test fixtures (
*Tests.swift) → intentional for testing, verify scope
- Placeholder values like
"YOUR_API_KEY" → not a real secret
2.2 Data Storage
Grep pattern="UserDefaults.*\b(password|token|secret|apiKey|credentials)" glob="**/*.swift" -i
Grep pattern="@AppStorage.*\b(password|token|secret|key|credential)" glob="**/*.swift" -i
Grep pattern="(UserDefaults|@AppStorage).*\b(email|phone|address)" glob="**/*.swift" -i
Grep pattern="(print|NSLog|os_log|Logger|dump)\s*\(.*\b(password|token|secret|apiKey|credential)" glob="**/*.swift" -i
Grep pattern="(Keychain|SecItem|kSecClass)" glob="**/*.swift" output_mode="files_with_matches"
Grep pattern="(CryptoKit|AES|encrypt|decrypt)" glob="**/*.swift" output_mode="files_with_matches"
Common false positives:
email as a form field label, not stored in UserDefaults → read context
- Logging a token variable name in a debug comment → check if actual value is logged
2.3 Network Security
Grep pattern="http://(?!localhost|127\.0\.0\.1)" glob="**/*.swift"
Grep pattern="http://" glob="**/*.plist"
Grep pattern="NSAllowsArbitraryLoads" glob="**/*.plist"
Grep pattern="NSExceptionAllowsInsecureHTTPLoads" glob="**/*.plist"
Grep pattern="URLSessionDelegate.*didReceive challenge" glob="**/*.swift"
Grep pattern="\.serverTrust" glob="**/*.swift"
Grep pattern="(print|dump)\s*\(.*\b(request|response)" glob="**/*.swift"
Common false positives:
http:// in XML namespace strings or URL scheme detection logic → not real HTTP traffic
.serverTrust used to ADD certificate pinning (strengthens security) → read the handler
- Logging behind
#if DEBUG → won't run in production
2.4 Input Validation
Grep pattern="onOpenURL" glob="**/*.swift"
Grep pattern="func application.*open url.*options" glob="**/*.swift"
Grep pattern="\"(SELECT|INSERT|UPDATE|DELETE).*\\\(" glob="**/*.swift" -i
Grep pattern="evaluateJavaScript.*\\\(" glob="**/*.swift"
Grep pattern="WKWebView.*loadHTMLString" glob="**/*.swift"
2.5 Privacy & Permissions
Glob pattern="**/Info.plist"
Glob pattern="**/PrivacyInfo.xcprivacy"
Grep pattern="(fileModificationDate|systemUptime|volumeAvailableCapacity)" glob="**/*.swift"
Grep pattern="UserDefaults" glob="**/*.swift" output_mode="count"
Required API declarations (if used):
| API | Privacy Manifest Key |
|---|
| File timestamp APIs | NSPrivacyAccessedAPICategoryFileTimestamp |
| System boot time | NSPrivacyAccessedAPICategorySystemBootTime |
| Disk space APIs | NSPrivacyAccessedAPICategoryDiskSpace |
| User defaults | NSPrivacyAccessedAPICategoryUserDefaults |
2.6 File Protection (iOS)
Grep pattern="\.completeFileProtection" glob="**/*.swift"
Grep pattern="fileProtection" glob="**/*.swift"
Grep pattern="\.write\(to:" glob="**/*.swift"
2.7 Third-Party Dependencies
Glob pattern="**/Package.resolved"
Glob pattern="**/*.xcframework"
Glob pattern="**/*.framework"
Step 3: Verification Rule (CRITICAL)
Before reporting ANY finding as a security issue:
- Read the flagged file — at minimum 20 lines of context around the match
- Check if it's test code — credentials in
*Tests.swift are intentional fixtures
- Check for debug guards — logging behind
#if DEBUG doesn't ship to production
- Check for Keychain proximity — a
password variable may be read FROM Keychain, not stored insecurely
- Classify — CONFIRMED, FALSE_POSITIVE, or INTENTIONAL before reporting
Security-specific false positives:
- Property/parameter named
apiKey with no hardcoded value
http://localhost or http://127.0.0.1 — development only
- Test credentials in test files — intentional fixtures
- Keychain access followed by force unwrap — may be intentional crash-on-missing-credential
.serverTrust used to ADD pinning, not bypass validation
Step 4: Grading
Grade Criteria
| Grade | Criteria |
|---|
| A | No secrets in code, Keychain for all credentials, privacy manifest complete, HTTPS everywhere, file protection on sensitive writes |
| B | No secrets in code, mostly Keychain usage, privacy manifest present but minor gaps, HTTPS everywhere |
| C | No production secrets but test keys in code, mixed storage (some UserDefaults for sensitive data), privacy manifest incomplete |
| D | Secrets or tokens in code, sensitive data in UserDefaults, missing privacy manifest, HTTP URLs |
| F | Production API keys/credentials in code, no Keychain usage, no privacy manifest |
Category Grades
Grade each scanned category independently:
| Category | What to Evaluate |
|---|
| Secrets & API Keys | Any hardcoded secrets? How are credentials managed? |
| Data Storage | Keychain vs UserDefaults for sensitive data? Logging sanitized? |
| Network Security | HTTPS everywhere? ATS configured? Certificate handling sound? |
| Input Validation | URL schemes validated? SQL parameterized? WebView inputs sanitized? |
| Privacy & Permissions | Privacy manifest present and complete? Usage descriptions accurate? |
| File Protection | Sensitive files written with protection? Correct API usage? |
Overall Grade
The weakest category dominates. Formula: start with the average of all categories, then cap at one grade above the lowest category. Security is only as strong as its weakest link.
Example: If 5 categories are A but Secrets is D → Overall is C (capped at one grade above D, regardless of the A categories).
Step 5: Output
Display the executive summary, grade summary, issue table, and privacy status inline, then write report to .agents/research/YYYY-MM-DD-security-audit.md.
Report Structure
# Security Audit Report
**Date:** YYYY-MM-DD
**Project:** [name]
**Scan Type:** Full / Quick / Focused
## Executive Summary
[2-3 sentences: overall security posture, biggest risk, top recommendation]
## Grade Summary
Overall: [grade] (Secrets [grade] | Storage [grade] | Network [grade] | Validation [grade] | Privacy [grade] | File Protection [grade])
## Positive Findings
[What's done well — Keychain usage, HTTPS, encryption, privacy manifest present]
## Issue Rating Table
| # | Finding | Urgency | Risk: Fix | Risk: No Fix | ROI | Blast Radius | Fix Effort |
|---|---------|---------|-----------|-------------|-----|-------------|------------|
| 1 | ... | 🔴 Critical | ... | ... | ... | ... | ... |
## Privacy Manifest Status
| Requirement | Status |
|-------------|--------|
| PrivacyInfo.xcprivacy exists | ✓/✗ |
| NSPrivacyAccessedAPITypes declared | ✓/✗ |
| Third-party SDK manifests included | ✓/✗ |
## Remediation Examples
[For each critical/high finding, show current vulnerable code and secure fix]
Use the Issue Rating scale:
- Urgency: 🔴 CRITICAL (actively exploitable) · 🟡 HIGH (serious if exploited) · 🟢 MEDIUM (moderate risk) · ⚪ LOW (minor)
- ROI: 🟠 Excellent · 🟢 Good · 🟡 Marginal · 🔴 Poor
- Fix Effort: Trivial / Small / Medium / Large
Step 6: Follow-up
AskUserQuestion with questions:
[
{
"question": "How would you like to proceed?",
"header": "Next",
"options": [
{"label": "Fix critical issues now", "description": "Walk through each critical/high issue with fixes"},
{"label": "Re-scan specific category", "description": "Deeper scan on one area"},
{"label": "Report is sufficient", "description": "Report saved to .agents/research/"}
],
"multiSelect": false
}
]
If "Fix critical issues now": Walk through each 🔴/🟡 finding, show the vulnerable code, propose a secure fix, apply after user approval.
Troubleshooting
| Problem | Solution |
|---|
Too many grep hits for password/token | Narrow glob to specific directories, exclude test files |
| Can't find Info.plist | Check for .plist in project root, target folders, or Xcode-generated location |
| Privacy manifest not found | May not exist yet — flag as critical and offer to create one |
| False positive rate too high | Read more context (30+ lines), check if value is hardcoded vs passed in |
| Dependency has no privacy manifest | Check SDK documentation — some declare in their own bundle |