App Transport Security (ATS) scope on macOS

Hello!

The documentation on ATS refers to "apps and app extensions" as scope. https://developer.apple.com/documentation/security/preventing_insecure_network_connections

Does this limit its availability to apps that are running in the user context?

The question is: do we get ATS protection in case of a launchd job? Let's say there is a launchd job (running in uid 1 context). It initiates network requests using URLSession API. Does it get ATS by default? Is there a way to validate that?

Is it correct to assume that high level network API such as URLSession use ATS by default?

Thanks.

Best regards, Arthur

a small update. Made a quick test by trying to make a network request to a http address from the daemon side. Got the following error log message: "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection". So it looks like ATS is indeed operational when the daemon is making a network request using URLSession.

So the only confusing part was the description of circumstances under which ATS is expected to be enabled.

Does this limit its availability to apps that are running in the user context?

Yes.

The question is: do we get ATS protection in case of a launchd job?

You do not.

IMO this is a shame, in that a launchd daemon should be able to opt in to the enhanced security provided by ATS. If you agree, I encourage you to file an enhancement request along those lines

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Huh. That's curious because this is not what's happening according to our testing (my last comment above). This is what I am currently observing when a process that runs in the daemon context tries to perform a POST request to a HTTP address.

2022-03-03 11:51:55.384 E daemonnamed[1467:2af9] [subsystem:category] Error occurred while trying to send data: Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSUnderlyingError=0x7ffbadf0ddd0 Unknown macro: {Error Domain=kCFErrorDomainCFNetwork Code=-1022 "(null)"}, NSErrorFailingURLStringKey=http://concealed, NSErrorFailingURLKey=http://concealed, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.}

So if I read this message correctly, ATS is restricting it from performing a network request due to an insecure connection. If I restore the URL to its original form (HTTPS), then the request is successful.

is the comment about not having ATS protection in daemon context is about it being limited in some other way? So it's not the same as for user apps?

This definitely works as I described. Consider the test code below. If I build it and install it in the usual way:

% sudo cp Test701588 /Library/PrivilegedHelperTools
% sudo cp com.example.Test701588.plist /Library/LaunchDaemons
% sudo launchctl load /Library/LaunchDaemons/com.example.Test701588.plist
% sudo launchctl start com.example.Test701588

I see this in the system log:

type: default
time: 09:49:18.417639+0000
process: Test701588
subsystem: com.example.Test701588
category: url
message: will start load, url: http://example.com

type: default
time: 09:49:18.655806+0000
process: Test701588
subsystem: com.example.Test701588
category: url
message: did load, status: 200, bytes: 1256

As you can see, ATS did not block my http request.

This is on macOS 12.2.1 with Xcode 13.2.1.

As to what’s happening in your case, I’m not sure what’s going on. As a first step I recommend that you rule out this:

running in uid 1 context

It’s possible that your use of the daemon (1) UID is causing ATS to kick in. You can easily test this by repeating my test with a tweaked launchd property list.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


import Foundation
import os.log

func main() {
    let logger = Logger(subsystem: "com.example.Test701588", category: "url")
    let url = URL(string: "http://example.com")!
    logger.log("will start load, url: \(url, privacy: .public)")
    let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
    URLSession.shared.dataTask(with: request) { (data, response, error) in
        if let error = error as NSError? {
            logger.log("did not load, transport error: \(error.domain as String, privacy: .public) / \(error.code)")
            exit(EXIT_FAILURE)
        }
        let response = response as! HTTPURLResponse
        let data = data!
        NSLog("task finished with status %d, bytes %d", response.statusCode, data.count)
        logger.log("did load, status: \(response.statusCode), bytes: \(data.count)")
        exit(EXIT_FAILURE)
    }.resume()
    dispatchMain()
}

main()

% cat com.example.Test701588.plist 
…
<dict>
    <key>Label</key>
    <string>com.example.Test701588</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Library/PrivilegedHelperTools/Test701588</string>
    </array>
</dict>
</plist>

Indeed, this example allows insecure connections which contradicts with our earlier observations. I've made a quick test by setting UserName and GroupName to daemon in the plist. That did not affect the behavior. Insecure connections were still allowed.

I have created a feedback about opting in ATS by launchd jobs. Sharing the number for your reference: FB9942862

I will investigate why ATS gets involved in our case. I can think of a few interesting details from the top of my head:

  • our daemon is sandboxed. Could that change the behavior?
  • our daemon binary has an Info.plist embedded into it. Could that make ATS look at it from bundle point of view?
  • it performs POST requests. Could that a reason why ATS gets kicked in?

I have created a feedback [FB9942862] about opting in ATS by launchd jobs

Thank you!

  • it performs POST requests. Could that a reason why ATS gets kicked in?

Definitely not. ATS applies at the connection level, long before the system considers what type of request you’re issuing.

  • our daemon is sandboxed. Could that change the behavior?

Probably not. My experience is that ATS and App Sandbox are completely orthogonal

  • our daemon binary has an Info.plist embedded into it. Could that make ATS look at it from bundle point of view?

That’s the most likely cause. I don’t know exactly how ATS determines whether you’re on app but the presence of an Info.plist seems like a good candidate.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

App Transport Security (ATS) scope on macOS
 
 
Q