Building a Linux Security Audit Tool in WSL: A Hybrid Bash + Python Adventure

by Johnathan Belcher 📅 July 1, 2026 ⏱️ 8 min read

Building tools you actually use is a different kind of satisfying. Not toy scripts or one-off exercises, but utilities that fit directly into your workflow. My encrypted journal CLI scratched that itch for personal data security. This time, I wanted to build something that would sharpen my Linux skills while feeding my growing interest in security engineering.

That led to LinuSec: a lightweight, modular Linux security audit tool built inside Ubuntu on WSL. It eventually grew into a hybrid Bash + Python system with a Rich-powered TUI dashboard, clean separation of concerns, and a development process that was genuinely fun.

This post walks through the design, the architecture, and the lessons learned while building it.

Why Build a Security Audit Tool?

I have been spending a lot of time inside WSL building homelab components, experimenting with SOC-style tooling, and getting more comfortable with Linux internals. One thing became obvious quickly: Linux is full of corners you do not think about until you start using it every day.

File permissions. SUID binaries. Cron jobs. SSH configs. World-writable directories. Failed logins. Kernel errors.

You can check all of these manually, but that gets tedious fast. In my experience, you also learn more by building something that ties them together than by running isolated commands one at a time.

So I decided to build a tool that:

  • Runs a series of security checks
  • Parses and scores the results
  • Generates a structured JSON report
  • Displays everything in a Rich-based terminal dashboard

I also wanted it to feel like a real project: modular, testable, and easy to extend.

Hybrid Architecture: Bash for Collection, Python for Intelligence

One of the early design decisions was choosing a hybrid model. Bash is perfect for running system commands and collecting raw data. Python is perfect for parsing, scoring, and presenting that data.

So the architecture ended up looking like this:

audit.sh -> runs Bash modules -> produces raw txt
Python modules -> parse txt -> produce JSON
Rich TUI -> reads JSON -> displays dashboard

That separation of concerns kept the project clean. Bash handles collection, Python handles parsing and scoring, and the TUI ties everything together.

Bash Modules: The Raw Data Layer

Each Bash module focuses on one category:

  • system.sh
  • users.sh
  • permissions.sh
  • services.sh
  • network.sh
  • logs.sh

They all follow the same pattern:

#!/usr/bin/env bash
mkdir -p tmp
COMMAND > tmp/module_raw.txt

Nothing fancy. Just Linux commands captured as raw text. This layer does not interpret anything; it only collects.

That simplicity makes the tool easy to extend. Want a new check? Add a new Bash module.

Python Modules: The Brain

The Python modules are where the real work happens.

Each one:

  • Reads the raw text
  • Parses it into structured data
  • Scores severity
  • Writes a JSON file

The scoring model is intentionally simple:

  • low - nothing concerning
  • medium - worth looking at
  • high - you should probably fix this

For example, the permissions module looks at:

  • SUID binaries
  • SGID binaries
  • World-writable directories

If there are a lot of them, severity goes up. If there are only a few, severity stays low. It is not meant to replace a full security scanner, just to provide a useful sanity check.

The Orchestrator: audit.sh

This script ties everything together:

  • Creates directories
  • Runs all Bash modules
  • Runs all Python modules
  • Combines all JSON into a single report

It is the "run everything" button.

I also ran into the classic WSL issue where pipefail breaks if the shebang is wrong. Lesson learned: always start your scripts with:

#!/usr/bin/env bash

WSL's /bin/sh is dash, and dash does not like pipefail.

The Rich TUI: A Dashboard for Your System

This was the most fun part of the project.

I wanted a dashboard that felt like a SOC console: clean, color-coded, and easy to read. Rich made that much easier. The TUI loads the latest audit report and displays:

  • A header
  • A summary panel
  • Detailed panels for each category
  • Severity colors (green, yellow, and red)
  • Tables and panels arranged in a Rich layout

Here is the result:

LinuSec Rich terminal dashboard summary view

When you select a category such as Networking, you get a more detailed screen showing what caused the warnings:

LinuSec networking detail screen in the terminal UI

Scrolling down further reveals remediation steps for each issue.

LinuSec remediation guidance view in the terminal UI

The Trend column updates each time you run make audit and launch the TUI, showing whether your risk score improved or got worse.

It is the kind of interface that makes you want to run the tool just to see the output, which is exactly my favorite kind of script.

Because everything is JSON, the TUI is completely decoupled from the audit logic. You could replace the audit engine entirely and the dashboard would still work.

Lessons Learned

  1. Hybrid architectures feel great. Bash and Python are a natural pairing for system-level tooling.
  2. WSL is a fantastic playground. You get a real Linux environment without spinning up a full VM.
  3. Rich makes terminal UI feel modern. It is hard to go back to plain print statements after using it.
  4. Modularity pays off. Adding new checks is trivial because each piece is isolated.
  5. JSON is the universal glue. It makes the TUI, testing, and future integrations much easier.

What Is Next

I already have a few ideas:

  • Threat intelligence enrichment
  • A live mode that refreshes every few seconds
  • A Textual-based interactive UI
  • Windows notifications when severity hits "high"
  • Integration with my SOC homelab

Even in its current form, the tool is useful. It gives me a quick snapshot of my system's security posture and taught me a lot about Linux internals and Bash scripting along the way.

The best part of building tools is when they teach you something new.

GitHub Repo Back to Blog