# Bluehood shows how much a Bluetooth adapter can see without asking

> Bluehood is a Python scanner that passively records BLE and Classic Bluetooth advertisements, maps MAC addresses to vendors, and builds presence timelines and co-presence correlations from the result. It is MIT licensed, flagged as alpha software, and it is a privacy awareness tool that doubles as a tracking tool, which is exactly the point the project makes about itself.

**dannymcc/bluehood** — Monitor your local neighbourhood's bluetooth activity

- Repository: https://github.com/dannymcc/bluehood
- Stars: 1,161 · Forks: 86
- Language: Python
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/dannymcc-bluehood

## The stated purpose is to demonstrate passive Bluetooth surveillance

The README is unusually direct about what this project is for. Bluehood is described as an educational tool to raise awareness about Bluetooth privacy, and it says outright that it is a weekend project while noting the implications are worth thinking about.

The inspiration is named: the WhisperPair vulnerability, tracked as CVE-2025-36911, which highlighted privacy risks in Bluetooth devices. The reasoning that follows is that thousands of Bluetooth devices surround us at all times, phones, cars, TVs, headphones, hearing aids and delivery vehicles among them, and that Bluehood demonstrates how simple it is to passively detect these devices and observe patterns in their presence.

Then come the examples of what enough data would yield. Understanding what time someone typically walks their dog. Detecting when a visitor arrives at a house. Identifying patterns in daily routines based on device presence. The README's summary is that this metadata can reveal surprisingly personal information without any active interaction with the devices.

That framing is the honest one, and it also describes what the software does. The scanner continuously scans for nearby devices, both BLE and Classic, identifies them by vendor through MAC address lookup and by BLE service UUIDs, classifies them into categories such as phones, audio, wearables, IoT and vehicles, tracks presence patterns over time with hourly and daily heatmaps, filters out noise from randomized MAC addresses, analyses correlations between devices, sends push notifications when watched devices arrive or leave, and provides a web dashboard for all of it.

So this is a privacy tool in the sense that it makes the surveillance visible. It is also, unavoidably, a surveillance tool, and the ethical and legal questions it raises are the same questions it asks of everyone else. Any evaluation has to hold both of those at once.

## Four dependencies and a SQLite database

The dependency list in pyproject.toml is short and says a lot about the implementation.

```toml
dependencies = [
    "bleak>=0.21.0",
    "aiosqlite>=0.19.0",
    "aiohttp>=3.9.0",
    "mac-vendor-lookup>=0.1.12",
]
```

bleak is the asynchronous Bluetooth stack, and the version floor is 0.21.0. aiosqlite means the storage is SQLite driven asynchronously from the same event loop, which is the natural choice for a scanner that writes a sighting every time it hears an advertisement. aiohttp serves the web dashboard and the API from the same process. mac-vendor-lookup is the vendor database.

The project requires Python 3.11 or newer per the requires-python field, and the console script is a single entry point.

```toml
[project.scripts]
bluehood = "bluehood.daemon:main"
```

There is an optional extras group for metrics, which pulls in prometheus-client at 0.21.0 or newer. The Dockerfile exposes two ports for this reason, 8080 for the web dashboard and 9199 for the metrics endpoint. Everything else in the packaging is plain setuptools with find_packages pointing at the repository root, and the source lives in a bluehood/ package directory.

The scale implied by that dependency set is worth noting. There is no database server, no message broker and no cloud component. The whole system is one Python process talking to the Bluetooth stack, writing to a local file and serving a dashboard, which is what makes it runnable on the Raspberry Pi class of hardware the topic list mentions.

## The container runs privileged with host networking, and BlueZ stays on the host

The Docker setup is where the operational requirements become concrete, and the compose file is unusually forthcoming about them.

```yaml
    # Required for Bluetooth access
    network_mode: host
    privileged: true

    # Mount D-Bus socket for BlueZ communication
    volumes:
      - /var/run/dbus:/var/run/dbus:ro
      - bluehood-data:/data
```

