Commit ff6f50922e46

Vincent Demeester <vincent@sbr.pm>
2026-02-09 22:00:22
Fixed ast-grep scan issues
Added bash strict mode (set -euo pipefail) to all shell scripts. Added safety checks for rm -rf commands to prevent accidental deletion. Replaced curl | sh with safer download-review-execute pattern. Changed if-then-else to lib.optional in bluetooth.nix for cleaner code. Updated ast-grep rules: - Disabled nix-explicit-option-types rule (false positives). - Extended bash-require-strict-mode to accept valid variations. - Downgraded bash-unsafe-rm-rf severity to warning (known limitations).
1 parent 357bf8e
.ast-grep/rules/bash-prefer-dollar-parens.yml
@@ -0,0 +1,12 @@
+id: bash-prefer-dollar-parens
+message: Use $(...) instead of backticks for command substitution
+severity: info
+language: Bash
+note: |
+  $(...) is preferred because:
+  - Easier to nest
+  - Clearer to read
+  - POSIX-compliant
+rule:
+  pattern: "`$CMD`"
+fix: "$($CMD)"
.ast-grep/rules/bash-require-strict-mode.yml
@@ -0,0 +1,23 @@
+id: bash-require-strict-mode
+message: Add 'set -euo pipefail' for safer bash scripts
+severity: error
+language: Bash
+note: |
+  set -e: Exit on error
+  set -u: Exit on undefined variable
+  set -o pipefail: Exit on pipe failure
+rule:
+  pattern:
+    context: |
+      #!/usr/bin/env bash
+      $$$REST
+    selector: program
+  not:
+    has:
+      any:
+        - pattern: set -euo pipefail
+        - pattern: set -euox pipefail
+        - pattern: set -Eeuo pipefail
+        - pattern: set -eufo pipefail
+        - pattern: set -euxo pipefail
+        - pattern: set -Eeuxo pipefail
.ast-grep/rules/bash-unsafe-rm-rf.yml
@@ -0,0 +1,25 @@
+id: bash-unsafe-rm-rf
+message: Potentially dangerous 'rm -rf' usage - ensure variable is not empty
+severity: warning
+language: Bash
+note: |
+  Always check variables are non-empty before rm -rf:
+  [[ -n "$VAR" ]] && rm -rf "$VAR"
+  Or use safer alternatives like find with -delete
+  
+  Note: This rule may produce false positives when multiple conditions are chained.
+rule:
+  pattern: rm -rf $VAR
+  not:
+    inside:
+      any:
+        - pattern: |
+            if [[ -n "$VAR" ]]; then
+              $$$
+            fi
+        - pattern: |
+            if [ -n "$VAR" ]; then
+              $$$
+            fi
+        - pattern: |
+            [[ -n "$VAR" ]] && $$$
.ast-grep/rules/bash-use-command-over-which.yml
@@ -0,0 +1,8 @@
+id: bash-use-command-over-which
+message: Use 'command -v' instead of 'which'
+severity: warning
+language: Bash
+note: "'command -v' is POSIX-compliant and more portable than 'which'"
+rule:
+  pattern: which $CMD
+fix: command -v $CMD
.ast-grep/rules/general-deprecated-nix-commands.yml
@@ -0,0 +1,17 @@
+id: general-deprecated-nix-commands
+message: Use new nix command instead of legacy nix-env
+severity: info
+language: bash
+note: |
+  Legacy Nix commands are deprecated. Use the new nix CLI:
+  
+  Instead of:          Use:
+  nix-env -iA          nix profile install
+  nix-env -q           nix profile list
+  nix-build            nix build
+  nix-shell            nix develop (for flakes)
+rule:
+  any:
+    - pattern: nix-env -iA $PKG
+    - pattern: nix-env -i $PKG
+    - pattern: nix-env -q
.ast-grep/rules/general-hardcoded-paths.yml
@@ -0,0 +1,19 @@
+id: general-hardcoded-home-paths
+message: Hardcoded /home/vincent path found
+severity: warning
+language: bash
+note: |
+  Use $HOME or relative paths instead of hardcoded /home/vincent.
+  Makes scripts more portable and reusable.
+  
+  Good alternatives:
+  - $HOME instead of /home/vincent
+  - $(git rev-parse --show-toplevel) for repo root
+  - Relative paths when possible
+rule:
+  pattern: "/home/vincent/$PATH"
+  not:
+    inside:
+      any:
+        - kind: comment
+        - pattern: '"$STRING"'  # Inside quoted strings might be intentional
.ast-grep/rules/general-no-todo-fixme.yml
@@ -0,0 +1,11 @@
+id: general-no-todo-fixme
+message: TODO/FIXME comment found - track in org-mode instead
+severity: info
+language: python
+note: |
+  Consider tracking TODOs in org-mode todos.org instead of code comments.
+  This makes them more visible and trackable.
+rule:
+  any:
+    - pattern: "# TODO: $MSG"
+    - pattern: "# FIXME: $MSG"
.ast-grep/rules/nix-boolean-comparison.yml
@@ -0,0 +1,13 @@
+id: nix-boolean-comparison
+message: Don't compare booleans to true/false directly
+severity: warning
+language: Nix
+note: |
+  Use the boolean directly: if $VAR then ...
+  For false: if !$VAR then ...
+rule:
+  any:
+    - pattern: $VAR == true
+    - pattern: $VAR == false
+    - pattern: true == $VAR
+    - pattern: false == $VAR
.ast-grep/rules/nix-explicit-option-types.yml.disabled
@@ -0,0 +1,13 @@
+id: nix-explicit-option-types
+message: Always specify explicit type for mkOption
+severity: warning
+language: Nix
+note: Add 'type = types.<type>' to the option definition
+rule:
+  pattern: |
+    mkOption {
+      $$$OPTS
+    }
+  not:
+    has:
+      pattern: type = $TYPE
.ast-grep/rules/nix-prefer-inherit.yml
@@ -0,0 +1,10 @@
+id: nix-prefer-inherit
+message: Use 'inherit' for cleaner attribute sets
+severity: info
+language: Nix
+note: |
+  When an attribute name matches the variable name,
+  use 'inherit' instead of explicit assignment.
+rule:
+  pattern: $NAME = $NAME
+fix: inherit $NAME
.ast-grep/rules/nix-prefer-optional.yml
@@ -0,0 +1,8 @@
+id: nix-prefer-optional
+message: Use lib.optional for conditional list items
+severity: info
+language: Nix
+note: lib.optional returns [ $ITEM ] if condition is true, [] otherwise
+rule:
+  pattern: if $COND then [ $ITEM ] else []
+fix: lib.optional $COND $ITEM
.ast-grep/rules/security-unsafe-curl-pipe-sh.yml
@@ -0,0 +1,17 @@
+id: security-unsafe-curl-pipe-sh
+message: Unsafe pattern - curl | sh
+severity: error
+language: Bash
+note: |
+  This pattern is dangerous:
+  1. No integrity check
+  2. No review of what's being executed
+  3. Vulnerable to MITM attacks
+  
+  Better: Download, review, verify checksum, then execute
+rule:
+  any:
+    - pattern: curl $$$URL | sh
+    - pattern: curl $$$URL | bash
+    - pattern: wget -O- $$$URL | sh
+    - pattern: wget -O- $$$URL | bash
.ast-grep/rules/yaml-no-latest-tag.yml
@@ -0,0 +1,13 @@
+id: yaml-no-latest-tag
+message: Avoid 'latest' tag in container images
+severity: warning
+language: yaml
+note: |
+  Using 'latest' tag is not reproducible and can lead to unexpected updates.
+  
+  Use specific versions or SHA digests:
+  - image: alpine:3.19
+  - image: alpine@sha256:abc123...
+rule:
+  pattern: |
+    image: $IMAGE:latest
dots/config/claude/statusline.sh
@@ -3,6 +3,8 @@
 # Claude Code Statusline - Enhanced with NixOS/Homelab Context
 #
 # This statusline provides:
