shiplint 0.1.0

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.
Files changed (130) hide show
  1. package/README.md +107 -0
  2. package/dist/cli/index.d.ts +3 -0
  3. package/dist/cli/index.d.ts.map +1 -0
  4. package/dist/cli/index.js +101 -0
  5. package/dist/cli/index.js.map +1 -0
  6. package/dist/core/index.d.ts +5 -0
  7. package/dist/core/index.d.ts.map +1 -0
  8. package/dist/core/index.js +21 -0
  9. package/dist/core/index.js.map +1 -0
  10. package/dist/core/scanner.d.ts +27 -0
  11. package/dist/core/scanner.d.ts.map +1 -0
  12. package/dist/core/scanner.js +104 -0
  13. package/dist/core/scanner.js.map +1 -0
  14. package/dist/formatters/index.d.ts +13 -0
  15. package/dist/formatters/index.d.ts.map +1 -0
  16. package/dist/formatters/index.js +29 -0
  17. package/dist/formatters/index.js.map +1 -0
  18. package/dist/formatters/json.d.ts +13 -0
  19. package/dist/formatters/json.d.ts.map +1 -0
  20. package/dist/formatters/json.js +17 -0
  21. package/dist/formatters/json.js.map +1 -0
  22. package/dist/formatters/sarif.d.ts +14 -0
  23. package/dist/formatters/sarif.d.ts.map +1 -0
  24. package/dist/formatters/sarif.js +108 -0
  25. package/dist/formatters/sarif.js.map +1 -0
  26. package/dist/formatters/text.d.ts +9 -0
  27. package/dist/formatters/text.d.ts.map +1 -0
  28. package/dist/formatters/text.js +128 -0
  29. package/dist/formatters/text.js.map +1 -0
  30. package/dist/index.d.ts +11 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +49 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/parsers/entitlements-parser.d.ts +26 -0
  35. package/dist/parsers/entitlements-parser.d.ts.map +1 -0
  36. package/dist/parsers/entitlements-parser.js +105 -0
  37. package/dist/parsers/entitlements-parser.js.map +1 -0
  38. package/dist/parsers/framework-detector.d.ts +76 -0
  39. package/dist/parsers/framework-detector.d.ts.map +1 -0
  40. package/dist/parsers/framework-detector.js +501 -0
  41. package/dist/parsers/framework-detector.js.map +1 -0
  42. package/dist/parsers/index.d.ts +10 -0
  43. package/dist/parsers/index.d.ts.map +1 -0
  44. package/dist/parsers/index.js +26 -0
  45. package/dist/parsers/index.js.map +1 -0
  46. package/dist/parsers/pbxproj-parser.d.ts +166 -0
  47. package/dist/parsers/pbxproj-parser.d.ts.map +1 -0
  48. package/dist/parsers/pbxproj-parser.js +423 -0
  49. package/dist/parsers/pbxproj-parser.js.map +1 -0
  50. package/dist/parsers/plist-parser.d.ts +26 -0
  51. package/dist/parsers/plist-parser.d.ts.map +1 -0
  52. package/dist/parsers/plist-parser.js +166 -0
  53. package/dist/parsers/plist-parser.js.map +1 -0
  54. package/dist/parsers/project-parser.d.ts +57 -0
  55. package/dist/parsers/project-parser.d.ts.map +1 -0
  56. package/dist/parsers/project-parser.js +618 -0
  57. package/dist/parsers/project-parser.js.map +1 -0
  58. package/dist/parsers/workspace-parser.d.ts +82 -0
  59. package/dist/parsers/workspace-parser.d.ts.map +1 -0
  60. package/dist/parsers/workspace-parser.js +287 -0
  61. package/dist/parsers/workspace-parser.js.map +1 -0
  62. package/dist/rules/auth/index.d.ts +5 -0
  63. package/dist/rules/auth/index.d.ts.map +1 -0
  64. package/dist/rules/auth/index.js +9 -0
  65. package/dist/rules/auth/index.js.map +1 -0
  66. package/dist/rules/auth/third-party-login-no-siwa.d.ts +11 -0
  67. package/dist/rules/auth/third-party-login-no-siwa.d.ts.map +1 -0
  68. package/dist/rules/auth/third-party-login-no-siwa.js +119 -0
  69. package/dist/rules/auth/third-party-login-no-siwa.js.map +1 -0
  70. package/dist/rules/base.d.ts +25 -0
  71. package/dist/rules/base.d.ts.map +1 -0
  72. package/dist/rules/base.js +37 -0
  73. package/dist/rules/base.js.map +1 -0
  74. package/dist/rules/config/ats-exception-without-justification.d.ts +12 -0
  75. package/dist/rules/config/ats-exception-without-justification.d.ts.map +1 -0
  76. package/dist/rules/config/ats-exception-without-justification.js +152 -0
  77. package/dist/rules/config/ats-exception-without-justification.js.map +1 -0
  78. package/dist/rules/config/index.d.ts +5 -0
  79. package/dist/rules/config/index.d.ts.map +1 -0
  80. package/dist/rules/config/index.js +9 -0
  81. package/dist/rules/config/index.js.map +1 -0
  82. package/dist/rules/index.d.ts +43 -0
  83. package/dist/rules/index.d.ts.map +1 -0
  84. package/dist/rules/index.js +103 -0
  85. package/dist/rules/index.js.map +1 -0
  86. package/dist/rules/metadata/index.d.ts +5 -0
  87. package/dist/rules/metadata/index.d.ts.map +1 -0
  88. package/dist/rules/metadata/index.js +9 -0
  89. package/dist/rules/metadata/index.js.map +1 -0
  90. package/dist/rules/metadata/missing-privacy-manifest.d.ts +12 -0
  91. package/dist/rules/metadata/missing-privacy-manifest.d.ts.map +1 -0
  92. package/dist/rules/metadata/missing-privacy-manifest.js +186 -0
  93. package/dist/rules/metadata/missing-privacy-manifest.js.map +1 -0
  94. package/dist/rules/privacy/att-tracking-mismatch.d.ts +12 -0
  95. package/dist/rules/privacy/att-tracking-mismatch.d.ts.map +1 -0
  96. package/dist/rules/privacy/att-tracking-mismatch.js +113 -0
  97. package/dist/rules/privacy/att-tracking-mismatch.js.map +1 -0
  98. package/dist/rules/privacy/index.d.ts +11 -0
  99. package/dist/rules/privacy/index.d.ts.map +1 -0
  100. package/dist/rules/privacy/index.js +21 -0
  101. package/dist/rules/privacy/index.js.map +1 -0
  102. package/dist/rules/privacy/location-always-unjustified.d.ts +11 -0
  103. package/dist/rules/privacy/location-always-unjustified.d.ts.map +1 -0
  104. package/dist/rules/privacy/location-always-unjustified.js +102 -0
  105. package/dist/rules/privacy/location-always-unjustified.js.map +1 -0
  106. package/dist/rules/privacy/missing-camera-purpose.d.ts +11 -0
  107. package/dist/rules/privacy/missing-camera-purpose.d.ts.map +1 -0
  108. package/dist/rules/privacy/missing-camera-purpose.js +83 -0
  109. package/dist/rules/privacy/missing-camera-purpose.js.map +1 -0
  110. package/dist/rules/privacy/missing-contacts-purpose.d.ts +11 -0
  111. package/dist/rules/privacy/missing-contacts-purpose.d.ts.map +1 -0
  112. package/dist/rules/privacy/missing-contacts-purpose.js +85 -0
  113. package/dist/rules/privacy/missing-contacts-purpose.js.map +1 -0
  114. package/dist/rules/privacy/missing-location-purpose.d.ts +12 -0
  115. package/dist/rules/privacy/missing-location-purpose.d.ts.map +1 -0
  116. package/dist/rules/privacy/missing-location-purpose.js +137 -0
  117. package/dist/rules/privacy/missing-location-purpose.js.map +1 -0
  118. package/dist/rules/privacy/missing-microphone-purpose.d.ts +11 -0
  119. package/dist/rules/privacy/missing-microphone-purpose.d.ts.map +1 -0
  120. package/dist/rules/privacy/missing-microphone-purpose.js +132 -0
  121. package/dist/rules/privacy/missing-microphone-purpose.js.map +1 -0
  122. package/dist/rules/privacy/missing-photo-library-purpose.d.ts +11 -0
  123. package/dist/rules/privacy/missing-photo-library-purpose.d.ts.map +1 -0
  124. package/dist/rules/privacy/missing-photo-library-purpose.js +102 -0
  125. package/dist/rules/privacy/missing-photo-library-purpose.js.map +1 -0
  126. package/dist/types/index.d.ts +140 -0
  127. package/dist/types/index.d.ts.map +1 -0
  128. package/dist/types/index.js +59 -0
  129. package/dist/types/index.js.map +1 -0
  130. package/package.json +68 -0
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ATTTrackingMismatchRule = void 0;
4
+ const index_js_1 = require("../../types/index.js");
5
+ const plist_parser_js_1 = require("../../parsers/plist-parser.js");
6
+ const framework_detector_js_1 = require("../../parsers/framework-detector.js");
7
+ const base_js_1 = require("../base.js");
8
+ const TRACKING_USAGE_KEY = 'NSUserTrackingUsageDescription';
9
+ const ATT_FRAMEWORK = 'AppTrackingTransparency';
10
+ exports.ATTTrackingMismatchRule = {
11
+ id: 'privacy-003-att-tracking-mismatch',
12
+ name: 'Tracking SDK Without App Tracking Transparency',
13
+ description: 'Checks for tracking SDKs without proper ATT implementation',
14
+ category: index_js_1.RuleCategory.Privacy,
15
+ severity: index_js_1.Severity.Critical,
16
+ confidence: index_js_1.Confidence.High,
17
+ guidelineReference: '5.1.2',
18
+ async evaluate(context) {
19
+ // Detect tracking SDKs from dependencies
20
+ const detectedSDKs = (0, framework_detector_js_1.detectTrackingSDKs)(context.dependencies);
21
+ if (detectedSDKs.length === 0) {
22
+ return [];
23
+ }
24
+ const findings = [];
25
+ const trackingDescription = context.plistString(TRACKING_USAGE_KEY);
26
+ const hasTrackingDescription = trackingDescription !== undefined;
27
+ const hasATTFramework = context.hasFramework(ATT_FRAMEWORK);
28
+ // Case 1: Missing NSUserTrackingUsageDescription entirely
29
+ if (!hasTrackingDescription) {
30
+ findings.push((0, base_js_1.makeFinding)(this, {
31
+ description: `Your app includes tracking/attribution SDKs (${detectedSDKs.join(', ')}) ` +
32
+ `but Info.plist is missing NSUserTrackingUsageDescription. Since iOS 14.5, apps that track ` +
33
+ `users must implement App Tracking Transparency and include a purpose string.`,
34
+ location: 'Info.plist',
35
+ fixGuidance: `Add NSUserTrackingUsageDescription to your Info.plist:
36
+
37
+ <key>NSUserTrackingUsageDescription</key>
38
+ <string>We use tracking to show you personalized ads and measure ad effectiveness.</string>
39
+
40
+ Then implement the ATT prompt in your code:
41
+
42
+ import AppTrackingTransparency
43
+
44
+ ATTrackingManager.requestTrackingAuthorization { status in
45
+ switch status {
46
+ case .authorized:
47
+ // Enable tracking
48
+ default:
49
+ // Disable tracking
50
+ }
51
+ }
52
+
53
+ Important: Only initialize tracking SDKs after the user grants permission.`,
54
+ documentationURL: 'https://developer.apple.com/documentation/apptrackingtransparency',
55
+ }));
56
+ }
57
+ // Case 2: Description is empty
58
+ else if (trackingDescription.trim() === '') {
59
+ findings.push((0, base_js_1.makeFinding)(this, {
60
+ title: 'Empty Tracking Usage Description',
61
+ description: `NSUserTrackingUsageDescription exists but is empty. Apple requires a meaningful ` +
62
+ `description explaining why your app tracks users.`,
63
+ location: 'Info.plist',
64
+ fixGuidance: `Update NSUserTrackingUsageDescription with a clear explanation of your tracking purpose.
65
+
66
+ Good example: "Allow tracking to receive personalized ads based on your interests."
67
+ Bad example: "" or "For tracking"
68
+
69
+ Be specific about what data is collected and how it's used.`,
70
+ documentationURL: 'https://developer.apple.com/documentation/apptrackingtransparency',
71
+ }));
72
+ }
73
+ // Case 3: Description is placeholder
74
+ else if ((0, plist_parser_js_1.isPlaceholder)(trackingDescription)) {
75
+ findings.push((0, base_js_1.makeFinding)(this, {
76
+ title: 'Placeholder Tracking Usage Description',
77
+ description: `NSUserTrackingUsageDescription appears to contain placeholder text: "${trackingDescription}". ` +
78
+ `Apple requires meaningful, user-facing descriptions.`,
79
+ location: 'Info.plist',
80
+ fixGuidance: `Replace the placeholder with a real explanation of why your app tracks users.
81
+
82
+ Current value: "${trackingDescription}"
83
+
84
+ Users should understand what tracking means for their privacy.`,
85
+ documentationURL: 'https://developer.apple.com/documentation/apptrackingtransparency',
86
+ }));
87
+ }
88
+ // Case 4: Has description but no ATT framework
89
+ if (hasTrackingDescription && !hasATTFramework) {
90
+ findings.push((0, base_js_1.makeCustomFinding)(this, index_js_1.Severity.Medium, index_js_1.Confidence.Medium, {
91
+ title: 'AppTrackingTransparency Framework Not Linked',
92
+ description: `Your app has NSUserTrackingUsageDescription but AppTrackingTransparency framework ` +
93
+ `does not appear to be linked. This may indicate an incomplete ATT implementation.`,
94
+ location: 'Project',
95
+ fixGuidance: `Ensure you're importing AppTrackingTransparency in your code and actually showing ` +
96
+ `the tracking permission prompt to users.
97
+
98
+ import AppTrackingTransparency
99
+
100
+ // Call this at an appropriate time (not immediately at launch)
101
+ ATTrackingManager.requestTrackingAuthorization { status in
102
+ // Handle response
103
+ }
104
+
105
+ Note: If you're using a different approach to ATT (like via a third-party SDK wrapper), ` +
106
+ `you can ignore this finding.`,
107
+ documentationURL: 'https://developer.apple.com/documentation/apptrackingtransparency',
108
+ }));
109
+ }
110
+ return findings;
111
+ },
112
+ };
113
+ //# sourceMappingURL=att-tracking-mismatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"att-tracking-mismatch.js","sourceRoot":"","sources":["../../../src/rules/privacy/att-tracking-mismatch.ts"],"names":[],"mappings":";;;AAUA,mDAA0E;AAC1E,mEAA8D;AAC9D,+EAAyE;AACzE,wCAA4D;AAE5D,MAAM,kBAAkB,GAAG,gCAAgC,CAAC;AAC5D,MAAM,aAAa,GAAG,yBAAyB,CAAC;AAEnC,QAAA,uBAAuB,GAAS;IAC3C,EAAE,EAAE,mCAAmC;IACvC,IAAI,EAAE,gDAAgD;IACtD,WAAW,EAAE,4DAA4D;IACzE,QAAQ,EAAE,uBAAY,CAAC,OAAO;IAC9B,QAAQ,EAAE,mBAAQ,CAAC,QAAQ;IAC3B,UAAU,EAAE,qBAAU,CAAC,IAAI;IAC3B,kBAAkB,EAAE,OAAO;IAE3B,KAAK,CAAC,QAAQ,CAAC,OAAoB;QACjC,yCAAyC;QACzC,MAAM,YAAY,GAAG,IAAA,0CAAkB,EAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAE9D,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,MAAM,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;QACpE,MAAM,sBAAsB,GAAG,mBAAmB,KAAK,SAAS,CAAC;QACjE,MAAM,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;QAE5D,0DAA0D;QAC1D,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC,IAAA,qBAAW,EAAC,IAAI,EAAE;gBAC9B,WAAW,EAAE,gDAAgD,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;oBACtF,4FAA4F;oBAC5F,8EAA8E;gBAChF,QAAQ,EAAE,YAAY;gBACtB,WAAW,EAAE;;;;;;;;;;;;;;;;;;2EAkBsD;gBACnE,gBAAgB,EAAE,mEAAmE;aACtF,CAAC,CAAC,CAAC;QACN,CAAC;QACD,+BAA+B;aAC1B,IAAI,mBAAmB,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC3C,QAAQ,CAAC,IAAI,CAAC,IAAA,qBAAW,EAAC,IAAI,EAAE;gBAC9B,KAAK,EAAE,kCAAkC;gBACzC,WAAW,EAAE,kFAAkF;oBAC7F,mDAAmD;gBACrD,QAAQ,EAAE,YAAY;gBACtB,WAAW,EAAE;;;;;4DAKuC;gBACpD,gBAAgB,EAAE,mEAAmE;aACtF,CAAC,CAAC,CAAC;QACN,CAAC;QACD,qCAAqC;aAChC,IAAI,IAAA,+BAAa,EAAC,mBAAmB,CAAC,EAAE,CAAC;YAC5C,QAAQ,CAAC,IAAI,CAAC,IAAA,qBAAW,EAAC,IAAI,EAAE;gBAC9B,KAAK,EAAE,wCAAwC;gBAC/C,WAAW,EAAE,wEAAwE,mBAAmB,KAAK;oBAC3G,sDAAsD;gBACxD,QAAQ,EAAE,YAAY;gBACtB,WAAW,EAAE;;kBAEH,mBAAmB;;+DAE0B;gBACvD,gBAAgB,EAAE,mEAAmE;aACtF,CAAC,CAAC,CAAC;QACN,CAAC;QAED,+CAA+C;QAC/C,IAAI,sBAAsB,IAAI,CAAC,eAAe,EAAE,CAAC;YAC/C,QAAQ,CAAC,IAAI,CAAC,IAAA,2BAAiB,EAAC,IAAI,EAAE,mBAAQ,CAAC,MAAM,EAAE,qBAAU,CAAC,MAAM,EAAE;gBACxE,KAAK,EAAE,8CAA8C;gBACrD,WAAW,EAAE,oFAAoF;oBAC/F,mFAAmF;gBACrF,QAAQ,EAAE,SAAS;gBACnB,WAAW,EAAE,oFAAoF;oBAC/F;;;;;;;;;yFAS+E;oBAC/E,8BAA8B;gBAChC,gBAAgB,EAAE,mEAAmE;aACtF,CAAC,CAAC,CAAC;QACN,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Privacy rules exports
3
+ */
4
+ export { MissingCameraPurposeRule } from './missing-camera-purpose.js';
5
+ export { MissingLocationPurposeRule } from './missing-location-purpose.js';
6
+ export { LocationAlwaysUnjustifiedRule } from './location-always-unjustified.js';
7
+ export { ATTTrackingMismatchRule } from './att-tracking-mismatch.js';
8
+ export { MissingPhotoLibraryPurposeRule } from './missing-photo-library-purpose.js';
9
+ export { MissingMicrophonePurposeRule } from './missing-microphone-purpose.js';
10
+ export { MissingContactsPurposeRule } from './missing-contacts-purpose.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rules/privacy/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MissingContactsPurposeRule = exports.MissingMicrophonePurposeRule = exports.MissingPhotoLibraryPurposeRule = exports.ATTTrackingMismatchRule = exports.LocationAlwaysUnjustifiedRule = exports.MissingLocationPurposeRule = exports.MissingCameraPurposeRule = void 0;
4
+ /**
5
+ * Privacy rules exports
6
+ */
7
+ var missing_camera_purpose_js_1 = require("./missing-camera-purpose.js");
8
+ Object.defineProperty(exports, "MissingCameraPurposeRule", { enumerable: true, get: function () { return missing_camera_purpose_js_1.MissingCameraPurposeRule; } });
9
+ var missing_location_purpose_js_1 = require("./missing-location-purpose.js");
10
+ Object.defineProperty(exports, "MissingLocationPurposeRule", { enumerable: true, get: function () { return missing_location_purpose_js_1.MissingLocationPurposeRule; } });
11
+ var location_always_unjustified_js_1 = require("./location-always-unjustified.js");
12
+ Object.defineProperty(exports, "LocationAlwaysUnjustifiedRule", { enumerable: true, get: function () { return location_always_unjustified_js_1.LocationAlwaysUnjustifiedRule; } });
13
+ var att_tracking_mismatch_js_1 = require("./att-tracking-mismatch.js");
14
+ Object.defineProperty(exports, "ATTTrackingMismatchRule", { enumerable: true, get: function () { return att_tracking_mismatch_js_1.ATTTrackingMismatchRule; } });
15
+ var missing_photo_library_purpose_js_1 = require("./missing-photo-library-purpose.js");
16
+ Object.defineProperty(exports, "MissingPhotoLibraryPurposeRule", { enumerable: true, get: function () { return missing_photo_library_purpose_js_1.MissingPhotoLibraryPurposeRule; } });
17
+ var missing_microphone_purpose_js_1 = require("./missing-microphone-purpose.js");
18
+ Object.defineProperty(exports, "MissingMicrophonePurposeRule", { enumerable: true, get: function () { return missing_microphone_purpose_js_1.MissingMicrophonePurposeRule; } });
19
+ var missing_contacts_purpose_js_1 = require("./missing-contacts-purpose.js");
20
+ Object.defineProperty(exports, "MissingContactsPurposeRule", { enumerable: true, get: function () { return missing_contacts_purpose_js_1.MissingContactsPurposeRule; } });
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/privacy/index.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,yEAAuE;AAA9D,qIAAA,wBAAwB,OAAA;AACjC,6EAA2E;AAAlE,yIAAA,0BAA0B,OAAA;AACnC,mFAAiF;AAAxE,+IAAA,6BAA6B,OAAA;AACtC,uEAAqE;AAA5D,mIAAA,uBAAuB,OAAA;AAChC,uFAAoF;AAA3E,kJAAA,8BAA8B,OAAA;AACvC,iFAA+E;AAAtE,6IAAA,4BAA4B,OAAA;AACrC,6EAA2E;AAAlE,yIAAA,0BAA0B,OAAA"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Rule: Location Always Permission Without Justification
3
+ *
4
+ * Detects when an app requests Always location permission but doesn't
5
+ * have the "location" background mode enabled in UIBackgroundModes.
6
+ *
7
+ * App Store Review Guideline: 5.1.1
8
+ */
9
+ import type { Rule } from '../../types/index.js';
10
+ export declare const LocationAlwaysUnjustifiedRule: Rule;
11
+ //# sourceMappingURL=location-always-unjustified.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"location-always-unjustified.d.ts","sourceRoot":"","sources":["../../../src/rules/privacy/location-always-unjustified.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,sBAAsB,CAAC;AAWvE,eAAO,MAAM,6BAA6B,EAAE,IAmG3C,CAAC"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocationAlwaysUnjustifiedRule = void 0;
4
+ const index_js_1 = require("../../types/index.js");
5
+ const base_js_1 = require("../base.js");
6
+ const ALWAYS_KEYS = [
7
+ 'NSLocationAlwaysUsageDescription',
8
+ 'NSLocationAlwaysAndWhenInUseUsageDescription',
9
+ ];
10
+ const BACKGROUND_MODES_KEY = 'UIBackgroundModes';
11
+ const LOCATION_BACKGROUND_MODE = 'location';
12
+ exports.LocationAlwaysUnjustifiedRule = {
13
+ id: 'entitlements-001-location-always-unjustified',
14
+ name: 'Location Always Permission Without Justification',
15
+ description: 'Checks for Always location permission without background mode or proper justification',
16
+ category: index_js_1.RuleCategory.Privacy,
17
+ severity: index_js_1.Severity.Critical,
18
+ confidence: index_js_1.Confidence.Medium,
19
+ guidelineReference: '5.1.1',
20
+ async evaluate(context) {
21
+ // Check if app requests Always location permission
22
+ const hasAlwaysPermission = ALWAYS_KEYS.some(key => context.hasPlistKey(key));
23
+ if (!hasAlwaysPermission) {
24
+ return [];
25
+ }
26
+ // Check if background location mode is enabled
27
+ const backgroundModes = context.plistArray(BACKGROUND_MODES_KEY) ?? [];
28
+ const hasLocationBackgroundMode = backgroundModes.includes(LOCATION_BACKGROUND_MODE);
29
+ const findings = [];
30
+ // Case 1: Always permission without background location mode
31
+ if (!hasLocationBackgroundMode) {
32
+ const presentKeys = ALWAYS_KEYS.filter(key => context.hasPlistKey(key));
33
+ findings.push((0, base_js_1.makeFinding)(this, {
34
+ description: `Your app requests Always location permission (${presentKeys.join(', ')}) ` +
35
+ `but UIBackgroundModes does not include "location". This configuration strongly suggests ` +
36
+ `your app doesn't have a legitimate continuous location feature, which Apple will ` +
37
+ `likely question during review.`,
38
+ location: 'Info.plist',
39
+ fixGuidance: `You have two options:
40
+
41
+ **Option 1: If you DO need Always permission (navigation, fitness, geofencing):**
42
+
43
+ Add "location" to UIBackgroundModes in Info.plist:
44
+
45
+ <key>UIBackgroundModes</key>
46
+ <array>
47
+ <string>location</string>
48
+ </array>
49
+
50
+ Also ensure your app has a visible, user-initiated feature that uses continuous location ` +
51
+ `(like run tracking or turn-by-turn navigation).
52
+
53
+ **Option 2: If you DON'T need Always permission (most apps):**
54
+
55
+ Switch to When In Use permission instead. Remove the Always description keys and ` +
56
+ `use requestWhenInUseAuthorization() instead of requestAlwaysAuthorization().
57
+
58
+ When In Use permission has a much higher approval rate and is sufficient for most ` +
59
+ `location features.
60
+
61
+ Note: Always permission is heavily scrutinized. Be prepared to justify your use case ` +
62
+ `in App Store Review notes.`,
63
+ documentationURL: 'https://developer.apple.com/documentation/corelocation/choosing_the_location_services_authorization_to_request',
64
+ }));
65
+ }
66
+ // Case 2: Has background mode but check for suspicious patterns
67
+ else {
68
+ const alwaysDesc = context.plistString('NSLocationAlwaysAndWhenInUseUsageDescription') ??
69
+ context.plistString('NSLocationAlwaysUsageDescription');
70
+ if (alwaysDesc) {
71
+ const lowercased = alwaysDesc.toLowerCase();
72
+ const vaguePatterns = ['nearby', 'location services', 'your location', 'we need', 'is required'];
73
+ const legitimatePatterns = ['track', 'background', 'navigation', 'running', 'workout', 'geofence', 'alert'];
74
+ const seemsVague = vaguePatterns.some(p => lowercased.includes(p)) &&
75
+ !legitimatePatterns.some(p => lowercased.includes(p));
76
+ if (seemsVague) {
77
+ findings.push((0, base_js_1.makeCustomFinding)(this, index_js_1.Severity.Medium, index_js_1.Confidence.Low, {
78
+ title: 'Always Location Description May Be Insufficient',
79
+ description: `Your Always location description doesn't clearly explain a continuous ` +
80
+ `location feature: "${alwaysDesc}". Apple expects clear justification for ` +
81
+ `Always permission.`,
82
+ location: 'Info.plist',
83
+ fixGuidance: `Update your description to clearly explain the continuous location feature:
84
+
85
+ Good examples:
86
+ - "Track your runs in the background so you can see your complete route."
87
+ - "Provide turn-by-turn directions even when the screen is off."
88
+ - "Send alerts when you arrive at or leave saved locations."
89
+
90
+ Bad examples:
91
+ - "We use your location" (too vague)
92
+ - "Location is required" (doesn't explain feature)
93
+ - "Show nearby places" (doesn't justify Always)`,
94
+ documentationURL: 'https://developer.apple.com/documentation/corelocation/choosing_the_location_services_authorization_to_request',
95
+ }));
96
+ }
97
+ }
98
+ }
99
+ return findings;
100
+ },
101
+ };
102
+ //# sourceMappingURL=location-always-unjustified.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"location-always-unjustified.js","sourceRoot":"","sources":["../../../src/rules/privacy/location-always-unjustified.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Rule: Missing Camera Usage Description
3
+ *
4
+ * Detects when an app uses camera-related frameworks but is missing
5
+ * the required NSCameraUsageDescription in Info.plist.
6
+ *
7
+ * App Store Review Guideline: 5.1.1
8
+ */
9
+ import type { Rule } from '../../types/index.js';
10
+ export declare const MissingCameraPurposeRule: Rule;
11
+ //# sourceMappingURL=missing-camera-purpose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-camera-purpose.d.ts","sourceRoot":"","sources":["../../../src/rules/privacy/missing-camera-purpose.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,sBAAsB,CAAC;AAOvE,eAAO,MAAM,wBAAwB,EAAE,IAiFtC,CAAC"}
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MissingCameraPurposeRule = void 0;
4
+ const index_js_1 = require("../../types/index.js");
5
+ const plist_parser_js_1 = require("../../parsers/plist-parser.js");
6
+ const base_js_1 = require("../base.js");
7
+ const CAMERA_FRAMEWORKS = ['AVFoundation', 'AVKit', 'VisionKit'];
8
+ exports.MissingCameraPurposeRule = {
9
+ id: 'privacy-001-missing-camera-purpose',
10
+ name: 'Missing Camera Usage Description',
11
+ description: 'Checks for camera framework usage without NSCameraUsageDescription',
12
+ category: index_js_1.RuleCategory.Privacy,
13
+ severity: index_js_1.Severity.Critical,
14
+ confidence: index_js_1.Confidence.High,
15
+ guidelineReference: '5.1.1',
16
+ async evaluate(context) {
17
+ // Check if any camera-related framework is linked
18
+ const detectedFrameworks = CAMERA_FRAMEWORKS.filter(f => context.hasFramework(f));
19
+ if (detectedFrameworks.length === 0) {
20
+ // No camera framework detected, rule doesn't apply
21
+ return [];
22
+ }
23
+ const cameraDescription = context.plistString('NSCameraUsageDescription');
24
+ // Case 1: Completely missing
25
+ if (cameraDescription === undefined) {
26
+ return [
27
+ (0, base_js_1.makeFinding)(this, {
28
+ description: `Your app links against camera-related frameworks (${detectedFrameworks.join(', ')}) ` +
29
+ `but Info.plist is missing NSCameraUsageDescription. Apps that access the camera must ` +
30
+ `provide a purpose string explaining why access is needed.`,
31
+ location: 'Info.plist',
32
+ fixGuidance: `Add NSCameraUsageDescription to your Info.plist with a clear, user-facing explanation ` +
33
+ `of why your app needs camera access. For example:
34
+
35
+ <key>NSCameraUsageDescription</key>
36
+ <string>We need access to your camera to take photos for your profile.</string>
37
+
38
+ The description should explain the specific feature that uses the camera and ` +
39
+ `be written from the user's perspective.`,
40
+ documentationURL: 'https://developer.apple.com/documentation/bundleresources/information_property_list/nscamerausagedescription',
41
+ }),
42
+ ];
43
+ }
44
+ // Case 2: Empty or whitespace only
45
+ if (cameraDescription.trim() === '') {
46
+ return [
47
+ (0, base_js_1.makeFinding)(this, {
48
+ title: 'Empty Camera Usage Description',
49
+ description: `NSCameraUsageDescription exists in Info.plist but is empty. ` +
50
+ `Apple requires a meaningful description explaining why your app needs camera access.`,
51
+ location: 'Info.plist',
52
+ fixGuidance: `Update NSCameraUsageDescription with a clear, specific explanation of why your app ` +
53
+ `needs camera access. Generic or empty descriptions may be rejected.
54
+
55
+ Good example: "We use your camera to scan QR codes for quick login."
56
+ Bad example: "Camera access required" or ""`,
57
+ documentationURL: 'https://developer.apple.com/documentation/bundleresources/information_property_list/nscamerausagedescription',
58
+ }),
59
+ ];
60
+ }
61
+ // Case 3: Placeholder text detected
62
+ if ((0, plist_parser_js_1.isPlaceholder)(cameraDescription)) {
63
+ return [
64
+ (0, base_js_1.makeFinding)(this, {
65
+ title: 'Placeholder Camera Usage Description',
66
+ description: `NSCameraUsageDescription appears to contain placeholder text: "${cameraDescription}". ` +
67
+ `Apple requires meaningful, user-facing descriptions.`,
68
+ location: 'Info.plist',
69
+ fixGuidance: `Replace the placeholder text with a clear explanation of why your app needs camera access. ` +
70
+ `The description should be specific to your app's features.
71
+
72
+ Current value: "${cameraDescription}"
73
+
74
+ Write a description that helps users understand what feature uses the camera and why.`,
75
+ documentationURL: 'https://developer.apple.com/documentation/bundleresources/information_property_list/nscamerausagedescription',
76
+ }),
77
+ ];
78
+ }
79
+ // All checks passed
80
+ return [];
81
+ },
82
+ };
83
+ //# sourceMappingURL=missing-camera-purpose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-camera-purpose.js","sourceRoot":"","sources":["../../../src/rules/privacy/missing-camera-purpose.ts"],"names":[],"mappings":";;;AASA,mDAA0E;AAC1E,mEAA8D;AAC9D,wCAAyC;AAEzC,MAAM,iBAAiB,GAAG,CAAC,cAAc,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;AAEpD,QAAA,wBAAwB,GAAS;IAC5C,EAAE,EAAE,oCAAoC;IACxC,IAAI,EAAE,kCAAkC;IACxC,WAAW,EAAE,oEAAoE;IACjF,QAAQ,EAAE,uBAAY,CAAC,OAAO;IAC9B,QAAQ,EAAE,mBAAQ,CAAC,QAAQ;IAC3B,UAAU,EAAE,qBAAU,CAAC,IAAI;IAC3B,kBAAkB,EAAE,OAAO;IAE3B,KAAK,CAAC,QAAQ,CAAC,OAAoB;QACjC,kDAAkD;QAClD,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;QAElF,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpC,mDAAmD;YACnD,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,0BAA0B,CAAC,CAAC;QAE1E,6BAA6B;QAC7B,IAAI,iBAAiB,KAAK,SAAS,EAAE,CAAC;YACpC,OAAO;gBACL,IAAA,qBAAW,EAAC,IAAI,EAAE;oBAChB,WAAW,EAAE,qDAAqD,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;wBACjG,uFAAuF;wBACvF,2DAA2D;oBAC7D,QAAQ,EAAE,YAAY;oBACtB,WAAW,EAAE,wFAAwF;wBACnG;;;;;8EAKkE;wBAClE,yCAAyC;oBAC3C,gBAAgB,EAAE,8GAA8G;iBACjI,CAAC;aACH,CAAC;QACJ,CAAC;QAED,mCAAmC;QACnC,IAAI,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACpC,OAAO;gBACL,IAAA,qBAAW,EAAC,IAAI,EAAE;oBAChB,KAAK,EAAE,gCAAgC;oBACvC,WAAW,EAAE,8DAA8D;wBACzE,sFAAsF;oBACxF,QAAQ,EAAE,YAAY;oBACtB,WAAW,EAAE,qFAAqF;wBAChG;;;4CAGgC;oBAClC,gBAAgB,EAAE,8GAA8G;iBACjI,CAAC;aACH,CAAC;QACJ,CAAC;QAED,oCAAoC;QACpC,IAAI,IAAA,+BAAa,EAAC,iBAAiB,CAAC,EAAE,CAAC;YACrC,OAAO;gBACL,IAAA,qBAAW,EAAC,IAAI,EAAE;oBAChB,KAAK,EAAE,sCAAsC;oBAC7C,WAAW,EAAE,kEAAkE,iBAAiB,KAAK;wBACnG,sDAAsD;oBACxD,QAAQ,EAAE,YAAY;oBACtB,WAAW,EAAE,6FAA6F;wBACxG;;kBAEM,iBAAiB;;sFAEmD;oBAC5E,gBAAgB,EAAE,8GAA8G;iBACjI,CAAC;aACH,CAAC;QACJ,CAAC;QAED,oBAAoB;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;CACF,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Rule: Missing Contacts Usage Description
3
+ *
4
+ * Detects when an app uses Contacts framework without the required
5
+ * NSContactsUsageDescription in Info.plist.
6
+ *
7
+ * App Store Review Guideline: 5.1.1
8
+ */
9
+ import type { Rule } from '../../types/index.js';
10
+ export declare const MissingContactsPurposeRule: Rule;
11
+ //# sourceMappingURL=missing-contacts-purpose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-contacts-purpose.d.ts","sourceRoot":"","sources":["../../../src/rules/privacy/missing-contacts-purpose.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,sBAAsB,CAAC;AAQvE,eAAO,MAAM,0BAA0B,EAAE,IAkFxC,CAAC"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MissingContactsPurposeRule = void 0;
4
+ const index_js_1 = require("../../types/index.js");
5
+ const plist_parser_js_1 = require("../../parsers/plist-parser.js");
6
+ const base_js_1 = require("../base.js");
7
+ const CONTACTS_FRAMEWORKS = ['Contacts', 'ContactsUI'];
8
+ const CONTACTS_KEY = 'NSContactsUsageDescription';
9
+ exports.MissingContactsPurposeRule = {
10
+ id: 'privacy-006-missing-contacts-purpose',
11
+ name: 'Missing Contacts Usage Description',
12
+ description: 'Checks for Contacts framework usage without NSContactsUsageDescription',
13
+ category: index_js_1.RuleCategory.Privacy,
14
+ severity: index_js_1.Severity.Critical,
15
+ confidence: index_js_1.Confidence.High,
16
+ guidelineReference: '5.1.1',
17
+ async evaluate(context) {
18
+ // Check if any Contacts-related framework is linked
19
+ const detectedFrameworks = CONTACTS_FRAMEWORKS.filter(f => context.hasFramework(f));
20
+ if (detectedFrameworks.length === 0) {
21
+ return [];
22
+ }
23
+ const contactsDescription = context.plistString(CONTACTS_KEY);
24
+ // Case 1: Completely missing
25
+ if (contactsDescription === undefined) {
26
+ return [
27
+ (0, base_js_1.makeFinding)(this, {
28
+ description: `Your app links against contacts frameworks (${detectedFrameworks.join(', ')}) ` +
29
+ `but Info.plist is missing NSContactsUsageDescription. Apps that access the user's contacts ` +
30
+ `must provide a purpose string explaining why access is needed.`,
31
+ location: 'Info.plist',
32
+ fixGuidance: `Add NSContactsUsageDescription to your Info.plist with a clear, user-facing explanation ` +
33
+ `of why your app needs contacts access. For example:
34
+
35
+ <key>NSContactsUsageDescription</key>
36
+ <string>We use your contacts to help you find friends who are also using the app.</string>
37
+
38
+ Important: Contact data is considered highly sensitive. Apple scrutinizes apps that request
39
+ contacts access, so ensure you have a legitimate user-facing feature that requires it.`,
40
+ documentationURL: 'https://developer.apple.com/documentation/bundleresources/information_property_list/nscontactsusagedescription',
41
+ }),
42
+ ];
43
+ }
44
+ // Case 2: Empty or whitespace only
45
+ if (contactsDescription.trim() === '') {
46
+ return [
47
+ (0, base_js_1.makeFinding)(this, {
48
+ title: 'Empty Contacts Usage Description',
49
+ description: `NSContactsUsageDescription exists in Info.plist but is empty. ` +
50
+ `Apple requires a meaningful description explaining why your app needs contacts access.`,
51
+ location: 'Info.plist',
52
+ fixGuidance: `Update NSContactsUsageDescription with a clear, specific explanation of why your app ` +
53
+ `needs contacts access. Generic or empty descriptions will be rejected.
54
+
55
+ Good example: "Find friends in your contacts who use the app."
56
+ Bad example: "Contacts access required" or ""
57
+
58
+ Note: Contacts are sensitive data. Only request access if you have a clear user-facing need.`,
59
+ documentationURL: 'https://developer.apple.com/documentation/bundleresources/information_property_list/nscontactsusagedescription',
60
+ }),
61
+ ];
62
+ }
63
+ // Case 3: Placeholder text detected
64
+ if ((0, plist_parser_js_1.isPlaceholder)(contactsDescription)) {
65
+ return [
66
+ (0, base_js_1.makeFinding)(this, {
67
+ title: 'Placeholder Contacts Usage Description',
68
+ description: `NSContactsUsageDescription appears to contain placeholder text: "${contactsDescription}". ` +
69
+ `Apple requires meaningful, user-facing descriptions.`,
70
+ location: 'Info.plist',
71
+ fixGuidance: `Replace the placeholder text with a clear explanation of why your app needs contacts access. ` +
72
+ `The description should be specific to your app's features.
73
+
74
+ Current value: "${contactsDescription}"
75
+
76
+ Write a description that helps users understand what feature uses contacts and why.`,
77
+ documentationURL: 'https://developer.apple.com/documentation/bundleresources/information_property_list/nscontactsusagedescription',
78
+ }),
79
+ ];
80
+ }
81
+ // All checks passed
82
+ return [];
83
+ },
84
+ };
85
+ //# sourceMappingURL=missing-contacts-purpose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-contacts-purpose.js","sourceRoot":"","sources":["../../../src/rules/privacy/missing-contacts-purpose.ts"],"names":[],"mappings":";;;AASA,mDAA0E;AAC1E,mEAA8D;AAC9D,wCAAyC;AAEzC,MAAM,mBAAmB,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AACvD,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAErC,QAAA,0BAA0B,GAAS;IAC9C,EAAE,EAAE,sCAAsC;IAC1C,IAAI,EAAE,oCAAoC;IAC1C,WAAW,EAAE,wEAAwE;IACrF,QAAQ,EAAE,uBAAY,CAAC,OAAO;IAC9B,QAAQ,EAAE,mBAAQ,CAAC,QAAQ;IAC3B,UAAU,EAAE,qBAAU,CAAC,IAAI;IAC3B,kBAAkB,EAAE,OAAO;IAE3B,KAAK,CAAC,QAAQ,CAAC,OAAoB;QACjC,oDAAoD;QACpD,MAAM,kBAAkB,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;QAEpF,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;QAE9D,6BAA6B;QAC7B,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;YACtC,OAAO;gBACL,IAAA,qBAAW,EAAC,IAAI,EAAE;oBAChB,WAAW,EAAE,+CAA+C,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;wBAC3F,6FAA6F;wBAC7F,gEAAgE;oBAClE,QAAQ,EAAE,YAAY;oBACtB,WAAW,EAAE,0FAA0F;wBACrG;;;;;;uFAM2E;oBAC7E,gBAAgB,EAAE,gHAAgH;iBACnI,CAAC;aACH,CAAC;QACJ,CAAC;QAED,mCAAmC;QACnC,IAAI,mBAAmB,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACtC,OAAO;gBACL,IAAA,qBAAW,EAAC,IAAI,EAAE;oBAChB,KAAK,EAAE,kCAAkC;oBACzC,WAAW,EAAE,gEAAgE;wBAC3E,wFAAwF;oBAC1F,QAAQ,EAAE,YAAY;oBACtB,WAAW,EAAE,uFAAuF;wBAClG;;;;;6FAKiF;oBACnF,gBAAgB,EAAE,gHAAgH;iBACnI,CAAC;aACH,CAAC;QACJ,CAAC;QAED,oCAAoC;QACpC,IAAI,IAAA,+BAAa,EAAC,mBAAmB,CAAC,EAAE,CAAC;YACvC,OAAO;gBACL,IAAA,qBAAW,EAAC,IAAI,EAAE;oBAChB,KAAK,EAAE,wCAAwC;oBAC/C,WAAW,EAAE,oEAAoE,mBAAmB,KAAK;wBACvG,sDAAsD;oBACxD,QAAQ,EAAE,YAAY;oBACtB,WAAW,EAAE,+FAA+F;wBAC1G;;kBAEM,mBAAmB;;oFAE+C;oBAC1E,gBAAgB,EAAE,gHAAgH;iBACnI,CAAC;aACH,CAAC;QACJ,CAAC;QAED,oBAAoB;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;CACF,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Rule: Missing Location Usage Description
3
+ *
4
+ * Detects when an app uses location-related frameworks but is missing
5
+ * the required NSLocationWhenInUseUsageDescription or
6
+ * NSLocationAlwaysAndWhenInUseUsageDescription in Info.plist.
7
+ *
8
+ * App Store Review Guideline: 5.1.1
9
+ */
10
+ import type { Rule } from '../../types/index.js';
11
+ export declare const MissingLocationPurposeRule: Rule;
12
+ //# sourceMappingURL=missing-location-purpose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-location-purpose.d.ts","sourceRoot":"","sources":["../../../src/rules/privacy/missing-location-purpose.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,sBAAsB,CAAC;AAUvE,eAAO,MAAM,0BAA0B,EAAE,IAqIxC,CAAC"}