Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

devscan

Find every dev server on your LAN — starting with your own.

Most scanners point outward. This one starts with you.

devscan --me     # what is my laptop exposing to this wifi right now?
devscan          # ...and what is the rest of this network exposing?
ENDPOINT               HTTP  STACK              TITLE                          SERVER
--------------------------------------------------------------------------------------
10.0.4.138:8000        200   Python http.server internal dashboard             SimpleHTTP/3.12
10.0.5.123:8501        200   Streamlit          Streamlit                      uvicorn
10.0.6.58:3000         200   Next.js            admin console                  Next.js
10.0.9.26:8000         200   FastAPI/uvicorn    -                              uvicorn
10.0.11.234:9000       403   MinIO              -                              MinIO
10.0.7.207:11434       200   Ollama             -                              -

128 dev endpoints on 90 hosts  |  2250 reachable  |  624 AirPlay (hidden)

Identifies the stack behind each open port — Next.js, Express, FastAPI, Vite, Streamlit, Ollama, MinIO, nginx, Jupyter, Gradio, Flask, Grafana, Elasticsearch. No root. No telemetry. One bash file.

Built after a scan where 86% of the "open ports" turned out to be macOS AirPlay. It knows the difference — and it knows three other ways a port scan lies to you silently. See Four things that make the output trustworthy.

Install

git clone <repo> devscan && cd devscan && ./install.sh

or just drop the single file anywhere on your PATH:

curl -o /usr/local/bin/devscan <raw-url> && chmod +x /usr/local/bin/devscan

Dependencies: nmap (brew install nmap) and curl. Falls back to netcat if nmap is missing. No root required.

Usage

devscan                          # scan the local subnet
devscan --me                     # what am I exposing to this network?
devscan --fast                   # top 14 ports, more workers (~3x faster)
devscan --thorough               # retry harder; use if you suspect missed ports
devscan -s 192.168.1.0/24        # a specific subnet
devscan -p 3000,8000,8080        # specific ports
devscan -j | jq '.services[]'    # JSON out
devscan -o ~/scan                # write txt/csv/json
devscan -a                       # ARP cache only, skip the sweep (fast)
devscan --me -all                # every one of the 65535 ports on MY machine (~5s)
devscan -s 192.168.1.0/24 -all   # ...on a whole subnet (slow; watch the ETA)

What it does

Four stages, each feeding the next so the expensive work only runs on live hosts:

  1. Host discovery — reads the ARP cache and expands the subnet CIDR.
  2. Liveness sweep — 250-way parallel ping. Cuts an 8190-address /19 down to the hosts that actually answer, in ~35 seconds. ARP-known hosts that stay silent get a cheap TCP liveness probe, so ping-blocking machines aren't lost — without dragging every stale ARP entry into the scan.
  3. Port scan — parallel nmap TCP connect scan across ~41 dev ports, with a live progress meter (hosts done, ports found, ETA).
  4. Identification — one GET / per endpoint, classified into Next.js, Express, FastAPI/uvicorn, Streamlit, Vite, MinIO, nginx, Jupyter, Gradio, Ollama, Flask, Grafana, Elasticsearch, or a router admin panel.

Five things that make the output trustworthy

These are the failure modes that made earlier hand-rolled versions of this scan quietly wrong. They're worth knowing because they'll bite any reimplementation.

macOS AirPlay masquerades as a dev server. Ports 5000 and 7000 are ControlCenter (AirPlay Receiver) on every modern Mac. On a network of 546 Macs that's 318 of 369 open ports — 86% of your results are noise, and they look exactly like a Flask app on 5000. devscan scans them, then reports them separately. --show-airplay if you want them.

--host-timeout silently zeroes your scan. When most ports are filtered rather than closed, each probe waits out its full timeout, so a host easily exceeds a 40s budget and nmap abandons it — recording Status: Timeout and emitting no open ports at all. The scan completes, exits 0, and reports nothing wrong. devscan drops --host-timeout entirely and bounds the individual probes instead (--max-rtt-timeout 900ms), so slow hosts finish rather than get abandoned. It then counts any residual timeouts and warns.

--max-retries 0 silently abandons ports. The obvious cure for #2 is to stop nmap retrying. Do that on lossy wifi and it starts printing giving up on port because retransmission cap hit (0) — once per abandoned port, to stdout, which most wrappers send to /dev/null. Measured on 200 hosts: --max-retries 0 produced dozens of drops, --max-retries 1 produced one. devscan defaults to 1, exposes 0 via --fast, and counts the warnings and tells you rather than under-reporting in silence.

sort -u with restricted keys eats your findings. This looks reasonable:

sort -u -t. -k1,1n -k2,2n -k3,3n -k4,4n endpoints.txt

With -t., the fourth field of 10.7.11.170:8000 is 170:8000, and -k4,4n parses that as 170. Every port on a host therefore shares one sort key, and -u deduplicates on the key, not the line — so a host with four open ports collapses to one. devscan builds an explicit zero-padded key and dedupes on the full line (sort_eps).

A dead worker looks exactly like an empty network. devscan splits the host list across parallel nmap processes. An early version of -all used --min-parallelism 512 across 8 workers — nmap itself warns "can hurt reliability" — and a worker scanning a remote host simply never finished. Its chunk contributed zero open ports and no error, so a subnet containing a machine with 16 open ports reported open: 0. devscan now counts how many hosts actually returned results and, if any are missing, says the output is a floor, not a total. All-ports scans use depth (4 workers, parallelism 128) rather than a stampede.

And one more, non-obvious: in zsh, for p in $PORTS does not word-split an unquoted scalar the way bash does — you get one iteration with the whole string. devscan is #!/usr/bin/env bash for exactly this reason.

Limitations

  • Dev servers bound to 127.0.0.1 are invisible from the network by design. Vite, Next.js and Rails all default to localhost, so absence here is not evidence nothing is running — it usually means it's bound correctly.
  • ICMP-filtered hosts are only probed if they appear in the ARP cache.
  • IPv6-only listeners are invisible. This is an IPv4 connect scan, so a service bound to :: without dual-stack will not appear. Verified on macOS: rapportd listeners that bind IPv6-only never show up.
  • lsof without sudo shows only your processes, so it is not a complete ground truth. In testing, devscan --me -all correctly found open 445 (SMB) and 3306 (MySQL) that unprivileged lsof could not see.
  • Identification is a single unauthenticated GET /. It reads banners; it does not probe paths, guess credentials, or test for vulnerabilities.

Scope

This is an inventory tool. It performs host discovery, a TCP connect scan, and one HTTP GET per open port — the same traffic as opening the page in a browser. It contains no exploitation, credential, or vulnerability-probing behaviour, and none will be added.

Only scan networks you own or have written authorization to assess. Port scanning third-party hosts without permission is unlawful in many jurisdictions, and shared conference, campus, or coworking Wi-Fi is not your network.

License

MIT

About

Find every dev server on your LAN - starting with your own. One bash file, no root, no telemetry.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages