- I've read and understood the CONTRIBUTING guidelines and have done my best effort to follow.
- Authors: @prohoney, @AliSoftware
- Date: 1 Jan, 2021
Starting from December 8, 2020, Apple has required new apps and app updates to include App privacy details on the App Store. App owners have to go through every pod and all its dependency's readmes or if nothing is revealed then reach out to pod creators to just figure out what information the pod collects. The current process is not transparent nor easy.
We propose to add a new array attribute to the the podspec named privacy_details
Expected structure for this attribute (Inspiration: [1])
s.privacy_details = [
{
"category": "NAME",
"purposes": [
"PRODUCT_PERSONALIZATION",
"APP_FUNCTIONALITY"
],
"data_protections": [
"DATA_LINKED_TO_YOU"
]
},
{
"category": "PURCHASE_HISTORY",
"purposes": [
"APP_FUNCTIONALITY"
],
"data_protections": [
"DATA_LINKED_TO_YOU",
"DATA_USED_TO_TRACK_YOU"
]
}
]
💡 Tip: Given that PodSpecs are ruby DSLs, pod owners can easily choose to provide the content of that attribute by reading it from a file (especially given that it might be more verbose than other typical PodSpec attributes).
We might want to mention that tip in the attribute documentation (and blog post that will accompany this new feature) especially since not everybody realizes that PodSpecs are interpreted ruby and can contain ruby code like this to make the spec more readable in some cases.
s.privacy_details = JSON.parse(File.read("privacy.json"))
Note that the new privacy_details
attribute will be allowed both at the root level of the podspec as well as for the definition of each subspec, if any. In such cases, the resolved privacy details for each subspec will be the result of merging the value of attribute defined at the root level (if one is defined there) with the value defined at the subspecies level.
Pod::Spec.new do |s|
s.name = 'CoolKit'
s.privacy_details = JSON.parse(File.read("common-privacy-details.json")) # includes NAME, EMAIL
s.subspec 'SoundKit' do |cs|
cs.dependency 'Alamofire'
cs.privacy_details = JSON.parse(File.read("soundkit-privacy-details.json")) # includes AUDIO
end
s.subspec 'Model' do |ms|
ms.privacy_details = JSON.parse(File.read("model-privacy-details.json")) # includes SENSITIVE_INFO
end
end
Then the privacy detail you get upon installation of a pod depends on which subspecies gets included:
- The whole pod, e.g.
pod 'CoolKit'
(by default it would install all pods):NAME
,EMAIL
,AUDIO
,SENSITIVE_INFO
. - Only
pod 'CoolKit/SoundKit'
:NAME
,EMAIL
,AUDIO
. - Only
pod 'CoolKit/Model'
:NAME
,EMAIL
,SENSITIVE_INFO
.
If your intention is that if only SoundKit
is installed then only AUDIO
to be reported as your privacy detail then you have to restructure your PodSpec accordingly, to remove s.privacy_details
from the spec's root and instead only add NAME
and EMAIL
to the on in the Model
's subspecies
Example of a Podspec when not all specs share common privacy details
Pod::Spec.new do |s|
s.name = 'CoolKit'
s.subspec 'SoundKit' do |cs|
cs.dependency 'Alamofire'
cs.privacy_details = JSON.parse(File.read("soundkit-privacy-details.json")) # includes AUDIO
end
s.subspec 'Model' do |ms|
ms.privacy_details = JSON.parse(File.read("model-privacy-details.json")) # includes NAME, EMAIL, SENSITIVE_INFO
end
end
- New pods or any update should include the new
privacy_details
attribute in each of their subspecs. Mention which of the 32 data types (along with its purpose and data protection) they are collecting. Otherwisepod lib lint
would fail. - Make a recommendation that if there is a change in the
privacy_details
then patches are not welcomed. At least a minor version update should happen.
Upon pod install
or pod update
, a json(s) is generated. The json could be generated in either of the following ways:
- Per target: For each target included in the
Podfile
atargetName-privacyDetails.json
is generated. - Single json. Top level key:
targetName
where the value is the privacy details.
Example of a single JSON report file
{
"target1": {
"privacy_details": [
{
"category": "NAME",
"purposes": [
"PRODUCT_PERSONALIZATION",
"APP_FUNCTIONALITY"
],
"data_protections": [
"DATA_LINKED_TO_YOU"
],
"collectors": ["pod1", "pod2"]
},
{
"category": "PURCHASE_HISTORY",
"purposes": [
"APP_FUNCTIONALITY"
],
"data_protections": [
"DATA_LINKED_TO_YOU",
"DATA_USED_TO_TRACK_YOU"
],
"collectors": ["pod1", "pod5"]
}
]
},
"target2": {
"privacy_details": [
{
"category": "NAME",
"purposes": [
"PRODUCT_PERSONALIZATION",
"APP_FUNCTIONALITY"
],
"data_protections": [
"DATA_LINKED_TO_YOU"
],
"collectors": ["pod1", "pod2", "pod8"]
}
]
}
}
Note: Every included permutation (of data types, purpose and data protection) should contain a key named "collectors". This provides app owners granularity to see what data each pod is collecting.
In regards to the app's own privacy details we can:
- Do nothing. Dev can figure out the rest in regards to how to add privacy details about what data their own app is collecting
- Add new attribute into
Podfile
. Name ittarget_privacy_details
. App owners can then add their privacy details. Then uponpod install/update
do a union with what’s generated from the pods.- The
target_privacy_details
can be set either a root option or a target configuration. The value provided for the target configuration will override the root option value if set.
- The
- Make a recommendation that if there is a change in the
privacy_details
then at least a minor version update should happen (not just a patch bump).
- Older versions published to CocoaPods would continue to work without their privacy details
- New pods or updates to existing pods would require a value for their privacy details
- The ultimate
PrivacyDetails.json
would mention all the values for updated pods and include a key namedunknown_privacy_details
for the (old) pods that don't have it set.
Example of a privacy report when some pods don't provide privacy details
{
"target1": {
"privacy_details": [
{
"category": "NAME",
"purposes": [
"PRODUCT_PERSONALIZATION",
"APP_FUNCTIONALITY"
],
"data_protections": [
"DATA_LINKED_TO_YOU"
],
"collectors": ["pod1", "pod2"]
},
{
"category": "PURCHASE_HISTORY",
"purposes": [
"APP_FUNCTIONALITY"
],
"data_protections": [
"DATA_LINKED_TO_YOU",
"DATA_USED_TO_TRACK_YOU"
],
"collectors": ["pod1", "pod5"]
}
]
},
"target2": {
"privacy_details": [
{
"category": "NAME",
"purposes": [
"PRODUCT_PERSONALIZATION",
"APP_FUNCTIONALITY"
],
"data_protections": [
"DATA_LINKED_TO_YOU"
],
"collectors": ["pod1", "pod2", "pod8"]
}
]
},
"unknown_privacy_details": ["pod13", "pod42"]
}
- Pod owners can push to the repo using
trunk
API even if they're failing locally with theirpod lib lint
- Yet it's a step forward, towards transparency and privacy. It cautions app owners to think again when a pod doesn't include privacy details.
- At then end a pod owner may have malicious intent or simply forget to mention something they log or keep track of. This should lead app owners to create issues with a
privacyDetails
label on repos. It will take some time from the community to get use to this process.
We're looking for feedbacks on this and see if maybe the CocoaPods team have been working on this. If not we're keen to implement the PR for this feature and would like to know what files to look into to start development on this
- Which permutation between the proposed ones should we choose?
I think Target based json + update json using the
target_privacy_details
attribute is the best choice. - Maybe we could have two attributes, a plural named
targets_privacy_details
which affects all targets vs.target_privacy_details
which is for a single target. - Based on my mediocre Ruby knowledge, is the following a good starting point:
add a new
attribute
to Pod::Specification::DSL like this:and then fix any error that I get throughattribute :privacy_details, :container => Array, :singularize => true, :inherited => true
pod ipc spec <podspec>
[1]: On Dec 3rd, 2020, fastlane added a new action upload_app_data_usage_to_app_store
(PR link). It requires a json with a similar shape, which can include a single category or multiple or none. The format/structure of the JSON we're using in this issue/proposal is directly inspired by the one expected by fastlane, both for consistency and so that it provides us the additional benefit of interoperability, as users could then use the JSON generated by CocoaPods via this new feature to feed it as an input for their fastlane actions directly 👌