appstore-precheck 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project are documented here. Versioning follows
4
4
  [SemVer](https://semver.org/). Released as git tags.
5
5
 
6
+ ## [1.5.2] - 2026-06-30
7
+
8
+ ### Fixed
9
+
10
+ - **§23 (1.6 ATS) false positive**: the App Transport Security check now anchors to the exact `NSAllowsArbitraryLoads` key, so the scoped exceptions `NSAllowsArbitraryLoadsInWebContent` / `NSAllowsArbitraryLoadsForMedia` — which do not disable ATS app-wide and are Apple's recommended alternative — no longer trip a spurious WARN.
11
+ - **Kids-signal double-count (§27 ↔ §39)**: when a child-audience term and a third-party ads/analytics SDK are both present, only §39 (5.1.4) fires now; §27 (2.3.8) is cross-gated so one root signal no longer costs two WARNs against the 5-WARN YELLOW threshold.
12
+ - **§33 (2.5.4) background-mode coverage**: `audio` now recognizes AVKit / `VideoPlayer` / `AVPlayerViewController` / MediaPlayer now-playing APIs, and `fetch` / `processing` recognize `BGTaskScheduler`, reducing "declared but unused" false positives.
13
+ - Pierre deep-review docs: corrected the Tier A definition (22 = all 28 except the 6 Tier B items; previously implied 17), fixed the stale "22 checks" label and broken table-of-contents anchor in the methodology and example files.
14
+ - §28 / §37 evidence now print the plist basename, matching the rest of the batch.
15
+
6
16
  ## [1.5.1] - 2026-06-30
7
17
 
8
18
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appstore-precheck",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Read-only iOS App Store pre-submission check, packaged as a portable Agent Skill with native Claude Code, Cursor, and Codex plugins, plus an npx CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Berkay Turk (https://github.com/berkayturk)",
@@ -4,7 +4,7 @@ description: Read-only pre-submission check for an iOS app before App Store revi
4
4
  license: MIT
5
5
  metadata:
6
6
  author: Berkay Turk
7
- version: 1.5.1
7
+ version: 1.5.2
8
8
  allowed-tools: Bash Read Grep Glob WebFetch
9
9
  ---
10
10
 
@@ -8,7 +8,7 @@ whole file to run the skill.
8
8
  - [Phase 0: Guideline drift check](#phase-0-guideline-drift-check)
9
9
  - [Phase 1: Rejection vectors](#phase-1-rejection-vectors)
10
10
  - [Phase 3: Pierre explains every finding](#phase-3-pierre-explains-every-finding)
11
- - [Phase 4: Pierre deep review (22 checks)](#phase-4-pierre-deep-review-22-semantic-checks)
11
+ - [Phase 4: Pierre deep review (28 checks)](#phase-4-pierre-deep-review-28-semantic-checks)
12
12
  - [Auto-detection rules](#auto-detection-rules)
13
13
  - [Verdict thresholds](#verdict-thresholds)
14
14
  - [Pre-submit manual checklist](#pre-submit-manual-checklist)
@@ -1,9 +1,10 @@
1
1
  # Phase 4: Pierre deep review (28 semantic checks)
2
2
 
3
3
  After Phase 3 (explaining every scan FAIL/WARN), Pierre runs a **read-only, project-wide
4
- semantic review** of 28 guideline areas the static scanner cannot fully judge. Checks **1–22**
5
- (minus Tier B items below) are high-confidence (Tier A); checks **4, 5, 7, 10, 15, and 28** are
6
- heuristic advisory (Tier B v1 higher false-positive risk, still useful pre-submit signals).
4
+ semantic review** of 28 guideline areas the static scanner cannot fully judge. The **22 Tier A**
5
+ checks (all 28 except the Tier B items below) are high-confidence; the **6 Tier B v1** checks
6
+ **4, 5, 7, 10, 15, and 28** are heuristic advisory (higher false-positive risk, still useful
7
+ pre-submit signals).
7
8
 
8
9
  This is the **Review Simulator** layer: Pierre reads Swift, metadata, entitlements, screenshots,
9
10
  xcstrings, paywall views, review notes, and fetches live privacy/support URLs — then cross-checks
@@ -564,7 +564,10 @@ fi
564
564
  # NSAllowsArbitraryLoads=true turns off ATS for the whole app, weakening
565
565
  # data-in-transit security and inviting a 1.6 / data-security question at review.
566
566
  if [[ -f "$INFO_PLIST" ]]; then
567
- if awk '/NSAllowsArbitraryLoads/{getline; if ($0 ~ /<true/) print "ON"}' "$INFO_PLIST" 2>/dev/null | grep -q ON; then
567
+ # Anchor to the exact key: `NSAllowsArbitraryLoads</key>` (followed by `<`), so the
568
+ # narrower scoped exceptions NSAllowsArbitraryLoadsInWebContent / ...ForMedia — which
569
+ # do NOT disable ATS app-wide and are the recommended alternative — don't false-fire.
570
+ if awk '/NSAllowsArbitraryLoads</{getline; if ($0 ~ /<true/) print "ON"}' "$INFO_PLIST" 2>/dev/null | grep -q ON; then
568
571
  warn "1.6 App Transport Security — NSAllowsArbitraryLoads=true in Info.plist disables ATS app-wide; prefer per-domain exceptions, and expect a data-security justification request at review (1.6)"
569
572
  fi
570
573
  fi
@@ -619,7 +622,10 @@ fi
619
622
  if [[ -d "$META_DIR" ]]; then
620
623
  kids_re='for kids|for children|for your (kid|child)|kids[[:space:]]?app|für kinder|para niños|pour enfants'
621
624
  kids_hits=$(grep -rEniI "$kids_re" "$META_DIR" 2>/dev/null | grep -v "^Binary file" | head -10)
622
- if [[ -n "$kids_hits" ]]; then
625
+ # Cross-gate with §39 (5.1.4): when an ads/analytics SDK is also linked, §39 fires the
626
+ # more specific Kids finding for the same wording signal — don't double-count it here
627
+ # (one root signal must not cost two WARNs against the 5-WARN YELLOW threshold).
628
+ if [[ -n "$kids_hits" && -z "${tracking_sdk:-}" && -z "${analytics_sdk:-}" ]]; then
623
629
  warn "2.3.8 'For Kids/Children' wording — terms implying a child audience are reserved for the Kids Category (2.3.8); if not enrolled, remove them from name/subtitle/keywords/description:"
624
630
  echo "$kids_hits" | sed 's/^/ /'
625
631
  fi
@@ -633,7 +639,7 @@ fi
633
639
  kb_plist=$(grep -rlE 'com\.apple\.keyboard-service' --include='Info.plist' "${GREP_PRUNE[@]}" . 2>/dev/null | pick_shallowest)
634
640
  if [[ -n "$kb_plist" ]]; then
635
641
  if grep -A1 'RequestsOpenAccess' "$kb_plist" 2>/dev/null | grep -q '<true'; then
636
- warn "4.4.1 Keyboard extension — RequestsOpenAccess=true in $kb_plist; keyboards must remain functional without full access and may only collect data to enhance the keyboard (4.4.1). Justify full access in your review notes."
642
+ warn "4.4.1 Keyboard extension — RequestsOpenAccess=true in $(basename "$kb_plist"); keyboards must remain functional without full access and may only collect data to enhance the keyboard (4.4.1). Justify full access in your review notes."
637
643
  else
638
644
  pass "4.4.1 Keyboard extension — present without requiring full access"
639
645
  fi
@@ -718,10 +724,10 @@ if [[ -f "$INFO_PLIST" && -n "$IOS_DIR" ]]; then
718
724
  [[ -z "$m" ]] && continue
719
725
  case "$m" in
720
726
  location) grep -rqE 'CLLocationManager|CoreLocation' "$IOS_DIR" --include="*.swift" 2>/dev/null || unused="$unused location" ;;
721
- audio) grep -rqE 'AVAudioSession|AVPlayer|AVAudioPlayer|AVQueuePlayer|AVAudioEngine' "$IOS_DIR" --include="*.swift" 2>/dev/null || unused="$unused audio" ;;
727
+ audio) grep -rqE 'AVAudioSession|AVPlayer|AVAudioPlayer|AVQueuePlayer|AVAudioEngine|AVKit|VideoPlayer|AVPlayerViewController|MPMusicPlayerController|MPNowPlayingInfoCenter' "$IOS_DIR" --include="*.swift" 2>/dev/null || unused="$unused audio" ;;
722
728
  voip) grep -rqE 'PushKit|PKPushRegistry|CallKit|CXProvider' "$IOS_DIR" --include="*.swift" 2>/dev/null || unused="$unused voip" ;;
723
- fetch) grep -rqE 'BGAppRefreshTask|BackgroundTasks|setMinimumBackgroundFetchInterval|performFetchWithCompletionHandler' "$IOS_DIR" --include="*.swift" 2>/dev/null || unused="$unused fetch" ;;
724
- processing) grep -rqE 'BGProcessingTask|BackgroundTasks' "$IOS_DIR" --include="*.swift" 2>/dev/null || unused="$unused processing" ;;
729
+ fetch) grep -rqE 'BGAppRefreshTask|BGTaskScheduler|BackgroundTasks|setMinimumBackgroundFetchInterval|performFetchWithCompletionHandler' "$IOS_DIR" --include="*.swift" 2>/dev/null || unused="$unused fetch" ;;
730
+ processing) grep -rqE 'BGProcessingTask|BGTaskScheduler|BackgroundTasks' "$IOS_DIR" --include="*.swift" 2>/dev/null || unused="$unused processing" ;;
725
731
  bluetooth-central|bluetooth-peripheral) grep -rqE 'CoreBluetooth|CBCentralManager|CBPeripheralManager' "$IOS_DIR" --include="*.swift" 2>/dev/null || unused="$unused $m" ;;
726
732
  remote-notification) grep -rqE 'didReceiveRemoteNotification|UNUserNotificationCenter|registerForRemoteNotifications' "$IOS_DIR" --include="*.swift" 2>/dev/null || unused="$unused remote-notification" ;;
727
733
  esac
@@ -774,7 +780,7 @@ fi
774
780
  # ===================================================================
775
781
  safari_ext=$(grep -rlE 'com\.apple\.Safari\.(content-blocker|web-extension|extension)' --include='Info.plist' "${GREP_PRUNE[@]}" . 2>/dev/null | pick_shallowest)
776
782
  if [[ -n "$safari_ext" ]]; then
777
- warn "4.4.2 Safari extension — a Safari content-blocker / web extension was detected ($safari_ext); it must use the extension APIs as intended, do only what it declares, and not include hidden analytics/ads or track without consent (4.4.2). Verify."
783
+ warn "4.4.2 Safari extension — a Safari content-blocker / web extension was detected ($(basename "$safari_ext")); it must use the extension APIs as intended, do only what it declares, and not include hidden analytics/ads or track without consent (4.4.2). Verify."
778
784
  fi
779
785
 
780
786
  # ===================================================================