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.
- iotaudit-0.1.0/.github/workflows/ci.yml +26 -0
- iotaudit-0.1.0/.github/workflows/publish.yml +50 -0
- iotaudit-0.1.0/.gitignore +10 -0
- iotaudit-0.1.0/CHANGELOG.md +32 -0
- iotaudit-0.1.0/LICENSE +21 -0
- iotaudit-0.1.0/PKG-INFO +143 -0
- iotaudit-0.1.0/README.md +113 -0
- iotaudit-0.1.0/ROADMAP.md +108 -0
- iotaudit-0.1.0/docs/sample-report.html +128 -0
- iotaudit-0.1.0/docs/sample-report.png +0 -0
- iotaudit-0.1.0/pyproject.toml +45 -0
- iotaudit-0.1.0/scripts/gen_sample_report.py +72 -0
- iotaudit-0.1.0/scripts/verify_wheel_install.py +19 -0
- iotaudit-0.1.0/src/iotaudit/__init__.py +3 -0
- iotaudit-0.1.0/src/iotaudit/analyze.py +260 -0
- iotaudit-0.1.0/src/iotaudit/arp.py +313 -0
- iotaudit-0.1.0/src/iotaudit/capture.py +356 -0
- iotaudit-0.1.0/src/iotaudit/cli.py +224 -0
- iotaudit-0.1.0/src/iotaudit/consent.py +29 -0
- iotaudit-0.1.0/src/iotaudit/data/NOTICE +8 -0
- iotaudit-0.1.0/src/iotaudit/data/dbip-country-lite.mmdb +0 -0
- iotaudit-0.1.0/src/iotaudit/data/ip2asn-lite.tsv.gz +0 -0
- iotaudit-0.1.0/src/iotaudit/data/oui.txt.gz +0 -0
- iotaudit-0.1.0/src/iotaudit/discover.py +271 -0
- iotaudit-0.1.0/src/iotaudit/enrich.py +327 -0
- iotaudit-0.1.0/src/iotaudit/grading.py +328 -0
- iotaudit-0.1.0/src/iotaudit/hotspot.py +41 -0
- iotaudit-0.1.0/src/iotaudit/identify.py +405 -0
- iotaudit-0.1.0/src/iotaudit/mcp_server.py +57 -0
- iotaudit-0.1.0/src/iotaudit/models.py +101 -0
- iotaudit-0.1.0/src/iotaudit/profiles/generic-camera.yaml +10 -0
- iotaudit-0.1.0/src/iotaudit/profiles/imipaw-feeder.yaml +13 -0
- iotaudit-0.1.0/src/iotaudit/profiles/iotbing-cloud.yaml +13 -0
- iotaudit-0.1.0/src/iotaudit/profiles/tuya-generic.yaml +10 -0
- iotaudit-0.1.0/src/iotaudit/profiles/xiaomi-miio.yaml +9 -0
- iotaudit-0.1.0/src/iotaudit/profiles.py +59 -0
- iotaudit-0.1.0/src/iotaudit/report.py +17 -0
- iotaudit-0.1.0/src/iotaudit/review.py +63 -0
- iotaudit-0.1.0/src/iotaudit/scan.py +59 -0
- iotaudit-0.1.0/src/iotaudit/templates/report.html.j2 +96 -0
- iotaudit-0.1.0/src/iotaudit/traffic.py +143 -0
- iotaudit-0.1.0/tests/fixtures/feeder_capture.json +172 -0
- iotaudit-0.1.0/tests/fixtures/feeder_capture_run3.json +902 -0
- iotaudit-0.1.0/tests/fixtures/feeder_capture_run4.json +973 -0
- iotaudit-0.1.0/tests/test_core.py +472 -0
- 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,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.
|
iotaudit-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://github.com/emirmrkaljevic/iotaudit/actions/workflows/ci.yml)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
[](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
|
+

|
|
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.
|
iotaudit-0.1.0/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# iotaudit
|
|
2
|
+
|
|
3
|
+
[](https://github.com/emirmrkaljevic/iotaudit/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](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
|
+

|
|
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 · iotaudit 0.1.0 · source: arp:en0 (anonymized sample) · 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 · SHENZHEN BILIAN ELECTRONIC
|
|
32
|
+
· profile: iotbing.com cloud device (Tuya-associated pet feeder / camera) · 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> — 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> — 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> — 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> — Telnet exposes an unencrypted login shell.</li>
|
|
50
|
+
|
|
51
|
+
<li><span class="info">[info]</span> <b>Cloud servers outside the local network</b> — 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> — 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> — 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> — 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> — 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
|