Security and usability improvement for webbrowser.open() on macOS: Replace osascript with /usr/bin/open

Hi everyone,

I’d like to discuss modernizing the macOS implementation of webbrowser.open() and gather feedback/support for merging PR #146439.

Current Issues

The existing MacOSXOSAScript class constructs a short AppleScript and executes it via osascript. While this has worked reliably for many years, it introduces two clear concerns:

User Experience

On managed enterprise Macs (common in corporate environments using MDM/EDR tools such as CrowdStrike, SentinelOne, Jamf, or Santa), osascript is frequently monitored or restricted because of its history of abuse in malware campaigns. When restrictions apply, webbrowser.open() can fail silently or with unclear errors, creating a frustrating experience for Python developers and applications.

Security Risk

osascript is a general-purpose scripting interpreter and a classic Living-Off-the-Land binary (LOOBin). It was used on macOS in the recent Axios npm supply-chain attack (March 31, 2026), where malicious code wrote an AppleScript to a temporary file and executed it silently.

Even though the recent PATH-lookup vulnerability has been addressed in the PR, relying on osascript still poses issues:

  • It requires constructing and executing AppleScript code, a more powerful mechanism than needed for the simple task of opening a URL. AppleScript can run shell commands, interact with other applications via Apple Events, and perform many other actions.

  • Using a general scripting tool (instead of a limited, purpose-built utility) increases the overall attack surface of the standard library and makes the code more likely to be flagged by security tools.

  • It keeps Python tied to a binary that security teams often treat with caution due to its real-world abuse in supply-chain attacks and infostealers.

Proposed Solution

PR #146439 introduces a new MacOSX class that replaces the legacy code with Apple’s purpose-built /usr/bin/open utility (called via absolute path and subprocess.run with an argument list):

  • Uses open -b <bundle-id> for known browsers (e.g., com.google.Chrome), explicitly targeting the intended application.

  • Safely routes non-HTTP(S) URLs through a browser to avoid unintended OS file-handler behavior.

  • Eliminates AppleScript construction entirely.

  • Deprecates the old MacOSXOSAScript class with a clear DeprecationWarning.

The change preserves full backward compatibility, including support for named browsers, while simplifying the code.

4 Likes

This seems like a good change, particularly because it uses Apple’s preferred method for opening “a browser”.

+1 to this idea, I’ve worked in a number of security teams where Python’s use of OSAScript in this manner has been a consistent thorn.

This breaks custom application URIs, e.g. vscode:// or slack:// at least with some browsers, see issue 149454

I understand the logic of wanting to force standard URIs such as file:// through the browser, but I think doing it for custom application URIs is a bad idea. For Google Chrome, it breaks it completely; for Safari or Firefox, it still works, but with worse user experience – Python script tries to open Slack/VSCode/etc, previously it opened it directly, now it first opens your browser and displays a message saying a web page wants to open it.

If you look at how other platforms handle this – Java java.awt.Desktop.browse(URI) explicitly documents that custom URI schemes are sent to the OS-registered application instead of the browser. Wouldn’t it make sense for Python to adopt the same behaviour?

I think the major difference is in the name: webbrowser.open should always open a webbrowser. Java’s ...Desktop.browse still evokes a browser, but the “desktop” in the name at least hints that it might not be a web browser that gets opened.

However, I do agree that we should have a standard API for “do the system default for this URI”, but I’m not sure where it should live. webbrowser seems out of place regardless of the function name because the whole point is that we don’t necessarily want to open a web browser. The functionality most reminds me of os.startfile, which has always been a Windows-specific way to launch a file the way the OS wants, but os doesn’t strike me as a great place either (though it may still my front-runner of the options I’ve come up with) and I’m not sure what we would call the function – os.starturi? :face_with_diagonal_mouth: I wonder about something like urllib.launch(uri), but that feels different from anything else in urllib, even though that package is specifically about dealing with URIs.

For some further reading, see also Support for `file://` urls in webbrowser.open and webbrowser.open with file: URLs may launch editor instead of browser (gh-128540) which have also recently changed webbrowser on macOS. Also tangentially related is this old proposal to Add shutil.open (gh-47427) as a cross-platform os.startfile.

My viewpoint: that wasn’t actually true on macOS in Python 3.13 and earlier – and still might not be true on other platforms.

You can argue that was a bug, but the documentation wasn’t clear that it was a bug, and it wasn’t unreasonable for people to rely on the long-standing behaviour – which is now broken, albeit inconsistently.

It also isn’t clear that “always open a web browser” is a useful function for app-specific URIs. A web browser isn’t guaranteed to do anything useful with them.

If you look at other platforms, I think most of them just have an “open this URI with whatever app the OS is registered to open it with” – not two separate APIs, one which guarantees to always use a web browser (even if that’s nonsensical), the other which uses the OS-configured app. What’s the use case for splitting the API up like that? Does anything other than Python do it?

I also think, that if people are committed to breaking the pre-existing function – wouldn’t it be right to add a note to the documentation explaining the decision? i.e. “This used to work, but was removed in Python 3.14, because it was never intended to be supported”

My contention is that you shouldn’t be passing app-specific URIs to webbrowser.open :slight_smile:

Unfortunately, we don’t have a better option to offer at the moment. Fixing that is where I think effort would be best directed at this point.

Ideally, a proper “toss it to the OS and let it deal with it” API would eventually replace webbrowser, which has always been a bit fragile by its very nature.

If you come from a Java background, and are familiar with java.awt.Desktop.browse – it is natural to stumble upon webbrowser.open and think “Python has the same thing just with a narrow name”. If the position is “it isn’t meant to be the same thing, custom app URIs aren’t supported, and may not work, only URI schemes the user’s default browser itself supports are officially supported” – then shouldn’t the documentation say that explicitly?

Yes, I think the documentation should be explicit that webbrowser.open will do its best to open a URL with a webbrowser. Some implementations sometimes guessed with the default url launcher, depending on your platform, but this has been improving to more consistently launch a browser, as the docs say it will.

FWIW, I think it is fairly explicit already:

The webbrowser module provides a high-level interface to allow displaying web-based documents to users.

webbrowser.open(url): Display url using the default browser

If “launch a non-web url with a default system application” is desired, that should be a new API, and I’d argue it shouldn’t replace or deprecate webbrowser.open, since it serves a different purpose. It can also be way simpler. webbrowser.open has never served this purpose, though it would sometimes do this accidentally on some systems, depending on their configuration, state, and version. This use case is explicitly not supported in the documentation of the module and hasn’t ever worked reliably, but I’d fully support adding docs to be extra super clear that it’s not supported, if that would help.

FWIW, I’m coming from Jupyter, where we really do need “open a file:// url to an HTML file with a webbrowser” to work. Fortunately, the documentation states quite clearly that Python has exactly this functionality! I hope we don’t change that.