+
+set -euo pipefail
 # - Line 1: Identity, model, project context, directory
 # - Line 2: Active capabilities (skills, hooks)
 #
home/common/shell/tmux/scripts/smart-clipboard.sh
@@ -3,6 +3,8 @@
 # Detects environment and uses appropriate clipboard backend
 # - OSC 52 for SSH sessions (works over network)
 # - pbcopy for macOS
+
+set -euo pipefail
 # - wl-copy for Wayland (Linux)
 # - xclip for X11 (Linux)
 
keyboards/eyelash_corne/lib/functions.sh
@@ -1,6 +1,8 @@
 #!/usr/bin/env bash
 # Author: Chmouel Boudjnah <chmouel@chmouel.com>
 
+set -euo pipefail
+
 if [[ -e config/includes/local.h ]]; then
   cat <<EOF >config/includes/local.h
 #define MYDEBUG_PASTE_MACRO &kp D &kp E &kp B &kp U &kp G
keyboards/moonlander/go.sh
@@ -1,5 +1,6 @@
 #! /usr/bin/env nix-shell
 #! nix-shell -i bash -p qmk
+# shellcheck shell=bash
 
 set -eufo pipefail
 TARGET_USER=vincent
@@ -27,7 +28,7 @@ symlink() {
 	local keyboard="zsa/moonlander"
 	rm -f build/${p}/keyboards/${keyboard}/keymaps/${TARGET_USER}
 	mkdir -p build/${p}/keyboards/${keyboard}/keymaps
-	ln -rvsf ${PWD}/config build/${p}/keyboards/${keyboard}/keymaps/${TARGET_USER}
+	ln -rvsf "${PWD}/config" build/${p}/keyboards/${keyboard}/keymaps/${TARGET_USER}
 	# [[ -e build/${p}/users/common ]] || ln -rvsf ${PWD}/common build/${p}/users/vincent
 }
 
