2026 Bash Linux Sécurité Discord GitHub Actions Open Source

bq-watchdog

An open-source Linux security audit script born from the XMRig infection — IoC detection, Discord alerts, one-command install.

bq-watchdog

Genesis

After finding and cleaning an XMRig miner on my VPS (see the dedicated article), a question naturally arose: how do you make sure this doesn't silently happen again for 5 months?

I could have set up generic monitoring — Wazuh, Falco, AIDE. But these tools are heavy, complex to configure, and don't know the specific IoCs of the Diicot/color1337 campaign I had traced.

I preferred to write a targeted, lightweight, modular, open-source script.

What it does

bq-watchdog is a Linux security monitor written in pure bash. It runs via cron every 30 minutes, performs a series of checks, and only sends a Discord alert if something suspicious is detected. Silent when everything is fine.

Installation

root@serveur:~
×
curl -fsSL https://github.com/BugQuest/bq-watchdog/releases/latest/download/install.sh | sudo bash

Modular architecture

Each check is an independent bash file in `checks/`. The main script loads them all in numerical order and automatically calls the corresponding function. Adding a check = creating a file.

checks/08-mon-check.sh
×
check_mon_check() {
    # finding <warning|critical> "titre" "détail"
    if [[ -f /fichier/suspect ]]; then
        finding critical "Fichier suspect" "Chemin: /fichier/suspect"
    fi
}

Included checks

**01 — IoC color1337/Diicot**: `ElPatrono1337` SSH backdoor key, `.ladyg0g0` `.pr1nc35` `.b4nd1d0` files, connections to known C2s (195.24.237.240, digital.digitaldatainsights.org), obfuscated hex binaries, `myservices.service`, `node` account.

**02 — SSH config**: `PasswordAuthentication yes` in the effective config (including cloud-init overrides), `PermitRootLogin yes`, `PermitEmptyPasswords yes`, the classic trap `/etc/ssh/sshd_config.d/50-cloud-init.conf`.

**03 — Crontabs**: `curl | bash` patterns, execution from `/tmp`, base64 encoding, hardcoded C2 IPs, perl/python one-liners.

**04 — Temp files**: ELF binaries in `/tmp`, `/var/tmp`, `/dev/shm`, hidden directories in these locations.

**05 — Users/SSH keys**: system accounts with interactive shell, SSH keys with suspicious names (`1337`, `h4x`, `backdoor`...), recently created sudo accounts.

**06 — Network**: connections to known malicious IPs/ranges, mining stratum ports (3333, 4444, 5555...), processes from `/tmp` with active connections.

**07 — Processes**: 8-character hex names (Diicot pattern), execution from temporary directories, deleted binaries still in memory, known miners (`xmrig`, `kswapd0`...).

Discord alerts

Each finding produces a colored Discord embed — red for critical, yellow for warning. The summary embed gives the hostname, UTC timestamp, and total number of findings. Details follow with full context.

lib/notify.sh — embed Discord
×
# Extrait : construction du payload Discord
discord_send() {
    local embeds="$1"

    # Couleur selon la sévérité (rouge critique / jaune warning)
    if [[ $SEVERITY -ge 2 ]]; then
        status_color=15158332; status_emoji="🚨"
    else
        status_color=16776960; status_emoji="⚠️"
    fi

    # Embed de résumé + liste des findings
    curl -fsSL -X POST "$DISCORD_WEBHOOK" \
        -H "Content-Type: application/json" \
        -d "$(jq -n --argjson e "$all_embeds" \
            '{username:"bq-watchdog",embeds:$e}')"
}

GitHub Actions releases

A GitHub Actions workflow automatically generates a release on each git tag. The release contains the compressed tarball and the install script. The install URL stays stable via `/releases/latest/`.

.github/workflows/release.yml
×
# Déclenché par: git tag v1.1.0 && git push --tags
on:
  push:
    tags: ["v*"]

steps:
  - name: Create tarball
    run: |
      tar -czf dist/bq-watchdog-${{ steps.version.outputs.VERSION }}.tar.gz \
        --exclude='.git' --exclude='dist' --exclude='.github' .

  - name: Create GitHub Release
    uses: softprops/action-gh-release@v2
    with:
      files: |
        dist/bq-watchdog-*.tar.gz
        install.sh

Updates

The script manages its own updates. An automatic check runs every Monday at 3am via cron — if a new release is available on GitHub, it's downloaded, installed, and the `config` file is preserved. A Discord notification confirms the update.

root@serveur:~
×
# Mise à jour manuelle
sudo /opt/bq-watchdog/watchdog.sh --update

# Version installée
sudo /opt/bq-watchdog/watchdog.sh --version

False positives encountered

Deploying on a real server is the best test. Three false positives surfaced on the first audit and led to three targeted fixes.

**certbot + perl** — The Let's Encrypt renewal cron contains `perl -e 'sleep int(rand(43200))'` to randomize the renewal time. The `perl.*-e` pattern was too broad. Fixed to only alert if the perl one-liner calls truly dangerous functions: `socket`, `exec`, `system`, `eval`, `base64`, `chr()`.

**vmail** — The virtual mail account (Dovecot) runs with `/bin/sh`, which is expected on a mail server. Fixed with a whitelist of known service accounts: `vmail`, `dovecot`, `postfix`, `www-data`, `git`, `postgres`...

**kswapd0** — The kernel thread `[kswapd0]` (swap management) has the same name as a known XMRig miner. The distinction is simple: a kernel thread has no `/proc/<pid>/exe` symlink, unlike a userspace process. The bug came from `readlink -f` returning the literal path when the target doesn't exist — replaced by `readlink` without `-f`, which returns empty in that case.

Roadmap

v1 covers the known IoCs from the Diicot campaign and generic attack vectors. Planned next:

- Support for other documented Linux malware families
- `--report` option for machine-readable JSON output
- Clean uninstaller
- Support for Slack and ntfy webhooks in addition to Discord

realitynauts@bugquest:~
×
Realitynauts
RN

$ cat auteur.txt

Realitynauts — Dev PHP / Python / Hardware
Autistic. Intensely focused. Always in prod.

$ cat opportunites.txt

Does this project resonate? Got a similar
or complementary project? Let's talk.

$ ./contact.sh