Host networking and privileged mode are both required for BLE scanning, and the compose file says so in a comment. The D-Bus socket is mounted read only, because the container talks to BlueZ over the system bus rather than talking to the adapter directly.

The prerequisite that catches people out is that BlueZ must be installed and running on the host before the container starts, and the image itself does not include it. The README gives the install commands for Debian and Ubuntu and for Arch, and the failure message to expect if you skip it.

```bash
sudo apt install bluez
sudo systemctl enable --now bluetooth
```

The error you would see is a D-Bus ServiceUnknown for org.bluez, and the compose file encodes that check as a healthcheck that runs dbus-send against the system bus and greps for org.bluez. That is a good piece of engineering, since the most common failure of this container is a missing host dependency rather than anything inside the image.

The second adapter requirement is the hardware one. The host needs a BLE-capable adapter, Bluetooth 4.0 or newer, that supports the Central role, and older Bluetooth 2.x and 3.x adapters will produce a message about no adapters with BLE central role found. The compose file also shows how to select specific adapters, with BLUEHOOD_ADAPTER and BLUEHOOD_CLASSIC_ADAPTER for hci0 and hci1 style device names, which is how a machine with several radios would separate the BLE scan from the Classic scan.

## The image bundles the MAC vendor database at build time

The Dockerfile has one step that deserves attention, and it is the one that keeps the scanner working without an internet connection.

```dockerfile
RUN python -c "from mac_vendor_lookup import MacLookup; MacLookup().update_vendors()" \
    && cp /root/.cache/mac-vendors.txt /data/mac-vendors.txt \
    && chown bluehood:bluehood /data/mac-vendors.txt \
    || echo "Warning: could not bundle vendor DB"
```

The vendor database is fetched at image build time and copied into the data directory, so the running container has a local lookup table. That matches the feature description, which says the MAC address vendor lookup uses a local database with an online API fallback. The build step tolerates failure with a warning rather than failing the build, which is the right call for a non-essential table, and the `||` at the end of the chain covers the case where the fetch did not produce a file.

The rest of the image is straightforward. It is based on python:3.12-slim with bluez, libglib2.0-dev, libdbus-1-dev and gosu installed. The gosu package is there for the entrypoint script, which handles PUID and PGID user switching, so the container process can run as the host user for bind mount permissions. The bluehood user is created and added to the bluetooth group.

Two environment variables are baked in with the default RUN command.

```dockerfile
ENV BLUEHOOD_DATA_DIR=/data
ENV PYTHONUNBUFFERED=1
```

The data directory is where the database and the bundled vendor table live, and the unbuffered output is what makes docker compose logs -f show scan activity as it happens rather than in chunks. The image is published at ghcr.io/dannymcc/bluehood:latest, and the compose file also references a systemd unit, bluehood.service, plus an install.sh and a grafana/ directory, so there is a native install path alongside the container one.

## The analytics layer is the part that is genuinely unusual

The feature list under Analytics is longer than a hobby dashboard usually goes, and several items describe real analytical work rather than presentation.

There is a 30-day presence timeline and a signal strength history chart with 7-day data. There are hourly and daily activity heatmaps showing when devices are active, and a pattern analysis that produces statements of the form Weekdays, evenings 5PM-9PM. There is dwell time analysis showing total time devices spend in range, and proximity zones, immediate, near, far and remote, derived from signal strength.

Three of these deserve more than a mention.

Device correlation detection finds devices that appear together, based on co-presence plus synchronized arrival and departure. MAC-rotation linkage is the most interesting, because it attacks the privacy mechanism directly. It heuristically links randomized identifiers that hand off in time, share a similar signal strength, and ping at a similar cadence, and it presents the result as likely same device. In other words, it tries to re-identify a device precisely when that device is trying not to be re-identified, which is a direct measurement of how weak the randomization defence is.

