Skip to main content

HostLens

Find and understand devices on your local network from Python

Test and publish PyPI Python License

Simple public API · Async first · Explainable results · No cloud calls by default

HostLens discovers devices on a LAN, collects useful facts about them, and turns those facts into one clean device profile

It uses ARP, the local neighbor table, reverse DNS, OUI data, mDNS, SSDP, UPnP, NetBIOS, and a small set of useful service checks

The normal API stays small even though the scan can use several protocols behind the scenes

Install

pip install hostlens

HostLens supports Python 3.11 and newer on Windows, Linux, and macOS

For local development

git clone https://github.com/Cyber0x3a/hostlens.git
cd hostlens
python -m venv .venv

Windows PowerShell

.\.venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"

Linux and macOS

source .venv/bin/activate
python -m pip install -e ".[dev]"

Identify one device

from hostlens import identify

device = identify("192.168.1.42")

print(device.best_name)
print(device.manufacturer)
print(device.device_type)
print(device.model)
print(device.os)
print(device.confidence)

Possible result

Samsung QN90C
Samsung Electronics
smart_tv
QN90C
Tizen
0.94

Unknown values stay None

HostLens does not fill gaps with strings like Unknown or N/A

Async API

Async is the main implementation and the sync helpers are only thin wrappers

from hostlens import HostLens

intel = HostLens()
device = await intel.identify("192.168.1.42")

Scan one host deeply

device = await intel.scan_host("192.168.1.42", mode="deep")

Scan several hosts with bounded concurrency

devices = await intel.scan_hosts(
    [
        "192.168.1.20",
        "192.168.1.21",
        "192.168.1.30",
    ],
    mode="normal",
)

Scan a subnet

devices = await intel.scan_network("192.168.1.0/24")

Let HostLens detect the local subnet

devices = await intel.scan_network()

Pick an interface when the machine has more than one active connection

intel = HostLens(interface="Wi-Fi")
devices = await intel.scan_network()

Scan modes

Mode Good for What it does
fast A quick inventory Neighbor table, ARP, reverse DNS, and OUI
normal Everyday use Fast scan plus mDNS, SSDP, UPnP, and local rules
deep More detail Normal scan plus NetBIOS and selected TCP services
passive Quiet observation Reads devices already visible in the OS neighbor table

Normal mode is the default

Deep mode checks a small curated set of useful ports rather than scanning every port on every device

from hostlens import HostLens, ScanMode

intel = HostLens(timeout=3, concurrency=64)
devices = await intel.scan_network(mode=ScanMode.FAST)

Progressive results

Applications do not need to wait for every collector to finish

HostLens first reports the device and then sends enriched updates

from hostlens.models import DeviceFound, DeviceUpdated, ScanCompleted

async for event in intel.scan_stream("192.168.1.0/24", mode="normal"):
    match event:
        case DeviceFound(device=device):
            print("found", device.ip)

        case DeviceUpdated(device=device):
            print("updated", device.best_name)

        case ScanCompleted(summary=summary):
            print(summary)

Ask for a full scan result when summary data matters

result = await intel.scan_network(mode="fast", return_result=True)

print(result.summary.duration)
print(result.summary.targets_checked)
print(result.summary.devices_found)

Device profile

Every scan returns the same public model

device.ip
device.mac
device.hostname
device.manufacturer
device.device_type
device.model
device.os
device.services
device.confidence
device.fields
device.evidence
device.best_name

Pydantic serialization is available without another conversion layer

data = device.model_dump()
json_data = device.model_dump_json(indent=2)

Evidence and confidence

HostLens keeps the raw facts that produced a profile

for item in device.evidence:
    print(item.source, item.field, item.value, item.confidence)

You can also get a readable explanation

print(device.explain())
Samsung QN90C
Confidence: 94%

