<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Billy</title>
    <description>The latest articles on DEV Community by Billy (@devyjones).</description>
    <link>https://dev.to/devyjones</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3986902%2Fb8e99535-4b22-4d59-bcec-ffd433c964fc.png</url>
      <title>DEV Community: Billy</title>
      <link>https://dev.to/devyjones</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devyjones"/>
    <language>en</language>
    <item>
      <title>The Cheapest Dedicated Servers in 2026: A Price-Checked Shortlist</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Mon, 06 Jul 2026 11:28:16 +0000</pubDate>
      <link>https://dev.to/devyjones/the-cheapest-dedicated-servers-in-2026-a-price-checked-shortlist-3803</link>
      <guid>https://dev.to/devyjones/the-cheapest-dedicated-servers-in-2026-a-price-checked-shortlist-3803</guid>
      <description>&lt;p&gt;If you have ever tried to price a bare-metal server, you know the game: a big "&lt;strong&gt;from $X/mo&lt;/strong&gt;" on the landing page, then the actually-orderable configs cost two or three times that, half of them are out of stock, and the cheap one ships "in 3–5 business days." This is a price-checked shortlist of where to rent a genuinely cheap dedicated server in 2026 — who each provider is for, roughly where pricing starts, and the catch.&lt;/p&gt;

&lt;p&gt;I pulled the numbers for the last entry from a live, daily-refreshed dataset (linked at the bottom) so you can verify them instead of trusting a screenshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "cheap" should actually mean
&lt;/h2&gt;

&lt;p&gt;Before the list, four things that separate a real deal from bait:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The price you see is the price you pay.&lt;/strong&gt; Watch for setup/install fees and "from" pricing that no in-stock config matches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In stock now, not "provisioning in 5 days."&lt;/strong&gt; A $16 box you can't deploy today isn't a $16 box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bandwidth and traffic caps.&lt;/strong&gt; Unmetered 1 Gbps and a 30 TB cap are very different economics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location.&lt;/strong&gt; A server two continents from your users is cheap and useless.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The shortlist
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Kimsufi (OVH budget brand) — legacy boxes, rock-bottom
&lt;/h3&gt;

&lt;p&gt;OVH's budget label recycles older Atom/Xeon hardware at the lowest headline prices in the market. Great for a cheap always-on box, a seedbox, or a lab. Catch: stock is famously thin, hardware is old, and support is minimal.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Scaleway Dedibox — cheap French metal
&lt;/h3&gt;

&lt;p&gt;Long-running budget bare-metal out of Paris/Amsterdam, entry configs among the cheapest in the EU. Good if you specifically want France/NL and Scaleway's ecosystem. Catch: entry SKUs sell out and the cheapest tiers use aging CPUs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Hetzner (server auction) — best price/performance in the EU
&lt;/h3&gt;

&lt;p&gt;Not the lowest sticker, but the auction floor gets you modern Ryzen/EPYC with lots of RAM for what budget hosts charge for an Atom. The default answer for "cheap &lt;em&gt;and&lt;/em&gt; fast" in Germany/Finland. Catch: auction inventory rotates, and it's EU-only locations.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Contabo — cheap high-spec, oversubscribed
&lt;/h3&gt;

&lt;p&gt;Known for a lot of cores/RAM/disk per dollar. Fine for storage or batch workloads where you don't need guaranteed peak performance. Catch: reputation for oversubscription and variable I/O; read recent reviews for your region.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Leaseweb / enterprise carriers — not cheap, but predictable
&lt;/h3&gt;

&lt;p&gt;When uptime SLAs and DDoS protection matter more than the invoice, the enterprise carriers start higher but deliver consistency. Catch: you'll pay for it, and it's overkill for a hobby box.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Valebyte — cheap &lt;em&gt;and&lt;/em&gt; in-stock across 20 countries
&lt;/h3&gt;

&lt;p&gt;Aggregates in-stock bare-metal across a wide provider network, so the differentiator is &lt;strong&gt;breadth + honesty&lt;/strong&gt;: ~800 servers deployable now across 20 countries, prices are final (no "from $X" bait), and the whole catalog is published as a machine-readable dataset. Entry is &lt;strong&gt;$16/mo&lt;/strong&gt; (Atom-class in France/NL), with modern Ryzen from ~$72 and EPYC from ~$160. Catch: it's an aggregator, so the cheapest tiers are still older CPUs — same as everyone else at $16.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheapest by region (live figures)
&lt;/h2&gt;

&lt;p&gt;Where the boxes actually are and what the cheapest one costs, at the time of writing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;In stock&lt;/th&gt;
&lt;th&gt;Cheapest&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;France&lt;/td&gt;
&lt;td&gt;241&lt;/td&gt;
&lt;td&gt;$16/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Netherlands&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;$16/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Germany&lt;/td&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;td&gt;$59/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;United States&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;$41/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Poland&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;$54/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Singapore&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;$42/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canada&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;$40/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Australia&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;$40/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;France and the Netherlands are where the floor is; if you don't need a specific country, that's where the cheapest usable metal lives in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually verify prices
&lt;/h2&gt;

