<?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: Joseph Avatiants</title>
    <description>The latest articles on DEV Community by Joseph Avatiants (@joavat2009).</description>
    <link>https://dev.to/joavat2009</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%2F4246191%2Fc88b1572-73ab-406d-8ba1-2e13731fcca4.png</url>
      <title>DEV Community: Joseph Avatiants</title>
      <link>https://dev.to/joavat2009</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joavat2009"/>
    <language>en</language>
    <item>
      <title>Building an async OSINT scanner: from a local script to a distributable tool</title>
      <dc:creator>Joseph Avatiants</dc:creator>
      <pubDate>Sun, 15 Nov 2026 15:59:28 +0000</pubDate>
      <link>https://dev.to/joavat2009/building-an-async-osint-scanner-from-a-local-script-to-a-distributable-tool-c53</link>
      <guid>https://dev.to/joavat2009/building-an-async-osint-scanner-from-a-local-script-to-a-distributable-tool-c53</guid>
      <description>&lt;p&gt;A while ago I shared my utility Web-Recon — an asynchronous scanner that probes&lt;br&gt;
hundreds of domains at high speed and, when it hits a network anomaly or a dead&lt;br&gt;
host, automatically pulls a deep WHOIS profile and stores everything in SQLite.&lt;/p&gt;

&lt;p&gt;After that first post, the repo came alive: views went up, and people started&lt;br&gt;
cloning the script into their Kali Linux setups. That gave me a bold idea:&lt;br&gt;
"Why not apply to have Web-Recon included in the official Kali Linux&lt;br&gt;
distribution, so it can be installed with a simple &lt;code&gt;sudo apt install&lt;/code&gt;?"&lt;/p&gt;

&lt;p&gt;I registered on the Offensive Security bug tracker (the team behind Kali) and&lt;br&gt;
opened a ticket in the "New Tool Request" category. The response from the&lt;br&gt;
repository admin (daniruiz) came a couple of weeks later, and it made me&lt;br&gt;
completely rethink the project's architecture:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hi! Thanks for your submission. We physically can't package every infosec&lt;br&gt;
tool in the world, so we give strict priority to utilities that have wider&lt;br&gt;
adoption and community usage. Good luck with your project!"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Translated into engineering terms, it meant: "Your software looks like a cool&lt;br&gt;
local script, but Kali Linux is an industrial-grade distribution. Turn the&lt;br&gt;
script into a proper package, solve the WAF blocking problem, add automated&lt;br&gt;
tests, show that the project is alive, and we'll come back to this&lt;br&gt;
conversation."&lt;/p&gt;

&lt;p&gt;Challenge accepted. Over the following weeks, Web-Recon evolved from v0.5.0 to&lt;br&gt;
release candidate v0.6.2. In this article I'll walk through how I tamed the&lt;br&gt;
async power, built a three-tier WAF evasion pipeline, and set up CI/CD&lt;br&gt;
pipelines in the cloud.&lt;/p&gt;
&lt;h2&gt;
  
  
  Chapter 1: Taming async power (semaphores and delays)
&lt;/h2&gt;

&lt;p&gt;In its first iteration, Web-Recon ran on a reactive &lt;code&gt;asyncio&lt;/code&gt; + &lt;code&gt;aiohttp&lt;/code&gt;&lt;br&gt;
core. The throughput was enormous: the engine threw hundreds of network&lt;br&gt;
requests at once. But in real pentesting, this led to a disaster. When trying&lt;br&gt;
to mass-probe a large corporate perimeter, the tool produced such a dense&lt;br&gt;
barrage of simultaneous connections that it turned into an unintentional DoS&lt;br&gt;
attack.&lt;/p&gt;

&lt;p&gt;The result? Weak web resources went down, and advanced Web Application&lt;br&gt;
Firewalls (WAFs) like Cloudflare instantly triggered on the abnormal rate and&lt;br&gt;
permanently banned the scanner's IP address, returning batches of &lt;code&gt;403&lt;br&gt;
Forbidden&lt;/code&gt; errors.&lt;/p&gt;

&lt;p&gt;To make the network assault controlled, I had to introduce hardware-level&lt;br&gt;
concurrency control — &lt;code&gt;asyncio.Semaphore&lt;/code&gt;. Now, no matter how many thousands of&lt;br&gt;
domains are in the target file, the semaphore acts as a strict turnstile: only&lt;br&gt;
a fixed number of requests (the &lt;code&gt;-c&lt;/code&gt; flag, default 50) are in flight at any&lt;br&gt;
given time.&lt;/p&gt;

