Skip to content

Instantly share code, notes, and snippets.

@mfaani
Created January 19, 2021 06:00
Show Gist options
  • Save mfaani/95fd65da4ea1737c7f0f83cb7b6a9aa8 to your computer and use it in GitHub Desktop.
Save mfaani/95fd65da4ea1737c7f0f83cb7b6a9aa8 to your computer and use it in GitHub Desktop.
original proposal written for privacy_details cocoapods contribution
  • Authors: @prohoney, @AliSoftware
  • Date: 1 Jan, 2021

Motivation

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.

Proposal

PodSpec changes

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"))

Handling Subspecs

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

Policy for requiring the attribute

  • 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. Otherwise pod 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.

Generated Report file

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 a targetName-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.

Podfile changes

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 it target_privacy_details. App owners can then add their privacy details. Then upon pod 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.
  • 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).

Backwards Compatibility

  • 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 named unknown_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"]
}

Ways to bypass this

  • Pod owners can push to the repo using trunk API even if they're failing locally with their pod 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.

Other notes

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

Open questions

  • 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:
    attribute :privacy_details,
        :container   => Array,
        :singularize => true,
        :inherited => true
    and then fix any error that I get through 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 👌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment