Local Network permission prompt for daemon on macOS 15

Hi Team,

OS is prompting for local network permission for our application which runs as root level daemon.

As per the our analysis, it looks like it is prompting from our own library which is trying to get network info ' using /usr/sbin/system_profiler with "-xml -detailLevel basic SPNetworkDataType" and then trying to iterate to find DNS.ServerAddresses for each item. Then using [NSHost hostWithAddress:IPAddress];(When this library is not linked to the app then there is no prompt, so most likely this is the code that is resulting in the prompt).

Is this expected ? . Is there any other way that we can get DNS host name without being prompted for local network permission on mac OS 15

Answered by DTS Engineer in 812072022

We believe this is fixed in macOS 15.1. Please try it out there and let us know otherwise.

Oh, and we just published TN3179 Understanding local network privacy, full of detailed info about local network privacy.

Share and Enjoy

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

In general, a daemon should be exempt from local network privacy checks. Before I sent you off to file a bug, I want to confirm one thing. You wrote:

root level daemon.

How is this daemon started? As a launchd daemon? With a property list in /Library/LaunchDaemons? Or something else?

Also, please make sure you’re testing on the macOS 15.0 release. macOS 15 had some late changes in this space. I think all the changes were in the release candidate, but it’s best to test with the release version now that it’s available.

Share and Enjoy

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

It is with a property list in /Library/LaunchDaemons, We are seeing the alert on macOS 15.0 (24A335) also.

It is with a property list in /Library/LaunchDaemons and during installation post install script, it is launched using below command. (In actual instead of APP, company it is the name of the app and company , just modified to paste it here) sudo /bin/bash -c "(/bin/sleep 5; $log 'Launching the APP!!!’; /bin/launchctl load -F /Library/LaunchDaemons/com.company.APP.launchdaemon.plist | $log; $log 'Finished launching the APP’) &". I have removed the dylib with reference to NSHOST, now it prompts from the code that uses websockets library(which communicates over localhost/127.0.0.0) . Is this expected? We are seeing the alert on macOS 15.0 (24A335) also.

I suspect that you’re triggering a known bug in the interaction between local network privacy and DNS (r. 133953401). In theory, a launchd daemon running as root should always be allowed to use the local network. In practice, there’s a bug in the DNS infrastructure that means that it can be blocked )-:

I can’t see any workaround for this. All I can say is:

  • This is not fixed in the current macOS 15.1b4 beta seed.

  • As alway, you should test with macOS beta releases as we seed them.

Share and Enjoy

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

Are there any updates on when this will be fixed?

We believe this is fixed in macOS 15.1. Please try it out there and let us know otherwise.

Oh, and we just published TN3179 Understanding local network privacy, full of detailed info about local network privacy.

Share and Enjoy

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

Thanks @DTS Engineer I'm still seeing this issue on latest Mac OS 15.1 beta.

Our app is a dotnet 9 executable called "Agent" that's code signed and has all the networking permissions added in entitlements and it's plist. It has a UUID. It works fine when run from the terminal under my (admin) account but will not connect when run as a launchd service using the same account.

The server responds so it has network access but webrtc setup fails and STUN responses are blocked.

Again - works fine from the terminal.

In our app logs network device discovery code is being blocked:

"Discover: No route to host at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.CreateException(SocketError error, Boolean forAsyncThrow) at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.SendToAsync"

  • the docs say that the mdns permission isn't needed to macos

and in console i see

default 16:34:32.099971+0800 nehelper UUID cache miss for Agent default 16:34:32.103499+0800 neagent Failed to find Agent in LaunchServices default 16:34:32.104192+0800 nehelper Failed to find Agent using neagent default 16:34:32.104723+0800 nehelper 0 UUIDs for Agent are already in the cache default 16:34:32.106107+0800 neagent Failed to find Agent in LaunchServices default 16:34:32.106211+0800 nehelper Failed to find Agent using neagent default 16:34:32.106319+0800 nehelper Removing UUIDs for ( Agent ) default 16:34:32.108937+0800 nehelper Setting UUID cache generation to 165 default 16:34:32.109041+0800 nehelper Local network allowed by preference for Agent (Agent), but received prompt. Clearing cached UUIDs and restarting session.

This stuff should just work right?

[Hey hey, our posts cross on the ‘wire’!]

TN3179 should be your guide here. As I make clear in that technote, there’s an importance difference between daemons and agents. A daemon runs in the global login session, typically as root. An agent runs in one or more user login sessions. Daemons are not subject to LNP. Agents are.

So, is the program with this problem a daemon or an agent?

[it] will not connect when run as a launchd service using the same account.

Exactly how is that service set up?

Share and Enjoy

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

