<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://threatvector762.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://threatvector762.dev/" rel="alternate" type="text/html" /><updated>2026-07-08T15:49:21+00:00</updated><id>https://threatvector762.dev/feed.xml</id><title type="html">ThreatVector762</title><subtitle>Windows Server Administrator and serving Senior Non-Commissioned Officer in Canadian Forces Military Police. Transitioning into cybersecurity.</subtitle><author><name>Ryan Jorgensen</name></author><entry><title type="html">Segmenting the Homelab: OPNsense and Remote Access With Tailscale</title><link href="https://threatvector762.dev/blog/2026/07/01/opnsense-segmentation-tailscale/" rel="alternate" type="text/html" title="Segmenting the Homelab: OPNsense and Remote Access With Tailscale" /><published>2026-07-01T00:00:00+00:00</published><updated>2026-07-01T00:00:00+00:00</updated><id>https://threatvector762.dev/blog/2026/07/01/opnsense-segmentation-tailscale</id><content type="html" xml:base="https://threatvector762.dev/blog/2026/07/01/opnsense-segmentation-tailscale/"><![CDATA[<p>Proxmox was up. Ubuntu Server was running as my first VM. And every part of that lab sat on the exact same 192.168.1.0/24 network as my phone, my wife’s laptop, and everything else in the house.</p>

<p>That bothered me before the first build was even finished. A typo’d scan target, a compromised VM during malware analysis, a misconfigured rule somewhere down the line, any of those could reach devices that had nothing to do with the lab. Fixing that meant building real network segmentation, and getting there took a second SIEM-adjacent VM, a firewall, and a lot more troubleshooting than I expected.</p>

<h2 id="picking-a-firewall">Picking a firewall</h2>

<p>The plan was straightforward on paper: a second internal bridge inside Proxmox with no physical NIC attached, a firewall VM straddling both networks, and every lab VM migrated behind it. pfSense was the obvious first choice, it’s the name everyone learning network security runs into first.</p>

<p>Then I actually tried to download it. pfSense CE now routes through the Netgate Store, requires a free account, and installs through an online installer that fetches packages during setup rather than shipping a self-contained image. Netgate’s own forums are full of 2025 and 2026 threads describing repo failures and installs that silently fall back to plain FreeBSD instead of pfSense. After the fight I’d already had getting a bootable USB stick working for Proxmox, I wasn’t interested in repeating that with an installer known to have its own reliability problems.</p>

<p>OPNsense, forked from pfSense back in 2014, still offers a direct ISO download with no account gate. Same underlying FreeBSD concepts, same firewall logic, and it turned out to have better native WireGuard support too, which mattered later. I switched plans and never looked back.</p>

<h2 id="the-install-that-would-not-cooperate">The install that would not cooperate</h2>

<p>Getting OPNsense running on the OptiPlex took four attempts, and each one taught me something.</p>

<p>The first install aborted mid-partitioning with a warning that 2GB of RAM wasn’t enough for the live installer to comfortably copy its filesystem. I proceeded anyway, thinking it was just a caution. It wasn’t. The install failed and left the disk in a half-partitioned state.</p>

<p>The second attempt hit “Partition destroy failed” trying to clean up what the first attempt left behind. I dropped into the installer’s own FreeBSD shell and ran <code class="language-plaintext highlighter-rouge">dd if=/dev/zero of=/dev/da0 bs=1m count=10</code> to zero out the first 10MB of the disk directly, wiping the leftover GPT metadata the installer couldn’t clean up on its own. That got me past the partitioning error, but the VM then booted with a “vm_fault: pager read error” loop, an unreadable filesystem from two interrupted installs stacked on top of each other.</p>

<p>At that point I stopped patching and deleted the VM outright. Rebuilt it clean, bumped memory to 3072MB just for the install, and moved the disk from IDE to a proper VirtIO SCSI controller. That version installed without a single error, root password set, GPT partitioning clean, UFS filesystem intact.</p>

<figure>
  <img src="/assets/images/homelab/proxmox-node-summary-three-vms.png" alt="Proxmox VE node summary showing three running VMs" />
  <figcaption>The Proxmox host, three VMs deep: ubuntu-server-01, opnsense-fw, tailscale-gw.</figcaption>
</figure>

<h2 id="fixing-the-subnet-nobody-asked-about">Fixing the subnet nobody asked about</h2>

<p>OPNsense ships with LAN defaulting to 192.168.1.1/24, the identical subnet my WAN interface was already pulling from my home router. Identical WAN and LAN subnets break routing entirely, and I caught it by reading the console output rather than assuming the defaults were sane.</p>

<p>I reassigned LAN to 10.10.10.1/24, turned on its DHCP server with a range of 10.10.10.100 to 10.10.10.200, and regenerated the self-signed web certificate so it actually matched the new address. WAN kept pulling its address from my home network over DHCP, same as before.</p>

<p>Once that was set, the real test wasn’t whether the GUI loaded. It was pinging in both directions from inside a VM sitting on the lab network. A ping to 1.1.1.1 came back clean, four packets, zero loss, confirming NAT and the WAN gateway both worked. A ping to my ThinkPad’s address on the home network timed out completely, four packets sent, zero received. That’s the actual proof the segmentation was real rather than theoretical: the lab could reach the internet and nothing else on my home network could see it at all.</p>

<h2 id="a-jump-host-instead-of-a-hole-in-the-firewall">A jump host instead of a hole in the firewall</h2>

<p>With OPNsense’s web interface sitting on the isolated LAN side, my ThinkPad had no direct route to it anymore, which is the entire point. The tempting shortcut was opening a WAN rule to let myself in. I didn’t want a rule sitting there that I’d inevitably forget to remove later.</p>

<p>Instead I gave the Proxmox host itself an address on the lab bridge (10.10.10.2) and built an SSH config on my ThinkPad using ProxyJump, so reaching any lab VM is one command instead of a manual tunnel every time:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host ubuntu-lab
    HostName 10.10.10.10
    User ryan
    ProxyJump proxmox
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">ssh ubuntu-lab</code> now authenticates through Proxmox as a bastion host and lands directly on the target VM. It’s the same pattern real organizations use to reach segmented internal networks, and building it by hand made the concept click in a way reading about it never did.</p>

<h2 id="getting-off-the-home-network-entirely">Getting off the home network entirely</h2>

<p>None of this helped if I was standing in a coffee shop. Everything so far only worked from inside my own house.</p>

<p>A VPN server running on OPNsense itself was the obvious next move, until I worked through what it would actually require: my ISP router would need to forward a port to a VM whose WAN interface sits on a private address, not a public one, meaning double NAT or bridging my ISP router into passthrough mode just to make an inbound connection possible. Tailscale sidesteps all of it. No inbound ports, no router changes, the tunnel establishes outbound from both ends.</p>

<p>I built a third VM, tailscale-gw, on the lab network. It hit the same wall the OPNsense install did, the Ubuntu installer pegged a single CPU core to 100% and hung during setup. I recognized the pattern immediately this time: stop, bump the resources temporarily, reinstall clean, then drop them back down once it’s actually running. One core and 1GB is plenty for a VM whose only job is running a routing daemon at rest.</p>

<p>Tailscale itself took three commands: install, enable IP forwarding, then bring the tunnel up while advertising the entire 10.10.10.0/24 subnet as a route. Tailscale doesn’t auto-trust advertised routes, a deliberate safety check, so I approved it manually from the admin console before it actually started forwarding anything.</p>

<figure>
  <img src="/assets/images/homelab/opnsense-lobby-dashboard.png" alt="OPNsense dashboard showing active WAN gateways and running services" />
  <figcaption>OPNsense settled in: WAN gateways active, Dnsmasq serving the lab subnet, load average sitting under half a core.</figcaption>
</figure>

<p>With the Windows client installed and the route approved, every host on the lab subnet became directly reachable by its 10.10.10.x address from any Tailscale-connected device, home network or not. The SSH jump host and tunnel setup I’d just finished building became redundant for remote use, though it still works exactly the same way from inside the house.</p>

<p>One thing I deliberately left out: Proxmox itself never got a Tailscale identity. The lab subnet is what I want reachable from anywhere. The hypervisor controlling the physical box stays reachable only from my home network. If a Tailscale account is ever compromised, the blast radius stops at the VMs, not at the thing running them.</p>