&lt;p&gt;But limiting thread count alone isn't enough — WAFs can detect a stable&lt;br&gt;
packet rate. To break that pattern, I added non-blocking delays based on&lt;br&gt;
&lt;code&gt;asyncio.sleep()&lt;/code&gt; (the &lt;code&gt;-d&lt;/code&gt; flag). Unlike the standard &lt;code&gt;time.sleep()&lt;/code&gt;, which&lt;br&gt;
freezes the entire OS thread and hangs the UI, an async pause gently puts a&lt;br&gt;
specific task to sleep, returning CPU time to the Event Loop. As a result, the&lt;br&gt;
Rich progress bar keeps spinning smoothly while requests fly out with&lt;br&gt;
carefully irregular pauses, mimicking organic traffic:&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;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--delay"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Delay in seconds between checks (default: 0.5)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Chapter 2: The great disguise (cascading User-Agent rotation)
&lt;/h2&gt;

&lt;p&gt;The second mine the scanner hit was static HTTP request headers. Initially, the code had a single hardcoded headers dictionary posing as Chrome 120 on Windows. But when the server's defense sees 100 requests arriving in one second, each carrying an identical User-Agent string down to the last character, the protection system instantly triggers a block.&lt;/p&gt;

&lt;p&gt;In v0.6.2 I removed the global static headers entirely and designed a three-tier browser fingerprint masking pipeline based on cascading fallbacks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 1 (Custom file):&lt;/strong&gt; A professional pentester can feed the tool their own pre-warmed list of fingerprints via the &lt;code&gt;-A&lt;/code&gt; / &lt;code&gt;--UserAgents&lt;/code&gt; flag. The script reads it in isolation and loads it into memory:&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UserAgents&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UserAgents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quiet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"[bold green]► Loading custom User-Agents from file: {args.UserAgents}[/bold green]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UserAgents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'utf-8'&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;file&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;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;file&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;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&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;loaded_agents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&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;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"[bold red]✖ Error: User-Agents file '{args.UserAgents}' does not exist.[/bold red]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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;Tier 2 (Automatic live update):&lt;/strong&gt; If the flag is empty, Web-Recon doesn't use stale lists. It makes a quick background request to a continuously updated raw GitHub repository (currently a placeholder URL), pulling the freshest desktop browser strings as of this morning. No heavy HTML parsers like BeautifulSoup — just a plain text stream:&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="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quiet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[bold cyan]► Fetching fresh User-Agents from online database...[/bold cyan]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;raw_site_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://githubusercontent.com"&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;r_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="n"&gt;raw_site_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&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;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&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;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Mozilla/5.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;loaded_agents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&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="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tier 3 (Built-in shield):&lt;/strong&gt; If scanning happens in an isolated air-gapped lab with no internet, the &lt;code&gt;try/except&lt;/code&gt; block gracefully catches the network failure and deploys an embedded fallback list of stable Chrome, Firefox, Edge, and Safari strings:&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;loaded_agents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quiet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[bold yellow]⚠️ Internet offline. Activating built-in hardcoded User-Agent fallback shield.[/bold yellow]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;loaded_agents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How it works in the network loop: when the async &lt;code&gt;futures&lt;/code&gt; generator packs a fan of requests, the utility uses &lt;code&gt;random.choice()&lt;/code&gt; to pull a random User-Agent for each individual link. Inside &lt;code&gt;check_single_link_async&lt;/code&gt;, headers are compiled locally right before calling &lt;code&gt;session.get()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From the target server's perspective, it looks as if a crowd of completely different people on different devices and operating systems hit the site at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 3: Engineering according to Debian Policy
&lt;/h2&gt;