Thanks for the extra info. Just FYI, it’s better to a reply as a reply rather than in the comments. Quinn’s Top Ten DevForums Tips explains why.

It's just the application is confusingly called "Agent".

Thanks for clarifying. And yeah, that’s not uncommon (think SNMP agent) and it can be confusing, which is why I wanted to clarify.

Your template launchd property list has a YOUR_USERNAME. What’s that about? Generally daemons run as root.

Share and Enjoy

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

Our service runs under a user account because it also accesses local devices like cameras and microphones. YOUR_USERNAME gets merged in by the installer. I found that if i install our app to Library/Application Support and run it as root then the networking issue goes away (but no local device access which makes this pointless). If it's installed like it was before on previous macOS iterations - on say the desktop and running with the user account then we get local device access but networking is borked.

Also macOS won't prompt for permissions with root but will with the user account. I'm on latest macOS 15.2 beta.

Also this help system isn't working properly - replies aren't displaying and the UI keeps telling me i'm at a negative word count and responses "must include text in the body"

Our service runs under a user account

Which user account?

macOS supports multiple users. Multiple GUI users can be logged in simultaneously, via Fast User Switching. Multiple GUI users can be simultaneously active, via screen sharing. My experience is that any time a daemon tries to act as a user, things end badly. I talk about this in gory detail in Technote 2083 Daemons and Agents.

Our service runs under a user account because it also accesses local devices like cameras and microphones.

Yowsers! Honestly, I’m surprised you’ve got this far.

You really need to be running this code as an agent rather than a daemon. If you also need daemon functionality — privilege escalation, or a persistent network presence — split that out into a daemon and have the agents call on that functionality via IPC.

While you’re not building a screen sharing product, I encourage you to have a read through this thread: Session, Desktops and login screen. In it, I talk another developer through the Five Stages of Mac Screen Sharing Developer Grief™ (-:


Also this help system isn't working properly

I’m interested in hearing more about your problems, but I’d prefer to keep this thread focused on networking. Please start a new thread over in Developer Tools & Services > Developer Forums and we can chat about this over there.

Share and Enjoy

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

Thanks for responding. "Yowsers! Honestly, I’m surprised you’ve got this far."

Well it was working fine on every version of macOS up to this one. It's also working fine as a service on Windows and every version of Linux - with local device access and networking. It's literally -=only=- Sequoia that there are problems on.

I guess i'll just have to tell people to either use another OS, use Docker or just run it from the terminal.

I don't understand why you make it so difficult to do things that on other OSes is simple. If someone gives an app root access then why can't it access local devices? It doesn't make any sense. And if an app is running as a service under an admin account (my account is an admin account) then why is it ONLY blocking these STUN responses? It's got network access and TCP works fine. That's the main issue here - the OS shouldn't be blocking this specific network traffic from our app - that's definitely a bug.

Our app is cross platform i'm not splitting it into different apps with varying levels of privelege and using IPC just to work around a bug in sequoia.

Our app is a video surveillance platform - it needs to start at system startup not login so running it as a launch agent isn't an option. It doesn't have a normal GUI it runs a web server.

LaunchDaemon plist files have a UserName field for the specific purpose of running a system level daemon with specific user privileges. That's not something i've exploited it's part of your own system design.

Well it was working fine on every version of macOS up to this one.

Such is the nature of undefined behaviour. Something might work today and then fail on some future OS release, or fail on some older OS release that you didn’t test on, or fail on some specific device configurations, or just seem to work and then fail at random. We document these rules [1] with the understand that, if you follow the rules, we will do our best to maintain binary compatibility going forward.

If someone gives an app root access then why can't it access local devices?

It can. That bit works just fine. The issue you’re having is that you’re running in a mixed execution context:

  • If you were running as a daemon, you get local network access without user approval [2].

  • If you were running as an agent, the user would be prompted to grant you local network access.

You’re running as a daemon but pretending to be a user. The system is not set up for that because it’s not a supported configuration.

LaunchDaemon plist files have a UserName field for the specific purpose of running a system level daemon with specific user privileges.

Right. But that’s intended to be used for role accounts, like the _www user. It’s not designed to move your daemon into a user session.

Share and Enjoy

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

[1] Well, we try to document these rules )-:

[2] On macOS 15.1 and later. There was a gnarly bug with this on 15.0.

OK - i've spent a month trying to get this to work. There's really one outstanding issue and that's that some UDP packets are being blocked when our daemon is running under an admin user account. Can you confirm that that is by design? Are there any workarounds you can think of short of us completely restructuring our application?

"You’re running as a daemon but pretending to be a user. The system is not set up for that because it’s not a supported configuration."

So, i'm sorry but what is the point of the UserName field in the launchd plist file then?