<h2 id="where-it-stands">Where it stands</h2>

<p>Three VMs now: Ubuntu Server behind the firewall, OPNsense running the segmentation, and a Tailscale gateway routing lab traffic to wherever I happen to be. Home network and lab network verified isolated in both directions, not assumed. Remote access working without a single forwarded port.</p>

<p>Next up is the actual analyst work this was all built for: picking between Wazuh and Elastic Stack for the SIEM, then getting Kali on the lab network to start generating something worth detecting.</p>]]></content><author><name>Ryan Jorgensen</name></author><category term="homelab" /><category term="opnsense" /><category term="tailscale" /><category term="networking" /><category term="proxmox" /><summary type="html"><![CDATA[The Proxmox host was running, but every VM sat on the same flat network as my phone and my wife's laptop. Here is how I fixed that with an internal bridge, OPNsense, and a Tailscale gateway.]]></summary></entry><entry><title type="html">Building a Cybersecurity Homelab on the Cheap: Proxmox Foundation</title><link href="https://threatvector762.dev/blog/2026/06/24/proxmox-homelab-foundation/" rel="alternate" type="text/html" title="Building a Cybersecurity Homelab on the Cheap: Proxmox Foundation" /><published>2026-06-24T00:00:00+00:00</published><updated>2026-06-24T00:00:00+00:00</updated><id>https://threatvector762.dev/blog/2026/06/24/proxmox-homelab-foundation</id><content type="html" xml:base="https://threatvector762.dev/blog/2026/06/24/proxmox-homelab-foundation/"><![CDATA[<p>I’m transitioning from IT infrastructure into cybersecurity, specifically targeting SOC analyst roles. Certifications are part of the path, but they only get you so far. Hiring managers want to see hands-on work, and that means a homelab.</p>

<p>Problem: I had close to zero budget for this.</p>

<p>This is how I got from rummaging through old hardware to a working Proxmox virtualization host, what I learned along the way, and the mistakes I made so you don’t have to.</p>

<h2 id="why-a-homelab-matters-for-soc-work">Why a Homelab Matters for SOC Work</h2>

<p>Reading about Wireshark is not the same as capturing your own traffic. Watching a YouTube tutorial on Elastic Stack is not the same as ingesting logs from a VM you built yourself. The whole point of an analyst lab is to break things, observe them, and learn how attacks actually look on the wire and in the logs.</p>

<p>I already hold CC, Fortinet FCA, Certified Zero Trust Practitioner, Certified Threat &amp; Malware Analysis, AZ-900 Azure Fundamentals, and a TAISE cert. I’m working on MD-102. None of that replaces actually doing the work, and the homelab is how I plan to close that gap.</p>

<h2 id="the-hunt-for-hardware">The Hunt for Hardware</h2>

<p>I work on an MSI Leopard gaming laptop as my daily driver and a Lenovo ThinkPad X1 Yoga Gen 7 as my secondary. Neither was going to become a dedicated lab machine. The MSI is my work environment. The X1 only has 16GB of soldered RAM, and once Windows takes its cut there isn’t enough left to run multiple VMs concurrently the way I wanted.</p>

<p>I needed a third machine, cheap and dedicated. Used business mini PCs are the usual recommendation because they show up everywhere on local marketplaces after corporate refresh cycles. I ended up with two: a Dell OptiPlex 3050 Micro and a Lenovo M720q.</p>

<p>The 3050 came with an i5-6500T, 16GB DDR4, and a 512GB NVMe SSD. The M720q had an i5-8400T, a 6-core chip and meaningfully better than what the 3050 shipped with. My plan was to transplant the better CPU into the 3050.</p>

<p>That plan died fast. Both CPUs use the LGA1151 socket physically, but Intel changed chipset requirements between 6th and 8th gen. The 3050’s 100-series board would not accept the 8400T even though it physically seated. Frustrating piece of trivia that cost me time, but a real lesson: socket compatibility is not chipset compatibility.</p>

<p>The M720q itself was a non-starter anyway. Burn mark near the CPU socket, which almost always means a VRM failure. Not worth repairing on a consumer mini PC.</p>

<h2 id="salvaging-what-i-could">Salvaging What I Could</h2>

<p>I stripped the M720q for usable parts: RAM, the i5-8400T (useless to me now but worth keeping for a future build), and three 2.5” SATA drives. A WD Black 750GB from 2013, an HGST 1TB from 2014, and a WD Black 500GB from 2017. All spinning disks, all old, all worth checking before committing them to anything.</p>

<figure>
  <img src="/assets/images/homelab/salvaged-parts-optiplex-m720q.jpg" alt="Salvaged RAM sticks, hard drives, fans, and two bare motherboards laid out on a towel" />
  <figcaption>Everything pulled from both machines before the build started: RAM, drives, heatsinks, and the two motherboards side by side.</figcaption>
</figure>

<p>The Dell already had a 2.5” caddy installed with the 500GB WD inside. The HGST 1TB had the best capacity-to-age ratio, and HGST built a strong reliability reputation before Western Digital acquired them. That one became my secondary storage candidate. The NVMe stays as the boot drive for Proxmox and active VM disks. The SATA drive will hold ISOs, backups, and snapshots. Fast primary, bulk secondary. Standard homelab pattern.</p>

<p>I wanted 32GB to run my full lab stack concurrently. DDR4 SODIMM prices in 2026 are genuinely brutal, $240 for a 2x8GB matched pair on Amazon at the moment. Someone locally was selling a single 16GB Timetec stick for $80, which sounds tempting until you do the math. The 3050’s 6500T caps memory speed at 2133MHz regardless of what’s installed. Dropping a single 16GB stick alongside one of my existing 8GB sticks also breaks dual-channel mode, which hurts memory bandwidth more than the extra capacity helps for virtualization workloads.</p>

<p>So I left it. 16GB for now, watching Facebook Marketplace and Kijiji for a used matched pair. The lab will function fine at 16GB until then.</p>

<h2 id="the-installation-war">The Installation War</h2>

<p>Reassembly was the easy part. Cleaned the dust out of the heatsink fins, applied fresh thermal paste to the 6500T, reseated everything. Initial POST and a 3-hour memory test passed clean.</p>

<p>Then the install started, and things got stupid.</p>

<p>I downloaded the Proxmox VE 9.2-1 ISO and tried to flash it to a no-name 16GB USB stick using Rufus 4.14. It threw “Failed to scan image” and never offered the DD-mode prompt that Proxmox hybrid ISOs require. Tried balenaEtcher next. It complained about a missing partition table, I clicked through, and the resulting USB wouldn’t boot. Tried Ventoy. The splash loaded fine on the 3050, but selecting the Proxmox ISO threw “chain empty failed” in legacy mode and “No bootfile found for UEFI” after I switched the BIOS to UEFI.</p>

<p>Through all of this, the no-name USB stick kept corrupting itself. Every time I tried it on the 3050, it came back unreadable on my ThinkPad. Diskpart <code class="language-plaintext highlighter-rouge">clean</code> would resurrect it once, then it would die again on the next attempt.</p>

<p>Eventually I gave up on the cheap stick and grabbed a 128GB SanDisk. Different story entirely. Rufus 4.14 with a freshly re-downloaded ISO, DD mode auto-selected, write completed clean.</p>

<figure>
  <img src="/assets/images/homelab/rufus-sandisk-proxmox-iso-flash.jpg" alt="Rufus 4.14 set to GPT partition scheme and UEFI target, ready to flash the Proxmox ISO to a SanDisk drive" />
  <figcaption>The SanDisk stick that finally cooperated. GPT, UEFI, volume label PVE, status READY.</figcaption>
</figure>

<p>Plugged into the 3050 in UEFI boot mode, and the Proxmox installer finally launched.</p>

<figure>
  <img src="/assets/images/homelab/proxmox-installer-create-lvs.jpg" alt="Proxmox VE Installer running on the OptiPlex 3050, creating logical volumes at three percent" />
  <figcaption>Proxmox actually installing, three percent into creating the LVM volumes, after three failed USB tools and a dead stick.</figcaption>
</figure>

<p>A few things stuck with me from that mess. Cheap USB sticks fail under BIOS read-write cycles in ways that look like software problems but aren’t, so spend the $15 on a known-good brand. Proxmox hybrid ISOs need DD mode in Rufus; ISO mode will not work no matter how many times you try. A fresh ISO re-download solves more than you’d expect, because partial corruption from a broken download manifests as tool incompatibility rather than an obvious file error. And the BIOS boot mode has to match how the USB was flashed, UEFI to UEFI or Legacy to Legacy, or nothing will detect anything.</p>

<h2 id="the-bios-gauntlet">The BIOS Gauntlet</h2>

<p>Once Proxmox booted, it threw “no network interfaces found.” Cable was in, link lights were dark. The Integrated NIC was disabled in BIOS by default on this unit. One toggle later, the NIC came alive and Proxmox proceeded.</p>

<p>The BIOS settings that actually mattered for this build, in case you’re working on the same hardware: Secure Boot off, Fast Boot off, VT-x and VT-d enabled, SATA Operation set to AHCI, AC Recovery set to Power On so the box auto-restarts after power loss, USB Boot Support enabled, Integrated NIC enabled (the one that got me), and Boot Mode set to UEFI. UEFI Network Stack and TPM both stayed disabled. Neither matters for what I’m building.</p>

<p>Network configuration in the installer went smoothly once the NIC was actually enabled. I gave the 3050 a static IP of 192.168.1.50, pointed it at my router’s gateway, and set DNS to Cloudflare’s 1.1.1.1. The install completed without further issues.</p>

<p>I opened https://192.168.1.50:8006 on my ThinkPad and the Proxmox web interface loaded. Logged in as root, dismissed the subscription nag, and there it was. A working Type 1 hypervisor on my own hardware, ready to host my SOC analyst learning environment.</p>

<h2 id="whats-next">What’s Next</h2>

<p>The actual lab work starts now.</p>

<p>Next steps are getting the 1TB HGST added as secondary storage, downloading my first Linux ISOs (Kali for the attacker side, Ubuntu Server for the target), and spinning up my first VMs. From there I want to build out pfSense as a virtual firewall for hands-on network segmentation, Elastic Stack for SIEM and log analysis, a vulnerability scanner pointed at my own target VMs, and a documented incident response runbook.</p>

<p>GitHub is the parallel project. I have an account I’ve never used. Every config, every playbook, every write-up from here forward gets documented there. A SOC analyst candidate with a public, well-documented homelab portfolio stands out from one with just certifications.</p>

<p>This entire build cost me effectively nothing in cash. The 3050 came from a local pickup. The drives were salvaged from a dead M720q. The biggest expense was the SanDisk USB stick at $15. The real cost was the hours wrestling with USB sticks, BIOS settings, three different imaging tools, and a NIC that wasn’t talking. That’s the part nobody puts in the YouTube videos.</p>]]></content><author><name>Ryan Jorgensen</name></author><category term="homelab" /><category term="proxmox" /><category term="cybersecurity" /><category term="hardware" /><summary type="html"><![CDATA[Salvaging old mini PCs, fighting USB sticks and BIOS settings, and finally landing a working Proxmox host as the foundation for SOC analyst skill-building.]]></summary></entry><entry><title type="html">Refactoring my OSINT recon tool: from script to Python package</title><link href="https://threatvector762.dev/blog/2026/06/12/script-to-package-refactor/" rel="alternate" type="text/html" title="Refactoring my OSINT recon tool: from script to Python package" /><published>2026-06-12T00:00:00+00:00</published><updated>2026-06-12T00:00:00+00:00</updated><id>https://threatvector762.dev/blog/2026/06/12/script-to-package-refactor</id><content type="html" xml:base="https://threatvector762.dev/blog/2026/06/12/script-to-package-refactor/"><![CDATA[<p>v0.3.0 shipped last night. Nothing changed for users. The CLI behaves identically, the output matches v0.2.1 byte for byte, every flag still works the same way. Release notes show zero new features.</p>

