Files
dotfiles/local/bin/x-codeql
Ismo Vuorinen 6d72003446 fix(lint): fix all sonarcloud detected issues (#279)
* fix(ci): replace broad permissions with specific scopes in workflows

Replace read-all/write-all with minimum required permission scopes
across all GitHub Actions workflows to follow the principle of least
privilege (SonarCloud rule githubactions:S8234).

* fix(shell): use [[ instead of [ for conditional tests

Replace single brackets with double brackets in bash conditional
expressions across 14 files (28 changes). All scripts use bash
shebangs so [[ is safe everywhere (SonarCloud rule shelldre:S7688).

* fix(shell): add explicit return statements to functions

Add return 0 as the last statement in ~46 shell functions across
17 files that previously relied on implicit return codes
(SonarCloud rule shelldre:S7682).

* fix(shell): assign positional parameters to local variables

Replace direct $1/$2/$3 usage with named local variables in _log(),
msg(), msg_err(), msg_done(), msg_run(), msg_ok(), and array_diff()
(SonarCloud rule shelldre:S7679).

* fix(python): replace dict() constructor with literal

Use {} instead of dict() for empty dictionary initialization
(SonarCloud rule python:S7498).

* fix(shell): fix husky shebang and tolerate npm outdated exit code

* docs(shell): add function docstring comments

* fix(shell): fix heredoc indentation in x-sonarcloud

* feat(python): add ruff linter and formatter configuration

* fix(ci): align megalinter config with biome, ruff, and shfmt settings

* fix(ci): disable black and yaml-prettier in megalinter config

* chore(ci): update ruff-pre-commit to v0.15.0 and fix hook name

* fix(scripts): check for .git dir before skipping clone in install-fonts

* fix(shell): address code review issues in scripts and shared.sh

- Guard wezterm show-keys failure in create-wezterm-keymaps.sh
- Stop masking git failures with return 0 in install-cheat-purebashbible.sh
- Add missing shared.sh source in install-xcode-cli-tools.sh
- Replace exit 1 with return 1 in sourced shared.sh

* fix(scripts): address code review and security findings

- Guard wezterm show-keys failure in create-wezterm-keymaps.sh
- Stop masking git failures with return 0 in install-cheat-purebashbible.sh
- Add missing shared.sh source in install-xcode-cli-tools.sh
- Replace exit 1 with return 1 in sourced shared.sh
- Remove shell=True subprocess calls in x-git-largest-files.py

* style(shell): apply shfmt formatting and add args to pre-commit hook

* fix(python): suppress bandit false positives in x-git-largest-files

* fix(python): add nosemgrep suppression for check_output call

* feat(format): add prettier for YAML formatting

Install prettier, add .prettierrc.json config (200-char width, 2-space
indent, LF endings), .prettierignore, yarn scripts (lint:prettier,
fix:prettier, format:yaml), and pre-commit hook scoped to YAML files.

* style(yaml): apply prettier formatting

* fix(scripts): address remaining code review findings

- Python: use list comprehension to filter empty strings instead of
  slicing off the last element
- create-wezterm-keymaps: write to temp file and mv for atomic updates
- install-xcode-cli-tools: fix shellcheck source directive path

* fix(python): sort imports alphabetically in x-git-largest-files

* fix(lint): disable PYTHON_ISORT in MegaLinter, ruff handles it

* chore(git): add __pycache__ to gitignore

* fix(python): rename ambiguous variable l to line (E741)

* style: remove trailing whitespace and blank lines

* style(fzf): apply shfmt formatting

* style(shell): apply shfmt formatting

* docs(plans): add design documents

* style(docs): add language specifier to fenced code block

* feat(lint): add markdown-table-formatter to dev tooling

Add markdown-table-formatter as a dev dependency with yarn scripts
(lint:md-table, fix:md-table) and a local pre-commit hook to
automatically format markdown tables on commit.
2026-02-07 19:01:02 +02:00

241 lines
5.5 KiB
Bash
Executable File

#!/bin/sh
# x-codeql: CodeQL security scanning wrapper
# POSIX-compatible shell script
set -e
VERSION="1.0.0"
# Language mappings: extension -> codeql language
LANG_MAP="c:.c,.h|cpp:.cpp,.cc,.cxx,.hpp,.hxx|csharp:.cs|go:.go|java:.java|
javascript:.js,.jsx,.mjs,.ts,.tsx|python:.py|ruby:.rb|swift:.swift"
# Display usage information and options
usage()
{
cat << EOF
Usage: $0 [OPTIONS]
Options:
--path PATH Source path to analyze (default: current directory)
--parallel Run language analyses in parallel
-h, --help Show this help message
-v, --version Show version
EOF
exit "${1:-0}"
}
# Log a timestamped message to stderr
log()
{
printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$*" >&2
}
# Log an error message and exit
err()
{
log "ERROR: $*"
exit 1
}
# Verify codeql binary is available in PATH
check_codeql()
{
command -v codeql > /dev/null 2>&1 || err "codeql binary not found in PATH"
log "Found codeql: $(codeql version --format=terse)"
}
# Get or create the CodeQL cache directory
get_cache_dir()
{
cache="${XDG_CACHE_HOME:-$HOME/.cache}/codeql"
mkdir -p "$cache" || err "Cannot create cache directory: $cache"
printf '%s' "$cache"
}
# Detect supported programming languages in source path
detect_languages()
{
src_path="$1"
detected=""
# Check for GitHub Actions workflows
if [ -d "$src_path/.github/workflows" ] \
&& find "$src_path/.github/workflows" -name '*.yml' -o -name '*.yaml' \
2> /dev/null | grep -q .; then
detected="actions"
fi
# Scan for language files
IFS='|'
for mapping in $LANG_MAP; do
lang="${mapping%%:*}"
exts="${mapping#*:}"
found=0
IFS=','
for ext in $exts; do
if find "$src_path" -type f -name "*$ext" -print -quit 2> /dev/null \
| grep -q .; then
found=1
break
fi
done
[ "$found" -eq 1 ] && detected="$detected $lang"
IFS='|'
done
[ -z "$detected" ] && err "No supported languages detected in $src_path"
# Remove duplicates and trim whitespace
printf '%s' "$detected" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/ $//'
}
# Create a CodeQL database for a language
create_database()
{
lang="$1"
src_path="$2"
db_path="$3"
log "Creating $lang database..."
codeql database create "$db_path" \
--language="$lang" \
--source-root="$src_path" \
--overwrite
}
# Display analysis result statistics from SARIF file
show_results_stats()
{
sarif_file="$1"
lang="$2"
if ! command -v jq > /dev/null 2>&1; then
# No jq, basic check only
if [ ! -s "$sarif_file" ] || ! grep -q '"results"' "$sarif_file"; then
return 1
fi
return 0
fi
result_count=$(jq -r '.runs[0].results | length' "$sarif_file" 2> /dev/null || echo "0")
if [ "$result_count" -eq 0 ]; then
return 1
fi
log "Found $result_count result(s) for $lang"
jq -r '.runs[0].results[] | "\(.ruleId): \(.message.text)"' "$sarif_file" \
2> /dev/null | head -5 | while read -r line; do
log " - $line"
done
[ "$result_count" -gt 5 ] && log " ... and $((result_count - 5)) more"
return 0
}
# Run CodeQL analysis for a single language
analyze_language()
{
lang="$1"
src_path="$2"
cache_dir="$3"
output_dir="$4"
# Extract last 2 path components for unique DB name
path_suffix="$(printf '%s' "$src_path" | awk -F/ '{print $(NF-1)"-"$NF}')"
db_path="$cache_dir/db-$path_suffix-$lang"
sarif_file="$output_dir/codeql-$lang.sarif"
create_database "$lang" "$src_path" "$db_path"
log "Analyzing $lang with security-and-quality suite..."
# Try security-and-quality suite, fall back to default if not found
if ! codeql database analyze "$db_path" \
--format=sarif-latest \
--output="$sarif_file" \
--sarif-category="$lang" \
--download \
"codeql/$lang-queries:codeql-suites/$lang-security-and-quality.qls" \
2> /dev/null; then
log "Suite not found, trying default pack for $lang..."
codeql database analyze "$db_path" \
--format=sarif-latest \
--output="$sarif_file" \
--sarif-category="$lang" \
--download \
"codeql/$lang-queries"
fi
# Check results and clean up if empty
if [ -f "$sarif_file" ]; then
if ! show_results_stats "$sarif_file" "$lang"; then
log "No results found for $lang, removing empty SARIF file"
rm -f "$sarif_file"
fi
fi
# Cleanup database
rm -rf "$db_path"
}
# Parse arguments and run CodeQL analysis pipeline
main()
{
src_path="."
parallel=0
while [ $# -gt 0 ]; do
case "$1" in
--path)
src_path="${2:?--path requires an argument}"
shift 2
;;
--parallel)
parallel=1
shift
;;
-v | --version)
printf 'x-codeql %s\n' "$VERSION"
exit 0
;;
-h | --help)
usage 0
;;
*)
err "Unknown option: $1"
;;
esac
done
[ -d "$src_path" ] || err "Path does not exist: $src_path"
src_path="$(cd "$src_path" && pwd)"
check_codeql
cache="$(get_cache_dir)"
log "Detecting languages in $src_path..."
languages="$(detect_languages "$src_path")"
log "Found languages: $languages"
output_dir="$(pwd)"
if [ "$parallel" -eq 1 ]; then
log "Running analyses in parallel..."
for lang in $languages; do
analyze_language "$lang" "$src_path" "$cache" "$output_dir" &
done
wait
else
for lang in $languages; do
analyze_language "$lang" "$src_path" "$cache" "$output_dir"
done
fi
log "Analysis complete. SARIF files in $output_dir"
}
main "$@"