quasiquarantine

In Apple’s TN2206, “macOS Code Signing in Depth”, there’s a section about “Checking Gatekeeper Compliance”.

  • Package your program the way you ship it, such as in a disk image.

  • Download it from its website, or mail it to yourself, or send it to yourself using AirDrop or Message. This will quarantine the downloaded copy. This is necessary to trigger the Gatekeeper check as Gatekeeper only checks quarantined files the first time they’re opened.

  • Drag-install your app and launch it.

I figured jumping through a “download” or “send” step was overkill. Surely there’s a way to get the same effect programmatically, right? Turns out the answer is yes, and here’s the code to do it:

#!/usr/bin/swift
// License: CC0 1.0 Public Domain Dedication
// https://creativecommons.org/publicdomain/zero/1.0/

import Foundation

guard CommandLine.arguments.count >= 2 else {
  print("usage: quasiquarantine file ...")
  exit(EXIT_FAILURE)
}

let urls = CommandLine.arguments.lazy.dropFirst().map {
  URL(fileURLWithPath: $0)
}
for url in urls {
  do {
    if url.pathExtension == "app" {
      print("\(url.path): warning: manual quarantine only seems to work on archives, not app bundles")
    }

    let existingInfo = try url.resourceValues(forKeys: [.quarantinePropertiesKey])
    guard existingInfo.quarantineProperties == nil else {
      print("\(url.path): already quarantined by \(existingInfo.quarantineProperties?[kLSQuarantineAgentNameKey as String] ?? "<unknown>")")
      continue
    }

    var newInfo = URLResourceValues()
    newInfo.quarantineProperties = [
      kLSQuarantineAgentNameKey as String: "quasiquarantine",
      kLSQuarantineTypeKey as String: kLSQuarantineTypeOtherAttachment,
      kLSQuarantineAgentBundleIdentifierKey as String: "com.belkadan.Quasiquarantine"]
    var mutableURL = url
    try mutableURL.setResourceValues(newInfo)
    print("\(url.path): quarantined!")

  } catch let error {
    print("\(url.path): \(error.localizedDescription)")
  }
}

Save this as “quasiquarantine” and run it with swift quasiquarantine, or mark it as executable and run it directly.

After I made this, I tried it out on a locally-built .app I had lying around. No dice. I went searching around and found that the homebrew project had made nearly the same script last year, and so I figured I’d better try again. This time I read the directions more carefully:

  • Package your program the way you ship it, such as in a disk image.

Ah, right. When you unarchive an app or load it from a disk image, it inherits the quarantine info from the original downloaded file. I tried zipping up my locally-built app, “quasi-quarantining” the archive, and then unzipping it. And that worked!

So, here you go. quasiquarantine. Hopefully it helps someone.