Skip to content

Security: ReconGrunt/FlipperTalk

Security

SECURITY.md

Security

Dependency posture

The complete third-party runtime tree is one package:

pyserial==3.5

That is not an accident of a small feature set. The MCP protocol layer, the protobuf wire codec and the PNG encoder are all implemented in-tree specifically to avoid pulling a large dependency graph into a program that talks to a USB device and nothing else.

For comparison, pip install mcp (the reference MCP SDK) resolves to 34 packages, including cryptography, PyJWT, uvicorn, starlette, httpx and python-multipart — a web server stack, its TLS layer and its authentication layer, none of which this server has any use for. Each is a package whose maintainer account, CI pipeline and release tooling becomes part of your attack surface.

What replaced them:

Concern Instead of In-tree
MCP transport mcp (+33 transitive) jsonrpc.py — newline-delimited JSON-RPC 2.0 on stdio
Protobuf protobuf proto.py + pb.py — the messages this server uses, across all three firmware dialects
PNG output Pillow png.py — stdlib zlib + struct

pyserial was kept because cross-platform serial I/O genuinely is fiddly (termios on POSIX, CreateFile handles on Windows). It is pure Python, has no build step and no install scripts, and has been stable since 2020.

Why the dependency count is the point

The npm incidents of mid-2026 are the model this posture is built against:

  • @redhat-cloud-services / Miasma (disclosed 1 June 2026) — 96 versions across 32 packages carried a credential stealer in a preinstall script, which runs automatically during install, before any application code, with no warning. The initial compromise was a maintainer's GitHub account, taken via a malicious VS Code extension.
  • The node-gyp / binding.gyp worm — self-propagating, and reached packages through native build steps.

Neither of these could reach a Python project, and that is the first line of defence here. But the mechanism generalises: install-time code execution in a transitive dependency you never chose. The mitigations that do generalise:

  • A dependency you do not have cannot be compromised. One package instead of thirty-four is the single largest reduction available.
  • No install scripts anywhere in the tree. pyserial is a pure-Python wheel; there is no setup.py build step, no native compilation, no post-install hook.
  • Hash pinning. requirements.lock pins by SHA-256 for both the wheel and the sdist. Install with pip install --require-hashes -r requirements.lock and a substituted artifact fails closed, even if PyPI itself is serving it.
  • Vendored schemas are data, never code. The proto/{official,momentum,unleashed}/ files are reference material for humans reading pb.py. Nothing reads, compiles or executes them at runtime — there is no code generation step to poison, and no build-time fetch from three separate upstream repositories.

Runtime behaviour

  • The server makes no network connections. It opens a serial port and spawns qFlipper-cli as a subprocess. There is no telemetry, no update check, and no outbound socket of any kind.
  • stdout carries only MCP protocol messages. Every diagnostic goes to stderr; a stray write to stdout would corrupt the transport.
  • Subprocess invocation passes an argument list, never a shell string, so there is no shell to inject into.

Guardrails

Destructive operations are hidden, not merely refused. A tool omitted from tools/list cannot be invoked by a model that never sees it — a stronger guarantee than asking it to supply a confirmation flag it can satisfy itself.

Variable Default Effect
FLIPPERTALK_TOOLSETS all but network Which tool groups are exposed at all
FLIPPERTALK_ALLOW_DESTRUCTIVE unset Exposes qflipper_restore, qflipper_flash_firmware, qflipper_erase, qflipper_wipe, flipper_install_update, recursive delete, and DFU/UPDATE reboot
FLIPPERTALK_READ_ONLY unset Withholds every mutating tool; only inspection and screenshots remain
FLIPPERTALK_LOCAL_ROOTS unset Confines host filesystem reads and writes to an allowlist of directories
FLIPPERTALK_DIALECT auto Forces a firmware family when detection is wrong
FLIPPERTALK_IDLE_TIMEOUT 120 Seconds before an idle serial connection is dropped

The QFLIPPER_MCP_* names used before the rename are still honoured for one version. Dropping them immediately would have silently removed a user's safety gating on upgrade, which is worse than carrying an alias.

The network toolset is opt-in

Unleashed firmware exposes TCP, HTTP and WebSocket calls plus GPS position over RPC. Those are implemented, but the network toolset is not enabled by default: letting a model open sockets from the device, or read its position, is a materially different trust decision from reading the SD card. Enable it deliberately with FLIPPERTALK_TOOLSETS=all, or by naming network explicitly.

Honest sensing is a safety property, not a feature

flipper_scan_nearby reports any source it could not run as not attempted, with a reason — never as "nothing found". No Flipper firmware can scan for BLE, and the device has no WiFi radio. A tool that quietly returned an empty result for those would lead a model to tell a user their surroundings are clear when nothing ever looked. The test suite asserts this directly, including that the summary text never describes an unrun source as clear.

Additional validation:

  • Device paths must resolve under /ext, /int or /any. .. segments are collapsed before the root is checked, so /ext/../../etc/passwd is rejected rather than silently escaping.
  • Uploads are verified by md5 against the device after writing, and the tool fails loudly on mismatch rather than reporting success.

The test suite asserts the negative case for each gate — that a hidden tool is absent from tools/list and refused when called directly. A guard that has only ever been tested against traffic it should allow is not a guard.

Physical access

This server gives an MCP client real control of attached hardware: reading and writing the SD card, launching apps, sending button presses, and (when enabled) flashing firmware. Treat granting it to a model as equivalent to handing over the device. Run with FLIPPERTALK_READ_ONLY=1 if you only need inspection.

Reporting a vulnerability

Open a security advisory on the repository rather than a public issue.

There aren't any published security advisories