Back to KB
Difficulty
Intermediate
Read Time
9 min

Building a JavaScript Keylogger: How Keystroke Capture Works in Node.js

By Codcompass Team··9 min read

Architecting Native-Grade Input Telemetry Pipelines in Node.js

Current Situation Analysis

Security researchers, EDR vendors, and desktop analytics teams frequently face a deployment friction problem: building lightweight, system-wide input monitoring agents traditionally requires dropping into C/C++, writing kernel-mode drivers, or implementing complex DLL injection chains. These approaches increase binary size, complicate cross-platform compatibility, and trigger aggressive heuristic detection in modern endpoint protection suites.

The misconception that JavaScript cannot handle low-level input telemetry stems from two architectural realities. First, Node.js operates on a single-threaded event loop, making synchronous native calls risky if not properly isolated. Second, Windows keyboard input does not deliver semantic characters directly to applications. The OS routes hardware interrupts through a scancode layer, translates them into virtual key codes (VK codes), and queues them for consumption. Bridging this gap requires precise FFI management, stateful modifier tracking, and cursor-aware buffer reconstruction.

Despite these hurdles, the Windows user-mode API provides a clean interception point: the WH_KEYBOARD_LL low-level keyboard hook. It operates entirely in user space, requires no DLL injection, and delivers structured event data (KBDLLHOOKSTRUCT) containing hardware scancodes, virtual key codes, state flags, and timestamps. When paired with modern Node.js native binding libraries, this hook enables a high-fidelity telemetry pipeline that matches C++ agents in data richness while retaining JavaScript's rapid iteration, cross-platform tooling, and stream-processing ecosystem.

WOW Moment: Key Findings

The choice of input capture mechanism dictates system overhead, reconstruction accuracy, and deployment footprint. Below is a comparative analysis of the primary Windows input interception strategies, measured against telemetry engineering requirements.

ApproachScopeCPU OverheadImplementation ComplexityReconstruction Fidelity
Low-Level Hook (WH_KEYBOARD_LL)System-wide<0.5%MediumHigh (structured events, timestamps)
Raw Input APIPer-applicationLowHighMedium (requires device registration)
Polling (GetAsyncKeyState)System-wide2-8% (interval-dependent)LowLow (misses rapid taps, no release events)
Thread-Specific Hook (WH_KEYBOARD)Per-threadLowMediumMedium (limited to target process queue)

The low-level keyboard hook emerges as the optimal foundation for Node.js telemetry agents. It avoids the polling waste of GetAsyncKeyState, eliminates the per-application registration overhead of Raw Input, and provides the complete key-down/key-up lifecycle required for accurate text reconstruction. Crucially, it runs in user mode, meaning the agent can be deployed as a standard executable without requiring kernel signatures or administrative driver installation.

Core Solution

Building a production-ready input telemetry pipeline requires decoupling three distinct concerns: native hook registration, VK-to-character translation, and session reconstruction. The architecture follows a stream-based design where raw events flow through a stateful translator and into an async reconstruction buffer.

Architecture Decisions & Rationale

  1. FFI Library Selection: koffi replaces older ffi-napi bindings. It offers zero-allocation callback marshaling, native TypeScript definitions, and significantly lower overhead when handling high-frequency Windows messages.
  2. Callback Isolation: Windows hook callbacks execute synchronously on the thread that installed the hook. Blocking this thread stalls the OS message queue. We route captured events into a Readable stream backed by a bounded queue, allowing the event loop to process telemetry asynchronously.
  3. Stateful Translation: VK codes represent physical key positions, not semantic output. Translation requires tracking modifier state (Shift, Ctrl, Alt, Caps Lock), handling layout variations, and resolving dead keys. We implement a dedicated VkTranslator class that maintains a mutable state snapshot.
  4. Cursor-Aware Reconstruction: Linear character appending fails when users navigate with arrow keys, paste content, or use backspace. The reconstructor maintains a mu

🎉 Mid-Year Sale — Unlock Full Article

Base plan from just $4.99/mo or $49/yr

Sign in to read the full article and unlock all 635+ tutorials.

Sign In / Register — Start Free Trial

7-day free trial · Cancel anytime · 30-day money-back