<p>It’s the release that mattered most so far.</p>

<h2 id="refactoring-without-features">Refactoring without features</h2>

<p>I started with one Python file. v0.1.0 was about 300 lines: WHOIS lookups, DNS records, HTTP probing. Two weeks later v0.2.0 added RDAP, full email auth audits, and service-token classification, and the file hit 800. The v0.2.1 cleanup pass added type hints and section dividers but kept everything in one file. That release crossed 1000 lines.</p>

<p>Subdomain enumeration is next on the roadmap. Adding it to a 1083-line file would have meant editing code scattered across half a dozen logical sections. Scrolling to find a specific function meant five page-downs. Each new feature was getting harder to land cleanly.</p>

<p>Refactoring is for the moment when adding features starts to feel like archaeology.</p>

<h2 id="from-script-to-package">From script to package</h2>

<p>Two layout options exist for a Python package. Flat layout puts the package directory at the repo root next to README.md. Src layout puts the package one level deep under <code class="language-plaintext highlighter-rouge">src/</code>. The Python Packaging User Guide recommends src for new projects because it prevents accidental imports of the development copy and forces you to install the package before testing it. Packaging bugs surface earlier that way.</p>

<p>I went with src.</p>

<p>Pair that with <code class="language-plaintext highlighter-rouge">pyproject.toml</code> declaring metadata, dependencies, and a console script entry point, and the project becomes pip-installable. After <code class="language-plaintext highlighter-rouge">pip install -e .</code> the tool runs as <code class="language-plaintext highlighter-rouge">osint-recon example.com</code> from anywhere on PATH inside the venv. That console script line in pyproject.toml is what turns “a Python file I wrote” into “a Python tool I built.”</p>

<p>1083 lines split across 11 files. <code class="language-plaintext highlighter-rouge">constants.py</code> holds the lookup tables. <code class="language-plaintext highlighter-rouge">utils.py</code> has the small display helpers used in two or three other modules. <code class="language-plaintext highlighter-rouge">validation.py</code> handles domain input sanitization. <code class="language-plaintext highlighter-rouge">registration.py</code> does RDAP with WHOIS fallback. <code class="language-plaintext highlighter-rouge">dns_enum.py</code> covers DNS records plus the service-token classifier. <code class="language-plaintext highlighter-rouge">email_security.py</code> is the SPF, DMARC, DKIM, BIMI audit. <code class="language-plaintext highlighter-rouge">http_probe.py</code> does HTTP and HTTPS checks. <code class="language-plaintext highlighter-rouge">reporting.py</code> writes the plaintext report. <code class="language-plaintext highlighter-rouge">cli.py</code> ties everything together with argparse, the dependency check, and the main coordinator. <code class="language-plaintext highlighter-rouge">__init__.py</code> and <code class="language-plaintext highlighter-rouge">__main__.py</code> make the package importable and runnable as a module.</p>

<p>The boundaries follow scan stages. Shared helpers got factored out where multiple modules needed them. The cli.py entry point does a friendly dependency check before importing modules that need third-party libraries, so a user missing whoisit or dnspython sees a clean install instruction instead of a Python traceback.</p>

<p>The dependency graph between modules is clean. No circular imports. Each module can be reasoned about independently. Subdomain enumeration in v0.4.0 will land as a new file in this directory, called from cli.py like any other scan stage.</p>

<h2 id="the-tests">The tests</h2>

<p>Five test files, 67 tests, all pytest, all green in under a second.</p>

<p>They cover the pure-logic helpers: domain validation, SPF parsing, DMARC parsing, service-token classification, datetime formatting, and the report section writers. Everything in that list operates on string inputs and produces string or structured outputs. No network mocking needed because nothing in the test suite touches the network.</p>

<p>The functions that do touch the network (DNS lookups, HTTP probing, RDAP queries) aren’t tested here. Those need either live network access or careful mocking, and both are bigger lifts than they’re worth right now. Unit tests cover the logic most likely to break during refactors, which is what unit tests are for.</p>

<p>Writing tests retroactively for code that already worked felt weird the first time. The code was already correct. I’d manually tested it. Why bother codifying that? Because “I tested it manually and it looked right” isn’t durable knowledge. Six months from now I won’t remember which inputs I covered. The test suite remembers for me.</p>

<h2 id="phased-shipping">Phased shipping</h2>

<p>I shipped v0.3.0 across three commits over the course of one evening. Phase A was the module split and the pyproject.toml. Phase B was the pytest test suite. Phase C was the CHANGELOG update, the v0.3.0 tag, and the GitHub release. Each phase got smoke-tested before commit. Each commit got pushed before the next phase started.</p>