That is the most valuable thing in the project for its stated purpose, and it is also the most sensitive thing in it. The feature list also includes a randomized MAC filter that hides privacy-rotated devices from the main view, so the tool both filters them and can link them, and a screenshot mode to obfuscate MACs and names for safe sharing, which is an acknowledgement that the dashboard is itself a leak.

The remaining operational features are about running it for a long time. Storage rotation automatically prunes sightings older than a configurable number of days, and can optionally restrict pruning to whole stale devices seen fewer than a minimum number of times, while watched devices are never pruned. A heartbeat check-in periodically POSTs status to an uptime monitoring service such as Uptime Kuma or Healthchecks.io, and both the heartbeat and the pruning are configurable from the web UI or via environment variables.

## Notifications, export and access control

Push notifications go through ntfy, either the public ntfy.sh service or a self-hosted ntfy server, and the README says a self-hosted one is set through the BLUEHOOD_NTFY_SERVER variable. The notification triggers are new device detected, watched device returned and watched device left, with configurable thresholds for arrival and departure, so the arrival and departure detection is not a fixed time window but a tunable one.

The web interface has a set of features aimed at people who use this daily. There is a compact and detailed view toggle, keyboard shortcuts with a question mark to list them, device groups for organising related devices, and optional authentication to secure access. Devices themselves can be marked as watched, given a custom name while the advertised name stays visible alongside it, put into custom groups, given a custom override of the detected classification, and annotated with notes and tags.

The CSV export is worth reading carefully, because it is a data export that reflects the whole filtered set rather than just the current page, and its column list is a useful inventory of what the system records about every device it hears. The columns are MAC, vendor, identifier, type, BT type, device class, watched and ignored flags, first and last seen, sightings count, group, service UUIDs and notes. That is the complete record of a person's radio environment, per device, in one file.

The project also has a security history. The release list includes v0.7.1 on 2026-06-10 labelled as a security fix for CVE-2026-49994, followed by v0.7.0 in April 2026 and v0.8.0 on 2026-09-12, and the last push to master was on 2026-09-12. The optional authentication on the dashboard is a mitigating control, and a release that fixed a security vulnerability is a reminder that an unauthenticated web interface reachable on a network is a real exposure.

## Where Bluehood is the wrong tool

The first and most important limitation is legal and ethical, and the project states it. Bluehood is alpha software, in early development, not ready for production use, with features that may change, break or be removed without notice, and it says to use it at your own risk and to treat collected data as experimental. Running it on a network you do not control, or on hardware in a shared space, is likely to fall under laws that differ by jurisdiction, and the presence heatmaps and the routine inference features are precisely the kind of derived data that privacy law tends to treat as personal data even when the raw records are MAC addresses.

The second limitation is the hardware. The scan requires a BLE adapter with Central role support, and the README says older Bluetooth 2.x and 3.x adapters do not work. A Raspberry Pi with a built-in adapter may or may not have the right chipset, and the container needs privileged mode and host networking, which is a large privilege grant for a consumer device on your home network.

The third limitation is the data quality. Vendor lookup depends on a database that is a community maintained approximation, and the README describes a local database with an online API fallback, which means classification quality depends on network access. Classification itself is heuristic, based on BLE service UUID fingerprinting and Classic Bluetooth device class parsing, and the app lets you override a classification by hand, which is a hint that the automatic one is not always right.

The fourth limitation is scale and duration. This is a single-process SQLite application designed for one site. There is no multi-sensor aggregation, no federation between nodes, and no site-to-site correlation. Anyone trying to monitor a city rather than a room is looking at a different product category entirely.

## How it differs from a Bluetooth sniffer

The comparison worth making is with the packet capture tools that security researchers already use, and the difference in approach is instructive.

