Daniel sent us a follow-up after our Pegasus deep dive, and honestly it's one of those questions that makes you stop and realize how much of computing is just invisible plumbing. He heard us talk about how Android's privacy indicator, that little green dot, involves a cascade of APIs talking to each other before the kernel even knows what's happening. And his brain did what any reasonable person's brain would do. He pictured HTTP requests flying around inside his phone at millisecond speed. So he's asking, is that what's actually happening? Is every operating system running this massive hidden web of internal APIs passing HTTP requests back and forth just to turn on a microphone?
The short answer is no, it is not HTTP. But the long answer is so much weirder and more interesting. By the way, today's script is coming to us courtesy of DeepSeek V four Pro.
Yeah, let's see how it handles this one. So Daniel's question gets at something fundamental about how we think about software. When developers talk about APIs, they almost always mean the external kind. REST endpoints, GraphQL, gRPC. You send a request over the network, you get a response back. JSON payloads, status codes, the whole thing. And I think what happened is Daniel heard us describe this cascade of internal API calls and his brain quite reasonably mapped it onto the only API model most people ever see.
Which is fair, because the language is the same. We say API for both. Application Programming Interface. That term is doing a lot of heavy lifting.
It's doing absurd amounts of heavy lifting. And this is one of those places where the terminology actually obscures more than it reveals. Because an internal API inside an operating system looks nothing like a web API. The transport layer is completely different. The latency profile, the failure modes, the security model, the serialization format. Everything is different.
Let's just nail down the core misconception first. When Android needs to turn on that green privacy dot, there are not HTTP packets flying around inside the phone. There is no localhost server listening on port eight thousand something. There's no JSON being parsed.
And the reason this matters is that the actual mechanism tells us something important about why Pegasus could bypass it so cleanly. If it were HTTP, you could potentially intercept it with a proxy. You could man-in-the-middle your own phone's internal traffic. That would be a security nightmare of a completely different flavor. But instead, what we have is something much more tightly coupled, much faster, and in some ways much harder to audit from the outside.
Let's build this from the ground up. Daniel asked specifically about Android, so let's use Android as our reference architecture. What actually happens when an app requests microphone access and that green dot needs to light up?
Okay, so the first thing to understand is that Android is built on Linux, but Android is not a desktop Linux distribution. The way components talk to each other is radically different. On desktop Linux, inter-process communication is mostly done through things like D-Bus, Unix domain sockets, shared memory, or signals and pipes. Android has its own thing. And that thing is called Binder.
This is the driver that everyone in the Android security world talks about constantly.
Binder is the spinal cord of Android. Every single app runs in its own sandboxed process, and those processes need to talk to each other and to system services. Binder is how they do it. It's a kernel driver that provides a high-speed IPC mechanism. And I want to be really specific here because I think Daniel will appreciate the technical precision. Binder does not use HTTP. It does not use TCP/IP. It does not even use network sockets in the traditional sense. It uses a custom kernel-level protocol that operates through a device file, dev binder, and it passes data by copying it directly between process memory spaces through the kernel.
When we say an app makes an API call to the audio system, what's actually happening at the memory level?
The app process constructs a data parcel. This is a flattened binary representation of the method call and its arguments. Not JSON, not XML, not protocol buffers in the way most developers think of them. It's a custom serialization format specific to Binder. That parcel gets written into a memory buffer that the kernel has mapped into both the calling process and the target process. The kernel copies it over. The target service unmarshals it, executes the method, and sends a response parcel back the same way.
The latency on this is what, microseconds?
We're talking tens to hundreds of microseconds for a typical Binder transaction. For comparison, a localhost HTTP request even on the fastest stack is going to be at least a few hundred microseconds and often a millisecond or more, just from the TCP handshake overhead, even on loopback. And that's before you parse the HTTP headers.
If Android were actually using HTTP internally, every microphone access would add perceptible lag to the audio pipeline. Which for real-time audio processing would be catastrophic.
The audio HAL needs to deliver audio buffers to the DSP with extremely tight timing constraints. We're talking about buffer periods of five to twenty milliseconds on most devices. If you introduced even one millisecond of extra latency at every API boundary in the stack, you'd blow through your timing budget before the audio even reached the hardware.
Okay, so let's trace the actual path. App wants microphone. Green dot needs to appear. What's the chain?
All right, based on the AOSP audio architecture documentation. When an application wants to record audio, it calls MediaRecorder or AudioRecord, which are Java-level APIs in the Android SDK. That call goes through Binder to a system service called AudioService, which is part of the system server process. AudioService is the gatekeeper for all audio routing and policy decisions.
This is the first API boundary. App process to system server process, via Binder.
AudioService then checks permissions. Does this app have the RECORD_AUDIO permission? Is it in the foreground? If everything checks out, AudioService makes a call down to AudioFlinger. AudioFlinger is Android's audio server. It runs as a native process and is responsible for actually managing audio hardware access, handling mixing, resampling, routing, all of that.
That's another Binder call?
AudioService to AudioFlinger is also Binder. So we're two hops in, both Binder, no HTTP anywhere. AudioFlinger then talks to the hardware through the Hardware Abstraction Layer, the HAL. And this is interesting because the HAL is defined in HIDL or AIDL, depending on the Android version. HIDL was the older HAL interface language. AIDL for HALs is the newer approach starting around Android eleven.
This is where things get relevant to Daniel's question about internal APIs being a massive web. Because HALs are essentially internal APIs between the Android framework and the hardware-specific implementation.
There are dozens of HALs. Audio HAL, camera HAL, graphics HAL, sensors HAL, GPS HAL, Bluetooth HAL, WiFi HAL. Each defines a specific interface that the hardware vendor must implement. So when Samsung or Google builds an Android phone, they write HAL implementations that translate those standard interfaces into commands for their specific chipset.
The audio HAL defines something like openInputStream, and then Qualcomm provides the implementation that knows how to actually configure the Hexagon DSP to start pulling data from the microphone array.
And the privacy indicator, the green dot, is handled by a different component entirely. It's managed by the PermissionsController system app. When AudioService grants microphone access, it notifies PermissionsController through, you guessed it, another Binder call. PermissionsController then renders the privacy indicator in the status bar.
Daniel heard us say three APIs cascading. Let's count them. One, app to AudioService. Two, AudioService to AudioFlinger. Three, AudioService to PermissionsController for the green dot. And potentially a fourth, AudioFlinger to the audio HAL. All in kernel memory space. None of it observable from userspace without root access.
This is the part that I think is genuinely unsettling when you really sit with it. All of this communication is happening inside a kernel IPC mechanism that is completely opaque to the user. There is no log you can view. There is no packet capture you can run. There is no equivalent of Wireshark for Binder transactions unless you have a rooted device and specialized debugging tools.
Which brings us back to Pegasus. Because Pegasus, by compromising the kernel, was sitting at a level where it could observe or even inject Binder transactions. It didn't need to intercept HTTP. It didn't need to man-in-the-middle network traffic. It was inside the IPC fabric itself.
This is the distinction that I think Daniel was circling around. When we say internal APIs, we're talking about function calls that cross process boundaries. But they're still fundamentally function calls. They're synchronous or asynchronous method invocations, not RESTful resource requests. There is no URL. There is no HTTP method. There is no status code. There is just a method signature, arguments, and a return value, all packed into a binary parcel and shot through the kernel.
Let me push on that a little, because I think there's actually a deeper insight here about why the HTTP mental model is so sticky. When developers build microservices, they're used to thinking about services as independent entities that communicate over well-defined network protocols. And that architecture does look like a web of HTTP requests. The internal architecture of an operating system looks superficially similar. You've got independent processes. You've got well-defined interfaces. You've got request-response patterns. But the substrate is completely different.
The substrate is the kernel. And the kernel is not a network. The kernel is a single memory space that all processes share a window into. Binder exploits this by allowing the kernel to map the same physical memory pages into both the calling process and the callee process. When a Binder transaction happens, the data is copied exactly once from the caller's userspace buffer into the kernel, and then the kernel maps that same physical memory into the callee's address space. Zero copy on the receive side.
Try doing that with HTTP.
Even with zero-copy networking tricks like sendfile or splice, you're still moving data through socket buffers and the network stack. Binder sidesteps all of that because it's not pretending to be a network. It's a kernel-mediated memory sharing mechanism that happens to have an RPC interface layered on top.
Let's zoom out to the broader question Daniel asked. Is there a parallel world of internal APIs on every operating system? And the answer is yes, absolutely, but they don't look like what most people think of as APIs.
Every modern operating system has an IPC substrate. Windows has COM and ALPC, Advanced Local Procedure Call. macOS and iOS have Mach messages, which is the XNU kernel's native IPC mechanism, and on top of that they have XPC, a higher-level framework. Desktop Linux has D-Bus for desktop services. None of these are HTTP. None of them use text-based serialization. They're all binary protocols designed for minimum latency and maximum throughput within a single machine.
The scale of this is staggering. On a typical Android phone, there are hundreds of system services running at any given time, all communicating through Binder. The system server alone hosts something like eighty to a hundred different service threads. PackageManager, ActivityManager, WindowManager, NotificationManager, PowerManager, AlarmManager, the list goes on. Each of these exposes a Binder interface. Each of these is an internal API.
That's just the Java-level system services. Below that you've got native daemons like surfaceflinger, which handles display compositing, and servicemanager, which is the Binder service registry. Servicemanager is actually the first thing that comes up after init, because every other service needs to register with it. It's essentially a DNS for Binder. An app says, I need to talk to the activity manager, and servicemanager looks up the Binder handle and returns it.
There is literally a service registry. There's a naming system. There are published interfaces. In a very real sense, it is a massive internal web of services. It's just not the web we're used to.
I want to talk about AIDL, because this is where the API definition actually happens and it's surprisingly clean. AIDL stands for Android Interface Definition Language. You write a dot aidl file that defines the methods a service exposes. It looks a lot like a Java interface, but with annotations for data types and directional tags for parameters. The Android build system compiles this into Java or C++ stubs that handle all the Binder marshaling and unmarshaling automatically.
From the developer's perspective, you write what looks like a normal interface, you implement it, and the framework handles the rest. You never touch Binder directly unless you're doing something very unusual.
And this is the key insight. The internal APIs are real APIs. They're defined in interface definition languages. They have versioning. They have backwards compatibility requirements. Google has a whole program called the Vendor Test Suite, VTS, that verifies HAL implementations conform to the specified AIDL or HIDL interfaces. This is not ad-hoc communication. It's a rigorously specified contract.
Here's the thing that I think will make Daniel's brain hurt less, not more. All of this happens without any of the overhead that makes web APIs feel heavy. There's no DNS resolution. There's no TLS handshake. There's no HTTP header parsing. There's no JSON serialization or deserialization. The overhead of a Binder transaction is dominated by the kernel context switch, which on modern ARM processors is on the order of a few hundred nanoseconds to a microsecond or two.
Context switching is the real cost. Every time you cross a process boundary, the CPU has to save the current process's register state, flush the TLB, load the new process's page tables, and restore the new process's register state. This is expensive, but it's an order of magnitude cheaper than a network round trip, even a localhost one, because there's no TCP stack involved, no interrupt handling for network packets, no buffer copies through the socket layer.
If Android used HTTP internally, even on localhost, every Binder transaction would be replaced by a TCP connection setup, an HTTP request with headers, a server parsing loop, response headers, and teardown. You'd multiply the latency by a factor of ten to a hundred, and you'd burn CPU on parsing text headers for every single call. The phone would be unusably slow and the battery would last about twenty minutes.
This actually gets at something deeper about operating system design. The reason we have these specialized IPC mechanisms instead of just using network protocols for everything is that the design constraints are completely different. Inside a single machine, you can trust the kernel. You don't need encryption. You don't need authentication at the transport level because the kernel knows which process is calling and can enforce permissions directly. You don't need congestion control because there's no network. You don't need to worry about packet loss or reordering because the data is being copied through memory, not transmitted over a noisy channel.
The threat model is different. On a network, you're worried about man-in-the-middle attacks, packet injection, replay attacks. Inside the kernel, you're worried about privilege escalation, memory corruption, use-after-free vulnerabilities. Different attack surfaces, different mitigations.
This is exactly why Pegasus was so devastating. It didn't attack the network layer. It attacked the kernel. Once you're in the kernel, all the IPC mechanisms are transparent to you. You can read Binder transactions as they pass through. You can inject your own. You can modify parcels in flight. The kernel is the ultimate trusted computing base. If that trust is compromised, every API boundary becomes porous.
Now we can answer Daniel's question directly. Yes, there is a massive parallel world of internal APIs on every operating system. Android has hundreds of them, all talking through Binder at microsecond latency. But no, they are not HTTP. They are not REST. They are not JSON over TCP. They are binary RPC over kernel-mediated shared memory. And the reason that distinction matters is that it explains both why the system is so fast and why it's so hard to audit from the outside.
I want to add one more layer to this, because Daniel specifically mentioned the cascade of three APIs and I think we should map out exactly what that looks like in the privacy indicator case. When an app opens an AudioRecord stream, the Java AudioRecord constructor calls into the native framework, which sends a Binder transaction to AudioService. AudioService checks the app's UID against the permission database. If the permission is granted, AudioService sends a Binder call to AudioFlinger to open the hardware input stream.
That's two.
Then AudioService separately notifies PermissionsController, also via Binder, that microphone access has begun. PermissionsController updates its internal state and posts a notification to the status bar through the SystemUI process, which renders the green dot. That's the third API cascade Daniel heard us mention.
What's the actual data format in these Binder parcels? Because I think this is where the HTTP mental model really falls apart.
It's a tightly packed binary format. The first four bytes are typically a method index, just an integer that tells the receiving stub which method to dispatch to. Then you have the serialized arguments in order. For primitive types like integers and booleans, they're just written directly into the parcel. For objects, Binder can handle two cases. If the object is a simple data class that implements Parcelable, it gets flattened field by field. If the object is a Binder reference itself, meaning it represents a remote service, Binder writes a special flat binder object token that the kernel translates into a handle the receiving process can use to make calls back.
You can actually pass live service references through Binder calls. You can say, here is a callback object, call me back on this when the audio buffer is ready. And the kernel handles translating that reference across the process boundary.
This is how asynchronous callbacks work in Android. The app provides a Binder object that AudioFlinger can call back on when audio data is available. That callback object is itself a Binder interface. So you get chains of Binder calls going in both directions, all coordinated by the kernel's Binder driver.
This is elegant engineering. And it's completely invisible to users. Most developers don't even think about it unless something goes wrong.
When something does go wrong, the failure modes are nothing like HTTP failures. You don't get a five hundred status code. You get a Binder transaction error. The most famous one is the TransactionTooLargeException, which happens when you try to send more than one megabyte of data in a single Binder call. There's a hard limit because the kernel allocates a fixed-size buffer for each transaction.
That one megabyte limit has shaped Android API design in interesting ways. It's why you can't pass large bitmaps through Binder directly. You have to use shared memory through the ashmem driver or, in more recent versions, hardware buffers through the gralloc HAL. The system is designed around these constraints.
This is where the comparison to microservices actually becomes useful, but in a different way than Daniel was imagining. A microservices architecture and Android's internal architecture both solve the same problem, how do you decompose a complex system into communicating components. But they optimize for different things. Microservices optimize for independent deployability, fault isolation, and organizational scaling. Android's internal services optimize for latency, memory efficiency, and battery life. The trade-offs are completely different.
Let's talk about the other operating systems briefly, because Daniel asked about Linux and operating systems generally. On a typical Linux desktop, when you plug in a USB drive and the file manager pops up, that's D-Bus in action. The kernel detects the device and sends a uevent. Udev picks it up and emits a D-Bus signal. The file manager is listening for that signal and responds by mounting the drive and opening a window.
D-Bus is interesting because it actually has two layers. The low-level transport is a Unix domain socket, which is a kernel-level IPC mechanism that looks like a network socket but only works on the same machine. On top of that socket runs the D-Bus protocol, which is a binary protocol with its own message format, type system, and marshaling rules. And then there's a daemon, dbus-daemon, that routes messages between clients. It's a message bus, not a point-to-point RPC mechanism like Binder.
D-Bus is architecturally more like a publish-subscribe system. Services connect to the bus, they can emit signals that any interested client can receive, and they can expose methods that clients can call. It's closer to something like Apache Kafka than it is to HTTP, but shrunk down to the scale of a single machine.
MacOS and iOS use Mach messages, which are even more fundamental. Mach was a research microkernel from Carnegie Mellon, and its IPC mechanism is baked into the foundation of Apple's operating systems. In XNU, the kernel that powers macOS and iOS, Mach messages are the primitive that everything else is built on. When you make a system call in a Mach-based OS, it's often actually a Mach message sent to a kernel port.
Wait, so a syscall is an IPC? That's a wild design choice.
In a pure Mach system, almost everything is a message pass. The BSD subsystem that provides the POSIX system calls we're familiar with runs as a server process in Mach's original design. In XNU, they merged the BSD layer into the kernel for performance, but the Mach IPC substrate is still there at the bottom. So when an iOS app requests microphone access, it's not just one IPC mechanism at work. It's Mach messages at the kernel level, plus XPC at the framework level for communication with system daemons.
None of this is HTTP. None of it is REST. None of it is JSON.
The pattern is consistent across every major operating system. Specialized binary IPC protocols that make different trade-offs but share the same fundamental insight. When you control both endpoints and they're on the same physical machine, you can do much better than a network protocol designed for unreliable, high-latency, untrusted connections.
Let me bring this back to Daniel's specific phrasing, because I think there's something revealing about how he framed it. He said, even if you think about one API sending a request, formulating that request, testing it, webhooks, external APIs. And that list, formulating, testing, webhooks, that's the mental model of someone who builds and consumes web APIs professionally. And that model is correct for what he does. But it's the wrong model for what's happening inside a phone.
The formulation step, serializing data into a request, does exist in Binder, but it's compile-time generated code, not hand-written JSON serialization. The testing step, verifying the interface contract, exists but it's enforced by the VTS and CTS compatibility test suites at build time, not by runtime integration tests. And webhooks, the idea of an external service calling back to a URL you provide, that concept simply doesn't apply because there are no URLs. There are no webhooks inside a phone.
The callback mechanism in Android is a Binder object passed through a Binder transaction. It's a live reference to a remote interface, not a URL that gets pinged with an HTTP POST. The mental model is so different that using the same word, API, for both is almost misleading.
Yet it's the correct word. An application programming interface is exactly what it is. It's an interface that an application programs against. The fact that we've come to associate API with REST over HTTP is a historical accident of the web's dominance. But the concept is much older and much broader.
I want to talk about one more thing that I think will resonate with Daniel. He's an open source developer. He's worked on Linux. And on Linux, the boundary between internal APIs and external APIs is blurry in a way that's different from Android or iOS. On a Linux system, you have proc filesystem, sysfs, device files, ioctl calls, netlink sockets, and a dozen other mechanisms for userspace to talk to the kernel and to system services. Some of these look like files you read and write. Some look like function calls. Some look like network sockets. It's a mess, but it's a glorious mess that reflects the organic evolution of the system over thirty years.
The proc filesystem is a perfect example of how weird internal APIs can get. You want to know the CPU temperature? You read a file at proc cpuinfo or sys class thermal. You want to set a kernel parameter? You write a string to a file in proc sys. These aren't really files. They're kernel interfaces that pretend to be files because the Unix philosophy says everything is a file. But behind the scenes, reading from proc cpuinfo triggers a kernel function that formats the current CPU state into a text string and copies it to your buffer. There's no persistent storage. No disk I/O. It's an API disguised as a filesystem.
Ioctl is even stranger. It stands for input output control. It's a single system call that takes a device file descriptor, a command number, and an optional pointer to arbitrary data. The command number encodes both the operation and the size and direction of the data. It's essentially a multiplexed RPC mechanism where the device driver is the server and the command number is the method selector. It's been in Unix since Version Seven in nineteen seventy-nine and it's still used everywhere.
If you've ever wondered why graphics drivers are so complex, ioctl is a big part of the answer. The Direct Rendering Manager subsystem in Linux uses ioctl calls for everything. Setting display modes, allocating framebuffers, submitting rendering commands. And each GPU vendor has its own set of ioctl commands on top of the standard ones. It's an internal API surface that's enormous, hardware-specific, and constantly evolving.
This is the world Daniel inhabits as a Linux user. It's not HTTP everywhere. It's files everywhere, except they're not real files, and ioctls everywhere, except they're not real function calls, and netlink sockets everywhere, except they're not real network connections. The conceptual clarity of REST APIs completely breaks down when you get inside an operating system.
I think the most honest answer to Daniel's question is this. Yes, there is a massive hidden web of internal APIs inside every operating system. It's larger and more complex than most external API ecosystems. But it doesn't use HTTP, it doesn't use JSON, and it operates at speeds and with constraints that are almost unimaginable from a web development perspective. And the reason we talk about it using the same language, APIs, interfaces, services, is that the architectural patterns are similar even though the implementation is completely different.
The service-oriented architecture pattern exists inside your phone. It's just implemented in Binder parcels and kernel memory sharing instead of HTTP requests and JSON payloads. The principles of interface definition, versioning, backwards compatibility, and permission enforcement are the same. The engineering trade-offs are what's different.
I want to give one concrete example of how these trade-offs manifest. When you make an HTTP request from your phone to a remote server, that request goes through the WiFi or cellular modem, through the TCP/IP stack, and out to the physical radio. The round-trip time is measured in tens or hundreds of milliseconds. During that time, the app thread is blocked, the CPU can potentially enter a low-power state, and the radio is actively consuming power. A Binder transaction, by contrast, completes in microseconds. The CPU barely has time to change power states. The radio never wakes up. The energy cost is orders of magnitude lower.
If you replaced Binder with HTTP, not only would the phone be slower, the battery would be dead by lunchtime. The engineering constraints of a mobile device simply don't permit the luxury of text-based network protocols for internal communication.
This is why I find the whole topic so satisfying to explain. Because from the outside, it looks like a mess of ad-hoc complexity. Why so many different IPC mechanisms? Why not just standardize on one thing? But each mechanism is an optimization for a specific set of constraints. Binder is optimized for Android's security model, where every app is a separate UID and all communication must be mediated by the kernel. D-Bus is optimized for desktop Linux's model of cooperating processes with complex event-driven interactions. Mach messages are optimized for microkernel minimality and formal provability of certain security properties.
The diversity is a feature, not a bug. It's the result of different systems making different bets about what matters most.
I think there's a broader lesson here about how we teach computing. We teach people about REST APIs because they're universal and accessible. You can understand them with nothing more than curl and a text editor. But the internal APIs of operating systems are harder to explore, harder to visualize, and much harder to teach. So most developers go their entire careers without really understanding what happens inside their own devices. And that's not a criticism. It's just the reality of how much complexity is packed into modern systems.
Daniel's question came from that exact place. He heard us describe something that sounded like APIs and his brain tried to map it onto the API model he knows. And the answer required us to say, no, it's different in almost every particular, but similar in the abstract. Which is a deeply unsatisfying answer, but it's the truth.
Let me add one more technical detail that I think is fascinating and rarely discussed. The Binder driver in the Linux kernel is about four thousand lines of C code. That's it. Four thousand lines of kernel code enables the entire inter-process communication fabric of Android, a platform with billions of active devices. The driver handles thread management, memory mapping, reference counting, and a priority inheritance mechanism to prevent priority inversion during Binder calls.
This is the classic Mars Pathfinder bug, right?
In nineteen ninety-seven, the Mars Pathfinder rover kept resetting because a low-priority task was holding a lock that a high-priority task needed, and a medium-priority task was preempting the low-priority one. Binder has a built-in solution for this. When a high-priority thread makes a Binder call to a service running in a lower-priority thread, the kernel temporarily boosts the server thread's priority to match the caller. This prevents the exact scenario that nearly killed the Pathfinder mission. And it's all handled automatically by the Binder driver. The developers don't even need to think about it.
That's the kind of engineering that you only get from decades of hard-won experience with the ways concurrent systems fail. And it's completely invisible. A developer writes an AIDL interface, implements a method, and the priority inheritance just works. They never know it's there unless they read the kernel source code.
This circles back to Daniel's original question about whether there's a parallel world of internal APIs. The answer is yes, and it's more sophisticated in some ways than the external API world. The Binder protocol handles things that HTTP doesn't even have concepts for. Object lifetime management through reference counting. Recursive calls where a service calls back to its caller without deadlocking. Zero-copy transmission of large data through shared memory.
HTTP three and QUIC are just now getting to some of these optimizations for the web, and they're doing it over actual networks with all the constraints that implies. Binder has been doing it inside the Linux kernel since Android's earliest days.
I should mention, since we're being precise, that Binder was originally developed at Be Inc for the BeOS operating system in the mid-nineties. BeOS was designed for multimedia processing and had an aggressively optimized IPC architecture. When Andy Rubin and the Android team needed an IPC mechanism, they looked at what BeOS had done and brought the Binder concept to Linux. The Binder driver was merged into the mainline Linux kernel, but it's only used by Android. Desktop Linux distributions don't use it at all.
The lineage here is BeOS to Android to billions of devices, all running a four thousand line kernel driver that most developers have never heard of. That's the hidden world Daniel was sensing.
It's worth asking, what happens when this hidden world has bugs? Because it does. Binder vulnerabilities are real and they're serious. A use-after-free in the Binder driver can allow a local app to escalate privileges to kernel level. These bugs get patched in the monthly Android security bulletins. But because Binder is in the kernel, exploiting it means game over for the security model. Every Binder transaction from every app flows through the compromised kernel.
Which is exactly the Pegasus attack surface. Not Binder specifically, but the kernel generally. Once you're in the kernel, the distinction between internal APIs and external APIs ceases to matter. You can see everything.
I think this is the right place to bring up the privacy indicator specifically, because Daniel mentioned it and it's the perfect case study. The green dot exists to tell the user, your microphone is active. It's a userspace indicator driven by a kernel-mediated IPC chain. But if the kernel is compromised, the indicator means nothing. The kernel could be delivering audio buffers to a spyware process and simply never sending the Binder transaction that triggers PermissionsController to show the green dot.
The indicator relies on the integrity of the entire chain. App to AudioService, AudioService to PermissionsController, PermissionsController to SystemUI. If any link is compromised, or if the kernel itself is lying, the indicator is theater.
This isn't a design flaw. It's a fundamental limitation. Any software indicator can be subverted by software running at a lower level. The only way to have a trustworthy hardware indicator is to put it in hardware. A physical LED that's wired directly to the microphone power line, such that it's physically impossible for the microphone to be powered without the LED lighting up. Some laptops have this for the webcam. Very few phones have it for the microphone.
The Librem five from Purism has hardware kill switches for the microphone, camera, and wireless. Physical switches that cut power. That's the gold standard. But for a mainstream Android or iOS device, you're trusting the kernel to honestly report microphone state. And if you can't trust the kernel, you can't trust anything.
Which is a sobering note to end the technical discussion on, but I think it's important. Daniel asked about internal APIs because he wanted to understand how the system works. And understanding how it works means understanding both its elegance and its limitations.
Now, Hilbert's daily fun fact.
Hilbert: During the Cold War, a Soviet cartographer working on Caspian basin surveys inserted a fictional town called Gorod Vodnyy into official state maps, and the phantom settlement persisted in reprinted atlases for over a decade before a geodetic survey team in nineteen seventy-two discovered it did not exist.
A fake town on official maps for ten years.
Someone had a sense of humor. Or a very slow supervisor.
So here's what I'm left thinking about after all of this. We've spent thirty minutes talking about internal APIs, and we've barely scratched the surface. There's the entire world of hardware interfaces, I2C, SPI, PCIe, that are also internal communication protocols. There's the firmware layer, where your WiFi chip runs its own operating system with its own internal APIs that the main CPU never sees. There's the baseband processor, which runs a completely separate RTOS with its own IPC mechanisms.
The rabbit hole goes deep. But I think the core answer to Daniel's question is clear. Yes, there's a massive web of internal APIs. No, it's not HTTP. The speed difference is three to four orders of magnitude. The security model is kernel-mediated rather than transport-encrypted. And the whole thing is invisible to users, invisible to most developers, and absolutely fundamental to how every computing device we use actually works.
If you're a developer who builds against REST APIs all day, the internal API world is worth understanding, not because you'll ever write a Binder interface, but because it reveals the assumptions baked into the tools you use. The reason your phone feels responsive is that thousands of microsecond-latency IPC calls are happening every second, and none of them are