<p>Three small focused commits with clear messages. The alternative would have been one giant “v0.3.0” commit with hundreds of changes across a dozen new files. That commit would have been worse along every axis: harder to review, harder to bisect if something broke, less informative in the log, harder to roll back partially if Phase B revealed a problem in Phase A.</p>

<p>This phased rhythm is now my default. Big refactors get broken into shippable steps. Each step preserves a working state. The git history reads as a story instead of a wall of “fixed stuff” messages.</p>

<h2 id="what-this-signals">What this signals</h2>

<p>Portfolio projects tempt you to only ship things that show up on a feature list. New capability, demo-able output, something to brag about. Refactoring produces none of that.</p>

<p>But the tool went from “a script that grew” to “a Python package that can be installed, tested, and extended.” That’s a different kind of change. Adding subdomain enumeration to the old monolithic file would have meant pain. Adding it to the new structure is straightforward: new file, new tests, called from cli.py like any other scan stage. Structural work earns the next ten features.</p>

<p>Click through to the repo today and what shows up is a pyproject.toml, a tests directory with 67 passing tests, a CHANGELOG.md following Keep a Changelog, and four tagged releases. These are the things a recruiter or hiring engineer scans for in thirty seconds when evaluating a portfolio piece. Working code is the entry condition. Disciplined structure is what gets noticed.</p>

<h2 id="whats-next">What’s next</h2>

<p>v0.4.0 will add subdomain enumeration through wordlist probes and certificate transparency log lookups. That work is landable cleanly now because of what shipped tonight.</p>

<p>v0.3.0 is on GitHub. The CHANGELOG entry for it runs longer than the previous three releases combined.</p>

<p>Full project: <a href="https://github.com/Jorgy762/osint-domain-recon">github.com/Jorgy762/osint-domain-recon</a>.</p>

<p>If your Python script is approaching 1000 lines, refactor before adding the next feature. Phase the work. Smoke test between phases. Write tests for the pure-logic helpers first. Document everything in a CHANGELOG.</p>]]></content><author><name>Ryan Jorgensen</name></author><category term="python" /><category term="refactoring" /><category term="software-engineering" /><category term="packaging" /><category term="testing" /><category term="cybersecurity" /><summary type="html"><![CDATA[v0.3.0 of my OSINT recon tool shipped last night with no new features. Here's why that was the right call, and what refactoring a 1083-line script into a proper Python package taught me.]]></summary></entry><entry><title type="html">Upgrading my OSINT recon tool: RDAP, email auth, and what auditing my own domain taught me</title><link href="https://threatvector762.dev/blog/2026/05/31/rdap-email-auth-upgrade/" rel="alternate" type="text/html" title="Upgrading my OSINT recon tool: RDAP, email auth, and what auditing my own domain taught me" /><published>2026-05-31T00:00:00+00:00</published><updated>2026-05-31T00:00:00+00:00</updated><id>https://threatvector762.dev/blog/2026/05/31/rdap-email-auth-upgrade</id><content type="html" xml:base="https://threatvector762.dev/blog/2026/05/31/rdap-email-auth-upgrade/"><![CDATA[<p>The first version of my OSINT recon tool was a learning project. It worked, but it had real problems I did not see until I started using it on real targets. This is the second pass. Two big changes, one smaller one, and one moment that taught me more than the code did.</p>

<h2 id="switching-from-whois-to-rdap">Switching from WHOIS to RDAP</h2>

<p>The original tool used the <code class="language-plaintext highlighter-rouge">python-whois</code> library to pull registration data. That library parses raw WHOIS text returned by registrars. The problem is that every registrar formats their WHOIS output differently, and they change those formats whenever they want. The parser breaks silently. One day you get a registrar field, the next day you get an empty string because the registrar updated their template and added a colon.</p>

<p>RDAP solves this. It is the Registration Data Access Protocol, defined in RFC 7480 through 7484, and it has been mandated for gTLD registrars by ICANN since 2019. Instead of unstructured text, you get a structured JSON response with named fields. The data is the same, but you can actually rely on the parsing.</p>

<p>I switched the primary lookup to RDAP using the <code class="language-plaintext highlighter-rouge">whoisit</code> library, with the old WHOIS path kept as a fallback for the small number of TLDs that still do not run RDAP servers. The output now tells you which protocol returned the data, so you can see at a glance whether your scan got modern structured data or legacy text parsing.</p>

<p>There is a real portfolio signal in this change too. RDAP is where the industry has actually gone. Using the modern standard shows you are tracking the field, not just grabbing the first library that came up on Stack Overflow.</p>

<h2 id="adding-email-authentication-audits">Adding email authentication audits</h2>

<p>This is the bigger change. The tool now has a full SPF, DMARC, DKIM, and BIMI audit that runs by default. There is a <code class="language-plaintext highlighter-rouge">--no-email-security</code> flag if you want to skip it.</p>

<p>The interesting part is the SPF parser. Anyone can read an SPF record and count the includes in it. That is not useful. What actually matters is the recursive DNS lookup count, which RFC 7208 caps at 10 per evaluation. Most enterprise SPF records silently break because nested includes blow past that limit. So the parser walks the entire include chain, resolves each one, and counts the real DNS queries that would happen during a real SPF check. It also tracks void lookups (NXDOMAIN responses), which RFC 7208 caps at 2.</p>

<p>For DMARC, the parser pulls out every tag and flags the things that matter most. Is the policy actually enforcing, or is it sitting at <code class="language-plaintext highlighter-rouge">p=none</code> doing nothing? Are aggregate reports being collected anywhere?</p>

<p>DKIM was the hardest design call. There is no standard for selector names. Microsoft 365 uses <code class="language-plaintext highlighter-rouge">selector1</code> and <code class="language-plaintext highlighter-rouge">selector2</code>. Google Workspace uses <code class="language-plaintext highlighter-rouge">google</code>. Custom setups can use anything. The tool probes 29 common selectors based on what I have seen across major mail providers, and it tells you explicitly that a “no DKIM found” result means “no DKIM at common selectors,” not “DKIM does not exist.” Being honest about a tool’s limitations is the difference between a tool a security professional respects and one they roll their eyes at.</p>

<h2 id="service-token-classification">Service-token classification</h2>

<p>Smaller but useful addition. When a SaaS provider verifies you own a domain, they ask you to publish a TXT record with a specific prefix. Those prefixes are persistent fingerprints. <code class="language-plaintext highlighter-rouge">MS=ms...</code> means Microsoft 365. <code class="language-plaintext highlighter-rouge">google-site-verification=</code> means Google Workspace or Search Console. <code class="language-plaintext highlighter-rouge">facebook-domain-verification=</code> means Meta. There are dozens of these, and they are passive OSINT signal about a target’s entire SaaS footprint. The tool now maps 30+ of them against a domain’s TXT records and tells you which services have touched it.</p>

<h2 id="what-it-found-on-my-own-domain">What it found on my own domain</h2>

<p>I ran the upgraded tool against <code class="language-plaintext highlighter-rouge">threatvector762.dev</code>. The numbers were better than I expected. SPF is hard-fail with one DNS lookup out of ten. DMARC is at <code class="language-plaintext highlighter-rouge">p=reject</code> with <code class="language-plaintext highlighter-rouge">pct=100</code>, the strictest enforcement available. DKIM is signing at both Microsoft 365 selectors. Aggregate report collection is working.</p>

<p>That gave me a moment of pride and then a moment of awareness. I have been telling people for years that they should harden their mail authentication. I had actually done it, and my own tool confirmed it. The feeling of having a security-branded persona that holds up to its own audit is hard to overstate.</p>

<h2 id="the-lesson-that-was-not-in-the-code">The lesson that was not in the code</h2>

<p>There was a moment during this work where I removed a TXT verification token from my DNS because I read an offhand comment suggesting it was a minor OSINT leak. The advice was overstated. The token was already five degrees redundant given everything else discoverable about my mail setup. Worse, removing it carried a small but real operational risk: Microsoft 365 sometimes re-prompts for tenant verification, and that token can be required.</p>

<p>I acted without verifying. The token came out before I asked the question “what breaks if this advice is wrong?” The recovery was trivial, the lesson was not. Any change to live infrastructure (DNS, certificates, identity providers, firewall rules) deserves a 30-second pause and a recovery plan. That habit is easy to build on small infrastructure where the stakes are zero. It is much harder to build on big infrastructure where the stakes are everything. Build it now.</p>

<h2 id="what-is-next">What is next</h2>

<p>The roadmap is shorter than it was. The big remaining items:</p>

<ul>
  <li>Subdomain enumeration</li>
  <li>SSL/TLS certificate inspection</li>
  <li>JSON report export</li>
  <li>Refactor into proper modules before adding more</li>
</ul>

<p>Subdomain enumeration is where this tool stops being “a thing that audits one domain” and starts being “a thing that maps a target’s attack surface.” That is the next major feature, and it deserves a proper design pass before any code gets written.</p>

<h2 id="the-code">The code</h2>

<p>Full project on GitHub:</p>

<p><a href="https://github.com/Jorgy762/osint-domain-recon">github.com/Jorgy762/osint-domain-recon</a></p>

<p>If you want to try it on your own domain, you should. There is real value in running adversarial tools against your own infrastructure. You learn what is discoverable, what is exposed, and what you forgot to clean up. Just remember the disclaimer: only run it against domains you own or have explicit written permission to test.</p>]]></content><author><name>Ryan Jorgensen</name></author><category term="python" /><category term="osint" /><category term="cybersecurity" /><category term="rdap" /><category term="email-security" /><category term="dmarc" /><summary type="html"><![CDATA[Two weeks after the first version, I swapped WHOIS for RDAP, added a full email-authentication audit, and ran the tool against my own domain. Here is what I found, what I changed, and what it taught me about my own infrastructure.]]></summary></entry><entry><title type="html">The Grey Zone Is Not a Metaphor</title><link href="https://threatvector762.dev/blog/2026/05/25/the-grey-zone-is-not-a-metaphor/" rel="alternate" type="text/html" title="The Grey Zone Is Not a Metaphor" /><published>2026-05-25T00:00:00+00:00</published><updated>2026-05-25T00:00:00+00:00</updated><id>https://threatvector762.dev/blog/2026/05/25/the-grey-zone-is-not-a-metaphor</id><content type="html" xml:base="https://threatvector762.dev/blog/2026/05/25/the-grey-zone-is-not-a-metaphor/"><![CDATA[<p>We are already at war.</p>