&lt;p&gt;The single most useful habit: &lt;strong&gt;check a machine-readable price list instead of marketing pages.&lt;/strong&gt; The regional numbers above come from an open dataset that refreshes daily — &lt;a href="https://github.com/ValebyteServers/dedicated-server-prices" rel="noopener noreferrer"&gt;dedicated-server-prices on GitHub&lt;/a&gt; — with CSV/JSON of specs, location, monthly price and order URL for every in-stock server. Clone it, sort by price, done. No "from $X," no stale screenshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Lowest sticker, don't care about hardware age → &lt;strong&gt;Kimsufi / Scaleway Dedibox&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Cheap &lt;em&gt;and&lt;/em&gt; fast in the EU → &lt;strong&gt;Hetzner auction&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Lots of cores/RAM per dollar → &lt;strong&gt;Contabo&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;SLA and DDoS matter more than price → &lt;strong&gt;Leaseweb&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Cheap, in-stock now, specific country → &lt;strong&gt;&lt;a href="https://valebyte.com/en/dedicated-servers/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=cheapest-dedicated-2026" rel="noopener noreferrer"&gt;Valebyte&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever you pick, sort by &lt;em&gt;in-stock final price in your region&lt;/em&gt; — not the number on the hero banner.&lt;/p&gt;

</description>
      <category>hosting</category>
      <category>devops</category>
      <category>sysadmin</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Bypassing Telegram Blocks: How MTProto FakeTLS Proxies Actually Work</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Sat, 04 Jul 2026 12:48:41 +0000</pubDate>
      <link>https://dev.to/devyjones/bypassing-telegram-blocks-how-mtproto-faketls-proxies-actually-work-5c53</link>
      <guid>https://dev.to/devyjones/bypassing-telegram-blocks-how-mtproto-faketls-proxies-actually-work-5c53</guid>
      <description>&lt;p&gt;When an ISP or national firewall throttles or blocks Telegram, the usual advice is "use a VPN." But there's a lighter, Telegram-specific tool that is often more resilient and far simpler for end users: the &lt;strong&gt;MTProto proxy&lt;/strong&gt; with &lt;strong&gt;FakeTLS&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  DPI vs. Telegram
&lt;/h2&gt;

&lt;p&gt;Deep Packet Inspection classifies traffic by its protocol fingerprint. A plain MTProto stream or a naive proxy is easy to spot and drop. The trick is to make the connection look like something completely ordinary.&lt;/p&gt;

&lt;h2&gt;
  
  
  How FakeTLS works
&lt;/h2&gt;

