hostlens 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 (58) hide show
  1. hostlens-0.1.0/.github/workflows/workflow.yml +71 -0
  2. hostlens-0.1.0/.gitignore +9 -0
  3. hostlens-0.1.0/.pre-commit-config.yaml +7 -0
  4. hostlens-0.1.0/CHANGELOG.md +12 -0
  5. hostlens-0.1.0/CONTRIBUTING.md +15 -0
  6. hostlens-0.1.0/LICENSE +21 -0
  7. hostlens-0.1.0/PKG-INFO +415 -0
  8. hostlens-0.1.0/README.md +379 -0
  9. hostlens-0.1.0/benchmarks/local_scan.py +45 -0
  10. hostlens-0.1.0/examples/discover.py +11 -0
  11. hostlens-0.1.0/examples/evidence.py +12 -0
  12. hostlens-0.1.0/examples/fingerbank.py +12 -0
  13. hostlens-0.1.0/examples/identify.py +4 -0
  14. hostlens-0.1.0/examples/passive.py +11 -0
  15. hostlens-0.1.0/examples/scan.py +11 -0
  16. hostlens-0.1.0/pyproject.toml +71 -0
  17. hostlens-0.1.0/src/hostlens/__init__.py +18 -0
  18. hostlens-0.1.0/src/hostlens/api.py +28 -0
  19. hostlens-0.1.0/src/hostlens/cache.py +30 -0
  20. hostlens-0.1.0/src/hostlens/cli/__init__.py +5 -0
  21. hostlens-0.1.0/src/hostlens/cli/__main__.py +3 -0
  22. hostlens-0.1.0/src/hostlens/cli/app.py +88 -0
  23. hostlens-0.1.0/src/hostlens/cli/output.py +39 -0
  24. hostlens-0.1.0/src/hostlens/client.py +271 -0
  25. hostlens-0.1.0/src/hostlens/collectors/__init__.py +21 -0
  26. hostlens-0.1.0/src/hostlens/collectors/base.py +41 -0
  27. hostlens-0.1.0/src/hostlens/collectors/basic.py +65 -0
  28. hostlens-0.1.0/src/hostlens/collectors/mdns.py +96 -0
  29. hostlens-0.1.0/src/hostlens/collectors/netbios.py +49 -0
  30. hostlens-0.1.0/src/hostlens/collectors/services.py +45 -0
  31. hostlens-0.1.0/src/hostlens/collectors/ssdp.py +65 -0
  32. hostlens-0.1.0/src/hostlens/collectors/upnp.py +41 -0
  33. hostlens-0.1.0/src/hostlens/config.py +71 -0
  34. hostlens-0.1.0/src/hostlens/exceptions.py +37 -0
  35. hostlens-0.1.0/src/hostlens/identity/__init__.py +5 -0
  36. hostlens-0.1.0/src/hostlens/identity/fingerbank.py +63 -0
  37. hostlens-0.1.0/src/hostlens/identity/fingerprints.py +51 -0
  38. hostlens-0.1.0/src/hostlens/identity/fusion.py +140 -0
  39. hostlens-0.1.0/src/hostlens/models/__init__.py +30 -0
  40. hostlens-0.1.0/src/hostlens/models/device.py +66 -0
  41. hostlens-0.1.0/src/hostlens/models/events.py +42 -0
  42. hostlens-0.1.0/src/hostlens/models/evidence.py +45 -0
  43. hostlens-0.1.0/src/hostlens/models/target.py +13 -0
  44. hostlens-0.1.0/src/hostlens/network/__init__.py +6 -0
  45. hostlens-0.1.0/src/hostlens/network/addressing.py +74 -0
  46. hostlens-0.1.0/src/hostlens/network/discovery.py +131 -0
  47. hostlens-0.1.0/src/hostlens/parsers/__init__.py +7 -0
  48. hostlens-0.1.0/src/hostlens/parsers/dhcp.py +45 -0
  49. hostlens-0.1.0/src/hostlens/parsers/ssdp.py +14 -0
  50. hostlens-0.1.0/src/hostlens/parsers/upnp.py +44 -0
  51. hostlens-0.1.0/tests/unit/test_cli.py +21 -0
  52. hostlens-0.1.0/tests/unit/test_client.py +59 -0
  53. hostlens-0.1.0/tests/unit/test_config_cache.py +22 -0
  54. hostlens-0.1.0/tests/unit/test_fingerbank.py +24 -0
  55. hostlens-0.1.0/tests/unit/test_fusion.py +37 -0
  56. hostlens-0.1.0/tests/unit/test_models.py +33 -0
  57. hostlens-0.1.0/tests/unit/test_networking.py +45 -0
  58. hostlens-0.1.0/tests/unit/test_parsers.py +41 -0