<p>Not in the way that triggers Article 5. Not with uniformed soldiers crossing recognized borders – at least not yet. What we are in right now is a sustained, multi-domain conflict being waged below the threshold of conventional armed conflict by state actors who have calculated, correctly, that ambiguity is a strategic weapon.</p>

<p>This is the grey zone. And it is not a metaphor.</p>

<p>I want to write this series for two audiences: the corporate security professional who has never worn a uniform, and the military member who has never thought about their job in terms of NIST frameworks or threat modeling. Both of you are operating in the same environment, facing the same adversaries, using different language to describe identical problems. That gap costs us. This series is about closing it.</p>

<h2 id="what-the-grey-zone-actually-means">What the Grey Zone Actually Means</h2>

<p>The concept is not new. The term gets tossed around in think-tank papers and NATO briefings, but the underlying activity – operations designed to coerce, destabilize, and undermine a target state without triggering a formal military response – is older than the internet. Soviet dezinformatsiya campaigns during the Cold War were grey zone operations. KGB active measures targeting Western institutions were grey zone operations.</p>

<p>What has changed is the scale, the speed, and the attack surface.</p>

<p>NATO’s own framework describes hybrid threats as activities that are “deliberately designed to avoid attribution and to remain below the threshold of formally declared warfare.” The deliberate avoidance of attribution is the point. When a Russian GRU unit deploys destructive malware against Ukrainian infrastructure that then spreads globally, causing over ten billion dollars in damage to multinational corporations, that is not an accident of poor target selection. The deniability is the feature.</p>

<p>The Canadian Centre for Cyber Security (CCCS) laid this out plainly in the National Cyber Threat Assessment 2023-2024: state-sponsored actors are the number one strategic cyber threat to Canada. Not criminal ransomware groups, not hacktivists. Nation-states. And they are not waiting for a declaration of war to start.</p>

<h2 id="the-two-actors-you-need-to-understand">The Two Actors You Need to Understand</h2>

<p><strong>Russia</strong> operates with urgency and disruption as primary objectives. APT28, also known as Fancy Bear, linked to Russian military intelligence (GRU), has conducted operations against NATO member states including Canada for years. GRU Unit 74455, known publicly as Sandworm, executed the NotPetya attack in 2017 – initially a Ukraine-targeted operation that went global, hitting shipping companies, pharmaceutical manufacturers, and critical infrastructure operators worldwide. The UK’s National Cyber Security Centre, along with Canada’s Communications Security Establishment (CSE), formally attributed NotPetya to Russian military intelligence. The playbook is: deny, disrupt, demoralize.</p>

<p><strong>China</strong> plays a longer game. Where Russian operations tend toward disruption and noise, Chinese state-sponsored activity is often patient, quiet, and positioned for future use. Volt Typhoon, attributed by CISA, the NSA, and Canada’s CSE in a joint advisory in early 2024, represents something particularly alarming: pre-positioning inside Western critical infrastructure with no immediate intent to act. They are not stealing data or causing outages. They are installing themselves and waiting. The Canadian Security Intelligence Service (CSIS) also documented in its 2023 annual report that Chinese state actors conducted foreign interference operations against Canadian federal elections and targeted Canadian political figures, researchers, and diaspora communities.</p>

<p>Both actors operate simultaneously across cyber, information, economic, and physical domains. That is the key to understanding hybrid warfare – it is not a sequence of phases, it is a continuous, overlapping campaign across all available vectors.</p>

<h2 id="why-this-is-your-problem-too">Why This Is Your Problem Too</h2>

<p>If you work in corporate IT security, you might be thinking this sounds like a government problem. It is not. The vast majority of Canada’s critical infrastructure – energy, telecommunications, financial systems, transportation – is privately owned and operated. Public Safety Canada’s National Strategy for Critical Infrastructure identifies ten sectors. Most of them are run by companies with security teams that look like yours.</p>

<p>Supply chain compromise is the preferred corporate entry point. SolarWinds in 2020. 3CX in 2023. These were not attacks on governments – they were attacks through trusted software vendors to reach government and corporate networks simultaneously. If your organization has third-party software vendors, managed service providers, or cloud dependencies, you are already inside someone’s threat model.</p>

<p>If you wear a Canadian Armed Forces uniform part-time, you might be thinking this is an Active Force problem or a CSIS problem. It is not. Reservists present a unique intelligence collection opportunity for adversaries. You have security clearances, access to restricted areas and information, and you live your civilian life – with a personal phone, social media accounts, and home network – largely outside the security envelope of your unit. Your LinkedIn profile, your Facebook check-in at the armoury, your Instagram photo near a restricted area: these are data points. Aggregated, they are intelligence. CSIS has documented foreign interference efforts targeting CAF members specifically.</p>

<h2 id="the-framework-problem">The Framework Problem</h2>

<p>Here is something that frustrates me about how these two communities operate: the military has robust doctrinal frameworks for threat assessment, layered defence, and incident response. Corporate security has robust compliance and risk management frameworks. They describe the same problems with almost zero shared vocabulary.</p>

<p>A corporate security analyst talks about the NIST Cybersecurity Framework functions – Identify, Protect, Detect, Respond, Recover. A military intelligence officer talks about Intelligence Preparation of the Battlefield, threat actor capabilities, likely courses of action. These are the same analytical process. The language is different. The logic is identical.</p>

<p>NATO’s Strategic Concept 2022 explicitly named cyber as a domain of warfare and hybrid threats as a core security challenge for the alliance. Canada is a NATO member. Canada is also a Five Eyes partner. CSE and CCCS publish threat bulletins and advisories that corporate security teams rarely read, and military units almost never see.</p>

<p>Closing that gap – between doctrine and framework, between corporate and military security culture, between the analyst who thinks in ATT&amp;CK matrices and the MP who thinks in threat assessments and force protection – is the whole point of this series.</p>

<h2 id="the-bottom-line">The Bottom Line</h2>