&lt;p&gt;MTProto is Telegram's own transport protocol. &lt;strong&gt;FakeTLS&lt;/strong&gt; wraps it so the wire looks like a normal TLS 1.3 handshake to a real website:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client sends a TLS &lt;code&gt;ClientHello&lt;/code&gt; with the &lt;strong&gt;SNI of a popular domain&lt;/strong&gt; (e.g. &lt;code&gt;www.microsoft.com&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Authentication is a per-user secret verified via an &lt;strong&gt;HMAC computed over the ClientHello&lt;/strong&gt; (with the 32-byte random field acting as the digest).&lt;/li&gt;
&lt;li&gt;If the HMAC is invalid (a censor's active probe), the proxy transparently &lt;strong&gt;proxies the prober to the real fronting domain&lt;/strong&gt; — so an attacker who pokes the port just gets Microsoft's real certificate back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To a censor, a real client's flow is indistinguishable from an HTTPS visit to that domain. That's what keeps it working where generic proxies are already blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  A failure mode nobody documents: clock skew
&lt;/h2&gt;

&lt;p&gt;Here's a real-world gotcha we hit in production. FakeTLS embeds a &lt;strong&gt;timestamp&lt;/strong&gt; for anti-replay. If the &lt;strong&gt;proxy host's clock drifts&lt;/strong&gt; more than a couple of minutes, every legitimate client handshake is rejected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;invalid faketls client hello: incorrect timestamp. got=..., now=..., diff=2m54s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The nasty part: the proxy still accepts TCP, still answers TLS, still passes an &lt;code&gt;openssl s_client&lt;/code&gt; masquerade check — it looks perfectly alive. It just silently &lt;strong&gt;falls back to domain-fronting instead of relaying&lt;/strong&gt;, so &lt;em&gt;every&lt;/em&gt; user on that host is broken at once. The fix is boring but essential: &lt;strong&gt;run NTP (chrony) on every proxy host.&lt;/strong&gt; We shipped a box without it and spent an afternoon chasing "the proxy is down" tickets that were really a 3-minute clock drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  MTProto proxy vs VPN
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;MTProto proxy&lt;/th&gt;
&lt;th&gt;VPN&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;Telegram only&lt;/td&gt;
&lt;td&gt;whole device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;one tap on a link&lt;/td&gt;
&lt;td&gt;install + configure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detectability&lt;/td&gt;
&lt;td&gt;masked as TLS&lt;/td&gt;
&lt;td&gt;often fingerprinted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Battery/speed&lt;/td&gt;
&lt;td&gt;negligible / fast&lt;/td&gt;
&lt;td&gt;heavier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For unblocking Telegram specifically, MTProto is usually the better tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Notes + setup (open): &lt;a href="https://github.com/blureshot/free-telegram-proxy" rel="noopener noreferrer"&gt;https://github.com/blureshot/free-telegram-proxy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A free, working MTProto proxy (24h, no signup, one-tap): &lt;a href="https://t.me/NotifyGproxyBot?start=devto" rel="noopener noreferrer"&gt;https://t.me/NotifyGproxyBot?start=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full disclosure: that bot is the free tier of a proxy service I work on (GProxy). But the mechanics above are protocol-level and vendor-neutral — any FakeTLS MTProto proxy (mtg, mtprotoproxy) works the same way, and the clock-skew lesson applies to all of them.&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>networking</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>A free proxy list that's actually checked (HTTP/SOCKS4/SOCKS5, updated every 30 min)</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Sat, 04 Jul 2026 10:38:33 +0000</pubDate>
      <link>https://dev.to/devyjones/a-free-proxy-list-thats-actually-checked-httpsocks4socks5-updated-every-30-min-1k6k</link>
      <guid>https://dev.to/devyjones/a-free-proxy-list-thats-actually-checked-httpsocks4socks5-updated-every-30-min-1k6k</guid>
      <description>&lt;p&gt;Every "free proxy list" you find is the same story: a giant wall of &lt;code&gt;ip:port&lt;/code&gt; lines, 90% of them already dead, no idea which are HTTP or SOCKS, no idea where they exit. You paste 500 into your scraper and three of them work.&lt;/p&gt;

&lt;p&gt;So I put up a small one that's &lt;strong&gt;actually checked&lt;/strong&gt;: &lt;a href="https://github.com/gproxynet/free-proxy-list" rel="noopener noreferrer"&gt;gproxynet/free-proxy-list&lt;/a&gt;. A checker validates each proxy, tags it with protocol, country and latency, and the repo is regenerated &lt;strong&gt;every 30 minutes&lt;/strong&gt;. It's a rotating sample, not a 50k dump — the point is that what's in it was alive minutes ago, not last month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grab it
&lt;/h2&gt;

&lt;p&gt;Plain text, one &lt;code&gt;ip:port&lt;/code&gt; per line, split by protocol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/gproxynet/free-proxy-list/main/all.txt
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/gproxynet/free-proxy-list/main/socks5.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or the structured version with protocol, country and latency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/gproxynet/free-proxy-list/main/proxies.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"proxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"103.156.224.66:8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4225&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"checked_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2026-07-04T09:34:16Z"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"proxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"185.26.180.180:80"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"NL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;340&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"checked_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2026-07-04T09:41:02Z"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use it in Python
&lt;/h2&gt;

&lt;p&gt;Filter to the protocol and speed you want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;proxies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://raw.githubusercontent.com/gproxynet/free-proxy-list/main/proxies.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;fast_socks5&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;proxies&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;protocol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;socks5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fast_socks5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fast SOCKS5 proxies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're rotating them across a scraper, feed the list straight into a pool. I use &lt;a href="https://github.com/gproxynet/proxyspin" rel="noopener noreferrer"&gt;proxyspin&lt;/a&gt; (a small rotating pool with health tracking + ban detection), which can load a URL directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;proxyspin&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ProxyPool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;proxyspin.requests_adapter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RotatingSession&lt;/span&gt;

&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProxyPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://raw.githubusercontent.com/gproxynet/free-proxy-list/main/all.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RotatingSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# dead ones get benched automatically
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://httpbin.org/ip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The honest part
&lt;/h2&gt;

&lt;p&gt;These are &lt;strong&gt;public proxies&lt;/strong&gt;. They're shared by strangers, they're slow, they die within minutes to hours, and you should never send anything sensitive through them. A checked list saves you the "which of these 500 is alive" step — it does not make public proxies reliable. They're great for testing, learning, one-off checks and throwaway requests.&lt;/p&gt;

&lt;p&gt;The moment you need proxies that &lt;em&gt;stay&lt;/em&gt; up — real scraping, account work, ad verification — you'll want dedicated ones (residential, mobile or datacenter) instead of public ones. But if a free, checked, auto-updating list is what you need today, it's there:&lt;/p&gt;

&lt;p&gt;➡️ &lt;strong&gt;&lt;a href="https://github.com/gproxynet/free-proxy-list" rel="noopener noreferrer"&gt;github.com/gproxynet/free-proxy-list&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bookmark the raw URL, poll it every so often, and you've always got a fresh handful to work with.&lt;/p&gt;

</description>
      <category>proxy</category>
      <category>python</category>
      <category>webscraping</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Rotating proxies for Scrapy, Playwright and requests — one small library</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Sat, 04 Jul 2026 10:28:29 +0000</pubDate>
      <link>https://dev.to/devyjones/rotating-proxies-for-scrapy-playwright-and-requests-one-small-library-2j97</link>
      <guid>https://dev.to/devyjones/rotating-proxies-for-scrapy-playwright-and-requests-one-small-library-2j97</guid>
      <description>&lt;p&gt;If you scrape anything at scale, you know the drill: proxies die, sites start returning 403/429, and your run grinds to a halt. The classic Scrapy answer, &lt;a href="https://github.com/TeamHG-Memex/scrapy-rotating-proxies" rel="noopener noreferrer"&gt;&lt;code&gt;scrapy-rotating-proxies&lt;/code&gt;&lt;/a&gt;, hasn't seen a real update in years — and it's Scrapy-only, so the moment you reach for Playwright or plain &lt;code&gt;requests&lt;/code&gt;, you're rebuilding rotation from scratch.&lt;/p&gt;

&lt;p&gt;I wanted &lt;strong&gt;one&lt;/strong&gt; small proxy pool I could share across all three. So I wrote &lt;a href="https://github.com/gproxynet/proxyspin" rel="noopener noreferrer"&gt;&lt;code&gt;proxyspin&lt;/code&gt;&lt;/a&gt;: a rotating pool with health tracking, ban detection and sticky sessions, and thin adapters for Scrapy, Playwright and &lt;code&gt;requests&lt;/code&gt;. Zero required dependencies, pure standard library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;proxyspin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The pool
&lt;/h2&gt;

&lt;p&gt;Everything is built around one object. Load proxies from a list, a file, or a URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;proxyspin&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ProxyPool&lt;/span&gt;

&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProxyPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxies.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;round_robin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# or inline / from your provider's export endpoint:
&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProxyPool&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://user:pass@gate1.example.com:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.0.0.2:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProxyPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/api/my-list.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;        &lt;span class="c1"&gt;# -&amp;gt; Proxy; proxy.url is ready to use
&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# bench it after repeated failures
&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# reset its failure streak
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It parses every common list format — &lt;code&gt;host:port&lt;/code&gt;, &lt;code&gt;host:port:user:pass&lt;/code&gt;, &lt;code&gt;user:pass@host:port&lt;/code&gt;, &lt;code&gt;scheme://user:pass@host:port&lt;/code&gt; — for HTTP, HTTPS, SOCKS4 and SOCKS5.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rotation strategies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;round_robin&lt;/code&gt; — cycle through healthy proxies in order (default)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;random&lt;/code&gt; — pick one at random&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sticky&lt;/code&gt; — keep returning the &lt;strong&gt;same&lt;/strong&gt; proxy for a given key (a target domain, an account id, a worker name) until it goes unhealthy&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The health model
&lt;/h3&gt;

&lt;p&gt;Every proxy starts healthy. &lt;code&gt;mark_failed&lt;/code&gt; bumps its failure streak; when the streak hits &lt;code&gt;max_failures&lt;/code&gt; (default 2) the proxy is benched with &lt;strong&gt;exponential backoff&lt;/strong&gt; (&lt;code&gt;cooldown * 2**overshoot&lt;/code&gt;, base 60s, capped at 1h), then automatically rejoins rotation. &lt;code&gt;mark_ok&lt;/code&gt; resets the streak. So dead proxies quietly drop out and recover on their own — you never manually prune the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scrapy
&lt;/h2&gt;

&lt;p&gt;Enable the middleware and point it at your proxies. Ban detection and retry-through-the-next-proxy are automatic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;DOWNLOADER_MIDDLEWARES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxyspin.scrapy_middleware.ProxySpinMiddleware&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;610&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;PROXYSPIN_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxies.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PROXYSPIN_STRATEGY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sticky&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;       &lt;span class="c1"&gt;# one proxy per target host
&lt;/span&gt;&lt;span class="n"&gt;PROXYSPIN_BAN_CODES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="c1"&gt;# these responses rotate the proxy
&lt;/span&gt;&lt;span class="n"&gt;PROXYSPIN_MAX_RETRIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any &lt;code&gt;403&lt;/code&gt;/&lt;code&gt;429&lt;/code&gt; (configurable) marks the proxy as failed and retries the request through the next healthy one. No spider code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playwright
&lt;/h2&gt;

&lt;p&gt;Playwright takes a proxy per browser context, which is the natural rotation unit — great for one-IP-per-account flows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;proxyspin&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ProxyPool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;proxyspin.playwright_helper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;proxy_settings&lt;/span&gt;

&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProxyPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxies.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sticky&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;proxy_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="c1"&gt;# each account keeps its own IP for the whole session
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  requests
&lt;/h2&gt;

&lt;p&gt;A drop-in &lt;code&gt;Session&lt;/code&gt; that rotates on every call and retries failures through another proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;proxyspin&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ProxyPool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;proxyspin.requests_adapter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RotatingSession&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RotatingSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProxyPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxies.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://httpbin.org/ip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;   &lt;span class="c1"&gt;# new IP per call
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Check a list first
&lt;/h2&gt;

&lt;p&gt;Bad proxies waste run time. The bundled CLI tests a whole list concurrently and writes out the survivors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;proxyspin check proxies.txt &lt;span class="nt"&gt;--workers&lt;/span&gt; 100 &lt;span class="nt"&gt;--alive-out&lt;/span&gt; alive.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OK   45.155.10.4:8000        612 ms  HTTP 200
DEAD 91.10.77.2:3128                 TimeoutError
...
118/200 alive
wrote 118 proxies to alive.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting proxies to test with
&lt;/h2&gt;

&lt;p&gt;Want to try it right now without your own proxies? Bootstrap straight from a live public list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProxyPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://raw.githubusercontent.com/gproxynet/free-proxy-list/main/all.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fair warning: &lt;strong&gt;public proxies are unreliable by nature&lt;/strong&gt; — they're shared, slow, and die within minutes. They're fine for kicking the tires, not for a real crawl. For production you'll want dedicated proxies (residential/mobile/datacenter); a pool of one gateway entry per endpoint is all &lt;code&gt;proxyspin&lt;/code&gt; needs since rotation happens server-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;One pool, the same health model everywhere, and you stop babysitting dead proxies. The code is MIT-licensed and on &lt;a href="https://github.com/gproxynet/proxyspin" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; — issues and PRs welcome. If you've been limping along on an unmaintained rotation middleware, give it a spin.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>scrapy</category>
      <category>proxy</category>
    </item>
    <item>
      <title>Finding Exposed Services (and Fixing Them) with ScanSearch and Python</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Sat, 04 Jul 2026 04:00:19 +0000</pubDate>
      <link>https://dev.to/devyjones/finding-exposed-services-and-fixing-them-with-scansearch-and-python-17pl</link>
      <guid>https://dev.to/devyjones/finding-exposed-services-and-fixing-them-with-scansearch-and-python-17pl</guid>
      <description>&lt;p&gt;Ever wondered what parts of your infrastructure are inadvertently exposed to the internet? Or maybe you're trying to debug a network service that just isn't behaving as expected, and you suspect a firewall issue or misconfiguration somewhere upstream. Manually scanning IP ranges can be tedious and slow, especially if you're dealing with a dynamic environment.&lt;/p&gt;

&lt;p&gt;This article will walk you through a practical approach to quickly identify internet-facing services and potential vulnerabilities using &lt;a href="https://scansearch.net" rel="noopener noreferrer"&gt;ScanSearch&lt;/a&gt;, an internet-wide search engine for network devices and services, combined with a bit of Python to automate and interpret the results. The goal isn't to hack anything, but to gain insight into your own network's perimeter and improve its security posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: What's Visible?
&lt;/h2&gt;

&lt;p&gt;It's surprisingly easy to accidentally expose services that shouldn't be public. A developer might spin up a test database, forget to configure its firewall rules, and suddenly it's reachable from anywhere. Or a misconfigured router might forward ports intended for internal use. These oversights create attack vectors that bad actors actively scan for.&lt;/p&gt;

&lt;p&gt;Instead of waiting for a security incident, let's proactively find what's visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing ScanSearch
&lt;/h2&gt;

&lt;p&gt;ScanSearch is a powerful tool for discovering network devices, services, and even vulnerabilities across the internet. Think of it like Google, but for network ports and banners. You can query it for specific port numbers, service banners, protocols, and more.&lt;/p&gt;

&lt;p&gt;For instance, if you want to find all devices exposing port 22 (SSH), you can simply search for &lt;code&gt;port:22&lt;/code&gt; on their website. The real power, however, comes from combining these queries and using their API for programmatic access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario: Finding Unexpected SSH Exposure
&lt;/h2&gt;

&lt;p&gt;Let's say you manage a set of servers and want to ensure that only specific, hardened SSH servers are exposed to the public internet. You want to quickly check if any other devices within your known IP ranges are also inadvertently exposing SSH.&lt;/p&gt;

&lt;p&gt;We'll use ScanSearch to query for devices running SSH on port 22, filtered by a specific IP range (e.g., your organization's public IP block). For this example, let's assume your organization uses the fictional IP range &lt;code&gt;192.0.2.0/24&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Manual Query (for exploration)
&lt;/h3&gt;