We could run this as root but then we lose the ability to access the camera and microphone which is not ideal for video surveillance.

I just need to ascertain if this is a total dead end or if something is going to be done about it.

The manual documentation for UserName in launch daemon plist files says "This optional key specifies the user to run the job as."

It doesn't say " intended to be used for role accounts, like the _www user. It’s not designed to move your daemon into a user session."

If i ask chatgpt about it it says "In Apple's developer documentation, the <key>UserName</key> field in a launch daemon's property list (.plist) specifies the user account under which the daemon should run. By default, daemons run as the root user, but setting this key allows the daemon to operate with the permissions of a specified user."

I'm really struggling to find where it says anywhere in the developer documentation that what we are doing is "not a supported configuration". From everything i can find on your own docs and forums, what we are doing is exactly how we are supposed to be doing it.

Can you confirm that that is by design?

No.

My take on this is that your daemon is running in an unsupported configuration and thus it’s hard to predict what local network privacy is going to do.

My experience is that:

  • A daemon running as root can access the local network just fine [1].

  • An agent running in the GUI login session can access the local network subject to user approval.

what is the point of the UserName field in the launchd plist file then?

For role accounts.

I'm really struggling to find where it says anywhere in the developer documentation that what we are doing is "not a supported configuration".

Our documentation generally does not tell you what you can’t do; rather, it tends to focus on what you can [1]. And the design I’m recommending here is clearly described in the The Perils of the Monolith section of Technote 2083 Daemons and Agents.

Share and Enjoy

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

[1] On macOS 15.1 and later. On macOS 15.0 there’s a gnarly bug that affects this case.

[2] Having said that, the Daemons Accessing User State section of TN2083 says “It is not possible for a daemon to act on behalf of a user with 100% fidelity”, and that seems pretty clear to me. The example it uses to illustrate this claim is no longer relevant, but the statement itself is still correct.

So why doesn't the manual documentation say "This optional key specifies the role account to run the job as" then?

You keep saying it's an unsupported configuration when it clearly isn't - at least, it hasn't been an unsupported configuration for years over numerous macOS iterations.

If something has changed and it's now not supported then you need to update your documentation and manuals.

The report here is that daemons running as an administrator user account (which has always been a supported configuration) cannot for some reason communicate over UDP. At least look into why that is please.

You keep saying it's an unsupported configuration when it clearly isn't

You are arguing about what is or isn’t supported with someone whose literal job title is Developer Technical Support engineer. Speaking for DTS as a whole, we don’t support this monolithic approach. That’s because our goal is to help developers build software that works now and in the future, and the monolithic approach is likely to have ongoing problems are macOS evolves. So, even if you get past this issue, more issues are waiting for you down the pike.

I you believe that the documentation should do a better job of explaining this reality, please do file a bug against it. If you believe that macOS should support your monolithic design, you can file a bug requesting that too. If you do file any bugs, I’d appreciate you posting the bug numbers, just for the record.

If you would like to explore non-monolithic designs, I’d be happy to answer your questions on that front.

Share and Enjoy

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

I appreciate that you are an actual DTS engineer, and well done on that score, very impressive indeed.

Unfortunately that doesn't have any bearing whatsoever on the fact that daemon servers used to be able to be run with user permissions and now cannot.

The upshot of this is that it's categorically impossible to update our software to work fully on the latest version of macOS. We can either choose between a working server and no local device access or local device access and a broken server. A split up application using IPC calls isn't even an option because we'd need a user session to access the camera.

This is a shame as i'm currently sitting in an office surrounded by completely useless hardware and have lost a month of development time and we're no closer to a resolution. Your goal of helping developers build software now and in the future has failed spectacularly in our admittedly rather specific case as this is the future from when it worked fine in the past.

Fortunately macOS is a small part of our user base and we'll just have to tell people to use Linux or Windows instead and hope that at some point this is resolved.

We're having exactly the same issue running our application as a LaunchAgent as we did running it as a LaunchDaemon

No route to host at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.CreateException(SocketError error, Boolean forAsyncThrow)

https://github.com/ispysoftware/agent-install-scripts/blob/main/v2/launchagent.plist

It just doesn't work.

We've done everything to try to get this working. We get the exact same errors in the console when running it as a launch agent that we did when running it as a launch daemon

OK the thing that has cost us all this grief is that as of Sequoia the application needs to be running in /Applications - we've been testing it from the desktop folder/ other folders on the OS. For some reason if it's not running in /Applications you get this weird UDP error. Even if it's running as root. Moving it to /Applications has resolved all the issues.

Apple, why do you do this?

I am having issues as well. We are using a Gitea CI binary that accesses a few local IP addresses through TCP.