<p>The grey zone is active right now. It was active before you read this post and it will be active after. Russia and China are not preparing for a future conflict with Canada and its allies – they are conducting one. The threshold question is not whether conflict is happening, it is whether it will turn kinetic.</p>

<p>Your job, whether you manage endpoints for a mid-size enterprise or you run perimeter security at a Canadian Forces installation, sits inside that conflict. The frameworks exist to help you organize your response. The doctrine exists to help you understand the adversary. Neither is enough on its own.</p>

<p>That is why both worlds need to learn each other’s language.</p>

<hr />

<p><strong>Next in the series:</strong> Threat Actors Are Not Hackers in Hoodies – a deep dive into Russian and Chinese TTPs mapped against MITRE ATT&amp;CK and what they mean for both corporate and military targets.</p>

<hr />

<p><strong>References and Further Reading</strong></p>

<ul>
  <li>Canadian Centre for Cyber Security. <em>National Cyber Threat Assessment 2023-2024</em>. <a href="https://www.cyber.gc.ca/en/guidance/national-cyber-threat-assessment-2023-2024">cyber.gc.ca</a></li>
  <li>Communications Security Establishment Canada. <em>Cyber Threat Bulletins and Advisories</em>. <a href="https://www.cse-cst.gc.ca/en/information-and-tools/advisories">cse-cst.gc.ca</a></li>
  <li>Canadian Security Intelligence Service. <em>2023 Public Report</em>. <a href="https://www.canada.ca/en/security-intelligence-service/corporate/publications/2023-public-report.html">canada.ca</a></li>
  <li>CISA, NSA, FBI, and partner agencies including CSE. <em>Volt Typhoon Advisory AA24-038A</em>, February 2024. <a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa24-038a">cisa.gov</a></li>
  <li>NATO. <em>NATO 2022 Strategic Concept</em>. <a href="https://www.nato.int/cps/en/natohq/topics_210907.htm">nato.int</a></li>
  <li>National Institute of Standards and Technology. <em>Cybersecurity Framework 2.0</em>, February 2024. <a href="https://www.nist.gov/cyberframework">nist.gov</a></li>
  <li>Public Safety Canada. <em>National Strategy for Critical Infrastructure</em>. <a href="https://www.publicsafety.gc.ca/cnt/rsrcs/pblctns/srtg-crtcl-nfrstrctr/index-en.aspx">publicsafety.gc.ca</a></li>
  <li>UK National Cyber Security Centre. <em>Russian GRU Cyber Attacks Attribution</em>, October 2018. <a href="https://www.ncsc.gov.uk/news/reckless-campaign-cyber-attacks-russian-military-intelligence-service">ncsc.gov.uk</a></li>
</ul>]]></content><author><name>Ryan Jorgensen</name></author><category term="cybersecurity" /><category term="hybrid-warfare" /><category term="military" /><category term="corporate-security" /><category term="geopolitics" /><category term="canada" /><summary type="html"><![CDATA[We are already in an active conflict with peer and near-peer adversaries. It does not look like what the movies told us war looks like. Here is why that matters to every sysadmin, MP, and security professional in Canada.]]></summary></entry><entry><title type="html">Building a Browser-Based Windows Log Analyzer</title><link href="https://threatvector762.dev/blog/2026/05/19/windows-log-analyzer/" rel="alternate" type="text/html" title="Building a Browser-Based Windows Log Analyzer" /><published>2026-05-19T00:00:00+00:00</published><updated>2026-05-19T00:00:00+00:00</updated><id>https://threatvector762.dev/blog/2026/05/19/windows-log-analyzer</id><content type="html" xml:base="https://threatvector762.dev/blog/2026/05/19/windows-log-analyzer/"><![CDATA[<p>I spend a fair amount of time digging through Windows logs – triaging incidents, auditing policy changes on domain controllers, chasing down why IIS threw a wall of 500 errors at 2am. The standard approach is copying lines into Notepad and hammering Ctrl+F, which works right up until it doesn’t.</p>

<p><strong><a href="/projects/log-analyzer">Use the tool here →</a></strong></p>

<hr />

<h2 id="the-problem-with-evtx">The problem with .evtx</h2>

<p>Binary EVTX files are not parseable in the browser. The format is a structured binary log container and there is no native browser API to read it. The workaround is exporting from Event Viewer first:</p>

<p><em>Right-click any log channel → Save All Events As… → XML</em></p>

<p>Or on the command line:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-WinEvent</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\path\to\file.evtx"</span><span class="w"> </span><span class="o">|</span><span class="w">
  </span><span class="n">Select-Object</span><span class="w"> </span><span class="nx">TimeCreated</span><span class="p">,</span><span class="w"> </span><span class="nx">Id</span><span class="p">,</span><span class="w"> </span><span class="nx">LevelDisplayName</span><span class="p">,</span><span class="w"> </span><span class="nx">ProviderName</span><span class="p">,</span><span class="w"> </span><span class="nx">Message</span><span class="w"> </span><span class="o">|</span><span class="w">
  </span><span class="n">Export-Csv</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="s2">\Desktop\events.csv"</span><span class="w"> </span><span class="nt">-NoTypeInformation</span><span class="w">
</span></code></pre></div></div>

<p>The XML export gives you the full <code class="language-plaintext highlighter-rouge">&lt;System&gt;</code> block per event – EventID, Level, TimeCreated, Provider, Computer – plus all the <code class="language-plaintext highlighter-rouge">&lt;EventData&gt;</code> key-value pairs that carry the actual forensic detail.</p>

<hr />

<h2 id="format-detection">Format detection</h2>

<p>The tool auto-detects format on drop using the first 600 bytes of the file:</p>

<ul>
  <li>If the filename ends in <code class="language-plaintext highlighter-rouge">.xml</code> or the content opens with <code class="language-plaintext highlighter-rouge">&lt;Events</code> or <code class="language-plaintext highlighter-rouge">&lt;Event xmlns=</code> – Event XML parser</li>
  <li>If the content contains <code class="language-plaintext highlighter-rouge">#Software: Microsoft Internet Information Services</code> or <code class="language-plaintext highlighter-rouge">#Fields:</code> – IIS W3C parser</li>
  <li>Everything else – plain text parser with regex level detection</li>
</ul>

<p>No file extension guessing beyond <code class="language-plaintext highlighter-rouge">.xml</code>. A <code class="language-plaintext highlighter-rouge">.log</code> file gets sniffed by content.</p>

<hr />

<h2 id="one-thing-i-ran-into">One thing I ran into</h2>

<p>The initial version threw <code class="language-plaintext highlighter-rouge">illegal invocation</code> errors on every XML file. The cause was extracting <code class="language-plaintext highlighter-rouge">getAttribute</code> from a DOM element without binding <code class="language-plaintext highlighter-rouge">this</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// breaks -- getAttribute loses its context when detached like this</span>
<span class="kd">const</span> <span class="nx">rawTime</span> <span class="o">=</span> <span class="p">((</span><span class="nx">el</span> <span class="o">||</span> <span class="p">{}).</span><span class="nx">getAttribute</span> <span class="o">||</span> <span class="p">(()</span> <span class="o">=&gt;</span> <span class="dl">''</span><span class="p">))(</span><span class="dl">'</span><span class="s1">SystemTime</span><span class="dl">'</span><span class="p">);</span>

<span class="c1">// works</span>
<span class="kd">const</span> <span class="nx">rawTime</span> <span class="o">=</span> <span class="nx">el</span> <span class="p">?</span> <span class="nx">el</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">SystemTime</span><span class="dl">'</span><span class="p">)</span> <span class="p">:</span> <span class="dl">''</span><span class="p">;</span>
</code></pre></div></div>

<p>It’s a subtle JS gotcha. The fallback pattern <code class="language-plaintext highlighter-rouge">(el || {}).method</code> looks safe but the method is no longer bound to the element when you call it that way.</p>

<hr />

<h2 id="design-decisions">Design decisions</h2>

<p><strong>Client-side only.</strong> No server, no upload endpoint, no third-party seeing your logs. This was non-negotiable – these are production logs that may contain IP addresses, usernames, and authentication events.</p>

<p><strong>2,500 row render cap.</strong> The DOM gets slow rendering thousands of rows. The cap applies to the table only – export always writes the full filtered set regardless of how many rows that is.</p>

