How Firefox Extensions Communicate with Local Applications
This article explains how the Native Messaging API enables sandboxed Firefox extensions to securely communicate with local binary applications, such as password managers and download managers. By leveraging standard input and output streams, strict configuration manifests, and length-prefixed JSON data payloads, Firefox bridges the gap between web extensions and native operating system capabilities without compromising browser security.
The Core Architecture
Firefox extensions run in a restricted security sandbox, meaning they cannot directly access the local file system or execute native binary code. The Native Messaging API provides a controlled bridge out of this sandbox.
When an extension initiates communication, Firefox acts as an
intermediary. It locates the registered local binary application,
launches it as a separate process on the host machine, and establishes a
communication pipe. The extension and the local binary never interact
directly; instead, Firefox passes messages between the extension's
JavaScript environment and the local application's standard input
(stdin) and standard output (stdout)
streams.
Security and Configuration Requirements
To prevent malicious extensions from executing arbitrary programs on a user's computer, both the Firefox extension and the local binary must explicitly register and authorize each other.
1. The Extension Configuration The Firefox extension
must request permission to use the API by including
"nativeMessaging" in the permissions array of
its manifest.json file.
2. The Native Host Configuration When the user installs the local application (such as a desktop password manager), the installer must place a separate JSON file—known as the native messaging manifest—into a specific directory or registry location dictated by the operating system. This manifest contains:
name: The unique identifier the extension will use to call the application.path: The absolute path to the local binary executable.type: Set to"stdio"to indicate communication via standard input and output.allowed_extensions: An array of specific Firefox extension IDs. Firefox will block any extension not listed here from communicating with the binary.
Establishing the Connection
Once configured, the extension uses the browser.runtime
API to talk to the local binary. There are two methods of communication
depending on the use case:
Persistent Connections
(browser.runtime.connectNative) This method
launches the local binary and keeps the process running, returning a
runtime.Port object to the extension. Both the extension
and the local application can listen for and send multiple messages over
this port until one side explicitly closes it. This is commonly used by
password managers that require continuous, stateful communication to
lock, unlock, and autofill credentials.
One-Time Messages
(browser.runtime.sendNativeMessage) This method is
used for single, stateless transactions. Without creating a persistent
port, Firefox launches the binary, sends a single JSON message, waits
for the binary to reply with a single message, and then terminates the
connection. This is ideal for download managers, where the extension
simply needs to hand off a download URL and associated cookies to the
local application and requires only a brief confirmation in return.
The Data Framing Protocol
Because stdin and stdout are continuous
streams of bytes, the local binary needs a reliable way to know where
one message ends and the next begins. The Native Messaging API enforces
a strict data protocol for all communication:
- Serialization: All messages must be formatted as valid JSON and encoded in UTF-8.
- Length Prefixing: Every message is preceded by a 32-bit (4-byte) integer in the host computer's native byte order. This integer specifies the exact byte length of the upcoming JSON string.
- Reading and Writing: When the local binary reads
from
stdin, it must first read the initial 4 bytes to determine the size of the incoming payload. It then reads that exact number of bytes from the stream and parses the result as JSON. When sending data back to the Firefox extension viastdout, the binary must pack the length of its JSON response into 4 bytes, write those bytes to the stream, and immediately write the UTF-8 JSON string.
To prevent memory exhaustion, Firefox enforces size limits on these messages: a single message sent from the extension to the native application cannot exceed 4 GB, and a single message sent from the native application back to Firefox cannot exceed 1 MB.