-
Notifications
You must be signed in to change notification settings - Fork 0
Releases: emrecdr/netlens
v0.4.0
Added
- Bluetooth capability callouts: 24 new curated capability entries
covering every BLE finding template. Every BLE check (gatt_misconfig,
pairing,privacy,spoofing,mesh,beacons,companion_app,
continuity_leak,tracking, plus platform notices and the
bettercap escalation) now constructs itsFindingwith an explicit
template="..."id, and a matching entry in
security/capabilities.CAPABILITIESdescribes the attacker
capability in plain language. The HTML report andnetlens demo
terminal output render the curated "What this means" callout for
BLE findings just like they do for IP-side ones. - New
Capability.attacker_contextvalue"radio-range"covers BLE
findings whose threat model is "an attacker within Bluetooth range"
rather than "an attacker on the same LAN". The lead-in phrase
becomes "An attacker within Bluetooth range could:" in both the
HTML report and demo terminal output. All 24 BLE entries use this
context. - SQLite schema migrates V7 → V8:
bluetooth_findings.template
column added. History-replayed BLE findings (netlens demo --scan-id <id>on a stored BLE scan) now carry their template id
through and the capability callout renders for them too. Migration
is idempotent via column-existence check; pre-V8 BLE rows have
NULLtemplate and fall back to the generic capability description.
Added
netlens demo --scan-id <id>and--latest-scan— historical
replay. Reuses the existingHistoryStore.load_scan(scan_id)/
list_scans(limit=1)paths to demonstrate against a stored scan
instead of running a fresh one. Findings carry their template ids
via the V7 schema migration, so the same curated "What this means"
callouts render for replayed findings. Flags are mutually exclusive;
both unset preserves the existing live-scan behavior.make watchtarget — re-runs the test suite viaentrwhenever a
.pyfile undersrc/ortests/changes. Optional system dep;
prints an install hint ifentris missing instead of failing
cryptically.
Changed
- Capability descriptions now carry an
attacker_contextfield
selecting the lead-in phrase used by the report and demo terminal
output. Three values:"lan"(default — "An attacker on the same
LAN could:"),"user-browser"(DNS rebinding — "A public web page
the device owner visits could:"),"lan-to-wan"(UPnP IGD control
— "An attacker on the LAN could pivot WAN exposure to:"). The
framing is now accurate for the two findings whose threat model
isn't a same-subnet attacker. dns_rebindingcapability rewritten — now correctly describes the
attacker as the device owner's browser (driven by an attacker-
controlled web page), not a same-LAN attacker. Previous framing was
technically misleading.unauth_upnp_dangerouscapability taggedattacker_context="lan-to-wan"
to make the WAN-exposure pivot legible in the report rendering.- HTML report's "What this means" callout styles moved from inline
style="..."attributes into the existing<style>block as
.capability-calloutrules. Easier to theme, lighter markup.
Added
-
18 additional
verify_commandentries — coverage doubles from
14 to 32 of 59 templates. Curated one-line commands for the
highest-leverage remaining finding types:- Printers:
unauth_ipp→lpstat -h {address};
unauth_jetdirect→ PJL status query vianc {address} 9100. - Cameras:
unauth_rtsp/rtsp_default_creds→ffprobeagainst
the RTSP URL (with or without creds). - TLS:
weak_tls_cipher/old_tls_protocol→
nmap --script ssl-enum-ciphers;
expired_tls_cert/self_signed_cert→openssl s_client+
x509 -noout -dates / -issuer / -subject. - HTTP:
http_only_admin→curl -v http://{address}/;
dangerous_http_methods→curl -X OPTIONS -i http://{address}/. - MQTT:
mqtt_full_control→mosquitto_pubto own topic;
mqtt_subscribe_all→mosquitto_sub -t '#' -C 5 -W 5. - UPnP:
unauth_upnp_service/exposed_upnp→netlens demo
against the device (recurses through the existing UPnP capture). - CoAP:
coap_resources_exposed→coap-client -m get coap://{address}/.well-known/core. - ARP:
arp_ip_mac_conflict→arp -n {address}(compare
against evidence MACs). - BLE:
ble_pairing_dual_mode→netlens probe {address};
ble_bettercap_extra_device→sudo bettercap -eval 'ble.recon on; sleep 30; ble.show; quit'.
The remaining 27 templates without a
verify_commandare
intentional skips: passive-observation findings (beacons, Apple
Continuity, PII in BLE names), reference-only findings (cve_found,
firmware_outdated,favicon_identified), or commands too complex
to reduce to one line (dns_rebinding,rogue_dhcp_server,
llmnr_active,nbtns_active,gratuitous_arp_flood,
ble_findmy_separated,ble_tracking_*,ble_mesh_*,
ble_macos_gatt_blocked). The structural test
(test_every_verify_command_substitutes_without_leftover_braces)
catches any new template with a broken substitution before merge. - Printers:
Refactored
- Extracted shared finding-panel renderer to
output/finding_panel.py.
Before: three commands (netlens demo,netlens risks,
netlens bluetooth) each had its own ~50-line copy of the same
"render finding as Rich Panel with capability headline + lead-in +
action bullets + verify_command" code. The render shape evolved
three times in three places this session. Now there is one
build_actionable_panel(address, finding) → Panelbuilder and one
render_actionable_section(rows, console)that filters to
CRIT/HIGH and prints panels. Three consumers collapse to a few
lines each. Net: ~90 lines removed, single source of truth for the
panel shape, identical rendered output verified by the 10 existing
verify-command tests.
Added
-
netlens correlate --jsonand--verboseflags.--json
emits the correlations as a single JSON payload to stdout
(suitable forjqconsumption);--verboseexpands the per-signal
score breakdown in the rendered table when the matcher exposes it.
Bringscorrelateto CLI-flag parity withscan,risks,
bluetooth,demo,traffic,segment,topology,history,
diff,devices. -
netlens watch --jsonand--verboseflags.--jsonswitches
the loop from a live status table to JSONL output — one object per
cycle on stdout withcycle,scan_id,timestamp,devices,
findings, and (when--verbose) the full diff payload. Suitable
for piping into a log aggregator. Webhook delivery still fires
independently when--webhook-urlis also set. -
README documents the "What this means / What to test next" UX
introduced this session (curated capability callouts +
copy-pasteable verify commands), thenetlens bluetooth --verify
interactive walk-through, and themake-target convenience
targets including the newmake bluetooth-verify. The Quick
Start now references the Makefile so contributors don't have to
memorise theuv runincantations. -
netlens risksnow renders capability + verify-command callouts
for CRIT/HIGH findings. After the existing per-severity tables,
an "Actionable findings (N CRIT/HIGH) — what to test next:" block
prints one panel per finding showing the title, evidence, curated
capability headline, and the substituted verify command (e.g.
netlens demo 192.168.1.50 --prove-creds). Closes the gap where
the most-frequently-run command had no actionable next-step
rendering despite having all the data available. -
BLE HTML report parity for capability callouts. Every CRIT/HIGH
BLE finding in the standalone HTML audit report (netlens bluetooth --report PATH) now ships with a collapsible "What this means" panel
showing the curated attacker capability, attacker-context-aware
lead-in ("An attacker within Bluetooth range could:"), bulleted
action list, and verify-command block. Brings the BLE HTML report
to feature parity with the IP scan HTML report. -
make bluetooth-verifyMakefile target — runs the BLE audit with
the interactive--verifywalk-through enabled, so the new
guided y/N-per-finding flow is discoverable viamake help. -
10 new tests covering the
render_verify_commandhelper (substitution,
None handling, literal-placeholder pass-through), a structural test
asserting every curatedverify_commandsubstitutes cleanly without
leftover braces, integration tests for the risks-table actionable
section, and a BLE-HTML-context test verifying capability blocks
attach to CRIT/HIGH findings only. -
verify_commandper Capability — copy-pasteable next-step tests.
Each curated capability entry can now carry a one-line command the
user can run to verify or actively demonstrate the finding on their
own device.{address}is substituted with the device's IP or BLE
MAC at render time. Currently wired for the highest-leverage finding
types:ble_gatt_writable_no_auth→
netlens probe {address},ble_manufacturer_mismatch→
python scripts/bt_read_all.py {address},ble_pairing_discoverable,
ble_companion_app_match,default_creds_ssh/default_creds_http/
default_creds_admin_panel→netlens demo {address} --prove-creds,
default_creds_telnet/open_telnet→telnet {address},
default_snmp_community→snmpwalk -v 2c -c public {address} system,
unauth_admin_panel→curl -sS http://{address}/ | head -20,
unauth_mqtt→mosquitto_sub -h {address} -t '#' -C 5,
anonymous_ftp,unauth_upnp_dangerous. The render layer surfaces
the command in three places:netlens demo's per-finding panel,
the HTML report's "What this means" capability callout, and the
JSON bundle (capability.verify_command). -
**
netlens bluetoothnow rend...
Assets 2
v0.3.0
Architecture-level refactor release. Introduces netlens.net as the
canonical home for shared transport primitives (probe HTTP client, port
constants, scheme helper) and documents the layering rule that keeps
feature modules from cross-importing each other for plumbing. Fixes a
silent cross-store MAC-case mismatch via a v5 → v6 schema migration,
re-roots netlens risks to read from history by default (saving an
unintentional live scan on every invocation), and collapses N+1 SQLite
patterns in load_scan and post-scan baseline drift. Bluetooth checks
now fan out concurrently. All 14 HTTP probe sites flow through a single
probe_client() factory with consistent secure-by-default knobs.
Architecture
- Introduced
netlens.netas the canonical home for shared transport
primitives.probe_client(formerlysecurity/_http_client.py),
HTTPS_PORTS,HTTP_SERVICE_NAMES, and the newscheme_for_port()
helper now live there. Eighteen feature-module call sites import from
netlens.netinstead of cross-importing fromsecurity/credentials.
Deleted the orphanedsecurity/_http_client.py. Documented the
layering rule in CLAUDE.md (cli → orchestration → feature modules → storage/output → net → models). - Replaced the
scheme = "https" if port in HTTPS_PORTS else "http"ternary
duplicated across six probe modules with a single
scheme_for_port(port)helper.
Fixed
- MAC case mismatch across stores (silent data bug). The
devices.mac
column was stored lowercase (ARP-side normalization) whilebaselines.mac
anddevice_profiles.macwere stored uppercase (profiles-side normalization).
Cross-store queries by MAC silently returned zero results — most visible
innetlens devices show <MAC> --history. The two divergent
normalize_mac()implementations are unified into a single
lowercase-canonical helper innetlens.fingerprint.mac_lookup. Schema
migrates v5 → v6 in place:UPDATE device_profiles SET mac = LOWER(mac)
and the same forbaselines. Idempotent viaWHERE mac != LOWER(mac). netlens risksno longer triggers a full live scan on invocation. It
now reads the most recent scan from history (matching the post-scan
"Next Steps" panel's promise ofShow findings (N found)). Pass
--scan-idto target a specific stored scan or--rescanto force a
live audit. A positionalnetworkargument still implies--rescan
to avoid showing stale history for the wrong subnet.
Changed
HistoryStore.load_scanno longer issues 3 queries per device. Ports,
findings, and services are now batched into one query per child table
(1 + 1 + 3 instead of 1 + 1 + 3N round-trips). Large stored scans
open noticeably faster innetlens reportandnetlens risks.HistoryStore.save_scannow auto-prunes past the 500 most recent
scans. Long-runningwatchsessions no longer growhistory.db
without bound. FK cascade wipes the device/port/finding/service rows._attach_baseline_findings(post-scan baseline drift) now reads all
baselines in a single query (ProfileStore.get_latest_baselines)
instead of one SQLite open + WAL pragma per device MAC.- Bluetooth checks fan out via
asyncio.gatherinstead of running
serially per device. An audit pass now scales with the slowest check,
not the sum of all checks. Per-check exception isolation preserved.
Security
- All HTTP probe traffic now routes through a single
netlens.security._http_client.probe_client()factory with
consistent secure-by-default knobs (verify=Falsefor self-signed
IoT certs,follow_redirects=Falsefor SSRF defence, identifying
User-Agent: NetLens/<version>so operators can recognise probes
in their logs). Replaces 14 inlinehttpx.AsyncClient(verify=False, ...)
instantiations acrosssecurity/*.pyandfingerprint/*.py. The
webhook client (notify/webhook.py) and the SSDP discovery client
(discovery/ssdp.py) keep their own configuration — they're not
probe traffic.
Internal
make_finding(*, severity, title, description, evidence, template, **vars)
factory insecurity/remediation.pycollapses the
Finding(... remediation=render_remediation("X", ip=ip, port=port))
boilerplate repeated across 38 probe sites (security/*.py,
traffic/arp_anomalies.py,snmp/community.py,
discovery/dhcp_probe.py,bluetooth/checks/cve.py,
fingerprint/favicon.py). Template vars are stringified inside the
factory so callers can passport=int.Severity.rankproperty +ALL_SEVERITIES_BY_RANKtuple added to
models/finding.pyas the single source of truth for severity sort
order. Replaces hand-rolled_SEVERITY_ORDERlists inpanels.py,
risks_table.py,scan_dashboard.py,bluetooth_table.py, and
the stringly-typed_SEVERITY_PRIORITYdict indeviation_table.py.
DEVIATION_SEVERITY_RANKpromoted toprofiles/models.py.severity_badge()helper inoutput/theme.pyreplaces the
f"[{style}]LABEL[/{style}]"markup repeated across three renderers.HTTPS_PORTSandHTTP_SERVICE_NAMESconsolidated to one definition
insecurity/credentials.py. Six modules
(protocols.py,tls.py,admin_panels.py,dns_rebinding.py,
rest_api.py,fingerprint/banner_grab.py) now import the canonical
copy instead of redeclaring it.- Stale
noqa: PLC0415deferred imports removed from
history/store.py(load_scan/load_bluetooth_scan). No real
cycle existed; imports hoisted to module top.
Assets 2
v0.2.0
The biggest release since 0.1.0. Three new pillars: a 10-phase
Bluetooth security audit (netlens bluetooth) with industry-standard
coverage (firmware-CVE pipeline, beacon/continuity parsers, anti-stalking
detection, companion-app DB, optional active write-probe), a WiFi
security audit surfacing WPS / PMKID / WEP / Open findings end-to-end
through the CLI and HTML report, and cross-radio correlation
(netlens correlate) that joins WiFi/IP devices with their BLE adverts
into a single device view. Adjacent additions: interactive netlens probe for one-target BLE GATT exploration, netlens watch --webhook-url
for change notifications, behavioral baseline drift findings, and a
self-contained scripts/ toolkit (bt_connect_demo.py, bt_read_all.py,
bt_control.py) for hands-on BLE exploration outside the main CLI.
Adds the optional [bluetooth] extra (uv sync --extra bluetooth) for
bleak + pyyaml. SQLite history schema migrates v3 → v4 → v5 in place;
existing IP-scan history is preserved.
Added
-
New
netlens bluetoothsubcommand — industry-standard BLE security
audit. Ten phases shipped:- Phase 1 Passive BLE discovery via
bleak, advertisement
fingerprinting (manufacturer ID lookup, service UUIDs, RSSI), BLE
address-type detection (Public / Random Static / RPA / Random
Non-Resolvable), persistence to SQLite history. - Phase 2 GATT enumeration of the Device Information Service
(firmware/hardware/software revision, manufacturer string, model
number, serial number, PnP ID) plus a 3-stage CVE pipeline:
NVD CPE search → NVD keyword-search fallback → offline curated
chipset DB (vulnerable_chipsets.yaml) covering the SweynTooth
family across Cypress / TI / Nordic / Espressif chipsets,
BrakTooth on Intel and Qualcomm, BleedingTooth (Linux host-side),
and KNOB. GATT enum is allowlist-gated by default — connections
only attempted for devices in~/.netlens/bluetooth_allowlist.txt;
--connect-alloverrides with a 5-second consent banner. - Phase 3 iBeacon / Eddystone (UID/URL/TLM/EID) / AltBeacon
parsing, Bluetooth Mesh detection, and service-UUID device
classification (Heart Rate Monitor, Smart Lock, AirTag,
Eddystone beacon, etc.). - Phase 4 Apple Continuity Protocol parsing (Handoff, AirDrop,
Nearby Info, Wi-Fi Settings, AirPods, FindMy separated) and
Microsoft Swift Pair parsing. Surfaces detached-AirTag warnings,
Wi-Fi-setup social-engineering windows, and PII in laptop names. - Phase 5 Curated checks: pairing-mode posture (discoverable +
connectable, dual-mode BR/EDR + LE), writable-without-auth GATT
characteristics, manufacturer-ID ↔ DIS string spoofing, PII in
advertised name (apostrophe-s, room labels, MAC fragments), and
BLE address-randomization posture (Public / Random Static flagged
as trackable). - Phase 6 Curated companion-app fingerprint DB
(companion_apps.yaml) with 15+ products: AirTag, Tile, Chipolo,
SmartTag, August / Schlage / Yale / Wyze locks, Apple Watch,
Fitbit, Garmin, Sonos, AirPods, Hue, LIFX. CVE attachment per
product where published. - Phase 7 Active write-probe (
--probe-writes) — writes a
benign0x00to writable GATT characteristics to confirm or
mitigate Phase 5 "writable-no-auth" findings. Allowlist-gated by
default;--probe-writes-alloverrides with a 5-second consent
banner. Outcomes classified assuccess/auth_required/
timeout/disconnect. - Phase 8 Cross-scan tracking / stalking detection. Reuses the
history DB to score fingerprint persistence across the lookback
window (default 10 scans). Flags devices appearing in ≥60% of
scans across ≥2 location tags as MEDIUM (or HIGH when also matched
by the companion-app DB). Special handling for AirTag-class
beacons that rotate identifiers — surfaces persistent FindMy-class
presence even when individual AirTag identifiers can't be
deduped.~/.netlens/bluetooth_owned.txtsuppresses tracking
findings for the user's own devices. - Phase 9 Platform escalation engines (best-effort augmentation,
silently skipped when unavailable):- macOS
system_profiler SPBluetoothDataType -jsonparser
enumerates paired Classic Bluetooth devices (keyboards, speakers,
gamepads — invisible to BLE-only bleak scanning) with their real
MAC addresses, and cross-references with discovered BLE devices
to mark host-paired ones as trusted. - Linux invokes
bettercapwhen the binary is on PATH; parses
its JSON event stream forble.device.newand
ble.device.service.discoveredevents; merges into the device
records.
- macOS
- Phase 10 Standalone HTML audit report (
--report PATH).
Self-contained: inlined CSS, no network requests, severity-coloured
findings table, beacon detail, continuity-protocol breakdown.
Reuses the existing Jinja2 environment + autoescape config from
netlens.reports.generator.ReportGeneratorso any future HTML-
escaping hardening applies automatically.
CLI flags:
--duration,--connect/--no-connect,--connect-all,
--gatt-timeout,--gatt-concurrency,--allowlist,--skip-cve,
--offline-only,--probe-writes,--probe-writes-all,
--tracking/--skip-tracking,--tracking-lookback,
--tracking-threshold,--owned-devices,--skip-escalation,
--report PATH,--location,--json,--verbose. Ships behind
the[bluetooth]extra:uv sync --extra bluetooth. - Phase 1 Passive BLE discovery via
-
New first-class
netlens probesubcommand: the interactive BLE
GATT control-surface probe that previously lived as a stand-alone
script (scripts/bt_control_probe.py) is now discoverable via
netlens --help. Same flags (--list,--latest,--write,
--payload,--no-notify,--timeout) and same workflow — enumerate
characteristics, mark writable ones, optionally drop into a REPL or
fire a one-shot targeted write. Registered as a leaf command (not a
sub-typer) so the naturalnetlens probe <address> --write <uuid>
invocation works without the options-before-positional gotcha that
applies to the otherinvoke_without_command=Truesubcommands.
The stand-alone script remains as a DEPRECATED shim so older
pip-installed copies keep working. -
New
read_full_value(client, char)helper innetlens.bluetooth.gatt
that loopsread_gatt_charpast the ATT-MTU saturation thresholds
(20, 244, 251, 509, 512, 514) and concatenates the resulting chunks
so callers see the full payload — not just the first chunk truncated
at the BLE PDU ceiling. Returns(bytes, truncated_flag)where the
flag is True when the read hit themax_sizesafety cap (default
4096) or the per-call iteration cap (8). Defences against pathological
backends include duplicate-chunk detection (some bleak backends ignore
offset and return the same first bytes on every call) and partial-read
preservation on mid-loop exceptions. Wired intonetlens probe's
REPLr <index>path and the--writeread-back so Chromecast-style
gzipped config blobs come out whole instead of truncating at 512B.
netlens probealso now prints the negotiated MTU at connect so the
user has the context to interpret a ⚠ truncated annotation. -
BLE device fingerprints now expose their contributing signals
(manufacturer ID, manufacturer-data prefix, sorted service UUIDs,
advertised name) alongside the SHA-256 hash. The hash contract is
unchanged so existing tracking state survives — what's new is
BluetoothDevice.fingerprint_components(Pydantic field) and a
matchingbluetooth_devices.fingerprint_components_jsonSQLite
column (V4→V5 migration; non-destructiveALTER TABLE ADD COLUMN,
pre-V5 rows returnNone).netlens bluetooth -vprints a
per-device "fingerprint signals" line so users can see why two
adverts collapsed to the same device — an explainability story
increasingly expected of security tooling. -
netlens bluetooth --load-external-checksflag enabling community-
contributed BLE checks from~/.netlens/checks/*.py. Each module
exposes aregister() -> list[BluetoothCheck]. Loader executes
arbitrary Python from disk so it is opt-in and prints a 5-second
consent banner matching the existing--connect-all/
--probe-writes-allUX. Built-in check names cannot be shadowed;
import failures, missingregister(), and type mismatches log a
WARNING and are skipped without crashing the scan. -
Four new entries in
companion_apps.yamlcovering devices empirically
observed in the user's environment:- LG webOS TV (vendor 0x00C4, name regex
[LG] webOS TV...,
confidencehigh) — carries real CVE-2023-6317 (RootedWebOS
auth-bypass on HTTP service ports 3000/3001 enabling RCE; webOS
4-7 until vendor patch in early 2024). Adds a newtvcategory. - LG DS60Q Soundbar (vendor 0x00C4, model-code name regex,
confidencemedium) —cves: []. Identification surfaces
remediation guidance on top of the existing curated
manufacturer-mismatchHIGH-severity check that already fires
for this device. Adds a newsoundbarcategory. - Polar Heart-Rate Sensor / Fitness Tracker (vendor 0x0501 +
standard HR service 0x180D + Polar name patterns,
confidencemedium) —cves: []. Live HR is cleartext-by-spec,
so identification + the "power off when not in use" remediation
are the actionable surface. MatchesMOTION_*(Polar Verity
Sense) plus the broader Polar strap family. - Apple AirPods / Beats (name-PII leak) (vendor 0x004C +
AirPods/Beats name regex, confidencelow) —cves: [].
Personalised device names like "Alice's AirPods Pro" leak owner
identity to passive BLE listeners; the entry's remediation tells
the user to rename the device. Mirrors the AirTag/Tile
"iden...
- LG webOS TV (vendor 0x00C4, name regex
Assets 2
v0.1.1
Post-0.1.0 polish release: fixes the Live scan dashboard against
multiple sources of frame tearing (third-party log noise, per-CPE NVD
warnings, render-wrapper clobber), tightens visual hierarchy by merging
two adjacent panels, calms the spinner cadence to a less frantic pulse,
and corrects the privilege-recovery instructions in the HTML report.
Changed
- Scan dashboard merges "Scan Stages" and "Current Activity" into a
single "Scan Progress" panel. The two adjacent panels read as one
region anyway and any tearing of the Live frame visually duplicated
both — collapsing them halves that surface and tightens the visual
hierarchy. - Scan dashboard active-stage spinner now uses an arc rotation
(◜◠◝◞◡◟) instead of subtle braille dots, so motion is visibly
obvious during long-running stages. - Spinner cadence calmed from 600 ms full rotation (~1.67 Hz) to
2.0 s (~0.5 Hz). The faster rate was inherited from a popular
default but only became observable once the freeze bug below was
fixed; in motion it read as frantic. 2 s reads as a confident
pulse — slow enough to be ambient, fast enough to signal liveness.
Fixed
- Per-CPE NVD lookup failures (almost all of which are 404s — the
CPE simply has no record in NVD) no longer log at WARNING. They
now log at DEBUG with a corrected message ("No NVD result" rather
than "NVD API unreachable"). Per-device WARNINGs were tearing the
Live dashboard region the same way the SSDPConnection lost
noise did, and the level was wrong: a missing CPE record is not
actionable. Real network outages are still discoverable under
--verbose/ DEBUG. - HTML report's "ran without root" banner now spells out the full
recovery command (sudo -E uv run netlens scan) instead of just
sudo -E. On macOS,sudowithout-EresetsHOMEandPATH,
causinguv runto resolve a different venv path under/var/root
and the python process to come back with a non-zero EUID — so the
banner you were trying to escape just reappeared.-Epreserves
the env souv runlands on the editable project venv as root. - Scan dashboard no longer torn by stray
Connection lostlog lines
from third-party libraries.ssdp.aiologs"Connection lost"at
ERROR level on every M-SEARCH teardown — even successful ones where
exc=None— and stdlib'slogging.lastResorthandler routes that
to stderr outside Rich'sConsole, fragmenting the Live region.
The scan command now bumpsssdp,asyncssh,telnetlib3, and
aiohttploggers to CRITICAL via a new
netlens.output.log_policy.silence_noisy_libraries()helper before
entering the dashboard. - Scan dashboard active-stage spinner now actually animates during
silent stages. The dynamic-render wrapper installed in__enter__
was being clobbered byLive.update(...)on every event callback,
leaving auto-refresh ticks repainting a static snapshot — the
spinner appeared frozen on whatever frame was current at the last
callback. Switched event-driven repaints toLive.refresh()so the
wrapper survives and each tick re-evaluates state. - Scan dashboard no longer leaves a tall blank gap between the Live
Counters panel and the post-scan summary; the live region now ends
at its natural height instead of claiming the full terminal. - Post-scan KPI cards (Devices / Critical / High / Medium / Low) now
pack tightly instead of being spread across the full terminal width
with large gutters.
Assets 2
v0.1.0
Initial release.
Added
- Network discovery via ARP, mDNS, SSDP, and DHCP rogue detection.
- Fingerprinting stage: nmap port/service scans, MAC vendor lookup, SNMP
probing, banner grabbing, favicon hashing, adaptive nmap chunk sizing. - SNMP deep-walk stage with interface/ARP/routing table extraction.
- Device classification across 12 categories with confidence scoring.
- Security audit: default credentials (SSH/Telnet/HTTP), CVE lookup via
NVD, firmware staleness, TLS analysis, admin-panel discovery, UPnP
control surface, MQTT injection, REST API exposure, DNS rebinding,
protocol security, dangerous HTTP methods, printer vulnerabilities. - Traffic analysis stage: packet capture, DNS extraction, anomaly
detection. - WiFi audit: nearby networks, rogue AP detection, encryption audit.
- Topology mapping (gateway → switches → devices).
- Per-device profiles, scan history, and diff between scans (SQLite).
- Self-contained HTML reports (Jinja2, all CSS inline).
- Live multi-region scan dashboard with per-stage spinner, elapsed
clock, and device/finding counters; keeps animating during silent
stages and showsTraffic Analysis · Ns / Msagainst--capture-time. - Adaptive device table with priority tiers and inter-row separators
for wrapped multi-line cells. - Theme system (
--theme {auto,dark,light}) with semantic tokens. - Global CLI flags:
--version/-V,--quiet/-q,--no-color,
--no-progress,--theme. VERSIONfile as single source of truth for hatchling, runtime
netlens.__version__, and the CLI flag.- Release runbook in
docs/RELEASING.md.