Metadata-Version: 2.5
Name: ble-doctor
Version: 0.1.0
Summary: Score the security posture of a BLE device from a capture or live GATT enumeration. Defensive / CTF / own-gear use.
Project-URL: Homepage, https://github.com/Vaibhav91one/ble-doctor
Project-URL: Repository, https://github.com/Vaibhav91one/ble-doctor
Project-URL: Issues, https://github.com/Vaibhav91one/ble-doctor/issues
Project-URL: Changelog, https://github.com/Vaibhav91one/ble-doctor/blob/main/CHANGELOG.md
Author-email: Vaibhav91one <108279746+Vaibhav91one@users.noreply.github.com>
License: MIT
License-File: LICENSE
Keywords: audit,ble,bluetooth,ctf,gatt,pcap,pentest,security
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: System :: Networking
Requires-Python: >=3.10
Requires-Dist: bleak>=0.22
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Vaibhav91one/ble-doctor/main/docs/assets/logo-dark.svg">
  <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Vaibhav91one/ble-doctor/main/docs/assets/logo-light.svg">
  <img alt="ble-doctor" src="https://raw.githubusercontent.com/Vaibhav91one/ble-doctor/main/docs/assets/logo-light.svg" width="360" height="56">
</picture>

[![CI](https://github.com/Vaibhav91one/ble-doctor/actions/workflows/ci.yml/badge.svg)](https://github.com/Vaibhav91one/ble-doctor/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/ble-doctor?style=flat&color=000000&labelColor=000000&logo=pypi&logoColor=white)](https://pypi.org/project/ble-doctor/)
[![npm](https://img.shields.io/npm/v/ble-doctor?style=flat&color=000000&labelColor=000000&logo=npm&logoColor=white)](https://www.npmjs.com/package/ble-doctor)
[![Python](https://img.shields.io/badge/python-3.10%2B-000000?style=flat&logo=python&logoColor=white)](https://github.com/Vaibhav91one/ble-doctor/blob/main/pyproject.toml)
[![License: MIT](https://img.shields.io/badge/license-MIT-000000?style=flat)](https://github.com/Vaibhav91one/ble-doctor/blob/main/LICENSE)
[![Telemetry: none](https://img.shields.io/badge/telemetry-none-000000?style=flat)](#known-limits)

> **Authorization and scope.** ble-doctor is for **your own devices, CTF targets, and systems you
> have written permission to test**. It is a defensive analysis tool. `probe-writes` transmits to
> the target and is opt-in per characteristic. Do not point it at hardware you do not own or have
> explicit authorization to assess.

**Hand it a BLE capture or a device address. It scores the security posture 0-100 and tells you what is weak: pairing method, unauthenticated writes, DFU exposure, address rotation.**

```bash
ble-doctor analyze capture.pcap                       # offline: score a btmon/Ubertooth capture
ble-doctor scan --target AA:BB:CC:DD:EE:FF            # live read-only GATT enumeration + score
ble-doctor probe-writes AA:BB:CC:DD:EE:FF --char <uuid>   # opt-in: test one characteristic for open writes
```

Every subcommand takes `--json` for a stable machine-readable envelope. No telemetry; nothing
leaves your machine.

## Get started

### 1. Install

```bash
uvx ble-doctor analyze capture.pcap      # Python, via uv; nothing to install
pipx install ble-doctor
npx ble-doctor analyze capture.pcap      # Node launcher: runs the same version through uvx or pipx
pip install ble-doctor
```

From a clone: `pip install -e .`. Requirements:

| Need | For |
|---|---|
| Python 3.10+ | everything |
| `tshark` (Wireshark) on PATH | `analyze` (capture parsing, SMP pairing dissection) |
| `bleak` (installed with the package) and a BLE adapter | `scan`, `probe-writes` |
| `bluesploit` (optional) | an INFO cross-check inventory in `scan`; never moves the score |

### 2. Score a capture

```bash
ble-doctor analyze capture.pcap
```

```text
BLE Doctor — score: 85/100

[FAIL] mac_rotation (medium): ...
[PASS] pairing_method (info): ...
```

(Illustrative; real detail strings depend on the capture.) If the capture contains no SMP pairing
exchange the pairing check is skipped and the reason is printed, not scored.

### 3. Enumerate a live device

```bash
ble-doctor scan --target AA:BB:CC:DD:EE:FF      # or set BLE_DOCTOR_TARGET
```

`--target` takes an address or advertised name; with none given and exactly one device in range, it
uses that one. The connection is read-only.

### 4. Probe for unauthenticated writes (opt-in)

```bash
ble-doctor probe-writes AA:BB:CC:DD:EE:FF --char 0000ffe1-0000-1000-8000-00805f9b34fb
```

Connects **without bonding** and attempts one harmless write per named characteristic, then
classifies what the device answered. Nothing is written unless you name characteristics
(`--char`, `--service`) or pass `--allow-all`. Firmware-update characteristics are refused unless
you also pass `--allow-dangerous`, which can brick a target.

### 5. Teach your coding agent

```bash
ble-doctor install                  # Claude Code skill + Cursor rule + AGENTS.md block in this repo
ble-doctor install --print-only     # show what would be written
```

---

## CLI reference

### `ble-doctor scan [--target ADDR_OR_NAME] [--json]`

Live checks: rig state, GATT enumeration, write surface, DFU, bluesploit inventory.
`--target` defaults to `$BLE_DOCTOR_TARGET`.

### `ble-doctor analyze <capture_file> [--json]`

Offline checks on a btmon/btsnoop/Ubertooth capture: advertising-address rotation and SMP pairing.

### `ble-doctor probe-writes <address> [flags]`

| Flag | Meaning |
|---|---|
| `--char UUID` | characteristic to probe; repeatable |
| `--service UUID` | service whose characteristics may be probed; repeatable |
| `--allow-all` | probe every writable characteristic (firmware-update ones still refused) |
| `--allow-dangerous` | also probe firmware-update/erase characteristics. Real side effects. |
| `--payload HEX` | probe payload in hex (default is a harmless one) |
| `--json` | machine-readable output |

### `ble-doctor install [--agent claude|cursor|codex] [--print-only] [--force] [--dir PATH]`

Writes `.claude/skills/ble-doctor/SKILL.md`, `.cursor/rules/ble-doctor.mdc` and a managed block in
`AGENTS.md`. `--agent` is repeatable (default: all). Existing differing skill/rule files are kept
unless `--force`.

Exit codes: `0` ok, `1` the operation failed (missing tshark/bleak, unreadable capture), `2` bad arguments.

---

## JSON output

`analyze --json`:

```json
{
  "pairing_skipped": "no SMP pairing exchange in capture",
  "score": 100,
  "findings": [
    {"id": "mac_rotation", "severity": "info", "passed": true, "detail": "..."}
  ]
}
```

`scan --json` has the same `score` and `findings`, plus `rig_state`, `gatt_checks_skipped`
(null or a reason), `gatt_enumeration` (`depth`, `service_count`, `writable_char_count`),
`bluesploit_skipped`, `bluesploit_recon`, and, when enumeration succeeded, the raw `gatt_info`.

`probe-writes --json`: `{"results": [...], "writable_chars": [...], "findings": {"score", "findings": [...]}}`.

A non-null skip field means those checks **did not run**. It is never a pass.

## The score

`score = 100 x product(1 - weight/100)` over **failed** checks, rounded half-up. Weights: high 30,
medium 15, low 5, info 0. Monotonic (more or worse findings never raise it), with diminishing
returns, and one HIGH leaves 70 rather than zero.

## Check catalog

| ID | Severity | What it measures |
|---|---|---|
| `pairing_method` | high / medium / low / info | Pairing association model inferred from the SMP exchange (Core Spec Vol 3 Part H Table 2.8). Just Works is weakest; undeterminable or reserved IO capability is medium. |
| `unauthenticated_writes` | high / info | Characteristics writable without bonding. `scan` uses a heuristic; `probe-writes` measures it. |
| `dfu_entry_point` | high / info | HIGH only when the DFU control point is writable **without** bonding or encryption. A DFU service that is bond-protected, or merely present, is INFO. |
| `dfu_signing_enforced` | high / medium / info | Reads a recorded signing probe. With no probe it is INCONCLUSIVE at INFO (0 points): a queued probe, not a pass. |
| `dfu_surface_absent` | info | States whether "no DFU surface" is backed by a complete enumeration or is unknown. |
| `mac_rotation` | medium / info | Whether the advertising address rotates, duration-aware; too short a capture says so instead of judging. |
| `bluesploit_recon` | info | Read-only bluesploit module inventory as a manual-reproduction pointer. Never moves the score. |

Where a row lists several severities, the check picks one from the evidence; see `detail`.

## Known limits

- **Validated only against an nRF Connect test double and synthetic fixtures**, not a genuine
  third-party target. Treat results as unproven on real hardware until
  [#40](https://github.com/Vaibhav91one/ble-doctor/issues/40) lands.
- `requires_bonding` from `scan` is a heuristic (vendor characteristics assumed bond-protected);
  only `probe-writes` measures it.
- DFU **signing** is not probed live yet ([#41](https://github.com/Vaibhav91one/ble-doctor/issues/41)): it stays INCONCLUSIVE.
- `scan` cannot observe the pairing method (it is an SMP fact); use `analyze` on a capture.
- bleak + BlueZ can wedge discovery with `org.bluez.Error.InProgress`; the tool retries and then
  tells you to `sudo systemctl restart bluetooth`.
- Radio-layer only. Companion-app and cloud-side security are out of scope.
- No SARIF, MCP server, or CI action yet: see the [open issues](https://github.com/Vaibhav91one/ble-doctor/issues).

## Testing

```bash
pip install -e '.[dev]' && python -m pytest test/ -q
```

Capture tests need `tshark` and skip without it; CI installs it and fails if they skip.
See [AGENTS.md](AGENTS.md) for the contributor contract.

## License

[MIT](LICENSE)