@@ -0,0 +1,71 @@
1
+ name: Test and publish
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ quality:
15
+ name: Python ${{ matrix.python-version }}
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ python-version: ["3.11", "3.12", "3.13"]
21
+
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+ cache: pip
29
+
30
+ - run: python -m pip install -e ".[dev]"
31
+ - run: python -m ruff format --check .
32
+ - run: python -m ruff check .
33
+ - run: python -m pyright
34
+ - run: python -m pytest --cov=hostlens --cov-report=term-missing
35
+
36
+ build:
37
+ name: Build package
38
+ needs: quality
39
+ runs-on: ubuntu-latest
40
+
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+
44
+ - uses: actions/setup-python@v5
45
+ with:
46
+ python-version: "3.13"
47
+
48
+ - run: python -m pip install build
49
+ - run: python -m build
50
+
51
+ - uses: actions/upload-artifact@v4
52
+ with:
53
+ name: python-package
54
+ path: dist/
55
+
56
+ publish:
57
+ name: Publish to PyPI
58
+ if: startsWith(github.ref, 'refs/tags/v')
59
+ needs: build
60
+ runs-on: ubuntu-latest
61
+ permissions:
62
+ contents: read
63
+ id-token: write
64
+
65
+ steps:
66
+ - uses: actions/download-artifact@v4
67
+ with:
68
+ name: python-package
69
+ path: dist/
70
+
71
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .coverage
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .venv*/
8
+ build/
9
+ dist/
@@ -0,0 +1,7 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.13.1
4
+ hooks:
5
+ - id: ruff-check
6
+ args: [--fix]
7
+ - id: ruff-format
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-09-20
4
+
5
+ - First public API for host identification and subnet scanning
6
+ - Fast, normal, deep, and passive profiles
7
+ - Neighbor-table and ARP discovery with progressive events
8
+ - Reverse DNS, OUI, mDNS, SSDP, UPnP, NetBIOS, and selected service collectors
9
+ - Explainable evidence fusion and opt-in Fingerbank enrichment
10
+ - Typer and Rich CLI
11
+ - Clean feature folders for models, network code, collectors, parsers, identity, and CLI
12
+ - PyPI trusted publishing workflow
@@ -0,0 +1,15 @@
1
+ # Contributing
2
+
3
+ Create a focused branch, add observable-behavior tests, and run
4
+
5
+ ```bash
6
+ ruff check .
7
+ ruff format --check .
8
+ pyright
9
+ pytest
10
+ python -m build
11
+ ```
12
+
13
+ Keep collection, parsing, fingerprinting, fusion, and presentation separate
14
+
15
+ Unit tests should not need network access
hostlens-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HostLens contributors
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,415 @@
1
+ Metadata-Version: 2.5
2
+ Name: hostlens
3
+ Version: 0.1.0
4
+ Summary: High-level Python device discovery and identification for local networks
5
+ Project-URL: Homepage, https://github.com/cyber0x3a/hostlens
6
+ Project-URL: Issues, https://github.com/cyber0x3a/hostlens/issues
7
+ Author: HostLens contributors
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: arp,discovery,fingerprinting,lan,network,upnp
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: System :: Networking
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: httpx<1,>=0.27
20
+ Requires-Dist: ifaddr<1,>=0.2
21
+ Requires-Dist: manuf<2,>=1.1.5
22
+ Requires-Dist: pydantic<3,>=2.7
23
+ Requires-Dist: rich<15,>=13.7
24
+ Requires-Dist: scapy<3,>=2.5
25
+ Requires-Dist: typer<1,>=0.12
26
+ Requires-Dist: zeroconf<1,>=0.132
27
+ Provides-Extra: dev
28
+ Requires-Dist: build>=1.2; extra == 'dev'
29
+ Requires-Dist: pre-commit>=3.7; extra == 'dev'
30
+ Requires-Dist: pyright>=1.1.380; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
32
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
33
+ Requires-Dist: pytest>=8.2; extra == 'dev'
34
+ Requires-Dist: ruff>=0.6; extra == 'dev'
35
+ Description-Content-Type: text/markdown
36
+
37
+ <div align="center">
38
+
39
+ # HostLens
40
+
41
+ ### Find and understand devices on your local network from Python
42
+
43
+ [![Test and publish](https://github.com/Cyber0x3a/hostlens/actions/workflows/workflow.yml/badge.svg)](https://github.com/Cyber0x3a/hostlens/actions/workflows/workflow.yml)
44
+ [![PyPI](https://img.shields.io/pypi/v/hostlens?color=3775A9)](https://pypi.org/project/hostlens/)
45
+ [![Python](https://img.shields.io/pypi/pyversions/hostlens?color=FFD43B)](https://pypi.org/project/hostlens/)
46
+ [![License](https://img.shields.io/github/license/Cyber0x3a/hostlens)](LICENSE)
47
+
48
+ Simple public API · Async first · Explainable results · No cloud calls by default
49
+
50
+ </div>
51
+
52
+ HostLens discovers devices on a LAN, collects useful facts about them, and turns
53
+ those facts into one clean device profile
54
+
55
+ It uses ARP, the local neighbor table, reverse DNS, OUI data, mDNS, SSDP, UPnP,
56
+ NetBIOS, and a small set of useful service checks
57
+
58
+ The normal API stays small even though the scan can use several protocols behind
59
+ the scenes
60
+
61
+ ## Install
62
+
63
+ ```bash
64
+ pip install hostlens
65
+ ```
66
+
67
+ HostLens supports Python 3.11 and newer on Windows, Linux, and macOS
68
+
69
+ For local development
70
+
71
+ ```bash
72
+ git clone https://github.com/Cyber0x3a/hostlens.git
73
+ cd hostlens
74
+ python -m venv .venv
75
+ ```
76
+
77
+ Windows PowerShell
78
+
79
+ ```powershell
80
+ .\.venv\Scripts\Activate.ps1
81
+ python -m pip install -e ".[dev]"
82
+ ```
83
+
84
+ Linux and macOS
85
+
86
+ ```bash
87
+ source .venv/bin/activate
88
+ python -m pip install -e ".[dev]"
89
+ ```
90
+
91
+ ## Identify one device
92
+
93
+ ```python
94
+ from hostlens import identify
95
+
96
+ device = identify("192.168.1.42")
97
+
98
+ print(device.best_name)
99
+ print(device.manufacturer)
100
+ print(device.device_type)
101
+ print(device.model)
102
+ print(device.os)
103
+ print(device.confidence)
104
+ ```
105
+
106
+ Possible result
107
+
108
+ ```text
109
+ Samsung QN90C
110
+ Samsung Electronics
111
+ smart_tv
112
+ QN90C
113
+ Tizen
114
+ 0.94
115
+ ```
116
+
117
+ Unknown values stay `None`
118
+
119
+ HostLens does not fill gaps with strings like `Unknown` or `N/A`
120
+
121
+ ## Async API
122
+
123
+ Async is the main implementation and the sync helpers are only thin wrappers
124
+
125
+ ```python
126
+ from hostlens import HostLens
127
+
128
+ intel = HostLens()
129
+ device = await intel.identify("192.168.1.42")
130
+ ```
131
+
132
+ Scan one host deeply
133
+
134
+ ```python
135
+ device = await intel.scan_host("192.168.1.42", mode="deep")
136
+ ```
137
+
138
+ Scan several hosts with bounded concurrency
139
+
140
+ ```python
141
+ devices = await intel.scan_hosts(
142
+ [
143
+ "192.168.1.20",
144
+ "192.168.1.21",
145
+ "192.168.1.30",
146
+ ],
147
+ mode="normal",
148
+ )
149
+ ```
150
+
151
+ Scan a subnet
152
+
153
+ ```python
154
+ devices = await intel.scan_network("192.168.1.0/24")
155
+ ```
156
+
157
+ Let HostLens detect the local subnet
158
+
159
+ ```python
160
+ devices = await intel.scan_network()
161
+ ```
162
+
163
+ Pick an interface when the machine has more than one active connection
164
+
165
+ ```python
166
+ intel = HostLens(interface="Wi-Fi")
167
+ devices = await intel.scan_network()
168
+ ```
169
+
170
+ ## Scan modes
171
+
172
+ | Mode | Good for | What it does |
173
+ | --- | --- | --- |
174
+ | `fast` | A quick inventory | Neighbor table, ARP, reverse DNS, and OUI |
175
+ | `normal` | Everyday use | Fast scan plus mDNS, SSDP, UPnP, and local rules |
176
+ | `deep` | More detail | Normal scan plus NetBIOS and selected TCP services |
177
+ | `passive` | Quiet observation | Reads devices already visible in the OS neighbor table |
178
+
179
+ Normal mode is the default
180
+
181
+ Deep mode checks a small curated set of useful ports rather than scanning every
182
+ port on every device
183
+
184
+ ```python
185
+ from hostlens import HostLens, ScanMode
186
+
187
+ intel = HostLens(timeout=3, concurrency=64)
188
+ devices = await intel.scan_network(mode=ScanMode.FAST)
189
+ ```
190
+
191
+ ## Progressive results
192
+
193
+ Applications do not need to wait for every collector to finish
194
+
195
+ HostLens first reports the device and then sends enriched updates
196
+
197
+ ```python
198
+ from hostlens.models import DeviceFound, DeviceUpdated, ScanCompleted
199
+
200
+ async for event in intel.scan_stream("192.168.1.0/24", mode="normal"):
201
+ match event:
202
+ case DeviceFound(device=device):
203
+ print("found", device.ip)
204
+
205
+ case DeviceUpdated(device=device):
206
+ print("updated", device.best_name)
207
+
208
+ case ScanCompleted(summary=summary):
209
+ print(summary)
210
+ ```
211
+
212
+ Ask for a full scan result when summary data matters
213
+
214
+ ```python
215
+ result = await intel.scan_network(mode="fast", return_result=True)
216
+
217
+ print(result.summary.duration)
218
+ print(result.summary.targets_checked)
219
+ print(result.summary.devices_found)
220
+ ```
221
+
222
+ ## Device profile
223
+
224
+ Every scan returns the same public model
225
+
226
+ ```python
227
+ device.ip
228
+ device.mac
229
+ device.hostname
230
+ device.manufacturer
231
+ device.device_type
232
+ device.model
233
+ device.os
234
+ device.services
235
+ device.confidence
236
+ device.fields
237
+ device.evidence
238
+ device.best_name
239
+ ```
240
+
241
+ Pydantic serialization is available without another conversion layer
242
+
243
+ ```python
244
+ data = device.model_dump()
245
+ json_data = device.model_dump_json(indent=2)
246
+ ```
247
+
248
+ ## Evidence and confidence
249
+
250
+ HostLens keeps the raw facts that produced a profile
251
+
252
+ ```python
253
+ for item in device.evidence:
254
+ print(item.source, item.field, item.value, item.confidence)
255
+ ```
256
+
257
+ You can also get a readable explanation
258
+
259
+ ```python
260
+ print(device.explain())
261
+ ```
262
+
263
+ ```text
264
+ Samsung QN90C
265
+ Confidence: 94%
266
+
267
+ Evidence:
268
+ - MAC prefix matches Samsung Electronics
269
+ - UPnP manufacturer reports Samsung Electronics
270
+ - UPnP modelName reports QN90C
271
+ - mDNS advertises _airplay._tcp.local
272
+ ```
273
+
274
+ Confidence values are heuristic scores for ranking evidence
275
+
276
+ They are not presented as mathematically calibrated probabilities
277
+
278
+ ## Privacy and Fingerbank
279
+
280
+ Local scanning works without an account or API key
281
+
282
+ Cloud enrichment is disabled by default and HostLens never sends device data to
283
+ Fingerbank unless the caller enables it
284
+
285
+ ```python
286
+ intel = HostLens(
287
+ cloud=True,
288
+ fingerbank_api_key="your-api-key",
289
+ )
290
+ ```
291
+
292
+ Preview the payload before any cloud lookup
293
+
294
+ ```python
295
+ payload = intel.preview_cloud_payload(device)
296
+ print(payload)
297
+ ```
298
+
299
+ API keys are not stored in evidence and are not written to logs
300
+
301
+ ## CLI
302
+
303
+ The CLI calls the same Python API used by applications
304
+
305
+ ```bash
306
+ hostlens identify 192.168.1.20
307
+ hostlens identify 192.168.1.20 --deep
308
+
309
+ hostlens scan
310
+ hostlens scan --fast
311
+ hostlens scan --deep
312
+ hostlens scan 192.168.1.0/24
313
+
314
+ hostlens discover
315
+ hostlens watch --passive
316
+ ```
317
+
318
+ ## How the code is organized
319
+
320
+ ```text
321
+ src/hostlens/
322
+ ├── api.py sync and async convenience functions
323
+ ├── client.py public orchestration API
324
+ ├── config.py scan mode defaults
325
+ ├── models/ public Pydantic models and scan events
326
+ ├── network/ addressing, neighbor table, and ARP discovery
327
+ ├── collectors/ one focused module for each protocol
328
+ ├── parsers/ deterministic protocol parsing
329
+ ├── identity/ fingerprints, Fingerbank, and evidence fusion
330
+ └── cli/ commands and terminal output
331
+ ```
332
+
333
+ The dependency flow stays simple
334
+
335
+ ```text
336
+ network discovery
337
+
338
+ evidence collectors
339
+
340
+ local or cloud identity hints
341
+
342
+ evidence fusion
343
+
344
+ DeviceProfile
345
+ ```
346
+
347
+ Collectors collect facts
348
+
349
+ Parsers parse protocol data
350
+
351
+ Fingerprint rules interpret facts
352
+
353
+ Fusion picks the final values
354
+
355
+ The client only coordinates those steps
356
+
357
+ ## Platform notes
358
+
359
+ Active ARP discovery uses Scapy
360
+
361
+ Windows may need [Npcap](https://npcap.com/) and an elevated terminal for raw ARP
362
+ access
363
+
364
+ Linux may need root or the relevant raw socket capability
365
+
366
+ When raw ARP is unavailable, HostLens can still use the local neighbor table and
367
+ the collectors that work in the current environment
368
+
369
+ ## Development
370
+
371
+ ```bash
372
+ python -m ruff format --check .
373
+ python -m ruff check .
374
+ python -m pyright
375
+ python -m pytest
376
+ python -m build
377
+ ```
378
+
379
+ The normal unit test suite does not need a live network
380
+
381
+ Tests that use a real LAN should use the `network` marker
382
+
383
+ ## Local benchmark
384
+
385
+ The benchmark runs three fast scans and prints only aggregate timing and counts
386
+
387
+ It never prints local IP addresses or MAC addresses
388
+
389
+ ```bash
390
+ python benchmarks/local_scan.py
391
+ ```
392
+
393
+ Fast scan performance depends on ARP access, the operating system neighbor table,
394
+ DNS response time, interface size, and the number of visible devices
395
+
396
+ ## Publishing
397
+
398
+ The GitHub Actions workflow lives at `.github/workflows/workflow.yml`
399
+
400
+ Every push and pull request runs formatting, linting, typing, tests, and package
401
+ builds
402
+
403
+ Publishing uses PyPI trusted publishing when a version tag such as `v0.1.0` is pushed
404
+
405
+ The PyPI project needs a trusted publisher for
406
+
407
+ ```text
408
+ Owner Cyber0x3a
409
+ Repository hostlens
410
+ Workflow workflow.yml
411
+ ```
412
+
413
+ ## License
414
+
415
+ [MIT](LICENSE)