Evidence:
- MAC prefix matches Samsung Electronics
- UPnP manufacturer reports Samsung Electronics
- UPnP modelName reports QN90C
- mDNS advertises _airplay._tcp.local

Confidence values are heuristic scores for ranking evidence

They are not presented as mathematically calibrated probabilities

Privacy and Fingerbank

Local scanning works without an account or API key

Cloud enrichment is disabled by default and HostLens never sends device data to Fingerbank unless the caller enables it

intel = HostLens(
    cloud=True,
    fingerbank_api_key="your-api-key",
)

Preview the payload before any cloud lookup

payload = intel.preview_cloud_payload(device)
print(payload)

API keys are not stored in evidence and are not written to logs

CLI

The CLI calls the same Python API used by applications

hostlens identify 192.168.1.20
hostlens identify 192.168.1.20 --deep

hostlens scan
hostlens scan --fast
hostlens scan --deep
hostlens scan 192.168.1.0/24

hostlens discover
hostlens watch --passive

How the code is organized

src/hostlens/
├── api.py              sync and async convenience functions
├── client.py           public orchestration API
├── config.py           scan mode defaults
├── models/             public Pydantic models and scan events
├── network/            addressing, neighbor table, and ARP discovery
├── collectors/         one focused module for each protocol
├── parsers/            deterministic protocol parsing
├── identity/           fingerprints, Fingerbank, and evidence fusion
└── cli/                commands and terminal output

The dependency flow stays simple

network discovery
      ↓
evidence collectors
      ↓
local or cloud identity hints
      ↓
evidence fusion
      ↓
DeviceProfile

Collectors collect facts

Parsers parse protocol data

Fingerprint rules interpret facts

Fusion picks the final values

The client only coordinates those steps

Platform notes

Active ARP discovery uses Scapy

Windows may need Npcap and an elevated terminal for raw ARP access

Linux may need root or the relevant raw socket capability

When raw ARP is unavailable, HostLens can still use the local neighbor table and the collectors that work in the current environment

Development

python -m ruff format --check .
python -m ruff check .
python -m pyright
python -m pytest
python -m build

The normal unit test suite does not need a live network

Tests that use a real LAN should use the network marker

Local benchmark

The benchmark runs three fast scans and prints only aggregate timing and counts

It never prints local IP addresses or MAC addresses

python benchmarks/local_scan.py

Fast scan performance depends on ARP access, the operating system neighbor table, DNS response time, interface size, and the number of visible devices

Publishing

The GitHub Actions workflow lives at .github/workflows/workflow.yml

Every push and pull request runs formatting, linting, typing, tests, and package builds

Publishing uses PyPI trusted publishing when a version tag such as v0.1.0 is pushed

The PyPI project needs a trusted publisher for

Owner       Cyber0x3a
Repository  hostlens
Workflow    workflow.yml

License

MIT

Metadata

Release files for hostlens 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hostlens 0.1.0
File Size Uploaded
hostlens-0.1.0.tar.gz 24.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hostlens 0.1.0
File Interpreter ABI Platform
hostlens-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 54.7 kB

Release files / hostlens-0.1.0.tar.gz

Download URL hostlens-0.1.0.tar.gz
Size 24.0 kB
Tags Source
SHA-256 checksum
How to use checksums
7ae23eeffa97760a60771aa652abda187219f2b6d343523d11dd8a1f6e52326d
BLAKE2b-256 checksum
How to use checksums
f5868b29de4fb87f5e73b42b2231da1227b451c068a10ab44658f5bc1be96935
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / hostlens-0.1.0-py3-none-any.whl

Download URL hostlens-0.1.0-py3-none-any.whl
Size 30.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a1b99da6ea4c4a4728c14475587b70f5e1232058aabe6f3b0a32138aa00f1b31
BLAKE2b-256 checksum
How to use checksums
9302d04c010d2932e595245fecf9443dede22d59d73dbb6267a7a05b612762d8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.0

2 release files

0.1.1

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page