iotaudit 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. iotaudit-0.1.0/.github/workflows/ci.yml +26 -0
  2. iotaudit-0.1.0/.github/workflows/publish.yml +50 -0
  3. iotaudit-0.1.0/.gitignore +10 -0
  4. iotaudit-0.1.0/CHANGELOG.md +32 -0
  5. iotaudit-0.1.0/LICENSE +21 -0
  6. iotaudit-0.1.0/PKG-INFO +143 -0
  7. iotaudit-0.1.0/README.md +113 -0
  8. iotaudit-0.1.0/ROADMAP.md +108 -0
  9. iotaudit-0.1.0/docs/sample-report.html +128 -0
  10. iotaudit-0.1.0/docs/sample-report.png +0 -0
  11. iotaudit-0.1.0/pyproject.toml +45 -0
  12. iotaudit-0.1.0/scripts/gen_sample_report.py +72 -0
  13. iotaudit-0.1.0/scripts/verify_wheel_install.py +19 -0
  14. iotaudit-0.1.0/src/iotaudit/__init__.py +3 -0
  15. iotaudit-0.1.0/src/iotaudit/analyze.py +260 -0
  16. iotaudit-0.1.0/src/iotaudit/arp.py +313 -0
  17. iotaudit-0.1.0/src/iotaudit/capture.py +356 -0
  18. iotaudit-0.1.0/src/iotaudit/cli.py +224 -0
  19. iotaudit-0.1.0/src/iotaudit/consent.py +29 -0
  20. iotaudit-0.1.0/src/iotaudit/data/NOTICE +8 -0
  21. iotaudit-0.1.0/src/iotaudit/data/dbip-country-lite.mmdb +0 -0
  22. iotaudit-0.1.0/src/iotaudit/data/ip2asn-lite.tsv.gz +0 -0
  23. iotaudit-0.1.0/src/iotaudit/data/oui.txt.gz +0 -0
  24. iotaudit-0.1.0/src/iotaudit/discover.py +271 -0
  25. iotaudit-0.1.0/src/iotaudit/enrich.py +327 -0
  26. iotaudit-0.1.0/src/iotaudit/grading.py +328 -0
  27. iotaudit-0.1.0/src/iotaudit/hotspot.py +41 -0
  28. iotaudit-0.1.0/src/iotaudit/identify.py +405 -0
  29. iotaudit-0.1.0/src/iotaudit/mcp_server.py +57 -0
  30. iotaudit-0.1.0/src/iotaudit/models.py +101 -0
  31. iotaudit-0.1.0/src/iotaudit/profiles/generic-camera.yaml +10 -0
  32. iotaudit-0.1.0/src/iotaudit/profiles/imipaw-feeder.yaml +13 -0
  33. iotaudit-0.1.0/src/iotaudit/profiles/iotbing-cloud.yaml +13 -0
  34. iotaudit-0.1.0/src/iotaudit/profiles/tuya-generic.yaml +10 -0
  35. iotaudit-0.1.0/src/iotaudit/profiles/xiaomi-miio.yaml +9 -0
  36. iotaudit-0.1.0/src/iotaudit/profiles.py +59 -0
  37. iotaudit-0.1.0/src/iotaudit/report.py +17 -0
  38. iotaudit-0.1.0/src/iotaudit/review.py +63 -0
  39. iotaudit-0.1.0/src/iotaudit/scan.py +59 -0
  40. iotaudit-0.1.0/src/iotaudit/templates/report.html.j2 +96 -0
  41. iotaudit-0.1.0/src/iotaudit/traffic.py +143 -0
  42. iotaudit-0.1.0/tests/fixtures/feeder_capture.json +172 -0
  43. iotaudit-0.1.0/tests/fixtures/feeder_capture_run3.json +902 -0
  44. iotaudit-0.1.0/tests/fixtures/feeder_capture_run4.json +973 -0
  45. iotaudit-0.1.0/tests/test_core.py +472 -0
  46. iotaudit-0.1.0/tests/test_ipv6_and_fixtures.py +138 -0
