First update to NWBrowser is always ready, irrespective of Local Networking privacy status

I'm trying to detect the state of Local Network privacy on macOS Sequoia via NWBrowser, as recommended in https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy

Regardless of the state of Local Network privacy - undetermined, allowed or denied, NWBrowser receives an update indicating that its in the ready state.

Scanning does not seem to trigger the Local Network privacy alert for me - I have to use the other recommended method to trigger the prompt. Enabling or disabling Local Network privacy does not seem to send any updates for NWBrowser.

https://developer.apple.com/forums/thread/666431 seems related, and implies that they did receive further updates to NWBrowser.

Filed as FB16077972

Scanning does not seem to trigger the Local Network privacy alert for me

What OS release are you testing this on? 15.0? 15.1?

There’s a known bug in 15.0 that complicates this story, so if you’re testing on something older please update.

Beyond that, are you testing this from an app, that is, something that the user might double click in the Finder? Or in some other context?

Share and Enjoy

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

So I'm currently testing on Sequoia 15.1.1 (24B91), but I have access to the other Sequoia versions as well.

Testing from an app, launching via double click.

I did find some oddities when launching in other ways (e.g. invoking the binary from the Terminal - that did not seem to trigger the privacy alert - only double-clicking, or the equivalent did.

Just adding kDNSServiceErr_PolicyDenied to the thread, as this is how I picked up other relevant posts

Hmmm, I tested this stuff while working on TN3179 and I got pretty reasonable results, so I’m surprised you’re having problems. Just to be sure, I tested it again today.

Using Xcode 16.2 on macOS 14.7.1, I created a small test project that runs a browser and logs the result. The core code is at the end of this post. The startStop() method is wired up to a Start/Stop button.

I copied this to a ‘clean’ macOS 15.1 VM, one that’s never seen this app before.

In the VM, I moved the app to Applications folder.

In Console, I started monitoring the com.example.apple-samplecode.Test770563 subsystem.

I ran the app and clicked the Start/Stop button, resulting in two things:

  • In the UI, I saw the Local Network alert.

  • In Console, I saw these log entries:

browser will start
browser did change state, new: ready

This is a bit weird, but it’s reasonable enough. It’s in the .ready state, but the system hasn’t delivered any results because the user hasn’t made a choice yet.

In the alert, I clicked Deny, resulting in:

browser did change state, new: waiting(-65570: PolicyDenied)

That’s definitely what I was expecting.

In the app I clicked the Start/Stop button again, resulting in:

browser will stop

Then I clicked it again, resulting in:

browser will start
browser did change state, new: ready
browser did change state, new: waiting(-65570: PolicyDenied)

This time it went to the .waiting(…) state promptly.

Please try this sequence in your environment and let me know what you get.

Share and Enjoy

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


let log = Logger(subsystem: "com.example.apple-samplecode.Test770563", category: "browser")

var browserQ: NWBrowser? = nil

func start() -> NWBrowser {
    log.log("browser will start")
    let browser = NWBrowser(for: .bonjour(type: "_ssh._tcp", domain: "local"), using: .tcp)
    browser.stateUpdateHandler = { newState in
        self.log.log("browser did change state, new: \("\(newState)", privacy: .public)")
    }
    browser.browseResultsChangedHandler = { newResults, change in
        self.log.log("browser did receive results, count: \(newResults.count)")
    }
    browser.start(queue: .main)
    return browser
}

func stop(browser: NWBrowser) {
    log.log("browser will stop")
    browser.stateUpdateHandler = nil
    browser.cancel()
}

func startStop() {
    if let browser = self.browserQ {
        self.browserQ = nil
        self.stop(browser: browser)
    } else {
        self.browserQ = self.start()
    }
}

So ... using your exact steps, I get exactly the behavior you describe.

But if I skip this step:

"In the VM, I moved the app to Applications folder"

then I just get the "Ready" state. I never get the Local Network Privacy prompt.

If I launch the app from Applications folder, and then deny or disable Local Network privacy permissions, I get the "correct" behaviour.

If I then copy the app to the Desktop, and delete it from applications, I stop seeing waiting(-65570: PolicyDenied) - I just get Ready as I described.

I haven't really tried to see if I'm actually getting blocked when the app is not in the Applications folder, but quite possibly not.

Regardless of whether I do actually get blocked or not, the different behaviour depending on whether the app is in Applications or not seems a bit off.

My build environment was Sonoma 14.7.2 (23H311), Xcode 16.1 (16B40), and my target VM was running Sequoia 15.1.1 (24B2091)

One more piece of tangentially relevant data - the technique described in https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy#Trigger-the-local-network-alert does trigger the alert for me, regardless of whether the app is installed in Applications or not.

Regardless of whether I do actually get blocked or not, the different behaviour depending on whether the app is in Applications or not seems a bit off.

Agreed. There was a known bug with this in 15.0 but it should’ve been fixed in 15.1. In fact, I’m pretty sure I verified the fix in one of the 15.1 betas. However, on testing it again today I’m was able to reproduce it.

*sigh*

Please update FB16077972 with this titbits.

Also, if you get a chance to test on a 15.2 VM, that’d be grand. I’d do it myself but I’m not going to have time today. 

[The TN3179 code] does trigger the alert for me, regardless of whether the app is installed in Applications or not.

Interesting. That worked for me in all circumstances.

Share and Enjoy

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

About to update FB16077972 - I get exactly the same result on a 15.2 (24C101) VM - if the test app is on the Desktop the local network alert does not trigger, and kDNSServiceErr_PolicyDenied is never received.

If the app is moved to the Applications folder, then it behaves as expected.

Played around with this some more while writing my update for FB16077972 - here is some more detail of exactly what I'm seeing on 15.2 (24C101)

When the application is inside the Applications folder:

  1. Starting a scan will trigger the Local Network Privacy alert.

  2. Once the Local Network Privacy alert has been responded to, if it is disabled, then the browser will receive a Ready update, followed by a Waiting - Denied update,

However, on the initial run, before the privacy alert has been shown (i.e. the undetermined state), the browser will receive a Ready update. I never saw the Denied update though, either when the privacy alert was shown, or when it was responded to - if access is granted the browser will start to receive results. If access is denied, no further state updates are received.

When the browser is stopped and started again, it will then receive the Waiting Denied update.

This also applies if the browser is stopped and started while the Local Network privacy alert is being shown.

Once the Local Network privacy alert has been responded to, if the application is inside the Applications folder, and browsing has started, and then the Local Network privacy state is changed, the browser does not receive any updates. For example, if Local Network privacy is disabled, it does not transition from Ready to Waiting, and vice versa.

When the application is initially on the Desktop, instead of inside Applications, the privacy alert will not be triggered, and the browser never receives the denied update, and can apparently browse.

Once the privacy alert has been triggered, if the application is moved to the Desktop, then it will no longer get the denied update, even if Local Network privacy is disabled, and will be able to browse.

I'm kind of suspicious that it's behaving so strangely for me - from other threads I got the impression that people had a bit of trouble with it, but not as much as this.

First update to NWBrowser is always ready, irrespective of Local Networking privacy status
 
 
Q