Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ An [Omarchy](https://omarchy.com) bar-widget plugin that shows a live security b

| Scanner | What it checks | How to install |
|---|---|---|
| **AUR-Malware** | Atomic Arch IOC scan — pacman/AUR packages, npm/bun caches, eBPF rootkit artifacts, hidden processes | Clone [AUR-Malware](https://github.com/Atomic-Arch/AUR-Malware) to `/local/applications/AUR-Malware/` |
| **AUR-Malware** | Atomic Arch IOC scan — pacman/AUR packages, npm/bun caches, eBPF rootkit artifacts, hidden processes | Clone [AUR-Malware](https://github.com/nightdevil00/AUR-Malware) to `~/.local/share/AUR-Malware/` (the original `Atomic-Arch/AUR-Malware` this pointed at is gone; this fork ships the same `check-atomic-arch_new.sh`) |
| **[bumblebee](https://github.com/perplexityai/bumblebee)** | Endpoint package inventory across npm, pypi, go, rubygems, homebrew, etc. | `GOBIN=$HOME/.local/bin go install github.com/perplexityai/bumblebee@latest` |
| **bun-check** | Per-project dev-env one-shot scan (opens a terminal picker) | Bundled — run `install.sh` after adding the plugin |

Expand Down
10 changes: 5 additions & 5 deletions SecurityWidget.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import qs.Ui
// — preferences persisted to ~/.config/qs-security/settings.json
//
// Scanners:
// AUR-Malware git clone to /local/applications/AUR-Malware (or QS_SEC_AUR_MALWARE)
// AUR-Malware git clone to ~/.local/share/AUR-Malware (or QS_SEC_AUR_MALWARE)
// bumblebee go install github.com/anchore/bumblebee@latest
// bun-check bundled script → ~/.local/bin/qs-bun-check-oneshot.sh
BarWidget {
Expand Down Expand Up @@ -67,12 +67,12 @@ BarWidget {

readonly property string aurEffectivePath: {
var ov = Quickshell.env("QS_SEC_AUR_MALWARE")
return ov ? ov : "/local/applications/AUR-Malware/check-atomic-arch_new.sh"
return ov ? ov : root.home + "/.local/share/AUR-Malware/check-atomic-arch_new.sh"
}
readonly property string aurMalwareDir: {
var p = root.aurEffectivePath
var i = p.lastIndexOf("/")
return i > 0 ? p.substring(0, i) : "/local/applications/AUR-Malware"
return i > 0 ? p.substring(0, i) : root.home + "/.local/share/AUR-Malware"
}

readonly property string bunDst: home + "/.local/bin/qs-bun-check-oneshot.sh"
Expand Down Expand Up @@ -249,7 +249,7 @@ BarWidget {
root.aurOpBusy = true; root.aurOpMsg = ""; root.aurOpError = false
aurInstallProc.command = [
"bash", "-c",
"mkdir -p \"$(dirname \"$0\")\" && git clone https://github.com/Atomic-Arch/AUR-Malware.git \"$0\"",
"mkdir -p \"$(dirname \"$0\")\" && git clone https://github.com/nightdevil00/AUR-Malware.git \"$0\"",
root.aurMalwareDir
]
aurInstallProc.running = false; aurInstallProc.running = true
Expand All @@ -264,7 +264,7 @@ BarWidget {
root.bbOpBusy = true; root.bbOpMsg = "Installing via go…"; root.bbOpError = false
bbInstallProc.command = [
"/usr/bin/mise", "exec", "--", "sh", "-c",
"GOBIN=$HOME/.local/bin go install github.com/perplexityai/bumblebee@latest"
"GOBIN=$HOME/.local/bin go install github.com/perplexityai/bumblebee/cmd/bumblebee@latest"
]
bbInstallProc.running = false; bbInstallProc.running = true
}
Expand Down
44 changes: 39 additions & 5 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
#!/usr/bin/env bash
# Optional install step for the Security Scan plugin.
# Copies qs-bun-check-oneshot.sh to ~/.local/bin/ so the widget can
# show the per-project bun-check scan button.
# - Copies qs-bun-check-oneshot.sh to ~/.local/bin/ so the widget can
# show the per-project bun-check scan button.
# - Copies qs-security-scan.sh to ~/.local/bin/ and enables the systemd
# user timer that runs it every 6h -- without this, the widget has
# nothing writing ~/.cache/qs-security-status.json and never shows a
# result even once a scanner is installed.
#
# Usage:
# bash install.sh # asks whether to install bun-check
# bash install.sh --bun-check # install bun-check without prompting
# bash install.sh --no-bun-check # skip bun-check
# bash install.sh # asks about both steps
# bash install.sh --bun-check # install bun-check without prompting
# bash install.sh --no-bun-check # skip bun-check
# bash install.sh --scan-timer # install+enable the scan timer without prompting
# bash install.sh --no-scan-timer # skip the scan timer
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BIN_DIR="${HOME}/.local/bin"
SYSTEMD_DIR="${HOME}/.config/systemd/user"

BUN_CHECK_SRC="$SCRIPT_DIR/qs-bun-check-oneshot.sh"
BUN_CHECK_DST="$BIN_DIR/qs-bun-check-oneshot.sh"

SCAN_SCRIPT_SRC="$SCRIPT_DIR/qs-security-scan.sh"
SCAN_SCRIPT_DST="$BIN_DIR/qs-security-scan.sh"
SCAN_SERVICE_SRC="$SCRIPT_DIR/systemd/qs-security-scan.service"
SCAN_TIMER_SRC="$SCRIPT_DIR/systemd/qs-security-scan.timer"

install_bun_check=
install_scan_timer=

for arg in "$@"; do
case "$arg" in
--bun-check) install_bun_check=true ;;
--no-bun-check) install_bun_check=false ;;
--scan-timer) install_scan_timer=true ;;
--no-scan-timer) install_scan_timer=false ;;
esac
done

Expand All @@ -36,3 +52,21 @@ if [[ "$install_bun_check" == true ]]; then
else
echo "Skipped bun-check install. Run with --bun-check later to add it."
fi

if [[ -z "$install_scan_timer" ]]; then
read -rp "Install and enable the periodic security-scan timer (every 6h)? [y/N] " reply
[[ "$reply" =~ ^[Yy]$ ]] && install_scan_timer=true || install_scan_timer=false
fi

if [[ "$install_scan_timer" == true ]]; then
mkdir -p "$BIN_DIR" "$SYSTEMD_DIR"
cp "$SCAN_SCRIPT_SRC" "$SCAN_SCRIPT_DST"
chmod +x "$SCAN_SCRIPT_DST"
cp "$SCAN_SERVICE_SRC" "$SYSTEMD_DIR/qs-security-scan.service"
cp "$SCAN_TIMER_SRC" "$SYSTEMD_DIR/qs-security-scan.timer"
systemctl --user daemon-reload
systemctl --user enable --now qs-security-scan.timer
echo "Installed $SCAN_SCRIPT_DST and enabled qs-security-scan.timer"
else
echo "Skipped scan timer install. Run with --scan-timer later to add it."
fi
67 changes: 67 additions & 0 deletions qs-security-scan.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Writes ~/.cache/qs-security-status.json, read by SecurityWidget.qml's
# statusFile FileView. Referenced by the README's systemd timer but not
# previously shipped in the repo.
#
# AUR-Malware is skipped entirely unless something executable already sits
# at QS_SEC_AUR_MALWARE -- omitting the key is safe, the widget only shows
# a section when its own presence probe finds the tool installed. Default
# path matches SecurityWidget.qml's own install button, which clones
# nightdevil00/AUR-Malware -- the upstream Atomic-Arch/AUR-Malware this repo
# originally pointed at is gone (404); nightdevil00's fork ships the same
# check-atomic-arch_new.sh entry point.
set -uo pipefail

STATUS_FILE="${QS_SEC_STATUS_FILE:-$HOME/.cache/qs-security-status.json}"
AUR_MALWARE_PATH="${QS_SEC_AUR_MALWARE:-$HOME/.local/share/AUR-Malware/check-atomic-arch_new.sh}"
BUMBLEBEE_BIN="${QS_SEC_BUMBLEBEE:-$HOME/.local/bin/bumblebee}"
CATALOG="${QS_SEC_BUMBLEBEE_CATALOG:-$HOME/.local/share/qs-security/threat-intel}"

aur_json="null"
if [[ -x $AUR_MALWARE_PATH ]]; then
# --json still prints its live colored progress to stdout before the final
# JSON blob, and the script's exit code is always 0 regardless of findings
# -- a plain "last line" / exit-code check always reports "clean" with a
# disclaimer fragment as the summary, silently hiding real findings. The
# JSON itself is the last '{'-only line to EOF.
aur_out=$("$AUR_MALWARE_PATH" --json 2>/dev/null)
aur_json=$(awk '/^\{$/{f=1} f' <<<"$aur_out" | python3 -c '
import json, sys
try:
d = json.load(sys.stdin)
except Exception:
print("null"); sys.exit()
verdict = d.get("verdict", "")
status = {"CLEAN": "clean", "WARNINGS": "warn", "COMPROMISED": "fail"}.get(verdict, "error")
s = d.get("summary", {})
fail, warn, total = s.get("fail", 0), s.get("warn", 0), s.get("total", 0)
summary = str(fail) + " failures, " + str(warn) + " warnings out of " + str(total) + " checks"
print(json.dumps({"status": status, "summary": summary}))
')
fi

bb_json="null"
if [[ -x $BUMBLEBEE_BIN ]]; then
catalog_args=()
[[ -d $CATALOG ]] && catalog_args=(--exposure-catalog "$CATALOG")
scan_out=$("$BUMBLEBEE_BIN" scan --profile baseline "${catalog_args[@]}" 2>/dev/null)
packages=$(grep -c '"record_type":"package"' <<<"$scan_out")
findings=$(grep -c '"record_type":"finding"' <<<"$scan_out")
bb_status=$([[ $findings -gt 0 ]] && echo findings || echo clean)
bb_json=$(python3 -c '
import json, sys
status, packages, findings = sys.argv[1], sys.argv[2], sys.argv[3]
summary = f"{packages} packages inventoried, {findings} findings against threat-intel catalog"
print(json.dumps({"status": status, "summary": summary}))
' "$bb_status" "$packages" "$findings")
fi

mkdir -p "$(dirname "$STATUS_FILE")"
python3 -c '
import json, sys, datetime
aur, bb = json.loads(sys.argv[1]), json.loads(sys.argv[2])
out = {"checked": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")}
if aur is not None: out["aur_malware"] = aur
if bb is not None: out["bumblebee"] = bb
print(json.dumps(out))
' "$aur_json" "$bb_json" >"$STATUS_FILE"
6 changes: 6 additions & 0 deletions systemd/qs-security-scan.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[Unit]
Description=Security scan for omarchy bar

[Service]
Type=oneshot
ExecStart=%h/.local/bin/qs-security-scan.sh
9 changes: 9 additions & 0 deletions systemd/qs-security-scan.timer
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Unit]
Description=Periodic security scan for omarchy bar

[Timer]
OnBootSec=2min
OnUnitActiveSec=6h

[Install]
WantedBy=timers.target