A traditional Bluetooth sniffer captures over-the-air packets and decodes them, which gives you the protocol conversation. Bluehood does not decode packets. It reads the advertisements that devices broadcast, which is the passive, periodic broadcast every BLE device makes so that nearby phones and laptops can find it. The tool's data source is the beacon, not the conversation, which is why it needs no pairing, no credentials and no active interaction, and why it works on every device in range regardless of who owns it.

The consequence is that a sniffer sees far more but is far more intrusive to operate and legally messy in a shared space, because it is capturing traffic addressed to a specific peer. Bluehood sees far less and requires nothing, which is exactly what makes it a better demonstration of the point: the metadata a device broadcasts for practical reasons, to be discoverable, is enough to build a presence profile.

The second difference is the layer above. A sniffer produces a capture file that an analyst opens in Wireshark. Bluehood produces a running dashboard with a database, an analytics layer, notifications and a CSV export, so it is a product rather than a tool. That product layer is what separates an interesting script from something that changes behaviour: once the tool tells you that a device left at 8pm, you act on it differently than when you are reading a hex dump.

The third difference is intent. A sniffer is used to find a specific protocol interaction. Bluehood is used to look at the aggregate picture, and the analytics features exist to make aggregate patterns visible. The MAC-rotation linkage is the clearest example, since it is not looking for any particular device, it is asking how well the anonymisation layer on modern phones holds up against passive observation over weeks.

## Conclusion

Bluehood fits a security researcher, a privacy educator or a developer who wants to see concretely what a Bluetooth adapter can observe without any interaction with the devices around it, and the code reads as a competent implementation of a real product surface with a dashboard, CSV export, metrics and a container image. It does not fit a deployment on a shared or public network, and it is not something to run on hardware you do not control or in a space where other people have not consented to being recorded. Before running it, read the alpha warning in the README, because the project says features may change or be removed without notice and that collected data should be treated as experimental, and note the security fix in v0.7.1 for CVE-2026-49994 and the current release v0.8.0 from 2026-09-12.

## FAQ

### What Bluetooth hardware does Bluehood need?

A BLE-capable adapter, Bluetooth 4.0 or newer, that supports the Central role. Older Bluetooth 2.x and 3.x adapters do not support BLE scanning, and you will see a message about no adapters with BLE central role found. BlueZ must also be installed and running on the host, since the Docker image does not include it.

### How do I run Bluehood with Docker?

Create a docker-compose.yml or download the one from the repository and run docker compose up -d, then follow the logs with docker compose logs -f. The image is ghcr.io/dannymcc/bluehood:latest, the dashboard is at http://localhost:8080, and the container needs network_mode host and privileged true along with a read only mount of /var/run/dbus.

### What does Bluehood store about the devices it detects?

The CSV export lists the columns: MAC, vendor, identifier, type, BT type, device class, watched and ignored flags, first and last seen, sightings count, group, service UUIDs and notes. The README states that data collected should be treated as experimental and that the project is for privacy awareness.

### Can Bluehood link devices that rotate their MAC address?

Yes, the MAC-rotation linkage feature heuristically links randomized identifiers that hand off in time, share a similar signal strength, and ping at a similar cadence, and it labels the result as likely same device. The tool also has a randomized MAC filter that hides privacy-rotated devices from the main view.

### How do I send Bluehood notifications to my phone?

Notifications go through ntfy, using either ntfy.sh or a self-hosted ntfy server set with the BLUEHOOD_NTFY_SERVER variable. You can notify on new device detected, on watched devices returning and on watched devices leaving, with configurable thresholds for arrival and departure.

## Sources

- [dannymcc/bluehood on GitHub](https://github.com/dannymcc/bluehood)
- [Issues](https://github.com/dannymcc/bluehood/issues)
- [License: MIT](https://github.com/dannymcc/bluehood/blob/master/LICENSE)
- [README](https://github.com/dannymcc/bluehood/blob/master/README.md)
- [Releases](https://github.com/dannymcc/bluehood/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dannymcc-bluehood