&lt;p&gt;Before diving into code, it's often helpful to test your query directly on the &lt;a href="https://scansearch.net" rel="noopener noreferrer"&gt;ScanSearch website&lt;/a&gt;. Go to the site and try searching for:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;port:22 ip:192.0.2.0/24&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This query tells ScanSearch to look for devices with port 22 open &lt;em&gt;and&lt;/em&gt; that fall within the &lt;code&gt;192.0.2.0/24&lt;/code&gt; IP range. You'll get a list of results, each showing the IP, port, and often a service banner (e.g., &lt;code&gt;SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.3&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Automating with Python
&lt;/h3&gt;

&lt;p&gt;Manually checking results is fine for a one-off, but for regular auditing or more complex analysis, an API is essential. ScanSearch offers an API that allows you to programmatically submit queries and retrieve results. (Note: You'll typically need an API key for programmatic access beyond basic public queries. Refer to the &lt;a href="https://scansearch.net/docs" rel="noopener noreferrer"&gt;ScanSearch documentation&lt;/a&gt; for details on API usage and authentication).&lt;/p&gt;

&lt;p&gt;Here's a simple Python script to query ScanSearch for exposed SSH services within our example IP range and print the results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="c1"&gt;# Replace with your actual ScanSearch API endpoint and API key
# For simplicity, we'll use a placeholder URL here. 
# Check ScanSearch documentation for the correct API endpoint and authentication.
&lt;/span&gt;&lt;span class="n"&gt;SCANSEARCH_API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.scansearch.net/v1/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; 
&lt;span class="c1"&gt;# SCANSEARCH_API_KEY = "YOUR_API_KEY_HERE" # Uncomment and set if required by API
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_scansearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_range&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;full_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port:22 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ip:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip_range&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Executing ScanSearch query: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;full_query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# "Authorization": f"Bearer {SCANSEARCH_API_KEY}" # Uncomment if using API key
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;full_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="c1"&gt;# Adjust limit as needed, check API docs for max
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SCANSEARCH_API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Raise an exception for HTTP errors
&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error querying ScanSearch API: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;target_ip_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.0.2.0/24&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your actual public IP range
&lt;/span&gt;
    &lt;span class="c1"&gt;# We're specifically looking for SSH on port 22, so the initial query is simple.
&lt;/span&gt;    &lt;span class="c1"&gt;# You could expand this to include specific SSH versions, e.g., 'ssh-2.0-openssh'
&lt;/span&gt;    &lt;span class="n"&gt;query_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

    &lt;span class="n"&gt;search_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search_scansearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_ip_range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;search_results&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; potential SSH exposures in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;target_ip_range&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;service_banner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  IP: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Port: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Banner: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;service_banner&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Further analysis: Check if these IPs are expected or unexpected
&lt;/span&gt;        &lt;span class="c1"&gt;# For example, maintain a whitelist of allowed SSH servers.
&lt;/span&gt;        &lt;span class="n"&gt;expected_ssh_servers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.0.2.10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.0.2.20&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Analysis ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;expected_ssh_servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  WARNING: Unexpected SSH exposure on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (Port: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Banner: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;banner&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  INFO: Expected SSH server on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No SSH services found in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;target_ip_range&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to retrieve ScanSearch results.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;API Key:&lt;/strong&gt; The example above shows placeholders. You &lt;em&gt;will&lt;/em&gt; need to refer to the &lt;a href="https://scansearch.net/docs" rel="noopener noreferrer"&gt;ScanSearch API documentation&lt;/a&gt; for the correct API endpoint, authentication methods (likely involving an API key), and usage limits. Set &lt;code&gt;SCANSEARCH_API_KEY&lt;/code&gt; and uncomment the &lt;code&gt;Authorization&lt;/code&gt; header if required.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;IP Range:&lt;/strong&gt; Replace &lt;code&gt;192.0.2.0/24&lt;/code&gt; with your actual public IP address range or a specific IP address you want to monitor.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Error Handling:&lt;/strong&gt; The script includes basic error handling for API requests. In a production scenario, you'd want more robust logging and error management.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Rate Limiting:&lt;/strong&gt; Be mindful of API rate limits. The &lt;code&gt;limit&lt;/code&gt; parameter helps control the number of results per query.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Interpreting Results and Taking Action
&lt;/h2&gt;

&lt;p&gt;Once you run the script, you'll get a list of IPs within your specified range that have port 22 open. Your next steps depend on what you find:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Expected Services:&lt;/strong&gt; If an IP shows up and you know it's a legitimate, hardened SSH server, that's great. Document it as part of your known infrastructure.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Unexpected Services:&lt;/strong&gt; If an IP appears that you &lt;em&gt;don't&lt;/em&gt; expect to have SSH exposed, this is a critical finding. Investigate immediately:

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Identify the machine:&lt;/strong&gt; What server or device owns that IP? Who is responsible for it?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Determine the cause:&lt;/strong&gt; Is it a misconfigured firewall? An old test server that was never decommissioned? A rogue service?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Remediate:&lt;/strong&gt; Close the port, restrict access to specific trusted IPs, or remove the service if it's not needed. Ensure proper security configurations are in place (e.g., key-based authentication, disabled root login, up-to-date SSH daemon).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Beyond SSH: Finding Other Vulnerabilities
&lt;/h2&gt;

&lt;p&gt;The power of ScanSearch isn't limited to just SSH. You can adapt the &lt;code&gt;query_string&lt;/code&gt; to find a multitude of other potentially risky exposures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Databases:&lt;/strong&gt; &lt;code&gt;port:3306&lt;/code&gt; (MySQL), &lt;code&gt;port:5432&lt;/code&gt; (PostgreSQL), &lt;code&gt;port:27017&lt;/code&gt; (MongoDB) – often found with default credentials or no authentication.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Web Servers:&lt;/strong&gt; &lt;code&gt;port:80&lt;/code&gt; (HTTP), &lt;code&gt;port:443&lt;/code&gt; (HTTPS) – look for unexpected admin panels or outdated server versions (&lt;code&gt;server:nginx/1.10.3&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Remote Desktops:&lt;/strong&gt; &lt;code&gt;port:3389&lt;/code&gt; (RDP) – a common target for brute-force attacks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;IoT Devices:&lt;/strong&gt; Look for default credentials or known vulnerable device types.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Unsecured Storage:&lt;/strong&gt; &lt;code&gt;port:445&lt;/code&gt; (SMB) or &lt;code&gt;port:21&lt;/code&gt; (FTP) with anonymous access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By regularly scanning your external footprint with ScanSearch, you can proactively identify and mitigate risks before they become security incidents. It's a fundamental step in maintaining a robust security posture for any internet-connected infrastructure.&lt;/p&gt;

</description>
      <category>security</category>
      <category>networking</category>
      <category>python</category>
      <category>scanners</category>
    </item>
    <item>
      <title>Stop Guessing What’s Public: Automating Attack Surface Discovery</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Wed, 24 Jun 2026 04:00:19 +0000</pubDate>
      <link>https://dev.to/devyjones/stop-guessing-whats-public-automating-attack-surface-discovery-3edg</link>
      <guid>https://dev.to/devyjones/stop-guessing-whats-public-automating-attack-surface-discovery-3edg</guid>
      <description>&lt;h2&gt;
  
  
  The "Forgotten Server" Problem
&lt;/h2&gt;

&lt;p&gt;Every developer has been there. You spin up a temporary staging instance to test a deployment script, or you launch a quick Redis container to debug a caching issue. You intend to tear it down in an hour, but then a Slack notification hits, a meeting starts, and that instance stays live. &lt;/p&gt;

&lt;p&gt;Six months later, that "temporary" server is an unpatched entry point into your infrastructure. &lt;/p&gt;

&lt;p&gt;You can't secure what you don't know exists. While internal asset trackers are great, they often miss what the outside world can actually see. This is where internet-wide search engines come in. &lt;/p&gt;

&lt;p&gt;In this guide, we’ll look at how to use &lt;a href="https://scansearch.net" rel="noopener noreferrer"&gt;ScanSearch&lt;/a&gt; to programmatically audit your public-facing infrastructure and identify services you might have forgotten were exposed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is ScanSearch?
&lt;/h2&gt;

&lt;p&gt;ScanSearch is an internet-wide search engine designed to index network devices, services, and vulnerabilities across the entire IPv4 space. Think of it as a specialized crawler that doesn't look for web content, but for open ports, SSL certificates, service banners, and misconfigurations.&lt;/p&gt;

&lt;p&gt;For a developer or DevOps engineer, it’s a tool for &lt;strong&gt;External Attack Surface Management (EASM)&lt;/strong&gt;. Instead of manually running &lt;code&gt;nmap&lt;/code&gt; against your IP ranges (which is slow and can be blocked), you query a pre-indexed database of the entire internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal: Finding Exposed Databases
&lt;/h2&gt;

&lt;p&gt;Let’s build a practical Python script. We want to find any instances associated with a specific organization or IP range that are running services they shouldn't be—specifically, we'll look for exposed database ports (like MongoDB on 27017 or Redis on 6379) that might be leaking data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;To follow along, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.x installed.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;requests&lt;/code&gt; library.&lt;/li&gt;
&lt;li&gt;Access to the &lt;a href="https://scansearch.net" rel="noopener noreferrer"&gt;ScanSearch&lt;/a&gt; platform to retrieve your API credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Writing the Audit Script
&lt;/h3&gt;

&lt;p&gt;We’ll write a script that queries the ScanSearch API for a specific network range and flags any service that isn't on our "allowlist" (like 80 or 443).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="c1"&gt;# Configuration
&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_SCANSEARCH_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://scansearch.net/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Hypothetical API endpoint
&lt;/span&gt;&lt;span class="n"&gt;TARGET_NET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.1.0/24&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Replace with your actual public CIDR
&lt;/span&gt;&lt;span class="n"&gt;ALLOWED_PORTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_exposed_services&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;net_range&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Queries ScanSearch for all indexed services in a specific CIDR.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# We search for the net range using the 'net' filter
&lt;/span&gt;    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;net_range&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error fetching data: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;audit_infrastructure&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--- Starting Audit for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TARGET_NET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_exposed_services&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TARGET_NET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;flagged_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;service&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOWED_PORTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[!] ALERT: Unexpected service found!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    IP: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    Port: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    Service: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    Banner: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;banner&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;flagged_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flagged_count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No unexpected services found. Infrastructure looks clean.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Audit complete. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;flagged_count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; issues found.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;audit_infrastructure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;When you run the script above, ScanSearch doesn't just tell you a port is open; it gives you the &lt;strong&gt;banner data&lt;/strong&gt;. If you have an Nginx server running, it will tell you the version. If you have an expired SSL certificate, it will flag it. &lt;/p&gt;

&lt;p&gt;Common things to look for in your results:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Old Headers&lt;/strong&gt;: Are you still running &lt;code&gt;X-Powered-By: PHP/5.4&lt;/code&gt;? That’s a signal to attackers.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dev Endpoints&lt;/strong&gt;: Finding &lt;code&gt;/swagger-ui.html&lt;/code&gt; or &lt;code&gt;/_ast&lt;/code&gt; (Airflow) exposed to the public internet is a major risk.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Vulnerabilities&lt;/strong&gt;: ScanSearch indexes known vulnerabilities associated with specific service versions. You can refine your search to &lt;code&gt;net:1.2.3.4/24 has_vulnerability:true&lt;/code&gt; to prioritize your patching schedule.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Beyond Simple Port Scanning
&lt;/h2&gt;

&lt;p&gt;One of the most powerful ways to use ScanSearch is to find "shadow" assets that aren't even in your known IP range. You can search by organization name or SSL certificate common names.&lt;/p&gt;

&lt;p&gt;For example, searching for &lt;code&gt;ssl.cert.subject.cn:"*.yourcompany.com"&lt;/code&gt; might reveal a marketing microsite hosted on a random VPS that your security team never knew existed. These are often the weakest links because they fall outside your standard patch management cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating into your Workflow
&lt;/h2&gt;

&lt;p&gt;Security shouldn't be a one-time event. You can take the script above and:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run it as a Cron Job&lt;/strong&gt;: Get a weekly report of any new ports that appeared on your infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD Integration&lt;/strong&gt;: Add a step in your deployment pipeline to verify that a newly deployed service is visible (or invisible) as expected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack Alerts&lt;/strong&gt;: Instead of printing to the console, send a webhook to your team's security channel whenever a high-risk port (like 3389 for RDP or 22 for SSH) is detected on a production IP.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Visibility is the foundation of security. Tools like &lt;a href="https://scansearch.net" rel="noopener noreferrer"&gt;ScanSearch&lt;/a&gt; provide a "hacker's eye view" of your infrastructure, allowing you to find and fix holes before someone else does. By automating these checks, you move from reactive firefighting to a proactive security posture.&lt;/p&gt;

&lt;p&gt;Next time you spin up a "temporary" instance, you'll know exactly when you've forgotten to turn it off.&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>networking</category>
      <category>devops</category>
    </item>
    <item>
      <title>On-demand internet scanning in Python: scan any IP, CIDR, or country with ScanSearch</title>
      <dc:creator>Billy</dc:creator>
      <pubDate>Tue, 16 Jun 2026 08:05:30 +0000</pubDate>
      <link>https://dev.to/devyjones/on-demand-internet-scanning-in-python-scan-any-ip-cidr-or-country-with-scansearch-2n40</link>
      <guid>https://dev.to/devyjones/on-demand-internet-scanning-in-python-scan-any-ip-cidr-or-country-with-scansearch-2n40</guid>
      <description>&lt;p&gt;Search engines like Shodan and Censys are great, but they show you an &lt;em&gt;index&lt;/em&gt; — data collected on their schedule, which can be hours, days, or weeks old. Sometimes you don't want the last snapshot. You want to know what's open &lt;strong&gt;right now&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's the gap &lt;a href="https://scansearch.net" rel="noopener noreferrer"&gt;ScanSearch&lt;/a&gt; fills: instead of querying a pre-built index, you trigger a real SYN + service-detection scan of a target through a REST API and get current results back in seconds. Here's how to drive it from Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;The SDK is open source (MIT). Until the PyPI release it installs straight from GitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/ScanSearch/scansearch-python.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grab an API key from your dashboard at &lt;code&gt;https://scansearch.net/dashboard/api-keys/&lt;/code&gt; and export it — the client picks it up automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SCANSEARCH_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The free tier scans at 2 kpps, which is plenty to follow along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scan a CIDR and wait for the result
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;scansearch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# reads SCANSEARCH_API_KEY from the environment
&lt;/span&gt;
&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.0.2.0/24&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80,443,8080,8443&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;services&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open ports: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;open_ports_found&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;services:   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;services_found&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;modules=["ports", "services"]&lt;/code&gt; means you get open ports &lt;em&gt;plus&lt;/em&gt; service identification — banners, product/version, TLS certificate details, and JARM/JA3S fingerprints — per host/port.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fire-and-forget for big targets
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;/24&lt;/code&gt; finishes fast. A whole country does not, so don't block on it — kick it off, store the &lt;code&gt;task_id&lt;/code&gt;, and poll when you're ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country:DE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9200&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_id:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# ...later, from anywhere
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Targets can be a single IP, a CIDR, a list of CIDRs, a domain list, or a &lt;code&gt;country:&amp;lt;CC&amp;gt;&lt;/code&gt; code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn up the speed
&lt;/h2&gt;

&lt;p&gt;Scan rate is set per call (capped by your plan). Higher speed = the same job finishes sooner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.0.0.0/16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.0.0/16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1-1024&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;services&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# kpps
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;poll_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# changed your mind?
&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handle the errors you'll actually hit
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;scansearch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AuthError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RateLimitError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NotFoundError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;APIError&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;99999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;NotFoundError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;                       &lt;span class="c1"&gt;# no such task
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;RateLimitError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;                       &lt;span class="c1"&gt;# daily quota or per-minute limit
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;AuthError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;                       &lt;span class="c1"&gt;# bad / revoked key
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;APIError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prefer the shell?
&lt;/h2&gt;

&lt;p&gt;Everything above has a CLI equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scansearch scan 192.0.2.0/24 &lt;span class="nt"&gt;--ports&lt;/span&gt; 80,443,8080 &lt;span class="nt"&gt;--modules&lt;/span&gt; ports,services &lt;span class="nt"&gt;--wait&lt;/span&gt;
scansearch scan country:DE &lt;span class="nt"&gt;--ports&lt;/span&gt; 9200 &lt;span class="nt"&gt;--speed&lt;/span&gt; 1000 &lt;span class="nt"&gt;--wait&lt;/span&gt;
scansearch status 1234
scansearch stop 1234
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where on-demand scanning beats an index
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bug-bounty recon&lt;/strong&gt; — scan your in-scope CIDRs fresh and grab current open ports + banners, not last month's.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asset discovery / shadow IT&lt;/strong&gt; — point it at your own AS or netblocks and find what's exposed that shouldn't be.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous monitoring&lt;/strong&gt; — scan your ranges daily, diff the results, alert on new open ports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vulnerability triage&lt;/strong&gt; — combine &lt;code&gt;services&lt;/code&gt; enrichment with CVE matching to find newly exposed &lt;code&gt;ssh&lt;/code&gt;, &lt;code&gt;rdp&lt;/code&gt;, &lt;code&gt;elasticsearch&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Source (Python SDK + CLI): &lt;a href="https://github.com/ScanSearch/scansearch-python" rel="noopener noreferrer"&gt;https://github.com/ScanSearch/scansearch-python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;API docs: &lt;a href="https://scansearch.net/resources/api-docs/" rel="noopener noreferrer"&gt;https://scansearch.net/resources/api-docs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Free recon tools (port scanner, SSL checker, subdomain finder, CVE lookup): &lt;a href="https://scansearch.net/tools/" rel="noopener noreferrer"&gt;https://scansearch.net/tools/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build something with it, I'd love to hear what you scanned and why.&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>osint</category>
      <category>networking</category>
    </item>
  </channel>
</rss>