&lt;p&gt;To turn a hobbyist script into an adult open-source product that you wouldn't be ashamed to show to distribution maintainers, the codebase had to be restructured from scratch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Src-layout migration:&lt;/strong&gt; &lt;code&gt;recon.py&lt;/code&gt; moved deep into &lt;code&gt;src/web_recon/&lt;/code&gt;. The repository root was cleared of clutter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; manifest:&lt;/strong&gt; All the old &lt;code&gt;setup.py&lt;/code&gt; crutches were fully deprecated. The project now builds via the modern PEP 517/621 standard. The manifest declares automatic dependency management (&lt;code&gt;rich&lt;/code&gt;, &lt;code&gt;aiohttp&lt;/code&gt;, &lt;code&gt;aiosqlite&lt;/code&gt;, &lt;code&gt;python-whois&lt;/code&gt;) and registers a system entry point (&lt;code&gt;[project.scripts]&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Birth of the system command:&lt;/strong&gt; Thanks to this packaging, users no longer need to sit in a specific folder and type &lt;code&gt;python recon.py&lt;/code&gt;. After installation, the tool registers a system alias. Type one word anywhere in the Kali terminal — &lt;code&gt;web-recon&lt;/code&gt; — and the utility takes off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud CI/CD pipeline:&lt;/strong&gt; In the &lt;code&gt;tests/&lt;/code&gt; folder I added automated tests based on &lt;code&gt;pytest-asyncio&lt;/code&gt; (I'll admit, the &lt;code&gt;recon_test&lt;/code&gt; file was partly vibe-coded). They check SQLite transaction atomicity, &lt;code&gt;[]&lt;/code&gt; table escaping against SQL injection, and &lt;code&gt;argparse&lt;/code&gt; CLI flag collisions. A GitHub Actions workflow (&lt;code&gt;.github/workflows/tests.yml&lt;/code&gt;) is integrated into the repo. Now on every &lt;code&gt;git push&lt;/code&gt;, GitHub's servers spin up a free Linux VM, install Python 3.12 and 3.13, set up the environment, run the tests, and hang out a green PASSED stability marker.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion: one-click installation directly from source
&lt;/h2&gt;

&lt;p&gt;Yes, the Kali Linux curators have temporarily postponed packaging due to a lack of "media weight." But that's a completely normal working stage. Most top infosec tools start their journey as independent open-source projects distributed directly through repositories.&lt;/p&gt;

&lt;p&gt;The project is fully stabilized, migrated to src-layout, and ready for local builds. You can deploy Web-Recon in your Kali Linux as a full system command right now.&lt;/p&gt;

&lt;p&gt;To do so, just clone the repository and install it using &lt;code&gt;pipx&lt;/code&gt; (recommended for CLI tools on modern Debian/Kali systems):&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="c"&gt;# Clone the official repository&lt;/span&gt;
git clone https://github.com/joavat-2009/Web-Recon
&lt;span class="nb"&gt;cd&lt;/span&gt; Web-Recon

&lt;span class="c"&gt;# Install pipx if you don't have it&lt;/span&gt;
sudo apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sudo apt install pipx -y
pipx ensurepath

&lt;span class="c"&gt;# Install Web-Recon in an isolated environment and expose the `web-recon` command&lt;/span&gt;
pipx install .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;pipx&lt;/code&gt; instead of &lt;code&gt;pip install&lt;/code&gt;?&lt;/strong&gt; Modern Debian/Kali/Ubuntu systems block system-wide &lt;code&gt;pip install&lt;/code&gt; (PEP 668) to protect system packages. &lt;code&gt;pipx&lt;/code&gt; creates an isolated virtual environment for the tool while making the &lt;code&gt;web-recon&lt;/code&gt; command available globally — exactly what you want for a CLI utility. Using &lt;code&gt;pip install --break-system-packages&lt;/code&gt; is strongly discouraged as it can break your system Python.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Alternatively, you can skip building files from the main repository page entirely: starting with v0.6.0, the Releases branch contains pre-built &lt;code&gt;.whl&lt;/code&gt; files, and it will do so for every version. You can install them via &lt;code&gt;pipx install &amp;lt;path-to-whl&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After that, the package manager will distribute the dependencies, register the entry point, and you'll be able to run the utility from any terminal folder with a short system command: &lt;code&gt;web-recon --help&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A small call to work together (I'm not forcing anyone, I respect the community's choice, whatever it may be): friends, the Kali maintainers explicitly said they're waiting for community interest in the project. If you're into async OSINT perimeter scanning, stealth, and automation — drop by the repository, test the stealth mode of fan-out rotation, open Issues, and, of course, leave the coveted Stars ⭐! Let's build a fast, convenient, and cool recon tool together.&lt;/p&gt;

&lt;p&gt;Project link: &lt;a href="https://github.com/joavat-2009/Web-Recon" rel="noopener noreferrer"&gt;https://github.com/joavat-2009/Web-Recon&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clean code and a safe perimeter to everyone! I'd be glad to hear your feedback and criticism in the comments.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>linux</category>
      <category>opensource</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
