Bluehound
Continuous BLE and Classic Bluetooth scanner with time-searchable logging, a REST API, and a web dashboard. Built for macOS and embedded/sensor-node deployments with WiFi streaming support.
Bluehound scans nearby Bluetooth devices in a loop, enriches each observation (manufacturer, service UUIDs, device class, estimated distance, Apple Continuity data), and persists everything to SQLite. A built-in HTTP server exposes the data for the bundled dashboard or any client.
Features
- Dual-stack scanning — BLE via SimpleBLE, plus Classic Bluetooth inquiry and SDP service discovery on macOS via IOBluetooth.
- Time-searchable history — every observation is stamped with date, time, hour, and minute and indexed for fast range queries.
- Device enrichment — company/manufacturer lookup, standard service and characteristic UUID decoding, device-class decoding, and GATT characteristic enumeration on connect.
- Distance estimation — RSSI + Tx power path-loss model, with per-device distance history.
- Apple Continuity decoding — parses proximity/pairing advertisements.
- Passive security assessment — flags risky services (OBEX, SPP, HID, audio) and known-vulnerable device signatures, assigning a HIGH/MEDIUM/LOW/NONE risk level.
- Device nicknames — assign and manage human-readable labels per address.
- ESP32 ingestion — remote sensor nodes can POST scan batches over WiFi to
/esp32/scan. - REST API + web dashboard — a single-file HTML dashboard served over HTTP.
Requirements
- macOS (Classic Bluetooth support uses IOBluetooth/CoreBluetooth frameworks)
- CMake >= 3.15
- SQLite3
- Vendored dependencies (included in the repo):
- SimpleBLE static library at
simpleble/simpleble/build/lib/libsimpleble.a - cpp-httplib header at
third_party/httplib.h
- SimpleBLE static library at
Build
./build.sh
This verifies the vendored dependencies, configures with CMake into build/, and compiles. The resulting binary is build/bluehound.
To build manually:
mkdir -p build && cd build
cmake ..
make -j
Install system-wide with sudo make install from the build/ directory.
Run
./build/bluehound
On start, Bluehound initializes ble_scans.db, launches the HTTP server on port 8080 (bound to 0.0.0.0), and begins scanning. Shut down cleanly with Ctrl-C (SIGINT/SIGTERM are handled gracefully).
Default configuration (compiled into Config in bluehound.cpp):
| Setting | Default |
|---|---|
| Scan duration | 5000 ms per cycle |
| Scan interval | 1000 ms between cycles |
| Database path | ble_scans.db |
| HTTP port | 8080 |
| Max DB entries | 1,000,000 (rotates) |
Web dashboard
Serve the dashboard with the helper script (static server on port 8090):
python3 serve_dashboard.py
Then open http://127.0.0.1:8090. The dashboard queries the scanner's API on port 8080, so run the bluehound binary alongside it.
REST API
The scanner serves the following endpoints on port 8080:
| Method | Path | Description |
|---|---|---|
| GET | /health |
Health check |
| GET | /scans/recent?limit=N |
Most recent observations |
| GET | /scans/date/:date |
Observations for YYYY-MM-DD |
| GET | /scans/hour/:hour |
Observations for hour HH |
| GET | /stats |
Aggregate statistics |
| GET | /device/:address/count |
Observation count for a device |
| GET | /distance/:address |
Distance history for a device |
| GET | /characteristics/:address |
Discovered GATT characteristics |
| GET | /read/:address/:service/:characteristic |
Read a characteristic value |
| GET | /nickname/:address |
Get a device nickname |
| POST | /nickname/:address |
Set a device nickname |
| DELETE | /nickname/:address |
Remove a device nickname |
| GET | /nicknames |
List all nicknames |
| GET | /security/:address |
Security assessment for a device |
| GET | /security |
All security assessments |
| GET | /tracking/:address |
Tracking data (device info, Apple Continuity) |
| POST | /esp32/scan |
Ingest a scan batch from a remote ESP32 node |
CORS is enabled for all origins.
Data model
Observations are stored in ble_scans.db (SQLite). Key tables:
ble_scans— every observation, with timestamps, RSSI, manufacturer/company, service UUIDs, estimated distance, protocol type, and Classic device-class/SDP fields.device_distance_history— per-device RSSI/distance samples over time.device_characteristics— discovered GATT services and characteristics.device_nicknames— user-assigned labels.security_assessments— risk level, vulnerabilities, and recommendations per device.device_tracking_data— device model/firmware/serial, Apple Continuity, pairing status, and tracking identifiers.
Project layout
| Path | Purpose |
|---|---|
| bluehound.cpp | Scanner core: scan loop, SQLite logging, HTTP API, BLE decoding, security assessment |
| classic_bluetooth.h / classic_bluetooth.mm | Classic Bluetooth inquiry and SDP via IOBluetooth (Objective-C++) |
| web_dashboard.html | Single-file web dashboard |
| serve_dashboard.py | Static server for the dashboard |
| CMakeLists.txt / build.sh | Build configuration |
simpleble/ |
Vendored SimpleBLE |
third_party/httplib.h |
Vendored cpp-httplib |
Notes
- Classic Bluetooth scanning and the IOBluetooth-backed features are macOS-only. BLE scanning via SimpleBLE is portable, but the build as configured links macOS frameworks.
- The ESP32 endpoint uses lightweight string-based JSON parsing; use a proper JSON library for production ingestion.
- Security assessments are heuristic and intended for authorized inventory and awareness, not as a substitute for a full audit.