@@ -0,0 +1,26 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ matrix:
11
+ python-version: ["3.10", "3.12"]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: ${{ matrix.python-version }}
17
+ - run: pip install -e ".[dev,mcp]"
18
+ - run: ruff check src tests
19
+ - run: pytest -q
20
+ - name: Build wheel and verify install
21
+ run: |
22
+ pip install build
23
+ python -m build
24
+ python -m venv /tmp/iotaudit-ci
25
+ /tmp/iotaudit-ci/bin/pip install dist/*.whl
26
+ /tmp/iotaudit-ci/bin/python scripts/verify_wheel_install.py
@@ -0,0 +1,50 @@
1
+ name: Publish to PyPI
2
+
3
+ # Publishes to PyPI via Trusted Publishing (OIDC) when a GitHub Release is published.
4
+ # No API tokens are stored. Set up the PyPI "pending publisher" once:
5
+ # PyPI project 'iotaudit' -> owner emirmrkaljevic, repo iotaudit,
6
+ # workflow 'publish.yml', environment 'pypi'. Requires maintainer 2FA/approval to run.
7
+ on:
8
+ release:
9
+ types: [published]
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ build:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+ - name: Build sdist and wheel
24
+ run: |
25
+ pip install build
26
+ python -m build
27
+ - name: Verify the built wheel installs and runs
28
+ run: |
29
+ python -m venv /tmp/verify
30
+ /tmp/verify/bin/pip install dist/*.whl
31
+ /tmp/verify/bin/python scripts/verify_wheel_install.py
32
+ /tmp/verify/bin/iotaudit --version
33
+ - uses: actions/upload-artifact@v4
34
+ with:
35
+ name: dist
36
+ path: dist/
37
+
38
+ publish:
39
+ needs: build
40
+ runs-on: ubuntu-latest
41
+ environment: pypi # add a required reviewer here to gate releases
42
+ permissions:
43
+ id-token: write # OIDC token for Trusted Publishing
44
+ steps:
45
+ - uses: actions/download-artifact@v4
46
+ with:
47
+ name: dist
48
+ path: dist/
49
+ - name: Publish to PyPI
50
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.pcap
7
+ *.pcapng
8
+ iotaudit-report*.html
9
+ .pytest_cache/
10
+ .ruff_cache/
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ All notable changes to iotaudit are documented here. This project follows
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [0.1.0] - 2026-10-17
7
+
8
+ First tagged release. iotaudit audits **your own** smart-home devices: it watches what each one
9
+ does on the network and gives it an A–F grade with a one-page HTML report.
10
+
11
+ ### Added
12
+ - **Capture modes**: hotspot (Linux/NetworkManager), ARP mode (macOS + Linux, spoofs one owned
13
+ device and restores ARP tables + IP-forwarding on exit), and offline pcap analysis.
14
+ - **`--watch-lan`**: also watch device ↔ LAN-peer unicast so phone P2P video is visible.
15
+ - **IPv6 / NDP**: capture filter now covers the device's IPv6 and ICMPv6 neighbor discovery;
16
+ IPv6 flows, link-local bytes and NDP messages are counted and shown in the report.
17
+ Experimental `--ipv6-ndp` additionally NDP-spoofs IPv6 link-local pairs.
18
+ - **Honest "media not observed" note**: when a device sets up a P2P session (STUN/TURN) but no
19
+ sustained media stream is captured on the LAN, over IPv6 or to the cloud, the report says so
20
+ plainly — the video most likely went straight to your phone over Wi-Fi and was not observed.
21
+ - **Special-use / bogon peers**: addresses like `192.0.0.0/24` are no longer mistaken for real
22
+ Wi-Fi devices; they are flagged as placeholder / DS-Lite / P2P-signalling artifacts.
23
+ - **Analysis**: per-destination org/country (bundled DB-IP + ip2asn-lite, offline), plaintext
24
+ vs TLS vs custom-encryption (entropy) vs STUN, trackers, device profiles (YAML), time-series
25
+ with burst detection, idle-upload check, `nmap` open-port scan with risk notes.
26
+ - **MCP server** (`iotaudit-mcp`) and a consent gate (own-network only).
27
+ - `iotaudit.review.review_report()` to re-check a saved JSON report for the not-observed verdict.
28
+
29
+ ### Notes
30
+ - Two Wi-Fi clients can exchange media without it passing through your computer (client
31
+ isolation), so "not observed" never means "did not happen". Capture at the router for ground
32
+ truth. iotaudit reports indicators from observed traffic, not proof of vendor wrongdoing.
iotaudit-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emir Mrkaljevic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.5
2
+ Name: iotaudit
3
+ Version: 0.1.0
4
+ Summary: Audit your own smart-home devices: who they talk to, plaintext vs TLS, open ports, and an A-F grade with an HTML report.
5
+ Project-URL: Homepage, https://github.com/emirmrkaljevic/iotaudit
6
+ Project-URL: Issues, https://github.com/emirmrkaljevic/iotaudit/issues
7
+ Author: Emir Mrkaljevic
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: audit,iot,mcp,network,privacy,security,smart-home
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: MacOS :: MacOS X
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Security
16
+ Classifier: Topic :: System :: Networking :: Monitoring
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: jinja2>=3.1
19
+ Requires-Dist: maxminddb>=2.4
20
+ Requires-Dist: pyyaml>=6.0
21
+ Requires-Dist: scapy>=2.5
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7; extra == 'dev'
24
+ Requires-Dist: ruff>=0.5; extra == 'dev'
25
+ Provides-Extra: geo
26
+ Requires-Dist: maxminddb>=2.4; extra == 'geo'
27
+ Provides-Extra: mcp
28
+ Requires-Dist: mcp>=1.2; extra == 'mcp'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # iotaudit
32
+
33
+ [![CI](https://github.com/emirmrkaljevic/iotaudit/actions/workflows/ci.yml/badge.svg)](https://github.com/emirmrkaljevic/iotaudit/actions/workflows/ci.yml)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
35
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
36
+
37
+ **Is your smart pet feeder, camera or plug safe?** `iotaudit` watches what your own IoT devices
38
+ do on the network and gives each one an **A–F grade** with a one-page HTML report. Everything
39
+ runs locally: no account, no telemetry, no cloud.
40
+
41
+ It answers the questions you actually have about a cheap smart device:
42
+
43
+ - **Who does it talk to?** Domains (DNS + TLS SNI + HTTP Host), IPs, country, organisation.
44
+ - **Is it encrypted?** Plaintext HTTP, unencrypted TCP, old TLS, or "custom" high-entropy protocols.
45
+ - **What ports are open?** Optional `nmap` scan with risk notes (telnet, RTSP, UPnP, ADB, …).
46
+ - **Does it phone home while idle?** Idle-upload check and a traffic timeline with bursts.
47
+ - **Where did the video go?** LAN peer (phone P2P) vs cloud vs STUN/TURN — and an honest note
48
+ when a stream was set up but never actually passed through your computer.
49
+
50
+ > Status: **0.1.0**. Hotspot + ARP capture, pcap analysis, IPv6/NDP, grading, HTML report and the
51
+ > MCP server work. CVE lookup and UPnP checks are on the [roadmap](ROADMAP.md).
52
+
53
+ ## Sample report
54
+
55
+ A report for a Tuya-associated pet feeder (anonymized). It talks to US clouds over TLS/MQTT, keeps
56
+ **telnet open**, uses **hardcoded 8.8.8.8**, sets up a **P2P video session that was not observed**
57
+ on this run, and shows an odd **special-use "LAN peer"** that is not a real device:
58
+
59
+ ![Sample iotaudit report](docs/sample-report.png)
60
+
61
+ Open [`docs/sample-report.html`](docs/sample-report.html) in a browser for the full version.
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ # from source (PyPI release prepared via Trusted Publishing, not yet live)
67
+ pipx install "git+https://github.com/emirmrkaljevic/iotaudit"
68
+ # or, with the MCP server + geo extras:
69
+ pip install "iotaudit[mcp] @ git+https://github.com/emirmrkaljevic/iotaudit"
70
+ ```
71
+
72
+ Capturing needs root (`sudo`). The optional port scan needs `nmap`.
73
+
74
+ ## Quickstart
75
+
76
+ ### macOS
77
+
78
+ ```bash
79
+ brew install nmap
80
+ pipx install "git+https://github.com/emirmrkaljevic/iotaudit"
81
+
82
+ # Just run it: pick your device from the list, then leave it alone (or use its app).
83
+ sudo "$(which iotaudit)" capture --mode arp --i-own-this-network
84
+ ```
85
+
86
+ iotaudit scans your LAN, lists devices (IP, MAC, vendor; Tuya/Espressif flagged as likely IoT),
87
+ and lets you pick one. It then ARP-spoofs **only that device** so its traffic flows through your
88
+ Mac, captures for up to an hour (Ctrl+C stops early and still writes the report), and restores the
89
+ ARP tables and IP-forwarding on exit. With a device picked, `--watch-lan` (phone P2P) and the
90
+ IPv6/NDP filter are on by default.
91
+
92
+ ### Linux
93
+
94
+ ```bash
95
+ sudo apt install nmap
96
+ pipx install "git+https://github.com/emirmrkaljevic/iotaudit"
97
+
98
+ # Same ARP flow as macOS:
99
+ sudo "$(which iotaudit)" capture --mode arp --i-own-this-network
100
+
101
+ # Or hotspot mode: your laptop becomes an AP; re-pair the device to it (needs NetworkManager).
102
+ sudo iotaudit capture --mode hotspot --iface wlan0 --ssid iotaudit --password 'choose-a-pass' \
103
+ --device 10.42.0.50 --scan --out feeder.html
104
+ ```
105
+
106
+ ### Analyse an existing capture
107
+
108
+ ```bash
109
+ # from Wireshark, your router, or OpenWrt `tcpdump -w`
110
+ iotaudit analyze capture.pcap --device 192.168.1.50 --json results.json --out report.html
111
+ ```
112
+
113
+ The report lands at `./iotaudit-report.html` with a matching `.json` next to it.
114
+
115
+ ## IPv6, P2P video, and "not observed"
116
+
117
+ Live video from a camera or feeder often does **not** go to the cloud — it goes **directly to your
118
+ phone** over the local Wi-Fi (P2P), after a short STUN/TURN handshake. iotaudit tries hard to see
119
+ that stream (`--watch-lan` spoofs device↔phone, and the capture filter now includes IPv6 and NDP),
120
+ but two Wi-Fi clients can talk **without their traffic ever passing through your computer** —
121
+ because of Access-Point client isolation, a phone ignoring the spoof, or the stream riding IPv6
122
+ link-local (`fe80::`). When that happens the report says so honestly:
123
+
124
+ > *Live video set up but not observed here* — the session was negotiated (STUN) but no sustained
125
+ > stream was captured; it most likely went straight to your phone. **Not observed ≠ not happening.**
126
+
127
+ For ground truth, capture at the router instead. `--ipv6-ndp` (experimental) also NDP-spoofs IPv6
128
+ link-local pairs.
129
+
130
+ ## MCP server
131
+
132
+ ```bash
133
+ iotaudit-mcp # exposes audit tools to a local assistant ("audit my pet feeder")
134
+ ```
135
+
136
+ ## Consent & scope
137
+
138
+ iotaudit only audits **your own** network. It refuses non-private targets and requires
139
+ `--i-own-this-network` (or an interactive `I OWN THIS NETWORK` confirmation). ARP/NDP spoofing on a
140
+ network you do not own may be illegal.
141
+
142
+ Findings are **indicators based on observed traffic, not proof** of wrongdoing by any vendor.
143
+ IP geolocation by [DB-IP](https://db-ip.com). MIT licensed.
@@ -0,0 +1,113 @@
1
+ # iotaudit
2
+
3
+ [![CI](https://github.com/emirmrkaljevic/iotaudit/actions/workflows/ci.yml/badge.svg)](https://github.com/emirmrkaljevic/iotaudit/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
5
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
6
+
7
+ **Is your smart pet feeder, camera or plug safe?** `iotaudit` watches what your own IoT devices
8
+ do on the network and gives each one an **A–F grade** with a one-page HTML report. Everything
9
+ runs locally: no account, no telemetry, no cloud.
10
+
11
+ It answers the questions you actually have about a cheap smart device:
12
+
13
+ - **Who does it talk to?** Domains (DNS + TLS SNI + HTTP Host), IPs, country, organisation.
14
+ - **Is it encrypted?** Plaintext HTTP, unencrypted TCP, old TLS, or "custom" high-entropy protocols.
15
+ - **What ports are open?** Optional `nmap` scan with risk notes (telnet, RTSP, UPnP, ADB, …).
16
+ - **Does it phone home while idle?** Idle-upload check and a traffic timeline with bursts.
17
+ - **Where did the video go?** LAN peer (phone P2P) vs cloud vs STUN/TURN — and an honest note
18
+ when a stream was set up but never actually passed through your computer.
19
+
20
+ > Status: **0.1.0**. Hotspot + ARP capture, pcap analysis, IPv6/NDP, grading, HTML report and the
21
+ > MCP server work. CVE lookup and UPnP checks are on the [roadmap](ROADMAP.md).
22
+
23
+ ## Sample report
24
+
25
+ A report for a Tuya-associated pet feeder (anonymized). It talks to US clouds over TLS/MQTT, keeps
26
+ **telnet open**, uses **hardcoded 8.8.8.8**, sets up a **P2P video session that was not observed**
27
+ on this run, and shows an odd **special-use "LAN peer"** that is not a real device:
28
+
29
+ ![Sample iotaudit report](docs/sample-report.png)
30
+
31
+ Open [`docs/sample-report.html`](docs/sample-report.html) in a browser for the full version.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ # from source (PyPI release prepared via Trusted Publishing, not yet live)
37
+ pipx install "git+https://github.com/emirmrkaljevic/iotaudit"
38
+ # or, with the MCP server + geo extras:
39
+ pip install "iotaudit[mcp] @ git+https://github.com/emirmrkaljevic/iotaudit"
40
+ ```
41
+
42
+ Capturing needs root (`sudo`). The optional port scan needs `nmap`.
43
+
44
+ ## Quickstart
45
+
46
+ ### macOS
47
+
48
+ ```bash
49
+ brew install nmap
50
+ pipx install "git+https://github.com/emirmrkaljevic/iotaudit"
51
+
52
+ # Just run it: pick your device from the list, then leave it alone (or use its app).
53
+ sudo "$(which iotaudit)" capture --mode arp --i-own-this-network
54
+ ```
55
+
56
+ iotaudit scans your LAN, lists devices (IP, MAC, vendor; Tuya/Espressif flagged as likely IoT),
57
+ and lets you pick one. It then ARP-spoofs **only that device** so its traffic flows through your
58
+ Mac, captures for up to an hour (Ctrl+C stops early and still writes the report), and restores the
59
+ ARP tables and IP-forwarding on exit. With a device picked, `--watch-lan` (phone P2P) and the
60
+ IPv6/NDP filter are on by default.
61
+
62
+ ### Linux
63
+
64
+ ```bash
65
+ sudo apt install nmap
66
+ pipx install "git+https://github.com/emirmrkaljevic/iotaudit"
67
+
68
+ # Same ARP flow as macOS:
69
+ sudo "$(which iotaudit)" capture --mode arp --i-own-this-network
70
+
71
+ # Or hotspot mode: your laptop becomes an AP; re-pair the device to it (needs NetworkManager).
72
+ sudo iotaudit capture --mode hotspot --iface wlan0 --ssid iotaudit --password 'choose-a-pass' \
73
+ --device 10.42.0.50 --scan --out feeder.html
74
+ ```
75
+
76
+ ### Analyse an existing capture
77
+
78
+ ```bash
79
+ # from Wireshark, your router, or OpenWrt `tcpdump -w`
80
+ iotaudit analyze capture.pcap --device 192.168.1.50 --json results.json --out report.html
81
+ ```
82
+
83
+ The report lands at `./iotaudit-report.html` with a matching `.json` next to it.
84
+
85
+ ## IPv6, P2P video, and "not observed"
86
+
87
+ Live video from a camera or feeder often does **not** go to the cloud — it goes **directly to your
88
+ phone** over the local Wi-Fi (P2P), after a short STUN/TURN handshake. iotaudit tries hard to see
89
+ that stream (`--watch-lan` spoofs device↔phone, and the capture filter now includes IPv6 and NDP),
90
+ but two Wi-Fi clients can talk **without their traffic ever passing through your computer** —
91
+ because of Access-Point client isolation, a phone ignoring the spoof, or the stream riding IPv6
92
+ link-local (`fe80::`). When that happens the report says so honestly:
93
+
94
+ > *Live video set up but not observed here* — the session was negotiated (STUN) but no sustained
95
+ > stream was captured; it most likely went straight to your phone. **Not observed ≠ not happening.**
96
+
97
+ For ground truth, capture at the router instead. `--ipv6-ndp` (experimental) also NDP-spoofs IPv6
98
+ link-local pairs.
99
+
100
+ ## MCP server
101
+
102
+ ```bash
103
+ iotaudit-mcp # exposes audit tools to a local assistant ("audit my pet feeder")
104
+ ```
105
+
106
+ ## Consent & scope
107
+
108
+ iotaudit only audits **your own** network. It refuses non-private targets and requires
109
+ `--i-own-this-network` (or an interactive `I OWN THIS NETWORK` confirmation). ARP/NDP spoofing on a
110
+ network you do not own may be illegal.
111
+
112
+ Findings are **indicators based on observed traffic, not proof** of wrongdoing by any vendor.
113
+ IP geolocation by [DB-IP](https://db-ip.com). MIT licensed.
@@ -0,0 +1,108 @@
1
+ # iotaudit roadmap
2
+
3
+ iotaudit aims to be an all-rounder audit for **any** smart device you own: cameras, pet feeders,
4
+ plugs, bulbs, TVs, speakers, doorbells, robot vacuums, baby monitors, hubs. The goal is a clear,
5
+ plain-English verdict per device, built only from what can be observed on your own network.
6
+
7
+ **Principles**
8
+
9
+ - Local-only. No telemetry, no cloud account, no uploads.
10
+ - Own networks and own devices only; every active step is behind an explicit consent prompt.
11
+ - Facts vs inferences: observed facts are marked `certain`; anything inferred from traffic
12
+ patterns carries a confidence level (`low` / `medium` / `high`) and is worded as an
13
+ *indicator* ("looks like video upload"), never as a certainty or an accusation.
14
+
15
+ ## Data model (in place since 0.0.x)
16
+
17
+ Every result is a `Finding` with `category`, `severity`, `title`, `detail`, `confidence` and an
18
+ `evidence` dict (byte counts, packet-size histograms, timestamps, matched rules). Each destination
19
+ can carry a `TrafficClass` (`video`, `audio`, `audio+video`, `telemetry`, `firmware-download`,
20
+ `unknown`) with confidence and a `while_app_idle` flag. New analysers only add findings; grading
21
+ and reports pick them up automatically. Grading weights inferred findings by confidence (v0.2).
22
+
23
+ ## v0.1.0 – MVP (target: within a few days)
24
+
25
+ Done in the first commit:
26
+ - [x] Hotspot mode (laptop becomes the access point via NetworkManager) and pcap import
27
+ - [x] DNS / TLS SNI / HTTP Host extraction, per-device destinations
28
+ - [x] Country (local GeoLite2 / DB-IP Lite `.mmdb`), organisation, tracker tagging
29
+ - [x] Plaintext vs TLS detection, outdated TLS 1.0/1.1, QUIC
30
+ - [x] nmap open-port scan with risk notes
31
+ - [x] Device profile library (YAML), A–F grade, HTML report, JSON output
32
+ - [x] MCP server (`analyze_pcap`, `capture`, `scan_device`), consent gate, CI
33
+
34
+ Remaining for v0.1.0:
35
+ - [ ] Real-device validation (Tuya-based pet feeder first) and profile tuning
36
+ - [ ] Hotspot capture hardening (auto-detect Wi-Fi interface, client list, IPv6)
37
+ - [ ] Bundled download helper for the free country database
38
+ - [ ] Report polish: per-device plain-English summary paragraph, destination country summary
39
+ - [ ] PyPI release, MCP registry listing
40
+
41
+ ## v0.2 – "What is it sending?" (traffic classification)
42
+
43
+ Plain-English per-device findings such as:
44
+ > *Your camera sends data to `xyz.example.com` (Germany, Amazon AWS). Pattern looks like
45
+ > **video** (sustained ~1.2 Mbit/s upload, large packets, steady timing) – confidence: medium.
46
+ > This also happened while the app was closed.*
47
+
48
+ - Per-destination time series: bitrate up/down, packet-size distribution, inter-arrival timing
49
+ - Heuristics: video (sustained high upload, large near-MTU packets, keyframe bursts),
50
+ audio (steady low bitrate ~20–130 kbit/s, small regular packets), telemetry (small periodic
51
+ heartbeats), firmware download (large one-off downstream burst)
52
+ - Protocol hints: RTSP/RTP, WebRTC (STUN/TURN, DTLS-SRTP), P2P relay services, MQTT
53
+ - "Upload while app idle": user marks idle periods (CLI prompt / web UI button) or uses the
54
+ phone-traffic timeline (below) to know when the app was open
55
+ - Confidence scoring, labelled test captures, documented false-positive cases
56
+ - Grading weighted by confidence
57
+
58
+ ## v0.3 – Phone & web UI
59
+
60
+ - **Web UI** served by the laptop or a Raspberry Pi: start/stop captures, mark "app open/closed",
61
+ view reports from a phone browser on the same network
62
+ - **Android**: capture the companion app's own traffic with a local VPN-based capture
63
+ (PCAPdroid-style: `VpnService`, no root). Export pcap or stream to iotaudit; first step is
64
+ documenting how to use PCAPdroid itself, then an optional companion app
65
+ - **iOS (honest limits)**: no packet capture on a stock iPhone. Options:
66
+ - capture on the laptop/Pi hotspot (the iPhone joins it) – sees destinations, not app identity
67
+ - `rvictl` remote virtual interface from a Mac over USB (macOS + Xcode tools)
68
+ - a local VPN app (`NEPacketTunnelProvider`) would need a paid Apple developer account and
69
+ App Store review – out of scope for now
70
+ - TLS contents of pinned apps stay hidden; we report metadata only
71
+ - Correlate phone-app traffic with device traffic (app open → device starts streaming)
72
+
73
+ ## v0.4 – Security checks
74
+
75
+ - CVEs per vendor/model/firmware: banner + profile → OSV / NVD / CISA KEV (cached locally)
76
+ - Default credentials: short vendor-default list, only on a user-confirmed own device, rate-limited,
77
+ never brute force
78
+ - UPnP IGD: list port forwards on your router and attribute them to devices
79
+ - DNS bypass: hardcoded resolvers (8.8.8.8 etc.), DNS-over-HTTPS / DoT endpoints
80
+ - Device scanning your LAN (ARP sweeps, port probes from the IoT device)
81
+ - Firmware update checks: is the device phoning a firmware endpoint, version vs latest known
82
+ - Insecure local services: telnet, ADB, unauthenticated RTSP/ONVIF, open MQTT
83
+
84
+ ## v0.5 – Continuous mode
85
+
86
+ - Raspberry Pi image / OpenWrt package, scheduled reports
87
+ - Alerts: new/unexpected device on the network, new destination country, idle upload,
88
+ new open port
89
+ - ARP mode (own network only, extra consent) for devices that cannot be moved to a hotspot
90
+ - PDF export
91
+
92
+ ## Verdict
93
+
94
+ Each device gets a one-line safety verdict on top of the A–F grade, e.g.
95
+ *"B – Encrypted cloud traffic to the vendor in the EU; one analytics tracker; no risky ports."*
96
+ The verdict separates observed facts from inferred indicators.
97
+
98
+ ## Device coverage
99
+
100
+ Community YAML profiles per brand/model: expected domains, ports, protocols, known CVEs, notes.
101
+ Priority categories: cameras & doorbells, pet feeders, plugs & bulbs (Tuya, Xiaomi, Shelly),
102
+ smart TVs, speakers, robot vacuums, baby monitors.
103
+
104
+ ## Distribution
105
+
106
+ PyPI, MCP registries (official registry, Glama, Smithery, mcp.so), awesome lists
107
+ (awesome-mcp-servers, awesome-iot, awesome-privacy, awesome-selfhosted), DEV.to write-ups,
108
+ r/homelab, r/privacy, r/selfhosted.
@@ -0,0 +1,128 @@
1
+ <!doctype html>
2
+ <html lang="en"><head><meta charset="utf-8">
3
+ <meta name="viewport" content="width=device-width,initial-scale=1">
4
+ <title>iotaudit report</title>
5
+ <style>
6
+ body{font-family:system-ui,sans-serif;max-width:960px;margin:2rem auto;padding:0 1rem;color:#1d1d1f}
7
+ h1{margin-bottom:.2rem}.muted{color:#666;font-size:.9rem}
8
+ .card{border:1px solid #ddd;border-radius:10px;padding:1rem 1.2rem;margin:1.2rem 0}
9
+ .summary{background:#f6f8fa;border-radius:8px;padding:.8rem 1rem;margin:.8rem 0}
10
+ .grade{float:right;font-size:2.6rem;font-weight:700;width:3.6rem;height:3.6rem;line-height:3.6rem;
11
+ text-align:center;border-radius:10px;color:#fff}
12
+ .gA{background:#1a7f37}.gB{background:#4c9a2a}.gC{background:#c69026}.gD{background:#d4691c}
13
+ .gE{background:#c2410c}.gF{background:#b91c1c}
14
+ table{border-collapse:collapse;width:100%;font-size:.9rem}td,th{border-bottom:1px solid #eee;
15
+ padding:.35rem;text-align:left;vertical-align:top}
16
+ .high{color:#b91c1c;font-weight:600}.medium{color:#c2410c}.low{color:#666}
17
+ .tag{display:inline-block;padding:0 .4rem;border-radius:4px;font-size:.8rem;background:#eee}
18
+ .chart{display:flex;align-items:flex-end;gap:2px;height:80px;margin:0.6rem 0}
19
+ .chart .bar{flex:1;background:#4c9a2a;min-width:2px;border-radius:2px 2px 0 0}
20
+ .chart .bar.active{background:#c2410c}
21
+ .chart .bar.idle{background:#a8c5a0}
22
+ </style></head><body>
23
+ <h1>iotaudit report</h1>
24
+ <p class="muted">Generated 2026-10-17 10:16 &middot; iotaudit 0.1.0 &middot; source: arp:en0 (anonymized sample) &middot; capture 900s</p>
25
+ <p class="muted">Findings are indicators based on observed traffic, not proof of wrongdoing by any vendor.
26
+ All data was processed locally. IP Geolocation by DB-IP (https://db-ip.com).</p>
27
+
28
+ <div class="card">
29
+ <div class="grade gD">D</div>
30
+ <h2>192.168.0.50</h2>
31
+ <p class="muted">MAC 14:5d:34:00:00:01 &middot; SHENZHEN BILIAN ELECTRONIC
32
+ &middot; profile: iotbing.com cloud device (Tuya-associated pet feeder / camera) &middot; score 55/100</p>
33
+ <div class="summary"><b>Summary.</b> Device 192.168.0.50 (iotbing.com cloud device (Tuya-associated pet feeder / camera)). Grade D (55/100). Talked to: Amazon AWS, Google, OVH, iotbing (Tuya-associated cloud). Server countries observed: US. Telnet (port 23) is open on the LAN — change defaults / block it. Uses public DNS directly (bypasses your router). Used STUN/TURN (typical for P2P video setup). A P2P video session was set up but no media stream was captured here — it likely went straight to your phone over Wi-Fi (not observed). Uploads while app idle: insufficient data. 1 destination(s) look like custom/non-TLS encryption (unverified).</div>
34
+ <p><b>Uploads while app idle:</b> insufficient data</p>
35
+ <p class="muted"><b>IPv6:</b> 1 flow(s),
36
+ 168 bytes (168 on link-local fe80::); NDP messages:
37
+ 2
38
+ (NS 1, NA 1), neighbours 1.</p>
39
+
40
+
41
+ <h3>Findings</h3>
42
+ <ul>
43
+ <li><span class="medium">[medium]</span> <b>Non-standard encryption (custom protocol), unverified</b> &mdash; Traffic to 15.204.79.232 (ports [1443]) looks encrypted by entropy but is not TLS. High entropy (8.00) without TLS handshake — non-standard encryption (custom protocol), unverified. <span class="tag">confidence: medium</span></li>
44
+
45
+ <li><span class="info">[info]</span> <b>STUN/TURN (P2P video setup)</b> &mdash; STUN/TURN to 15.204.72.48 (OVH) — typical for peer-to-peer video/audio; the media may stay on the LAN or go via a relay. <span class="tag">confidence: high</span></li>
46
+
47
+ <li><span class="medium">[medium]</span> <b>Hardcoded public DNS (bypasses router)</b> &mdash; Device sent DNS to 8.8.8.8, bypassing your router's DNS. Queries may leave your network even if you run Pi-hole.</li>
48
+
49
+ <li><span class="high">[high]</span> <b>Open port 23/tcp (telnet)</b> &mdash; Telnet exposes an unencrypted login shell.</li>
50
+
51
+ <li><span class="info">[info]</span> <b>Cloud servers outside the local network</b> &mdash; Observed destination countries: US. This is a location note about servers the device contacted, not a judgement about where you live.</li>
52
+
53
+ <li><span class="info">[info]</span> <b>Odd "LAN peer" in special-use address space</b> &mdash; Saw traffic to/from 192.0.0.6 (3360B), which is reserved / protocol-assignment space (e.g. 192.0.0.0/24), not a real device on your Wi-Fi. It is almost always a placeholder/DS-Lite address or an address carried inside P2P/STUN signalling — not a second hidden gadget. <span class="tag">confidence: high</span></li>
54
+
55
+ <li><span class="info">[info]</span> <b>LAN discovery broadcasts</b> &mdash; 48000 bytes of broadcast/multicast (e.g. Tuya UDP 6667) — not media.</li>
56
+
57
+ <li><span class="info">[info]</span> <b>Live video set up but not observed here</b> &mdash; The device negotiated a peer-to-peer session (STUN/TURN) but no sustained video/audio stream was captured on this run. It most likely went directly between the device and your phone over Wi-Fi (P2P) and never passed through this computer: two Wi-Fi clients can talk without their traffic crossing your Mac, and spoofed ARP/NDP may not redirect it (client isolation, or the phone ignoring the spoof). IPv6 was active (NDP messages: 2, IPv6 flows seen: 1), so the stream may be riding IPv6 link-local between the two devices. Not observed does not mean it did not happen — re-run with the app streaming, or capture at the router. <span class="tag">confidence: high</span></li>
58
+
59
+ <li><span class="info">[info]</span> <b>IPv6 traffic observed</b> &mdash; 1 IPv6 flow(s), 168 bytes (168 on link-local fe80::); NDP neighbor-discovery messages: 2. IPv6 enrichment (country/org) is limited; link-local P2P between two Wi-Fi clients may not be fully capturable without router-side capture.</li>
60
+ </ul>
61
+ <h3>Internet destinations</h3>
62
+ <table><tr><th>Domain(s)</th><th>IP</th><th>Country</th><th>Organisation</th><th>Ports / proto</th><th>Bytes</th><th>Encryption</th><th>Class</th></tr>
63
+ <tr>
64
+ <td>—</td>
65
+ <td>15.204.79.232</td><td>US</td><td>OVH</td>
66
+ <td>1443 (custom-1443)</td>
67
+ <td>72000 out / 72000 in</td>
68
+ <td><span class="medium">custom (unverified)</span>
69
+
70
+
71
+ </td>
72
+ <td>—</td>
73
+ </tr><tr>
74
+ <td>—</td>
75
+ <td>52.32.71.30</td><td>US</td><td>Amazon AWS</td>
76
+ <td>8883 (MQTT-over-TLS)</td>
77
+ <td>23000 out / 23000 in</td>
78
+ <td>TLS/encrypted
79
+
80
+ </td>
81
+ <td>—</td>
82
+ </tr><tr>
83
+ <td>a1-us.iotbing.com</td>
84
+ <td>35.155.252.83</td><td>US</td><td>iotbing (Tuya-associated cloud)</td>
85
+ <td>443 (HTTPS)</td>
86
+ <td>17000 out / 17000 in</td>
87
+ <td>TLS/encrypted
88
+
89
+ </td>
90
+ <td>—</td>
91
+ </tr><tr>
92
+ <td>—</td>
93
+ <td>15.204.72.48</td><td>US</td><td>OVH</td>
94
+ <td>3478 (STUN/TURN)</td>
95
+ <td>4000 out / 4000 in</td>
96
+ <td>STUN/TURN
97
+
98
+
99
+ </td>
100
+ <td>—</td>
101
+ </tr><tr>
102
+ <td>—</td>
103
+ <td>8.8.8.8</td><td>US</td><td>Google</td>
104
+ <td>53 (DNS)</td>
105
+ <td>200 out / 0 in</td>
106
+ <td>TLS/encrypted
107
+
108
+ </td>
109
+ <td>—</td>
110
+ </tr>
111
+ </table>
112
+
113
+ <h3>LAN peers (same Wi-Fi — often phone P2P video)</h3>
114
+ <table><tr><th>IP</th><th>Ports</th><th>Bytes</th><th>Class</th></tr>
115
+ <tr>
116
+ <td>192.0.0.6 <span class="tag">special-use / not a real device</span></td>
117
+ <td>63720</td><td>3360 out / 0 in</td>
118
+ <td><span class="muted">bogon/placeholder</span></td>
119
+ </tr>
120
+ </table>
121
+
122
+ <h3>Open ports (LAN)</h3>
123
+ <table><tr><th>Port</th><th>Service</th><th>Product</th><th>Risk</th></tr>
124
+ <tr><td>23/tcp</td><td>telnet</td><td>BusyBox telnetd</td>
125
+ <td class="high">high</td></tr></table>
126
+ </div>
127
+
128
+ </body></html>
Binary file