verifyos_cli/rules/
privacy_manifest.rs1use crate::parsers::plist_reader::InfoPlist;
2use crate::rules::core::{
3 AppStoreRule, ArtifactContext, RuleCategory, RuleError, RuleReport, RuleStatus, Severity,
4};
5
6pub struct PrivacyManifestCompletenessRule;
7
8impl AppStoreRule for PrivacyManifestCompletenessRule {
9 fn id(&self) -> &'static str {
10 "RULE_PRIVACY_MANIFEST_COMPLETENESS"
11 }
12
13 fn name(&self) -> &'static str {
14 "Privacy Manifest Completeness"
15 }
16
17 fn category(&self) -> RuleCategory {
18 RuleCategory::Privacy
19 }
20
21 fn severity(&self) -> Severity {
22 Severity::Warning
23 }
24
25 fn recommendation(&self) -> &'static str {
26 "Declare accessed API categories in PrivacyInfo.xcprivacy."
27 }
28
29 fn evaluate(&self, artifact: &ArtifactContext) -> Result<RuleReport, RuleError> {
30 let Some(manifest_path) = artifact.bundle_relative_file("PrivacyInfo.xcprivacy") else {
31 return Ok(RuleReport {
32 status: RuleStatus::Skip,
33 message: Some("PrivacyInfo.xcprivacy not found".to_string()),
34 evidence: None,
35 });
36 };
37
38 let manifest = InfoPlist::from_file(&manifest_path)
39 .map_err(|_| crate::rules::entitlements::EntitlementsError::ParseFailure)?;
40
41 let scan = match artifact.usage_scan() {
42 Ok(scan) => scan,
43 Err(err) => {
44 return Ok(RuleReport {
45 status: RuleStatus::Skip,
46 message: Some(format!("Usage scan skipped: {err}")),
47 evidence: None,
48 });
49 }
50 };
51
52 if scan.required_keys.is_empty() && !scan.requires_location_key {
53 return Ok(RuleReport {
54 status: RuleStatus::Pass,
55 message: Some("No usage APIs detected".to_string()),
56 evidence: None,
57 });
58 }
59
60 let has_accessed_api_types = manifest
61 .get_value("NSPrivacyAccessedAPITypes")
62 .and_then(|v| v.as_array())
63 .map(|arr| !arr.is_empty())
64 .unwrap_or(false);
65
66 if has_accessed_api_types {
67 return Ok(RuleReport {
68 status: RuleStatus::Pass,
69 message: None,
70 evidence: None,
71 });
72 }
73
74 Ok(RuleReport {
75 status: RuleStatus::Fail,
76 message: Some("Privacy manifest missing accessed API types".to_string()),
77 evidence: Some("NSPrivacyAccessedAPITypes is missing or empty".to_string()),
78 })
79 }
80}