<p><strong>IIS field mapping is dynamic.</strong> The <code class="language-plaintext highlighter-rouge">#Fields:</code> header line in W3C logs defines column order, and it can vary between IIS versions and configurations. The parser reads that header first and maps values positionally, so it handles any field layout without hardcoding.</p>

<hr />

<p>The tool is at <a href="/projects/log-analyzer/">/projects/log-analyzer</a>. If you run into a format it doesn’t handle – Windows Firewall logs, DHCP audit logs, Sysmon XML – let me know.</p>]]></content><author><name>Ryan Jorgensen</name></author><category term="tools" /><category term="windows" /><category term="security" /><category term="sysadmin" /><category term="blue-team" /><category term="log-analysis" /><category term="javascript" /><summary type="html"><![CDATA[How and why I built a client-side log parser for Windows Event XML, IIS W3C logs, and plain text -- and what I learned about browser XML parsing along the way.]]></summary></entry><entry><title type="html">Building an OSINT domain recon tool from scratch</title><link href="https://threatvector762.dev/blog/2026/05/18/building-osint-recon-tool/" rel="alternate" type="text/html" title="Building an OSINT domain recon tool from scratch" /><published>2026-05-18T00:00:00+00:00</published><updated>2026-05-18T00:00:00+00:00</updated><id>https://threatvector762.dev/blog/2026/05/18/building-osint-recon-tool</id><content type="html" xml:base="https://threatvector762.dev/blog/2026/05/18/building-osint-recon-tool/"><![CDATA[<p>This is my first real coding project beyond PowerShell scripts and one-liners. I wanted to build something that was actually useful, tied directly to my security interests, and demonstrated skills relevant to the kind of work I want to do. A domain recon tool checked all three boxes.</p>

<h2 id="what-it-does">What it does</h2>

<p>The tool automates the first phase of any domain investigation. Given a target domain, it runs four checks in sequence:</p>

<ul>
  <li>IP resolution via standard DNS lookup</li>
  <li>WHOIS data (registrar, creation date, expiry, name servers, country)</li>
  <li>DNS record enumeration (A, AAAA, MX, NS, TXT, CNAME, SOA)</li>
  <li>HTTP/HTTPS probing (status code, server header, redirect chain)</li>
</ul>

<p>Results print to the terminal or export to a plaintext report with the <code class="language-plaintext highlighter-rouge">-o</code> flag.</p>

<h2 id="why-i-built-it-this-way">Why I built it this way</h2>

<p>Most people learning Python start with calculator apps or guessing games. Those are fine for syntax, but they don’t teach you anything about how tools actually work in the field. Starting with a recon tool meant I had to learn:</p>

<ul>
  <li>How DNS resolution actually works under the hood</li>
  <li>What WHOIS data tells you and what it doesn’t</li>
  <li>How HTTP redirects chain together</li>
  <li>How to handle errors gracefully when a host is unreachable</li>
  <li>How to structure a CLI tool with argument parsing</li>
</ul>

<p>Every one of those things is directly applicable to real security work.</p>

<h2 id="the-hardest-part">The hardest part</h2>

<p>Error handling. When you’re probing domains across the internet, everything can and will fail. DNS timeouts, NXDOMAIN responses, SSL certificate errors, connection refusals. The first version of this tool crashed constantly because I wasn’t catching exceptions properly.</p>

<p>Learning to anticipate failure modes and handle them gracefully is one of the most valuable things a security practitioner can learn. Production systems fail in the same ways.</p>

<h2 id="what-id-add-next">What I’d add next</h2>

<p>The current version is functional but basic. The roadmap includes:</p>

<ul>
  <li>Subdomain enumeration</li>
  <li>SSL/TLS certificate inspection</li>
  <li>Shodan API integration for open port data</li>
  <li>JSON report export</li>
</ul>

<h2 id="the-code">The code</h2>

<p>The full project with installation instructions is on GitHub:</p>

<p><a href="https://github.com/Jorgy762/osint-domain-recon">github.com/Jorgy762/osint-domain-recon</a></p>

<p>If you’re just getting started with Python and want a real project to work through, feel free to fork it, break it, and improve it.</p>]]></content><author><name>Ryan Jorgensen</name></author><category term="python" /><category term="osint" /><category term="cybersecurity" /><category term="tools" /><summary type="html"><![CDATA[How I built a Python-based domain reconnaissance tool as my first real coding project, what I learned along the way, and why it matters for practical security work.]]></summary></entry><entry><title type="html">What 13 Years in the CAF Taught Me About Security Culture</title><link href="https://threatvector762.dev/blog/2026/05/18/caf-security-culture/" rel="alternate" type="text/html" title="What 13 Years in the CAF Taught Me About Security Culture" /><published>2026-05-18T00:00:00+00:00</published><updated>2026-05-18T00:00:00+00:00</updated><id>https://threatvector762.dev/blog/2026/05/18/caf-security-culture</id><content type="html" xml:base="https://threatvector762.dev/blog/2026/05/18/caf-security-culture/"><![CDATA[<p>I joined the Canadian Armed Forces as a Military Police Non-Commissioned Member (NCM) in my early twenties. At the time, security meant physical things – controlled access points, proper identification, protecting people and assets from tangible threats you could see and respond to in real time.</p>

<p>I had no idea I was also starting a 13-year education in security culture. One that would fundamentally shape how I think about cyber defence today.</p>

<h2 id="what-i-thought-security-was">What I thought security was</h2>

<p>When I started, security felt binary. Either a door was locked or it wasn’t. Either someone had proper identification or they didn’t. Either a perimeter was intact or it had been breached. The responses were clear, the threats were visible, and the procedures were written down and drilled until they were second nature.</p>

<p>I thought civilian security would work the same way. Clear rules, clear threats, clear responses. I was wrong about that in ways that took years to fully understand.</p>

<h2 id="the-gap-i-didnt-expect">The gap I didn’t expect</h2>

<p>When I moved into IT infrastructure, the cultural shift was jarring. Not the technical part – that was just learning. The jarring part was watching how differently organizations approach security when the threats aren’t physical.</p>

<p>In the CAF, security isn’t a department you call when something goes wrong. It’s a baseline assumption woven into every procedure, every briefing, every piece of planning. You don’t prop a door open because it’s inconvenient to badge in twice. You don’t share credentials because it’s faster. You don’t skip the checklist because you’ve done it a hundred times and nothing has ever gone wrong.</p>

<p>In civilian IT, I kept running into the opposite assumption – that security is a layer you add on top of operations rather than something built into them from the start. Patches get delayed because they might break something. Default credentials stay in place because changing them takes time nobody has. Multi-factor authentication gets pushed back because users find it inconvenient.</p>

<p>The military calls that complacency. And complacency, in any security context, is how things go wrong.</p>

<h2 id="what-the-physical-world-taught-me-about-cyber">What the physical world taught me about cyber</h2>

<p>Spending years in physical security gave me a mental model that translates surprisingly well to cyber defence – once you learn to see the parallels.</p>

<p>Access control is access control. Whether you’re managing who can enter a secure facility or who can authenticate to a privileged account, the principles are identical. Least privilege, need-to-know, proper identification and verification. Zero Trust architecture isn’t a new idea to anyone who has worked a controlled access point. It’s just the same concept applied to network traffic instead of people walking through a door.</p>

<p>Layered defence is real. A single fence doesn’t secure a base. Neither does a single firewall secure a network. In the military you think in terms of rings – each layer slowing an adversary down, buying time, creating detection opportunities. Defence in depth in cybersecurity is the same concept with different tools.</p>

<p>Insider threat is the hardest problem in both worlds. The most difficult security challenges in the CAF weren’t external threats – they were people with legitimate access making bad decisions, whether through negligence, poor judgement, or deliberate action. The same is true in cyber. The perimeter matters far less than what’s happening inside it.</p>

<h2 id="how-i-see-things-differently-now">How I see things differently now</h2>

<p>When I started in the CAF, I followed security procedures because they were the rules and the rules existed for good reasons. I didn’t always understand the reasons, but I trusted the framework and I executed.</p>

<p>Now, after moving into IT infrastructure and working toward a deeper understanding of cybersecurity, I see the reasons behind the procedures much more clearly. And that changes how I approach both worlds.</p>

