How to Check Whether a Mac App Can Connect to the Internet
A practical macOS security workflow for inspecting an app bundle, looking for network-capable code, and deciding what to allow before you trust an app.
Before I open an unfamiliar Mac app, I like to look at what is inside the app bundle first. This does not prove the app is safe, and it does not replace dynamic monitoring, but it can answer a useful first question: does this app appear to contain code that can reach the network?
On macOS, a normal .app is a directory with metadata, resources, frameworks, helper tools, XPC services, extensions, and one or more Mach-O executables. The main executable is only one part of the picture. A serious check should inspect the whole bundle, not just Contents/MacOS/AppName.
What these commands are for
The gray blocks in this post are Terminal commands. Terminal is the macOS app that lets you type text commands and ask the system to inspect files.
You can open it from Finder at Applications > Utilities > Terminal, or press Command-Space, type Terminal, and press Return.
Copy each command block into Terminal and press Return. Some blocks are meant to be pasted as a group. If a command prints an error, stop and check that the app path is correct before continuing. Some filter commands use grep. If those commands print nothing, it usually means no matching clue was found.
Start with the app bundle
In Terminal, start by saving the app path into a variable named APP. Replace this example with the app you want to inspect:
APP="/Applications/Example App.app"
Keep the quotes around the path. Many app bundles contain spaces, parentheses, or other shell-sensitive characters in their names. If you drag an app from Finder into Terminal, macOS may insert backslash-escaped spaces instead. That also works, but quoted variables are easier to reuse safely in later commands.
Confirm the bundle metadata and read the executable name from Info.plist:
plutil -p "$APP/Contents/Info.plist"
BUNDLE_EXECUTABLE="$(plutil -extract CFBundleExecutable raw -o - "$APP/Contents/Info.plist")"
printf '%s\n' "$BUNDLE_EXECUTABLE"
Then build the path to the main binary. This still works when the executable name itself has spaces:
MAIN="$APP/Contents/MacOS/$BUNDLE_EXECUTABLE"
printf '%s\n' "$MAIN"
file "$MAIN"
Now create a temporary plain-name link to that binary:
INSPECT_DIR="$(mktemp -d /tmp/macho-inspect.XXXXXX)"
INSPECT_MAIN="$INSPECT_DIR/main-binary"
ln -s "$MAIN" "$INSPECT_MAIN"
printf '%s\n' "$INSPECT_MAIN"
This does not copy or modify the app. It creates a temporary link with a simple path. This matters because some Apple command-line tools, especially otool, can misread parentheses in a filename as archive syntax even when the shell quoting is correct.
All later commands assume APP, BUNDLE_EXECUTABLE, MAIN, and INSPECT_MAIN were set this way. Do not remove the quotes around variable expansions such as "$APP", "$MAIN", or "$INSPECT_MAIN".
Also check whether the executable contains more than one CPU architecture:
lipo -info "$INSPECT_MAIN"
Many apps are universal binaries with both arm64 and x86_64 code. The commands in this post inspect the binary as a whole, but this is still useful context. If you are doing a deeper investigation, remember that behavior can differ between architectures when an app ships different code paths or bundled helpers.
If file reports a Mach-O executable, you can inspect linked frameworks, imported symbols, strings, signing data, and entitlements.
Check signing and entitlements
Start with the code signature:
codesign -dv --verbose=4 "$APP" 2>&1
Then print the entitlements:
codesign -d --entitlements :- "$APP" 2>/dev/null
For sandboxed apps, these network entitlements are important:
com.apple.security.network.client
com.apple.security.network.server
com.apple.security.network.client means a sandboxed app is allowed to make outgoing network connections. com.apple.security.network.server means it is allowed to listen for incoming connections.
There is one important catch: if the app is not sandboxed, the absence of these entitlements does not mean the app cannot use the network. Unsandboxed apps do not need those sandbox entitlements to call networking APIs.
Look for networking frameworks
Next, check what the main binary links against:
otool -L "$INSPECT_MAIN"
Network-related clues may include frameworks or libraries such as:
CFNetwork.framework
Network.framework
WebKit.framework
SystemConfiguration.framework
libcurl
libresolv
This is not a complete list, and linking a framework is not a guilty verdict. Many normal apps link broad Apple frameworks for common system behavior. WebKit is a good example: an app may link it because it has a web view, help screen, login screen, documentation view, or WebApp mode. WebKit is a browser engine, so it can bring network-capable code with it even when your own app code does not directly call socket() or connect().
You can filter the output:
otool -L "$INSPECT_MAIN" | grep -Ei 'CFNetwork|Network\.framework|WebKit|SystemConfiguration|libcurl|libresolv'
List network-related calls
When people say “system calls” in this context, they often mean the low-level functions that eventually lead to socket activity. On macOS, you usually inspect imported symbols and Objective-C or Swift references rather than expecting every literal kernel syscall to appear cleanly in the binary.
For C and system-level networking, look for symbols such as:
nm -mu "$INSPECT_MAIN" 2>/dev/null | grep -Ei '(_socket|_connect|_send|_sendto|_recv|_recvfrom|_getaddrinfo|_gethostbyname|_nw_|_curl_|_SSL_)'
Also inspect the indirect symbol table:
otool -Iv "$INSPECT_MAIN" 2>/dev/null | grep -Ei '(_socket|_connect|_send|_sendto|_recv|_recvfrom|_getaddrinfo|_nw_|_curl_|_SSL_)'
For higher-level Apple networking APIs, strings can be useful:
strings "$INSPECT_MAIN" | grep -Ei 'NSURLSession|NSURLConnection|URLSession|CFHTTP|CFNetwork|NWConnection|WebSocket|https?://|wss?://'
Useful network indicators include:
socket
connect
send
sendto
recv
recvfrom
getaddrinfo
nw_connection
NSURLSession
URLSession
NSURLConnection
CFNetwork
CFHTTP
curl
WebSocket
http://
https://
wss://
If you find these, you have evidence that the binary contains network-capable code or network-related references. That still does not prove the code will run, or that it is malicious. It only proves the app has something worth understanding.
If the only obvious low-level hits are _socket and _connect, check whether the binary also links WebKit:
otool -L "$INSPECT_MAIN" | grep -Ei 'WebKit|WebCore|JavaScriptCore'
Then look for WebKit or web-view references:
strings "$INSPECT_MAIN" | grep -Ei 'WebKit|WKWebView|WebView|http://|https://'
If those commands show WebKit, the _socket and _connect findings may be related to web-view support rather than direct socket code you wrote. That still means the binary is technically network-capable, because WebKit can load web content. It does not prove that a specific feature or shortcut configuration will connect to the internet.
One detail is worth being precise about: nm -mu "$INSPECT_MAIN" reports imports from the binary you are inspecting. If the exact _socket or _connect import comes from your app’s own object code, a bundled static library, or a web-view wrapper, WebKit may explain the feature area but not necessarily be the exact object file that introduced the symbol. If you build the app yourself, a linker map is the best way to trace that.
If you have the source code, search it too:
grep -RInIE 'WebKit|WKWebView|WebView|URLSession|NSURLConnection|socket|connect|http://|https://' .
If you have rg installed, you can use the faster version:
rg -n 'WebKit|WKWebView|WebView|URLSession|NSURLConnection|socket|connect|http://|https://' .
If the source shows WebKit or WKWebView but does not show direct socket() or connect() calls, that is a strong clue that the low-level symbols are related to linked web-view functionality. For a final answer, combine this static evidence with runtime monitoring so you can see whether the app actually connects during the workflow you care about.
Read the common findings
Static scans produce clues, not verdicts. This is a practical way to interpret common hits:
WebKit.frameworkwith_socketor_connect: the app may include a web view, login screen, help screen, or WebApp mode. Search forWKWebView,WebView,http://, andhttps://, then watch runtime connections.Sparkle: the app may include an updater. Check whether update checks are expected and which domain is contacted.Sentry,Firebase, orCrashlytics: the app may include crash reporting, analytics, or telemetry. Check whether this is disclosed and whether it can be disabled.- Hardcoded
https://domains: the app contains fixed remote endpoints. Identify the domain owner and match it to app features. NSURLSession,CFNetwork, orNetwork.framework: the app likely has native network client code. Look for URLs, API names, and runtime connections.NSTask,posix_spawn,execve, orpopen: the app can run another executable. Check whether it launches trusted helpers, shell scripts, or tools such ascurl./usr/bin/curl,/usr/bin/ssh, or/bin/sh: the app may delegate work to external command-line tools. Treat this as higher priority and confirm with runtime monitoring.
The key question is not only whether a symbol exists. The better question is whether the symbol matches a feature the app reasonably needs.
Check whether the app can run external tools
Direct network APIs are not the only path to the internet. Even if a binary does not import socket, connect, NSURLSession, CFNetwork, or similar symbols, an unsandboxed app may still launch another program that performs the network request for it.
For example, an app could call /usr/bin/curl, /usr/bin/nc, /usr/bin/ssh, /usr/bin/scp, /usr/bin/sftp, /usr/bin/open, a bundled helper, a shell script, or another interpreter. The main app would not need to contain much obvious networking code. It only needs enough process-execution code to start the external tool.
Look for process-launching symbols in the main binary:
nm -mu "$INSPECT_MAIN" 2>/dev/null | grep -Ei '(_posix_spawn|_posix_spawnp|_execv|_execve|_execvp|_fork|_system|_popen)'
Check the indirect symbol table too:
otool -Iv "$INSPECT_MAIN" 2>/dev/null | grep -Ei '(_posix_spawn|_posix_spawnp|_execv|_execve|_execvp|_fork|_system|_popen)'
Then search strings for common process APIs and command paths:
strings "$INSPECT_MAIN" | grep -Ei 'NSTask|Process\.|posix_spawn|execv|popen|/bin/(sh|zsh|bash)|/usr/bin/(curl|nc|ssh|scp|sftp|osascript|open)|curl[[:space:]]|https?://'
Useful external-execution indicators include:
NSTask
Process
posix_spawn
posix_spawnp
execv
execve
execvp
fork
system
popen
/bin/sh
/bin/zsh
/bin/bash
/usr/bin/curl
/usr/bin/nc
/usr/bin/ssh
/usr/bin/scp
/usr/bin/sftp
/usr/bin/osascript
/usr/bin/open
If you see process-launching APIs, bring that finding to the front of your analysis. The next question becomes: what does the app execute?
Static analysis can sometimes answer that directly when command paths appear as strings. If you see /usr/bin/curl next to an update URL, telemetry URL, or shell command, that is strong evidence. If you only see posix_spawn or NSTask without readable command strings, the answer may not be visible from simple static inspection. The app may construct the command at runtime, decrypt it, read it from a config file, receive it from a server, or pass it through a helper.
In that case, use runtime options:
- Run the app under Little Snitch and watch whether the app itself or a child process connects.
- Use Activity Monitor or
psto look for child processes while the app starts. - Use
fs_usageto watch process execution and file access during launch. - Inspect bundled scripts, helper tools, XPC services, login items, and frameworks.
- Review logs in Console for launch, updater, helper, or crash-reporting activity.
This is why a clean network-symbol scan is not a final answer. For sandboxed apps, the sandbox can limit both network access and some forms of process execution. For unsandboxed apps, absence of direct network calls only means you did not find direct calls. The app may still reach the internet by delegating work to another executable.
Inspect every executable inside the app
Do not stop at the main executable. Many apps ship helpers, frameworks, plug-ins, login items, XPC services, app extensions, and updater tools. Any of those can contain the network code.
This command walks the bundle, finds Mach-O files, and prints likely network or process-execution references:
INSPECT_BUNDLE_DIR="$(mktemp -d /tmp/macho-bundle-inspect.XXXXXX)"
inspected_count=0
find "$APP" -type f -print0 | while IFS= read -r -d $'\0' file_path; do
if file "$file_path" | grep -q 'Mach-O'; then
inspected_count=$((inspected_count + 1))
safe_file="$INSPECT_BUNDLE_DIR/macho-$inspected_count"
ln -s "$file_path" "$safe_file"
hits="$(
{
nm -mu "$safe_file" 2>/dev/null
otool -Iv "$safe_file" 2>/dev/null
strings "$safe_file" 2>/dev/null
} | grep -Ei '(_socket|_connect|_send|_sendto|_recv|_recvfrom|_getaddrinfo|_nw_|_curl_|_SSL_|_posix_spawn|_posix_spawnp|_execv|_execve|_execvp|_fork|_system|_popen|NSURLSession|NSURLConnection|URLSession|CFHTTP|CFNetwork|NWConnection|WebSocket|NSTask|Process\.|/bin/(sh|zsh|bash)|/usr/bin/(curl|nc|ssh|scp|sftp|osascript|open)|https?://|wss?://|Sentry|Firebase|Crashlytics|Sparkle|Telemetry|Analytics)' | head -40
)"
if [ -n "$hits" ]; then
printf '\n%s\n' "$file_path"
printf '%s\n' "$hits"
fi
fi
done
The -print0 and read -r -d $'\0' parts matter. They preserve spaces, parentheses, quotes, and other characters in file names while the bundle is scanned. The safe_file link is there for the same reason as INSPECT_MAIN: it gives Mach-O inspection tools a plain filename while still inspecting the original file.
The results are usually noisy, but that is expected. You are not trying to make a final trust decision from one command. You are trying to build a list of evidence: which component contains network code, which component can launch other tools, what APIs it references, and whether the strings point to update servers, analytics, crash reporting, licensing, telemetry, shell commands, external tools, or something unexpected.
When you are done, you can remove the temporary inspection links:
case "$INSPECT_DIR" in
/tmp/macho-inspect.*) rm -rf -- "$INSPECT_DIR" ;;
esac
case "$INSPECT_BUNDLE_DIR" in
/tmp/macho-bundle-inspect.*) rm -rf -- "$INSPECT_BUNDLE_DIR" ;;
esac
Only run that cleanup in the same Terminal session where you created those variables. The case checks make sure the command only removes the temporary folders created by this workflow.
What a clean result means
If these checks do not find obvious network references, that is a good sign, but it is not absolute proof that the app can never connect.
An app may still load code dynamically, run a bundled script, call another helper process, use a framework in a way that is hard to see from simple string inspection, or hide behavior behind obfuscation. Static inspection is useful, but it is not the same as watching the app run.
Build confidence step by step
Think about the evidence in layers:
- Static symbols show that network-capable code exists somewhere in the binary.
- Linked frameworks can explain why those symbols exist.
- Source search can show whether your own code calls those APIs directly.
- Bundle-wide scanning can reveal helpers, extensions, frameworks, and updater tools.
- Runtime monitoring shows whether the app actually connects during the workflow you care about.
Each layer improves confidence. None of them alone proves the whole app is safe.
Network code does not prove malware
Finding network code is not the same as finding malicious behavior.
Many legitimate Mac apps connect to the internet for reasonable reasons: update checks, crash reporting, license activation, cloud sync, documentation, abuse prevention, remote configuration, or downloading optional content. Some apps also include frameworks that contain networking features even if the app rarely uses them.
The question is not only “can this app connect?” The better questions are:
- Does this app need internet access for what it claims to do?
- Which component is making the connection?
- Which domain is it contacting?
- Does it connect before I do anything?
- Can I block the connection without breaking the offline feature I care about?
For this part of the workflow, I recommend Little Snitch. I have used it for more than a decade. It is well maintained, and in my experience the developer answers questions promptly.
Little Snitch lets you run an app and see when it tries to connect to the internet. It shows the destination domain and helps you decide whether to allow or deny that connection. This is especially useful for apps that should be offline tools. If an app is meant to work without internet access, you can deny its network requests and keep using the parts that do not need a connection.
My before-opening workflow
For apps I do not already trust, I prefer this order:
- Inspect the app bundle before launching it.
- Check signature, notarization, sandbox, entitlements, and bundled components.
- Look for network frameworks, symbols, URLs, analytics, crash reporters, and updaters.
- If I decide to run it, watch the first launch with Little Snitch.
- Deny network access when the app has no reasonable offline reason to connect.
This gives me both static evidence from the bundle and runtime evidence from the actual launch.
App Trust Preview
I also made App Trust Preview for this exact kind of “before opening” decision.
App Trust Preview is a local-first macOS tool for auditing apps before you run them. It reads the app bundle locally and creates a plain-language trust report with information such as signature, notarization, sandbox status, Hardened Runtime, entitlements, privacy permissions, internal helpers, frameworks, app extensions, detected technologies, and other macOS trust signals.
It is designed to work offline and does not send network requests of its own. The inspected app is not uploaded, launched, modified, granted permissions, or denied permissions. The report is generated on your Mac.
One part I especially care about is privacy visibility. App Trust Preview can show what privacy permissions an app may request and what decision you already saved in macOS, such as allowed, denied, limited, add-only, not determined, or unknown. It also gives a fast way to open the relevant System Settings privacy section when you want to change those decisions.
That helps before you open an app. Instead of blindly trusting a downloaded .app, you can inspect the bundle, see the important security and privacy signals, decide whether the app deserves more caution, and then use a tool like Little Snitch if you want to watch what happens at runtime.
No single tool proves an app is safe. But a careful workflow can reduce blind trust. Static inspection shows what is inside the bundle. Runtime monitoring shows what the app actually tries to do. Together, they make macOS app trust decisions much clearer.