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.
- package/README.md +107 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +101 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +21 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/scanner.d.ts +27 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +104 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/formatters/index.d.ts +13 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +29 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/json.d.ts +13 -0
- package/dist/formatters/json.d.ts.map +1 -0
- package/dist/formatters/json.js +17 -0
- package/dist/formatters/json.js.map +1 -0
- package/dist/formatters/sarif.d.ts +14 -0
- package/dist/formatters/sarif.d.ts.map +1 -0
- package/dist/formatters/sarif.js +108 -0
- package/dist/formatters/sarif.js.map +1 -0
- package/dist/formatters/text.d.ts +9 -0
- package/dist/formatters/text.d.ts.map +1 -0
- package/dist/formatters/text.js +128 -0
- package/dist/formatters/text.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/entitlements-parser.d.ts +26 -0
- package/dist/parsers/entitlements-parser.d.ts.map +1 -0
- package/dist/parsers/entitlements-parser.js +105 -0
- package/dist/parsers/entitlements-parser.js.map +1 -0
- package/dist/parsers/framework-detector.d.ts +76 -0
- package/dist/parsers/framework-detector.d.ts.map +1 -0
- package/dist/parsers/framework-detector.js +501 -0
- package/dist/parsers/framework-detector.js.map +1 -0
- package/dist/parsers/index.d.ts +10 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +26 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/pbxproj-parser.d.ts +166 -0
- package/dist/parsers/pbxproj-parser.d.ts.map +1 -0
- package/dist/parsers/pbxproj-parser.js +423 -0
- package/dist/parsers/pbxproj-parser.js.map +1 -0
- package/dist/parsers/plist-parser.d.ts +26 -0
- package/dist/parsers/plist-parser.d.ts.map +1 -0
- package/dist/parsers/plist-parser.js +166 -0
- package/dist/parsers/plist-parser.js.map +1 -0
- package/dist/parsers/project-parser.d.ts +57 -0
- package/dist/parsers/project-parser.d.ts.map +1 -0
- package/dist/parsers/project-parser.js +618 -0
- package/dist/parsers/project-parser.js.map +1 -0
- package/dist/parsers/workspace-parser.d.ts +82 -0
- package/dist/parsers/workspace-parser.d.ts.map +1 -0
- package/dist/parsers/workspace-parser.js +287 -0
- package/dist/parsers/workspace-parser.js.map +1 -0
- package/dist/rules/auth/index.d.ts +5 -0
- package/dist/rules/auth/index.d.ts.map +1 -0
- package/dist/rules/auth/index.js +9 -0
- package/dist/rules/auth/index.js.map +1 -0
- package/dist/rules/auth/third-party-login-no-siwa.d.ts +11 -0
- package/dist/rules/auth/third-party-login-no-siwa.d.ts.map +1 -0
- package/dist/rules/auth/third-party-login-no-siwa.js +119 -0
- package/dist/rules/auth/third-party-login-no-siwa.js.map +1 -0
- package/dist/rules/base.d.ts +25 -0
- package/dist/rules/base.d.ts.map +1 -0
- package/dist/rules/base.js +37 -0
- package/dist/rules/base.js.map +1 -0
- package/dist/rules/config/ats-exception-without-justification.d.ts +12 -0
- package/dist/rules/config/ats-exception-without-justification.d.ts.map +1 -0
- package/dist/rules/config/ats-exception-without-justification.js +152 -0
- package/dist/rules/config/ats-exception-without-justification.js.map +1 -0
- package/dist/rules/config/index.d.ts +5 -0
- package/dist/rules/config/index.d.ts.map +1 -0
- package/dist/rules/config/index.js +9 -0
- package/dist/rules/config/index.js.map +1 -0
- package/dist/rules/index.d.ts +43 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +103 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/metadata/index.d.ts +5 -0
- package/dist/rules/metadata/index.d.ts.map +1 -0
- package/dist/rules/metadata/index.js +9 -0
- package/dist/rules/metadata/index.js.map +1 -0
- package/dist/rules/metadata/missing-privacy-manifest.d.ts +12 -0
- package/dist/rules/metadata/missing-privacy-manifest.d.ts.map +1 -0
- package/dist/rules/metadata/missing-privacy-manifest.js +186 -0
- package/dist/rules/metadata/missing-privacy-manifest.js.map +1 -0
- package/dist/rules/privacy/att-tracking-mismatch.d.ts +12 -0
- package/dist/rules/privacy/att-tracking-mismatch.d.ts.map +1 -0
- package/dist/rules/privacy/att-tracking-mismatch.js +113 -0
- package/dist/rules/privacy/att-tracking-mismatch.js.map +1 -0
- package/dist/rules/privacy/index.d.ts +11 -0
- package/dist/rules/privacy/index.d.ts.map +1 -0
- package/dist/rules/privacy/index.js +21 -0
- package/dist/rules/privacy/index.js.map +1 -0
- package/dist/rules/privacy/location-always-unjustified.d.ts +11 -0
- package/dist/rules/privacy/location-always-unjustified.d.ts.map +1 -0
- package/dist/rules/privacy/location-always-unjustified.js +102 -0
- package/dist/rules/privacy/location-always-unjustified.js.map +1 -0
- package/dist/rules/privacy/missing-camera-purpose.d.ts +11 -0
- package/dist/rules/privacy/missing-camera-purpose.d.ts.map +1 -0
- package/dist/rules/privacy/missing-camera-purpose.js +83 -0
- package/dist/rules/privacy/missing-camera-purpose.js.map +1 -0
- package/dist/rules/privacy/missing-contacts-purpose.d.ts +11 -0
- package/dist/rules/privacy/missing-contacts-purpose.d.ts.map +1 -0
- package/dist/rules/privacy/missing-contacts-purpose.js +85 -0
- package/dist/rules/privacy/missing-contacts-purpose.js.map +1 -0
- package/dist/rules/privacy/missing-location-purpose.d.ts +12 -0
- package/dist/rules/privacy/missing-location-purpose.d.ts.map +1 -0
- package/dist/rules/privacy/missing-location-purpose.js +137 -0
- package/dist/rules/privacy/missing-location-purpose.js.map +1 -0
- package/dist/rules/privacy/missing-microphone-purpose.d.ts +11 -0
- package/dist/rules/privacy/missing-microphone-purpose.d.ts.map +1 -0
- package/dist/rules/privacy/missing-microphone-purpose.js +132 -0
- package/dist/rules/privacy/missing-microphone-purpose.js.map +1 -0
- package/dist/rules/privacy/missing-photo-library-purpose.d.ts +11 -0
- package/dist/rules/privacy/missing-photo-library-purpose.d.ts.map +1 -0
- package/dist/rules/privacy/missing-photo-library-purpose.js +102 -0
- package/dist/rules/privacy/missing-photo-library-purpose.js.map +1 -0
- package/dist/types/index.d.ts +140 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +59 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ATSExceptionWithoutJustificationRule = void 0;
|
|
4
|
+
const index_js_1 = require("../../types/index.js");
|
|
5
|
+
const base_js_1 = require("../base.js");
|
|
6
|
+
const ATS_KEY = 'NSAppTransportSecurity';
|
|
7
|
+
const ALLOWS_ARBITRARY_LOADS_KEY = 'NSAllowsArbitraryLoads';
|
|
8
|
+
const ALLOWS_ARBITRARY_LOADS_WEBVIEW_KEY = 'NSAllowsArbitraryLoadsInWebContent';
|
|
9
|
+
const EXCEPTION_DOMAINS_KEY = 'NSExceptionDomains';
|
|
10
|
+
exports.ATSExceptionWithoutJustificationRule = {
|
|
11
|
+
id: 'config-001-ats-exception-without-justification',
|
|
12
|
+
name: 'ATS Exception Without Justification',
|
|
13
|
+
description: 'Checks for insecure App Transport Security configuration',
|
|
14
|
+
category: index_js_1.RuleCategory.Config,
|
|
15
|
+
severity: index_js_1.Severity.High,
|
|
16
|
+
confidence: index_js_1.Confidence.High,
|
|
17
|
+
guidelineReference: '2.1',
|
|
18
|
+
async evaluate(context) {
|
|
19
|
+
const atsConfig = context.infoPlist[ATS_KEY];
|
|
20
|
+
if (!atsConfig) {
|
|
21
|
+
// No ATS configuration, defaults are secure
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const findings = [];
|
|
25
|
+
const allowsArbitraryLoads = atsConfig[ALLOWS_ARBITRARY_LOADS_KEY];
|
|
26
|
+
const allowsArbitraryLoadsWebView = atsConfig[ALLOWS_ARBITRARY_LOADS_WEBVIEW_KEY];
|
|
27
|
+
const exceptionDomains = atsConfig[EXCEPTION_DOMAINS_KEY];
|
|
28
|
+
// Case 1: NSAllowsArbitraryLoads = true without exception domains
|
|
29
|
+
if (allowsArbitraryLoads === true) {
|
|
30
|
+
const hasExceptionDomains = exceptionDomains && Object.keys(exceptionDomains).length > 0;
|
|
31
|
+
if (!hasExceptionDomains) {
|
|
32
|
+
findings.push((0, base_js_1.makeFinding)(this, {
|
|
33
|
+
title: 'Insecure ATS Configuration - Arbitrary Loads Enabled',
|
|
34
|
+
description: `Your app has NSAllowsArbitraryLoads set to true without specifying NSExceptionDomains. ` +
|
|
35
|
+
`This disables App Transport Security for ALL network connections, which is a significant ` +
|
|
36
|
+
`security risk. Apple will require justification during App Store review and may reject ` +
|
|
37
|
+
`your app if there's no valid reason.`,
|
|
38
|
+
location: 'Info.plist (NSAppTransportSecurity)',
|
|
39
|
+
fixGuidance: `Instead of disabling ATS entirely, configure specific exceptions for domains that ` +
|
|
40
|
+
`require HTTP:
|
|
41
|
+
|
|
42
|
+
OPTION 1: Remove NSAllowsArbitraryLoads entirely (recommended)
|
|
43
|
+
Apple strongly recommends using HTTPS for all connections.
|
|
44
|
+
|
|
45
|
+
OPTION 2: Use targeted exceptions for specific domains
|
|
46
|
+
<key>NSAppTransportSecurity</key>
|
|
47
|
+
<dict>
|
|
48
|
+
<key>NSExceptionDomains</key>
|
|
49
|
+
<dict>
|
|
50
|
+
<key>legacy-api.example.com</key>
|
|
51
|
+
<dict>
|
|
52
|
+
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
|
53
|
+
<true/>
|
|
54
|
+
<key>NSExceptionMinimumTLSVersion</key>
|
|
55
|
+
<string>TLSv1.2</string>
|
|
56
|
+
</dict>
|
|
57
|
+
</dict>
|
|
58
|
+
</dict>
|
|
59
|
+
|
|
60
|
+
If you must use NSAllowsArbitraryLoads, you'll need to provide App Store Connect with a justification. ` +
|
|
61
|
+
`Valid reasons include: connecting to servers you don't control, media streaming requirements, ` +
|
|
62
|
+
`or supporting legacy enterprise systems.`,
|
|
63
|
+
documentationURL: 'https://developer.apple.com/documentation/security/preventing_insecure_network_connections',
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Has exception domains, but also has global disable - unusual config
|
|
68
|
+
findings.push((0, base_js_1.makeCustomFinding)(this, index_js_1.Severity.Medium, index_js_1.Confidence.Medium, {
|
|
69
|
+
title: 'Redundant ATS Configuration',
|
|
70
|
+
description: `Your app has both NSAllowsArbitraryLoads = true AND specific NSExceptionDomains. ` +
|
|
71
|
+
`This is an unusual configuration. If you're using exception domains, you likely don't ` +
|
|
72
|
+
`need NSAllowsArbitraryLoads at all.`,
|
|
73
|
+
location: 'Info.plist (NSAppTransportSecurity)',
|
|
74
|
+
fixGuidance: `Consider removing NSAllowsArbitraryLoads and keeping only NSExceptionDomains for ` +
|
|
75
|
+
`the specific domains that need HTTP access. This provides better security by limiting ` +
|
|
76
|
+
`insecure connections to only the domains you've explicitly allowed.
|
|
77
|
+
|
|
78
|
+
Current configuration appears redundant:
|
|
79
|
+
- NSAllowsArbitraryLoads = true (allows everything)
|
|
80
|
+
- NSExceptionDomains configured (allows specific domains)
|
|
81
|
+
|
|
82
|
+
The exception domains are ignored when NSAllowsArbitraryLoads is true.`,
|
|
83
|
+
documentationURL: 'https://developer.apple.com/documentation/security/preventing_insecure_network_connections',
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Case 2: NSAllowsArbitraryLoadsInWebContent = true
|
|
88
|
+
if (allowsArbitraryLoadsWebView === true) {
|
|
89
|
+
findings.push((0, base_js_1.makeCustomFinding)(this, index_js_1.Severity.Medium, index_js_1.Confidence.High, {
|
|
90
|
+
title: 'ATS Disabled for Web Content',
|
|
91
|
+
description: `NSAllowsArbitraryLoadsInWebContent is set to true, allowing web views to load ` +
|
|
92
|
+
`insecure HTTP content. While this is less severe than NSAllowsArbitraryLoads, it still ` +
|
|
93
|
+
`represents a security consideration.`,
|
|
94
|
+
location: 'Info.plist (NSAppTransportSecurity)',
|
|
95
|
+
fixGuidance: `If your app needs to display arbitrary web content (like a browser), this setting ` +
|
|
96
|
+
`may be justified. However, if your web views only load content from known sources, consider:
|
|
97
|
+
|
|
98
|
+
1. Using NSExceptionDomains for specific domains instead
|
|
99
|
+
2. Ensuring your web content servers support HTTPS
|
|
100
|
+
|
|
101
|
+
<key>NSAppTransportSecurity</key>
|
|
102
|
+
<dict>
|
|
103
|
+
<key>NSExceptionDomains</key>
|
|
104
|
+
<dict>
|
|
105
|
+
<key>trusted-content.example.com</key>
|
|
106
|
+
<dict>
|
|
107
|
+
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
|
108
|
+
<true/>
|
|
109
|
+
</dict>
|
|
110
|
+
</dict>
|
|
111
|
+
</dict>`,
|
|
112
|
+
documentationURL: 'https://developer.apple.com/documentation/security/preventing_insecure_network_connections',
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
// Case 3: Check for overly permissive exception domains
|
|
116
|
+
if (exceptionDomains && typeof exceptionDomains === 'object') {
|
|
117
|
+
for (const [domain, config] of Object.entries(exceptionDomains)) {
|
|
118
|
+
const domainConfig = config;
|
|
119
|
+
// Check for wildcard or overly broad domains
|
|
120
|
+
if (domain.startsWith('*') || domain === 'localhost') {
|
|
121
|
+
continue; // Wildcard and localhost are sometimes valid
|
|
122
|
+
}
|
|
123
|
+
// Check if exception requires minimum TLS version
|
|
124
|
+
const allowsInsecure = domainConfig['NSExceptionAllowsInsecureHTTPLoads'];
|
|
125
|
+
// NSExceptionRequiresForwardSecrecy available in domainConfig if needed
|
|
126
|
+
const minTLS = domainConfig['NSExceptionMinimumTLSVersion'];
|
|
127
|
+
if (allowsInsecure === true && !minTLS) {
|
|
128
|
+
findings.push((0, base_js_1.makeCustomFinding)(this, index_js_1.Severity.Low, index_js_1.Confidence.Medium, {
|
|
129
|
+
title: `Insecure HTTP Allowed for ${domain}`,
|
|
130
|
+
description: `Domain "${domain}" allows insecure HTTP connections without specifying ` +
|
|
131
|
+
`a minimum TLS version. Consider if this domain can support HTTPS.`,
|
|
132
|
+
location: 'Info.plist (NSExceptionDomains)',
|
|
133
|
+
fixGuidance: `If the server supports TLS, add NSExceptionMinimumTLSVersion:
|
|
134
|
+
|
|
135
|
+
<key>${domain}</key>
|
|
136
|
+
<dict>
|
|
137
|
+
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
|
138
|
+
<true/>
|
|
139
|
+
<key>NSExceptionMinimumTLSVersion</key>
|
|
140
|
+
<string>TLSv1.2</string>
|
|
141
|
+
</dict>
|
|
142
|
+
|
|
143
|
+
Better yet, work with the server operator to enable HTTPS and remove this exception entirely.`,
|
|
144
|
+
documentationURL: 'https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity/nsexceptiondomains',
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return findings;
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
//# sourceMappingURL=ats-exception-without-justification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ats-exception-without-justification.js","sourceRoot":"","sources":["../../../src/rules/config/ats-exception-without-justification.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rules/config/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,oCAAoC,EAAE,MAAM,0CAA0C,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ATSExceptionWithoutJustificationRule = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Config rules exports
|
|
6
|
+
*/
|
|
7
|
+
var ats_exception_without_justification_js_1 = require("./ats-exception-without-justification.js");
|
|
8
|
+
Object.defineProperty(exports, "ATSExceptionWithoutJustificationRule", { enumerable: true, get: function () { return ats_exception_without_justification_js_1.ATSExceptionWithoutJustificationRule; } });
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/config/index.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,mGAAgG;AAAvF,8JAAA,oCAAoC,OAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rules module - exports all rules and registry
|
|
3
|
+
*/
|
|
4
|
+
import type { Rule } from '../types/index.js';
|
|
5
|
+
export * from './privacy/index.js';
|
|
6
|
+
export * from './auth/index.js';
|
|
7
|
+
export * from './metadata/index.js';
|
|
8
|
+
export * from './config/index.js';
|
|
9
|
+
export * from './base.js';
|
|
10
|
+
/**
|
|
11
|
+
* All available rules
|
|
12
|
+
*/
|
|
13
|
+
export declare const allRules: Rule[];
|
|
14
|
+
/**
|
|
15
|
+
* Rule registry - maps rule IDs to rule instances
|
|
16
|
+
*/
|
|
17
|
+
export declare const ruleRegistry: Map<string, Rule>;
|
|
18
|
+
/**
|
|
19
|
+
* Get a rule by ID
|
|
20
|
+
*/
|
|
21
|
+
export declare function getRule(id: string): Rule | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Result of rule lookup with validation info
|
|
24
|
+
*/
|
|
25
|
+
export interface RuleLookupResult {
|
|
26
|
+
rules: Rule[];
|
|
27
|
+
unknownIds: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get rules by IDs with validation (returns all if no IDs specified)
|
|
31
|
+
* Returns both found rules and list of unknown IDs for validation
|
|
32
|
+
*/
|
|
33
|
+
export declare function getRulesWithValidation(ids?: string[]): RuleLookupResult;
|
|
34
|
+
/**
|
|
35
|
+
* Get rules by IDs (returns all if no IDs specified)
|
|
36
|
+
* @deprecated Use getRulesWithValidation for proper error handling
|
|
37
|
+
*/
|
|
38
|
+
export declare function getRules(ids?: string[]): Rule[];
|
|
39
|
+
/**
|
|
40
|
+
* Get rules excluding specified IDs
|
|
41
|
+
*/
|
|
42
|
+
export declare function getRulesExcluding(excludeIds: string[]): Rule[];
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAG9C,cAAc,oBAAoB,CAAC;AAGnC,cAAc,iBAAiB,CAAC;AAGhC,cAAc,qBAAqB,CAAC;AAGpC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,WAAW,CAAC;AAc1B;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,IAAI,EAW1B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAE1C,CAAC;AAEF;;GAEG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAEpD;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAkBvE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAE/C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAG9D"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.ruleRegistry = exports.allRules = void 0;
|
|
18
|
+
exports.getRule = getRule;
|
|
19
|
+
exports.getRulesWithValidation = getRulesWithValidation;
|
|
20
|
+
exports.getRules = getRules;
|
|
21
|
+
exports.getRulesExcluding = getRulesExcluding;
|
|
22
|
+
// Privacy rules
|
|
23
|
+
__exportStar(require("./privacy/index.js"), exports);
|
|
24
|
+
// Auth rules
|
|
25
|
+
__exportStar(require("./auth/index.js"), exports);
|
|
26
|
+
// Metadata rules
|
|
27
|
+
__exportStar(require("./metadata/index.js"), exports);
|
|
28
|
+
// Config rules
|
|
29
|
+
__exportStar(require("./config/index.js"), exports);
|
|
30
|
+
// Base utilities
|
|
31
|
+
__exportStar(require("./base.js"), exports);
|
|
32
|
+
// Import all rules for registry
|
|
33
|
+
const missing_camera_purpose_js_1 = require("./privacy/missing-camera-purpose.js");
|
|
34
|
+
const missing_location_purpose_js_1 = require("./privacy/missing-location-purpose.js");
|
|
35
|
+
const location_always_unjustified_js_1 = require("./privacy/location-always-unjustified.js");
|
|
36
|
+
const att_tracking_mismatch_js_1 = require("./privacy/att-tracking-mismatch.js");
|
|
37
|
+
const missing_photo_library_purpose_js_1 = require("./privacy/missing-photo-library-purpose.js");
|
|
38
|
+
const missing_microphone_purpose_js_1 = require("./privacy/missing-microphone-purpose.js");
|
|
39
|
+
const missing_contacts_purpose_js_1 = require("./privacy/missing-contacts-purpose.js");
|
|
40
|
+
const third_party_login_no_siwa_js_1 = require("./auth/third-party-login-no-siwa.js");
|
|
41
|
+
const missing_privacy_manifest_js_1 = require("./metadata/missing-privacy-manifest.js");
|
|
42
|
+
const ats_exception_without_justification_js_1 = require("./config/ats-exception-without-justification.js");
|
|
43
|
+
/**
|
|
44
|
+
* All available rules
|
|
45
|
+
*/
|
|
46
|
+
exports.allRules = [
|
|
47
|
+
missing_camera_purpose_js_1.MissingCameraPurposeRule,
|
|
48
|
+
missing_location_purpose_js_1.MissingLocationPurposeRule,
|
|
49
|
+
location_always_unjustified_js_1.LocationAlwaysUnjustifiedRule,
|
|
50
|
+
att_tracking_mismatch_js_1.ATTTrackingMismatchRule,
|
|
51
|
+
missing_photo_library_purpose_js_1.MissingPhotoLibraryPurposeRule,
|
|
52
|
+
missing_microphone_purpose_js_1.MissingMicrophonePurposeRule,
|
|
53
|
+
missing_contacts_purpose_js_1.MissingContactsPurposeRule,
|
|
54
|
+
third_party_login_no_siwa_js_1.ThirdPartyLoginNoSIWARule,
|
|
55
|
+
missing_privacy_manifest_js_1.MissingPrivacyManifestRule,
|
|
56
|
+
ats_exception_without_justification_js_1.ATSExceptionWithoutJustificationRule,
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* Rule registry - maps rule IDs to rule instances
|
|
60
|
+
*/
|
|
61
|
+
exports.ruleRegistry = new Map(exports.allRules.map(rule => [rule.id, rule]));
|
|
62
|
+
/**
|
|
63
|
+
* Get a rule by ID
|
|
64
|
+
*/
|
|
65
|
+
function getRule(id) {
|
|
66
|
+
return exports.ruleRegistry.get(id);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get rules by IDs with validation (returns all if no IDs specified)
|
|
70
|
+
* Returns both found rules and list of unknown IDs for validation
|
|
71
|
+
*/
|
|
72
|
+
function getRulesWithValidation(ids) {
|
|
73
|
+
if (!ids || ids.length === 0) {
|
|
74
|
+
return { rules: exports.allRules, unknownIds: [] };
|
|
75
|
+
}
|
|
76
|
+
const rules = [];
|
|
77
|
+
const unknownIds = [];
|
|
78
|
+
for (const id of ids) {
|
|
79
|
+
const rule = exports.ruleRegistry.get(id);
|
|
80
|
+
if (rule) {
|
|
81
|
+
rules.push(rule);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
unknownIds.push(id);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { rules, unknownIds };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get rules by IDs (returns all if no IDs specified)
|
|
91
|
+
* @deprecated Use getRulesWithValidation for proper error handling
|
|
92
|
+
*/
|
|
93
|
+
function getRules(ids) {
|
|
94
|
+
return getRulesWithValidation(ids).rules;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get rules excluding specified IDs
|
|
98
|
+
*/
|
|
99
|
+
function getRulesExcluding(excludeIds) {
|
|
100
|
+
const excludeSet = new Set(excludeIds);
|
|
101
|
+
return exports.allRules.filter(rule => !excludeSet.has(rule.id));
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AA0DA,0BAEC;AAcD,wDAkBC;AAMD,4BAEC;AAKD,8CAGC;AAvGD,gBAAgB;AAChB,qDAAmC;AAEnC,aAAa;AACb,kDAAgC;AAEhC,iBAAiB;AACjB,sDAAoC;AAEpC,eAAe;AACf,oDAAkC;AAElC,iBAAiB;AACjB,4CAA0B;AAE1B,gCAAgC;AAChC,mFAA+E;AAC/E,uFAAmF;AACnF,6FAAyF;AACzF,iFAA6E;AAC7E,iGAA4F;AAC5F,2FAAuF;AACvF,uFAAmF;AACnF,sFAAgF;AAChF,wFAAoF;AACpF,4GAAuG;AAEvG;;GAEG;AACU,QAAA,QAAQ,GAAW;IAC9B,oDAAwB;IACxB,wDAA0B;IAC1B,8DAA6B;IAC7B,kDAAuB;IACvB,iEAA8B;IAC9B,4DAA4B;IAC5B,wDAA0B;IAC1B,wDAAyB;IACzB,wDAA0B;IAC1B,6EAAoC;CACrC,CAAC;AAEF;;GAEG;AACU,QAAA,YAAY,GAAsB,IAAI,GAAG,CACpD,gBAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CACtC,CAAC;AAEF;;GAEG;AACH,SAAgB,OAAO,CAAC,EAAU;IAChC,OAAO,oBAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAC9B,CAAC;AAUD;;;GAGG;AACH,SAAgB,sBAAsB,CAAC,GAAc;IACnD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,gBAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,KAAK,GAAW,EAAE,CAAC;IACzB,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,oBAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,IAAI,EAAE,CAAC;YACT,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,SAAgB,QAAQ,CAAC,GAAc;IACrC,OAAO,sBAAsB,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;AAC3C,CAAC;AAED;;GAEG;AACH,SAAgB,iBAAiB,CAAC,UAAoB;IACpD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IACvC,OAAO,gBAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rules/metadata/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MissingPrivacyManifestRule = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Metadata rules exports
|
|
6
|
+
*/
|
|
7
|
+
var missing_privacy_manifest_js_1 = require("./missing-privacy-manifest.js");
|
|
8
|
+
Object.defineProperty(exports, "MissingPrivacyManifestRule", { enumerable: true, get: function () { return missing_privacy_manifest_js_1.MissingPrivacyManifestRule; } });
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/metadata/index.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,6EAA2E;AAAlE,yIAAA,0BAA0B,OAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: Missing Privacy Manifest
|
|
3
|
+
*
|
|
4
|
+
* iOS 17+ requires PrivacyInfo.xcprivacy for apps that use certain APIs
|
|
5
|
+
* known as "Required Reason APIs". This rule checks for common indicators
|
|
6
|
+
* that an app may need a privacy manifest.
|
|
7
|
+
*
|
|
8
|
+
* App Store Review Guideline: 5.1.1
|
|
9
|
+
*/
|
|
10
|
+
import type { Rule } from '../../types/index.js';
|
|
11
|
+
export declare const MissingPrivacyManifestRule: Rule;
|
|
12
|
+
//# sourceMappingURL=missing-privacy-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-privacy-manifest.d.ts","sourceRoot":"","sources":["../../../src/rules/metadata/missing-privacy-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,sBAAsB,CAAC;AA4DvE,eAAO,MAAM,0BAA0B,EAAE,IAuGxC,CAAC"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MissingPrivacyManifestRule = void 0;
|
|
37
|
+
const index_js_1 = require("../../types/index.js");
|
|
38
|
+
// Note: using Privacy category as privacy manifest relates to data privacy compliance
|
|
39
|
+
const base_js_1 = require("../base.js");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
/**
|
|
43
|
+
* SDK dependencies commonly known to require privacy manifests
|
|
44
|
+
*/
|
|
45
|
+
const SDK_REQUIRING_PRIVACY_MANIFEST = [
|
|
46
|
+
{ pattern: 'Firebase', name: 'Firebase', note: 'Firebase SDKs require privacy manifests as of May 2024' },
|
|
47
|
+
{ pattern: 'Facebook', name: 'Facebook SDK', note: 'Facebook SDK requires privacy manifests' },
|
|
48
|
+
{ pattern: 'FBSDK', name: 'Facebook SDK', note: 'Facebook SDK requires privacy manifests' },
|
|
49
|
+
{ pattern: 'GoogleMobileAds', name: 'Google Mobile Ads', note: 'Google Ad SDK requires privacy manifests' },
|
|
50
|
+
{ pattern: 'Google-Mobile-Ads', name: 'Google Mobile Ads', note: 'Google Ad SDK requires privacy manifests' },
|
|
51
|
+
{ pattern: 'Crashlytics', name: 'Crashlytics', note: 'Crashlytics requires privacy manifests' },
|
|
52
|
+
{ pattern: 'Amplitude', name: 'Amplitude', note: 'Amplitude SDK requires privacy manifests' },
|
|
53
|
+
{ pattern: 'Mixpanel', name: 'Mixpanel', note: 'Mixpanel SDK requires privacy manifests' },
|
|
54
|
+
{ pattern: 'Adjust', name: 'Adjust', note: 'Adjust SDK requires privacy manifests' },
|
|
55
|
+
{ pattern: 'AppsFlyer', name: 'AppsFlyer', note: 'AppsFlyer SDK requires privacy manifests' },
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Check if a privacy manifest exists in the project
|
|
59
|
+
*/
|
|
60
|
+
function findPrivacyManifest(projectPath) {
|
|
61
|
+
const searchPaths = [
|
|
62
|
+
path.join(projectPath, 'PrivacyInfo.xcprivacy'),
|
|
63
|
+
path.join(projectPath, 'Resources', 'PrivacyInfo.xcprivacy'),
|
|
64
|
+
];
|
|
65
|
+
// Also search in subdirectories (common app structure)
|
|
66
|
+
try {
|
|
67
|
+
const entries = fs.readdirSync(projectPath);
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
const fullPath = path.join(projectPath, entry);
|
|
70
|
+
try {
|
|
71
|
+
const stat = fs.statSync(fullPath);
|
|
72
|
+
if (stat.isDirectory()) {
|
|
73
|
+
searchPaths.push(path.join(fullPath, 'PrivacyInfo.xcprivacy'));
|
|
74
|
+
searchPaths.push(path.join(fullPath, 'Resources', 'PrivacyInfo.xcprivacy'));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Ignore stat errors
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ignore readdir errors
|
|
84
|
+
}
|
|
85
|
+
for (const searchPath of searchPaths) {
|
|
86
|
+
if (fs.existsSync(searchPath)) {
|
|
87
|
+
return searchPath;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
exports.MissingPrivacyManifestRule = {
|
|
93
|
+
id: 'metadata-001-missing-privacy-manifest',
|
|
94
|
+
name: 'Missing Privacy Manifest',
|
|
95
|
+
description: 'Checks for presence of PrivacyInfo.xcprivacy when using Required Reason APIs or common SDKs',
|
|
96
|
+
category: index_js_1.RuleCategory.Metadata,
|
|
97
|
+
severity: index_js_1.Severity.High,
|
|
98
|
+
confidence: index_js_1.Confidence.Medium,
|
|
99
|
+
guidelineReference: '5.1.1',
|
|
100
|
+
async evaluate(context) {
|
|
101
|
+
// Check if privacy manifest exists
|
|
102
|
+
const privacyManifestPath = findPrivacyManifest(context.projectPath);
|
|
103
|
+
if (privacyManifestPath) {
|
|
104
|
+
// Privacy manifest exists, no issue
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const findings = [];
|
|
108
|
+
// Check for SDKs that commonly require privacy manifests
|
|
109
|
+
const detectedSDKs = [];
|
|
110
|
+
for (const sdk of SDK_REQUIRING_PRIVACY_MANIFEST) {
|
|
111
|
+
const hasSDK = context.dependencies.some(dep => dep.name.toLowerCase().includes(sdk.pattern.toLowerCase()));
|
|
112
|
+
if (hasSDK && !detectedSDKs.includes(sdk.name)) {
|
|
113
|
+
detectedSDKs.push(sdk.name);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (detectedSDKs.length > 0) {
|
|
117
|
+
findings.push((0, base_js_1.makeFinding)(this, {
|
|
118
|
+
title: 'Missing Privacy Manifest for Third-Party SDKs',
|
|
119
|
+
description: `Your app uses SDKs that require privacy manifests: ${detectedSDKs.join(', ')}. ` +
|
|
120
|
+
`Starting Spring 2024, apps submitted to the App Store must include a privacy manifest ` +
|
|
121
|
+
`(PrivacyInfo.xcprivacy) that declares the data collection and Required Reason API usage ` +
|
|
122
|
+
`from these SDKs.`,
|
|
123
|
+
location: context.projectPath,
|
|
124
|
+
fixGuidance: `Create a PrivacyInfo.xcprivacy file in your project root and declare the Required Reason APIs ` +
|
|
125
|
+
`used by your app and its dependencies.
|
|
126
|
+
|
|
127
|
+
1. In Xcode, File > New > File > iOS > Resource > App Privacy
|
|
128
|
+
2. Add entries for each Required Reason API category your app uses
|
|
129
|
+
3. Ensure third-party SDK privacy manifests are bundled with your app
|
|
130
|
+
|
|
131
|
+
Most SDK vendors now provide privacy manifests with their SDKs. Update to the latest versions.
|
|
132
|
+
|
|
133
|
+
Example PrivacyInfo.xcprivacy structure:
|
|
134
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
135
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
136
|
+
<plist version="1.0">
|
|
137
|
+
<dict>
|
|
138
|
+
<key>NSPrivacyTracking</key>
|
|
139
|
+
<false/>
|
|
140
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
141
|
+
<array/>
|
|
142
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
143
|
+
<array>
|
|
144
|
+
<dict>
|
|
145
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
146
|
+
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
147
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
148
|
+
<array>
|
|
149
|
+
<string>CA92.1</string>
|
|
150
|
+
</array>
|
|
151
|
+
</dict>
|
|
152
|
+
</array>
|
|
153
|
+
</dict>
|
|
154
|
+
</plist>`,
|
|
155
|
+
documentationURL: 'https://developer.apple.com/documentation/bundleresources/privacy_manifest_files',
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
// If no SDK indicators but this is a newer project, provide informational warning
|
|
159
|
+
if (findings.length === 0) {
|
|
160
|
+
// Check for frameworks that might use Required Reason APIs
|
|
161
|
+
const commonFrameworks = ['Foundation', 'UIKit'];
|
|
162
|
+
const hasCommonFrameworks = commonFrameworks.some(f => context.hasFramework(f));
|
|
163
|
+
if (hasCommonFrameworks && context.dependencies.length > 0) {
|
|
164
|
+
findings.push((0, base_js_1.makeCustomFinding)(this, index_js_1.Severity.Info, index_js_1.Confidence.Low, {
|
|
165
|
+
title: 'Consider Adding Privacy Manifest',
|
|
166
|
+
description: `Your app uses third-party dependencies which may use Required Reason APIs. ` +
|
|
167
|
+
`Consider adding a PrivacyInfo.xcprivacy to avoid potential App Store submission issues.`,
|
|
168
|
+
location: context.projectPath,
|
|
169
|
+
fixGuidance: `Review Apple's Required Reason API documentation and check if your app or its ` +
|
|
170
|
+
`dependencies use any of these APIs:
|
|
171
|
+
|
|
172
|
+
- File timestamp APIs (NSFileCreationDate, NSFileModificationDate)
|
|
173
|
+
- System boot time APIs (systemUptime)
|
|
174
|
+
- Disk space APIs (volumeAvailableCapacity)
|
|
175
|
+
- User defaults (UserDefaults.standard)
|
|
176
|
+
- Active keyboard APIs (activeInputModes)
|
|
177
|
+
|
|
178
|
+
If any are used, create a PrivacyInfo.xcprivacy and declare the appropriate reasons.`,
|
|
179
|
+
documentationURL: 'https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api',
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return findings;
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=missing-privacy-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-privacy-manifest.js","sourceRoot":"","sources":["../../../src/rules/metadata/missing-privacy-manifest.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,mDAA0E;AAC1E,sFAAsF;AACtF,wCAA4D;AAC5D,uCAAyB;AACzB,2CAA6B;AAE7B;;GAEG;AACH,MAAM,8BAA8B,GAAG;IACrC,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,wDAAwD,EAAE;IACzG,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,yCAAyC,EAAE;IAC9F,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,yCAAyC,EAAE;IAC3F,EAAE,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,0CAA0C,EAAE;IAC3G,EAAE,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,0CAA0C,EAAE;IAC7G,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,wCAAwC,EAAE;IAC/F,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,0CAA0C,EAAE;IAC7F,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,yCAAyC,EAAE;IAC1F,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,uCAAuC,EAAE;IACpF,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,0CAA0C,EAAE;CAC9F,CAAC;AAEF;;GAEG;AACH,SAAS,mBAAmB,CAAC,WAAmB;IAC9C,MAAM,WAAW,GAAG;QAClB,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,uBAAuB,CAAC;QAC/C,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,uBAAuB,CAAC;KAC7D,CAAC;IAEF,uDAAuD;IACvD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAC5C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;YAC/C,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBACnC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;oBACvB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAC,CAAC;oBAC/D,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,uBAAuB,CAAC,CAAC,CAAC;gBAC9E,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,qBAAqB;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAEY,QAAA,0BAA0B,GAAS;IAC9C,EAAE,EAAE,uCAAuC;IAC3C,IAAI,EAAE,0BAA0B;IAChC,WAAW,EAAE,6FAA6F;IAC1G,QAAQ,EAAE,uBAAY,CAAC,QAAQ;IAC/B,QAAQ,EAAE,mBAAQ,CAAC,IAAI;IACvB,UAAU,EAAE,qBAAU,CAAC,MAAM;IAC7B,kBAAkB,EAAE,OAAO;IAE3B,KAAK,CAAC,QAAQ,CAAC,OAAoB;QACjC,mCAAmC;QACnC,MAAM,mBAAmB,GAAG,mBAAmB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAErE,IAAI,mBAAmB,EAAE,CAAC;YACxB,oCAAoC;YACpC,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,yDAAyD;QACzD,MAAM,YAAY,GAAa,EAAE,CAAC;QAClC,KAAK,MAAM,GAAG,IAAI,8BAA8B,EAAE,CAAC;YACjD,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAC7C,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAC3D,CAAC;YACF,IAAI,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/C,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC,IAAA,qBAAW,EAAC,IAAI,EAAE;gBAC9B,KAAK,EAAE,+CAA+C;gBACtD,WAAW,EAAE,sDAAsD,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;oBAC5F,wFAAwF;oBACxF,0FAA0F;oBAC1F,kBAAkB;gBACpB,QAAQ,EAAE,OAAO,CAAC,WAAW;gBAC7B,WAAW,EAAE,gGAAgG;oBAC3G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA6BD;gBACD,gBAAgB,EAAE,kFAAkF;aACrG,CAAC,CAAC,CAAC;QACN,CAAC;QAED,kFAAkF;QAClF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,2DAA2D;YAC3D,MAAM,gBAAgB,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;YAEhF,IAAI,mBAAmB,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3D,QAAQ,CAAC,IAAI,CAAC,IAAA,2BAAiB,EAAC,IAAI,EAAE,mBAAQ,CAAC,IAAI,EAAE,qBAAU,CAAC,GAAG,EAAE;oBACnE,KAAK,EAAE,kCAAkC;oBACzC,WAAW,EAAE,6EAA6E;wBACxF,yFAAyF;oBAC3F,QAAQ,EAAE,OAAO,CAAC,WAAW;oBAC7B,WAAW,EAAE,gFAAgF;wBAC3F;;;;;;;;qFAQyE;oBAC3E,gBAAgB,EAAE,wHAAwH;iBAC3I,CAAC,CAAC,CAAC;YACN,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: Tracking SDK Without App Tracking Transparency
|
|
3
|
+
*
|
|
4
|
+
* Detects when an app includes tracking/attribution SDKs but is missing
|
|
5
|
+
* NSUserTrackingUsageDescription in Info.plist and/or the AppTrackingTransparency
|
|
6
|
+
* framework.
|
|
7
|
+
*
|
|
8
|
+
* App Store Review Guideline: 5.1.2
|
|
9
|
+
*/
|
|
10
|
+
import type { Rule } from '../../types/index.js';
|
|
11
|
+
export declare const ATTTrackingMismatchRule: Rule;
|
|
12
|
+
//# sourceMappingURL=att-tracking-mismatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"att-tracking-mismatch.d.ts","sourceRoot":"","sources":["../../../src/rules/privacy/att-tracking-mismatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,sBAAsB,CAAC;AASvE,eAAO,MAAM,uBAAuB,EAAE,IA6GrC,CAAC"}
|