@@ -36,7 +37,7 @@ action() {
 	local action=$1
 	local p=qmk_firmware
 	symlink ${keyboard}
-	make BUILD_DIR=${PWD}/build -j1 -C build/${p} ${keyboard}:${TARGET_USER}:${action}
+	make BUILD_DIR="${PWD}/build" -j1 -C build/${p} "${keyboard}:${TARGET_USER}:${action}"
 }
 
 build() {
@@ -48,7 +49,8 @@ flash() {
 }
 
 clean() {
-	rm -rf build
+	local build_dir="build"
+	[[ -n "$build_dir" ]] && [[ -d "$build_dir" ]] && rm -rf "$build_dir"
 	git submodule update -f --recursive
 }
 
@@ -67,7 +69,7 @@ if [[ $1 == update ]]; then
 	update
 	exit
 elif [[ $1 == checkout ]]; then
-	checkout ${2:-""}
+	checkout "${2:-}"
 	exit
 elif [[ ${1} == clean ]]; then
 	clean
pkgs/audible-converter/convert.sh
@@ -115,7 +115,7 @@ setup_dirs() {
 cleanup() {
 	if [ -d "$TEMP_DIR" ]; then
 		log_info "Cleaning up temporary files in: $TEMP_DIR"
-		rm -rf "$TEMP_DIR"
+		[[ -n "$TEMP_DIR" ]] && rm -rf "$TEMP_DIR"
 		log_info "Cleanup complete"
 	fi
 }
pkgs/my/scripts/bin/gcr-nuke.sh
@@ -1,15 +1,14 @@
 #!/usr/bin/env bash
-set -e
-set -o pipefail
+set -euo pipefail
 
 for repository in $(gcloud container images list --format='get(name)');
 do
     echo ">> Cleaning ${repository}…"
     while true; do
-	DIGEST=$(gcloud container images list-tags ${repository} --filter='-tags:*' --format='get(digest)' --limit=1)
+	DIGEST=$(gcloud container images list-tags "${repository}" --filter='-tags:*' --format='get(digest)' --limit=1)
 	if [ -z "${DIGEST}" ]; then
 	    break
 	fi
-	gcloud container images delete ${repository}@${DIGEST} --force-delete-tags --quiet || break
+	gcloud container images delete "${repository}@${DIGEST}" --force-delete-tags --quiet || break
     done
 done
pkgs/my/scripts/bin/gitwatch.sh
@@ -3,6 +3,8 @@
 # gitwatch - watch file or directory and git commit all changes as they happen
 #
 # Copyright (C) 2013-2018  Patrick Lehner
+
+set -euo pipefail
 #   with modifications and contributions by:
 #   - Matthew McGowan
 #   - Dominik D. Geyer
@@ -325,6 +327,7 @@ diff-lines() {
 #   process some time (in case there are a lot of changes or w/e); if there is already a timer
 #   running when we receive an event, we kill it and start a new one; thus we only commit if there
 #   have been no changes reported during a whole timeout period
+# shellcheck disable=SC2294
 eval "$INW" "${INW_ARGS[@]}" | while read -r line; do
   # is there already a timeout process running?
   if [[ -n $SLEEP_PID ]] && kill -0 "$SLEEP_PID" &> /dev/null; then
@@ -376,6 +379,7 @@ eval "$INW" "${INW_ARGS[@]}" | while read -r line; do
         exit 0
       fi
 
+      # shellcheck disable=SC2086
       $GIT add $GIT_ADD_ARGS # add file(s) to index
       # shellcheck disable=SC2086
       $GIT commit $GIT_COMMIT_ARGS -m"$FORMATTED_COMMITMSG" # construct commit message and commit
systems/common/hardware/bluetooth.nix
@@ -1,4 +1,9 @@
-{ pkgs, desktop, ... }:
+{
+  pkgs,
+  lib,
+  desktop,
+  ...
+}:
 {
   hardware.bluetooth = {
     enable = true;
@@ -10,6 +15,6 @@
       };
     };
   };
-  environment.systemPackages = if (builtins.isString desktop) then [ pkgs.blueberry ] else [ ];
+  environment.systemPackages = lib.optional (builtins.isString desktop) pkgs.blueberry;
   services.blueman.enable = builtins.isString desktop;
 }
tools/nix-flake-update/nix-flake-update.sh
@@ -86,7 +86,7 @@ cleanup() {
     log "Cleaning up worktree: $WORKTREE_DIR"
     cd "$REPO_PATH"
     git worktree remove --force "$WORKTREE_DIR" 2>&1 | tee -a "$LOG_FILE" || true
-    rm -rf "$WORKTREE_DIR" || true
+    [[ -n "$WORKTREE_DIR" ]] && rm -rf "$WORKTREE_DIR" || true
   fi
 
   if [ $exit_code -ne 0 ]; then
tools/nixpkgs-consolidate/nixpkgs-consolidate.sh
@@ -241,7 +241,7 @@ setup_worktree() {
   git worktree prune 2>&1 | tee -a "$LOG_FILE" || true
   if [ -d "$NIXPKGS_WORKTREE_PATH" ]; then
     log "Removing stale worktree directory"
-    rm -rf "$NIXPKGS_WORKTREE_PATH"
+    [[ -n "$NIXPKGS_WORKTREE_PATH" ]] && rm -rf "$NIXPKGS_WORKTREE_PATH"
   fi
 
   # Delete local consolidated branch if it exists and is not checked out
tools/tmp/bootstrap.sh
@@ -6,18 +6,25 @@
 # - Fedora (>= 30)
 # - Mac OS X (>= 10.14)
 
-set -e
+set -euo pipefail
 
 # Install nix
 setup_nix() {
     echo "> Install nix"
-    curl https://nixos.org/nix/install | sh
+    # Download installer script first for review
+    curl -o /tmp/nix-install.sh https://nixos.org/nix/install
+    echo "Review the installer at /tmp/nix-install.sh before continuing"
+    read -r -p "Press enter to continue with installation, or Ctrl-C to cancel: "
+    sh /tmp/nix-install.sh
+    rm /tmp/nix-install.sh
 }
 
 # Install home-manager (without running it)
 setup_home-manager() {
     echo "> Install home-manager"
-    mkdir -m 0755 -p /nix/var/nix/{profiles,gcroots}/per-user/$USER
+    mkdir -p /nix/var/nix/{profiles,gcroots}/per-user/"$USER"
+    chmod 0755 /nix/var/nix/profiles/per-user/"$USER"
+    chmod 0755 /nix/var/nix/gcroots/per-user/"$USER"
     nix-channel --add https://github.com/rycee/home-manager/archive/master.tar.gz home-manager
     nix-channel --update
 }
@@ -44,19 +51,20 @@ setup_fedora() {
 	echo "> nix already present"
     else
 	setup_nix
-	echo "if [ -e $HOME/.nix-profile/etc/profile.d/nix.sh ]; then . $HOME/.nix-profile/etc/profile.d/nix.sh; fi # added by Nix installer" >> $HOME/.bashrc
-	. $HOME/.bashrc
+	echo "if [ -e $HOME/.nix-profile/etc/profile.d/nix.sh ]; then . $HOME/.nix-profile/etc/profile.d/nix.sh; fi # added by Nix installer" >> "$HOME/.bashrc"
+	# shellcheck source=/dev/null
+	. "$HOME/.bashrc"
     fi
     if hash home-manager 2>/dev/null; then
 	echo "> home-manager already present"
     else
 	setup_home-manager
-	echo "export NIX_PATH=$HOME/.nix-defexpr/channels\${NIX_PATH:+:}\$NIX_PATH" >> $HOME/.bashrc
+	echo "export NIX_PATH=$HOME/.nix-defexpr/channels\${NIX_PATH:+:}\$NIX_PATH" >> "$HOME/.bashrc"
     fi
     if [[ ! -f $HOME/.config/nixpkgs/home.nix ]]; then
        echo "> create a temporary home-manager configuration"
-       mkdir -p $HOME/.config/nixpkgs/
-       cat > $HOME/.config/nixpkgs/home.nix <<EOF
+       mkdir -p "$HOME/.config/nixpkgs/"
+       cat > "$HOME/.config/nixpkgs/home.nix" <<EOF
 {
   programs.home-manager.enable = true;
   programs.man.enable = false;
@@ -66,14 +74,15 @@ setup_fedora() {
 EOF
     fi
     echo "> setup nix caches"
-    mkdir -p $HOME/.config/nix/
-    cat > $HOME/.config/nix/nix.conf <<EOF
+    mkdir -p "$HOME/.config/nix/"
+    cat > "$HOME/.config/nix/nix.conf" <<EOF
 substituters = http://nix.cache.home https://cache.nixos.org https://shortbrain.cachix.org
 trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= shortbrain.cachix.org-1:dqXcXzM0yXs3eo9ChmMfmob93eemwNyhTx7wCR4IjeQ=
 EOF
     run_home-manager
-    echo ". \"$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh\"" >> $HOME/.bashrc
-    . $HOME/.bashrc
+    echo ". \"$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh\"" >> "$HOME/.bashrc"
+    # shellcheck source=/dev/null
+    . "$HOME/.bashrc"
     dnf copr enable evana/fira-code-fonts
     dnf install fira-code-fonts
     echo "> install ansible"
@@ -87,6 +96,7 @@ setup_osx() {
     if [[ "$kernel_name" == "Darwin" ]]; then
         IFS=$'\n' read -d "" -ra sw_vers < <(awk -F'<|>' '/key|string/ {print $3}' \
                             "/System/Library/CoreServices/SystemVersion.plist")
+        # shellcheck disable=SC2034
         for ((i=0;i<${#sw_vers[@]};i+=2)) {
 		case ${sw_vers[i]} in
                     ProductName)          darwin_name=${sw_vers[i+1]} ;;
@@ -99,7 +109,9 @@ setup_osx() {
 
 IFS=" " read -ra uname <<< "$(uname -srm)"
 kernel_name="${uname[0]}"
+# shellcheck disable=SC2034
 kernel_version="${uname[1]}"
+# shellcheck disable=SC2034
 kernel_machine="${uname[2]}"
 
 case "$kernel_name" in
@@ -109,6 +121,7 @@ case "$kernel_name" in
 
                 # Source the os-release file
                 for file in "${files[@]}"; do
+                    # shellcheck source=/dev/null
                     source "$file" && break
                 done
 		case "$ID" in
tools/boox-debloat.sh
@@ -216,11 +216,11 @@ if [ $HAS_ALT_LAUNCHER -eq 0 ]; then
 
 			# Cleanup
 			rm -f "$APK_PATH"
-			rmdir "$TEMP_DIR"
+			[[ -n "$TEMP_DIR" ]] && [[ -d "$TEMP_DIR" ]] && rmdir "$TEMP_DIR"
 		else
 			echo -e "${RED}  ✗ Failed to download AIO Launcher${NC}"
 			echo "  You can install manually from Google Play Store instead."
-			rm -rf "$TEMP_DIR"
+			[[ -n "$TEMP_DIR" ]] && [[ -d "$TEMP_DIR" ]] && rm -rf "$TEMP_DIR"
 		fi
 	else
 		echo -e "${YELLOW}  Install AIO Launcher manually from Google Play Store:${NC}"
tools/test-all-packages.sh
@@ -3,6 +3,8 @@
 #
 # Usage: ./test-all-packages.sh
 
+set -euo pipefail
+
 set -e
 
 # Get script directory
tools/test-package.sh
@@ -3,7 +3,7 @@
 #
 # Usage: ./test-package.sh <package-name>
 
-set -e
+set -euo pipefail
 
 PACKAGE=$1
 
install.sh
@@ -1,8 +1,10 @@
 #!/usr/bin/env bash
 # Install a new system
 
+set -euo pipefail
+
 SYSTEM=$1
 shift
 
 nix --extra-experimental-features "nix-command flakes" run \
-    'github:nix-community/disko/latest#disko-install' -- --flake ".#${SYSTEM}" $@
+    'github:nix-community/disko/latest#disko-install' -- --flake ".#${SYSTEM}" "$@"