If we run it from Terminal itself it works fine /Applications/DevTools/act_runner -c config.yaml daemon (no "local network" permission dialog either. It just works)

But as soon as we run it through a LaunchAgent the local IP addresses become inaccessible: Error: unavailable: dial tcp 192.168.1.239:10890: connect: no route to host

The dialog for allowing local network access doesn't appear either.

The config file is located in ~/Library/LaunchAgents and is setup like this:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>org.gitea.act_runner</string>
    <key>LowPriorityBackgroundIO</key>
    <false/>
    <key>LowPriorityIO</key>
    <false/>
    <key>KeepAlive</key>
    <true/>
    <key>RunAtLoad</key>
    <true/>
    <key>ProcessType</key>
    <string>Interactive</string>
    <key>WorkingDirectory</key>
    <string>/Applications/DevTools</string>
    <key>StandardErrorPath</key>
    <string>logs/ci.err.log</string>
    <key>StandardOutPath</key>
    <string>logs/ci.out.log</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Applications/DevTools/act_runner</string>
        <string>-c</string>
        <string>config.yaml</string>
        <string>daemon</string>
    </array>
    <key>SessionCreate</key>
    <true/>
</dict>
</plist>

Is this related to this particular issue? And if so, would there be a workaround in this case?

We are seeing a similar issue. Our use case is automating a build server for CI/CD. This has worked for all versions prior to Sequoia but now fails with a network error.

The process has two LaunchAgents configured for the user account of the build node. Each night one agent runs to reboot the machine and it is set to auto-login. On restart and login, the second LaunchAgent runs another script to restart the build service. This is a shell script that then runs a packer (Hashicorp) setup. The packer process also uses a plugin to run tart (wrapper around Apple hypervisor). What we see is the agent script runs correctly and call packer. Packer successfully starts the tart instance locally on the node and this comes up. Access is available from the user session to ping and ssh to node. However the launchd agent gets the network access error. We never see a user popup for the on-login launchd script even though the binary is authorised in Security Settings, Privacy.

So

  • binary has been authorised in user context for network access
  • packer binary called during live session works and will successfully connect to local services
  • packer binary called in launchd session during login is blocked from access and no user prompt is seen, but "no route to host" errors seen when it attempts to access local services.

Suggests a bug in logic in handling authorised network access.

Hi,

we are also affected by this issue after upgrading our Gitlab CI runners from Sonoma to Sequoia. We can no longer access our local network systems reliably.

  • A daemon running as root can access the local network just fine [1].

  • An agent running in the GUI login session can access the local network subject to user approval.

Quinn, from my understanding you propose those two options as expected to be working. I sadly cannot confirm this.

Option 1 (daemon running as root) raises a security concern as this would provide root permissions to the automated gitlab-runner process and its child processes.

Option 2 (agent running in the GUI login session) does not appear to be working for us. https://developer.apple.com/library/archive/technotes/tn2083/_index.html mentions the different launchd agent types. We have never deviated from the default LimitLoadToSessionType Aqua (GUI launchd agent) but observe that the CI pipeline scripts cannot reliably access local network resources anymore.

The automated gitlab-runner spawns a child bash process. Then in this context,

  • what works: curl http://<local network resource>
  • what doesn't work: python script.py which then accesses the same local network resource; resulting in OSError: [Errno 65] No route to host

python was installed the following:

brew install pyenv
pyenv install <version>
pyenv global <version>

The error behavior is identical for Apple Silicon arm64 and Intel x86_64 machines.

Our current workaround is to disable the launch agent completely by

launchctl unload -w /Users/<user>/Library/LaunchAgents/gitlab-runner.plist

and open a Terminal session after reboot which spawns

gitlab-runner run <additional_args>

This however is far from ideal and not well suited for automation. Quinn, do you have any advise on resolving this?

Thank You

We’ve been dealing with local network permission issues on macOS 15. Although 15.1 brought some improvement, users are now reporting similar problems again on 15.2.

Our setup:

  • A “launcher” app (installed from a web package, not sandboxed) uses NSTask to launch our main macOS app.

  • This macOS app connects to an iOS app via the local network.

  • We expect a local network permission prompt to appear when the new app launches, but for many users, it never does.

  • In cases where it worked on an earlier macOS version, there’s no entry in System Settings > Privacy & Security > Local Network, so they can’t toggle anything.

  • Oddly, if we run the macOS app directly in 15.2, local network access works, yet the privacy entry is still missing.

We haven’t found a clear way to troubleshoot this within the current API. Has anyone experienced a similar issue, or have suggestions on how to debug and resolve this? Thanks in advance!

Local Network permission prompt for daemon on macOS 15
 
 
Q