<p>In the CAF, I’m now the Unit Information Systems Security Officer. That role sits at exactly the intersection I’m describing – military discipline applied to information security in a way that most purely civilian security practitioners never have to think about. Classified information handling, operational security, the relationship between physical and digital access controls. It’s all connected.</p>

<h2 id="when-the-stakes-are-real">When the stakes are real</h2>

<p>The clearest example I can point to from my own experience is my deployment on Operation IMPACT in 2018. From February to July, I served as a Tactical Aircraft Security Officer attached to Air Task Force - Iraq, supporting Combined Joint Task Force - Operation Inherent Resolve out of Iraq.</p>

<p>That role put me responsible for the physical security of Canadian Armed Forces aircraft in an active operational theatre. The threat environment was real, the consequences of a security failure were unambiguous, and there was no margin for the kind of complacency I would later encounter in civilian IT.</p>

<p>What struck me most wasn’t the intensity of the environment – it was how naturally the security culture functioned under pressure. Procedures were followed not because someone was watching, but because everyone understood exactly what was at stake if they weren’t. Accountability wasn’t imposed from above – it was mutual and horizontal. Every person in that environment understood that their individual discipline was load-bearing for the people around them.</p>

<p>That experience gave me a reference point I’ve never lost. When I’m now assessing a security posture in an IT environment – reviewing access controls, looking at patch cycles, evaluating how policies are actually being followed versus how they’re written – I’m always measuring it against that standard. Not because I expect civilian organizations to operate like a deployed military unit, but because I know what genuine security culture looks like when it’s working, and I can recognize when it isn’t.</p>

<p>In my civilian role as a Windows Server Administrator, I find myself thinking like an MP more often than I expected. Who has access to what and why. Where the gaps are between what the policy says and what people actually do. What the blast radius looks like if a given account or system is compromised.</p>

<h2 id="the-thing-most-security-training-doesnt-teach">The thing most security training doesn’t teach</h2>

<p>Procedures only work if the culture supports them. You can write the best security policy in the world and it will fail if the people following it don’t understand why it matters or don’t feel accountable for the outcome.</p>

<p>The CAF gets this right in a way most organizations don’t. Security culture isn’t built through annual training modules or compliance checkboxes. It’s built through repetition, accountability, and leadership that models the behaviour it expects.</p>

<p>That’s the thing I carry from 13 years in uniform into every security conversation I have now. Tools and frameworks matter. But culture is the thing that either makes them work or renders them useless.</p>

<p>If you’re building a security program and the culture isn’t there yet, start there. Everything else is secondary.</p>]]></content><author><name>Ryan Jorgensen</name></author><category term="cybersecurity" /><category term="caf" /><category term="military" /><category term="career" /><category term="zero-trust" /><summary type="html"><![CDATA[I joined the Canadian Armed Forces as a Military Police Non-Commissioned Member (NCM) in my early twenties. I had no idea I was also starting a 13-year education in security culture -- one that would fundamentally shape how I think about cyber defence today.]]></summary></entry><entry><title type="html">Twenty Years in Vana’diel – What FFXI Taught Me About the Long Game</title><link href="https://threatvector762.dev/blog/2026/05/18/twenty-years-in-vanadiel/" rel="alternate" type="text/html" title="Twenty Years in Vana’diel – What FFXI Taught Me About the Long Game" /><published>2026-05-18T00:00:00+00:00</published><updated>2026-05-18T00:00:00+00:00</updated><id>https://threatvector762.dev/blog/2026/05/18/twenty-years-in-vanadiel</id><content type="html" xml:base="https://threatvector762.dev/blog/2026/05/18/twenty-years-in-vanadiel/"><![CDATA[<p>I have been playing Final Fantasy XI Online on and off for twenty years. That is longer than some careers, longer than some marriages, and longer than the entire lifespan of most online games.</p>

<p>This is an attempt to explain why.</p>

<h2 id="vanadiel-in-2004">Vana’diel in 2004</h2>

<p>I was a teenager when I first set foot in Vana’diel. The world was enormous, deliberately hostile to solo play, and almost completely unwilling to explain itself to you. There was no quest marker pointing you toward the next objective. No minimap cluttered with icons telling you where to go. You read the quest text, you talked to people, you figured it out – or you found someone who already had.</p>

<p>That friction was the point. FFXI built its community out of necessity. You needed other people to survive, which meant you learned to work with strangers, to communicate clearly, to show up when you said you would. A game that required six people to kill most worthwhile enemies taught you something about coordination that a solo experience never could.</p>

<h2 id="how-it-varied-over-twenty-years">How it varied over twenty years</h2>

<p>I have not played FFXI the same way twice. There were years of heavy investment – deep in linkshell drama, grinding Dynamis for hours, coordinating with people across multiple time zones to take down notorious monsters that required precise execution and a lot of patience. There were years where I barely logged in, pulled back by military service, by work, by life accumulating in the way it does.</p>

<p>And then there were the returns.</p>

<p>Coming back to FFXI after a long break is a particular kind of experience. The world is still there, largely unchanged in the ways that matter. The music starts and something settles in your chest that is difficult to name. Nostalgia is part of it, but not all of it. It is more like returning to a place you lived once – familiar in your bones even after years away.</p>

<h2 id="what-modern-mmos-get-wrong">What modern MMOs get wrong</h2>

<p>I have tried other MMOs over the years. Some of them are technically superior to FFXI in almost every measurable way – better graphics, smoother mechanics, more accessible onboarding. Most of them failed to hold my attention for more than a few months.</p>

<p>The difference is depth. FFXI was built in an era when MMO designers expected players to invest. The job system alone – with its combinations, subjobs, and situational optimization – rewards the kind of thinking that most modern games have abandoned in favour of accessibility. That depth is not always comfortable. It can be alienating, especially to new players. But for the people who stay, it creates something modern games rarely manage: genuine mastery of a complex system.</p>

<p>There is also something to be said for a game that has been running for over two decades and still has an active player base. The people still in Vana’diel in 2026 are not there because the game is convenient. They are there because it means something to them.</p>

<figure>
  <img src="/assets/images/gaming/ffxi-asura-novius-castleoztroja-2021.jpg" alt="Castle Oztroja, Asura server 2021" />
  <figcaption>Novius in Castle Oztroja, Asura server -- 2021.</figcaption>
</figure>

<figure>
  <img src="/assets/images/gaming/ffxi-asura-novius-uptala-2021.jpg" alt="Uptala, Asura server 2021" />
  <figcaption>Uptala, VWNM, Asura server -- 2021.</figcaption>
</figure>

<h2 id="the-friendships">The friendships</h2>

<p>The most honest answer to why I keep coming back is the people.</p>

<p>Some of the most memorable interactions I have had in any game happened in FFXI – strangers who became regulars, regulars who became friends, the kind of bonds that form when you spend hours working toward something genuinely difficult together. The game created conditions for those connections in a way that feels increasingly rare.</p>

<p>Not all of those friendships survived the years. People move on, lose touch, stop playing. But the ones that stuck are the kind that remind you what online gaming can be at its best – not a distraction, but a genuine shared experience with real people on the other end.</p>

<h2 id="twenty-years-later">Twenty years later</h2>

<p>I still log in. Not as often as I once did, and not with the same intensity. But Vana’diel is still there, and so am I.</p>

<p>Most games ask you to complete them. FFXI never really had an end state in the way a single-player game does. It just kept going, kept adding, kept being a place you could return to. For twenty years that has been enough.</p>

<p>I do not know how much longer the servers will run. Square Enix has kept them going far longer than anyone expected, and the community has earned every year of it. When they eventually shut down, it will feel like losing something real.</p>

<p>Until then, I will keep logging in. Partly for the game, partly for the nostalgia, partly because Vana’diel is one of those rare places that still feels worth returning to after all this time.</p>

<p>Some worlds are just built to last.</p>]]></content><author><name>Ryan Jorgensen</name></author><category term="gaming" /><category term="ffxi" /><category term="final-fantasy" /><summary type="html"><![CDATA[I have been playing Final Fantasy XI Online on and off for twenty years. That is longer than some careers, longer than some marriages, and longer than the entire lifespan of most online games. This is an attempt to explain why.]]></summary></entry></feed>