ISS
← Field Notes
Hardware screenshot
September 2, 2026 · Hardware · 3 min read

Building a USB HID That the Operating System Believes

What it takes to make an ESP32-S3 present as a keyboard and mouse indistinguishable from real hardware, and why "indistinguishable" is a systems problem, not a soldering one.

There is a whole class of automation that software cannot do cleanly, because the software is the thing being watched. Anything that inspects how input arrives (anti-cheat, kiosk lockdowns, remote-desktop guards, accessibility gates) can tell the difference between a synthetic event injected into the OS and a real keypress arriving over USB. The event looks identical once it is inside the machine. Getting it into the machine the way real hardware would is the hard part.

That is the problem ACID HID exists to solve: an ESP32-S3 that a host treats as an ordinary USB keyboard and mouse, because from the host's point of view that is exactly what it is.

Why software input isn't enough

When a script moves the mouse or types a key, it calls an OS API. The event is born inside the operating system, already past the driver layer, carrying metadata that says so. A determined observer on the same machine can read that metadata. The higher the stakes (competitive games, secured terminals, testing rigs that must mirror real users), the more likely something is reading it.

Real hardware doesn't have that tell. A USB keyboard enumerates as a Human Interface Device, negotiates a report descriptor with the host, and then streams input reports over an interrupt endpoint. The OS builds the same events, but they enter from outside, through the driver stack, exactly like every other device. There is no flag that says "a program made this," because no program did.

So the goal is not "send input." The goal is to be the device that input comes from.

The systems part

The ESP32-S3 has native USB, so presenting as a HID is table stakes. The interesting work is everything around making it convincing and controllable at the same time:

  • A believable descriptor. The report descriptor is the device's self-description: what kind of keys, what mouse axes, what report cadence. Get it subtly wrong and the host still works but looks synthetic to anything paying attention. It has to match what a real device of that class would actually declare.
  • Timing that looks human. Real input has jitter. Hardware that fires perfectly-spaced reports is its own signature. The controller has to shape movement and keystroke timing so the stream looks organic, not just the individual events.
  • A control channel that isn't the HID channel. You still need to tell the device what to do. Doing that over the same USB interface would defeat the point, so control rides a separate path, BLE or a second interface, kept cleanly off the surface the host inspects.
  • Determinism under load. A microcontroller juggling USB, BLE, and motion planning has to never stall the HID endpoint, because a hitch in the report stream is exactly the kind of anomaly that gets noticed.

None of that is soldering. It is the same discipline as any real-time system: keep the thing the observer can see perfectly ordinary, and push all the complexity onto channels and timing they can't.

The lesson that generalizes

The reason this is a field note and not just a hardware writeup is that the core move shows up everywhere I work. When you need a system to be trusted by something that is actively checking it, you don't win by being cleverer at the boundary the checker watches. You win by making that boundary boring, indistinguishable from the legitimate case, and moving your cleverness somewhere the checker isn't looking.

A HID the OS believes is the physical version of a principle that applies to APIs, agents, and protocols just as well: trust is granted at the interface, so the interface is where you spend your restraint, not your tricks.


ACID HID is built at Moonshine Labs and is open for pre-order at acidcat.dev.

Have something that needs building, or stabilizing?

These notes are the work log. The paid work runs through Moonshine Labs, my product and engineering studio. Tell us what you're building, or grab a call.

Work with us →
the studio: moonshinelabs.io