Add npm sudo config audit script

Checks npm prefix ownership, PATH wiring, cache ownership, shell history
for sudo npm usage, and n/nvm version manager config. Runs daily at 08:10
via cron and on initial setup.sh run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
pdm
2026-04-18 08:47:32 +00:00
parent dc299e4262
commit 080073a7d7
2 changed files with 209 additions and 2 deletions

197
check-npm-sudo-config.sh Executable file
View File

@@ -0,0 +1,197 @@
#!/usr/bin/env bash
# check-npm-sudo-config.sh
# Audits npm configuration on this VM for sudo-related issues and recommends fixes.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=config.sh
source "$SCRIPT_DIR/config.sh"
send_telegram() {
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="${TELEGRAM_CHAT_ID}" \
-d text="$1" \
-d parse_mode="HTML" > /dev/null || true
}
HOSTNAME=$(hostname)
DATE=$(date)
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BOLD='\033[1m'
RESET='\033[0m'
ISSUES=0
WARNINGS=0
log() { echo "$*"; }
ok() { printf "${GREEN}${RESET} %s\n" "$*"; }
warn() { printf "${YELLOW}${RESET} %s\n" "$*"; (( WARNINGS++ )) || true; }
fail() { printf "${RED}${RESET} %s\n" "$*"; (( ISSUES++ )) || true; }
rec() { printf " ${YELLOW}${RESET} %s\n" "$*"; }
header() { echo ""; echo "=========================================="; echo "$*"; echo "=========================================="; }
log "=========================================="
log " npm sudo config audit"
log "=========================================="
log "Hostname : $HOSTNAME"
log "Date : $DATE"
# ── 1. npm present? ───────────────────────────────────────────────────────────
header "1. npm availability"
if ! command -v npm &>/dev/null; then
warn "npm not found in PATH — skipping remaining checks"
echo ""
echo "RESULT: 0 issue(s), 1 warning(s)"
exit 0
fi
NPM_PATH=$(command -v npm)
ok "npm found: $NPM_PATH"
# ── 2. npm prefix ─────────────────────────────────────────────────────────────
header "2. npm prefix"
PREFIX=$(npm config get prefix 2>/dev/null || echo "unknown")
log "Current prefix: $PREFIX"
if [[ "$PREFIX" == "/usr" || "$PREFIX" == "/usr/local" ]]; then
fail "npm prefix is $PREFIX (system-wide) — global installs require sudo"
rec "npm config set prefix ~/.npm-global"
rec "Add to ~/.profile: export PATH=\"\$HOME/.npm-global/bin:\$PATH\""
elif [[ "$PREFIX" == "unknown" ]]; then
warn "Could not determine npm prefix"
else
PREFIX_OWNER=$(stat -c "%U" "$PREFIX" 2>/dev/null || echo "unknown")
if [[ "$PREFIX_OWNER" == "root" ]]; then
fail "npm prefix $PREFIX is owned by root — global installs require sudo"
rec "sudo chown -R \$(whoami) $PREFIX"
rec "Or set a user-owned prefix: npm config set prefix ~/.npm-global"
else
ok "npm prefix is $PREFIX (owned by $PREFIX_OWNER)"
fi
fi
# ── 3. .npmrc ────────────────────────────────────────────────────────────────
header "3. ~/.npmrc"
if [[ -f "$HOME/.npmrc" ]]; then
log "$(cat "$HOME/.npmrc")"
NPM_PREFIX_LINE=$(grep "^prefix=" "$HOME/.npmrc" 2>/dev/null || true)
if [[ -n "$NPM_PREFIX_LINE" ]]; then
ok ".npmrc explicitly sets: $NPM_PREFIX_LINE"
else
warn ".npmrc exists but does not pin the prefix"
rec "npm config set prefix ~/.npm-global"
fi
else
warn "No ~/.npmrc — prefix is not pinned to a user directory"
rec "npm config set prefix ~/.npm-global"
fi
# ── 4. prefix/bin in PATH ─────────────────────────────────────────────────────
header "4. npm prefix bin in PATH"
if [[ "$PREFIX" != "unknown" ]]; then
PREFIX_BIN="${PREFIX}/bin"
if echo "$PATH" | tr ':' '\n' | grep -qxF "$PREFIX_BIN"; then
ok "$PREFIX_BIN is in PATH"
else
warn "$PREFIX_BIN is NOT in PATH — globally installed binaries won't run"
PROFILE_FILE="$HOME/.profile"
[[ -f "$HOME/.zshrc" ]] && PROFILE_FILE="$HOME/.zshrc"
rec "Add to $PROFILE_FILE: export PATH=\"$PREFIX_BIN:\$PATH\""
rec "Then reload: source $PROFILE_FILE"
fi
fi
# ── 5. Root-owned files in npm prefix ────────────────────────────────────────
header "5. Root-owned files in npm prefix"
if [[ -d "$PREFIX" ]]; then
ROOT_FILES=$(find "$PREFIX" -maxdepth 3 -user root 2>/dev/null | head -5 || true)
if [[ -n "$ROOT_FILES" ]]; then
fail "Root-owned files found in npm prefix (past sudo npm usage):"
echo "$ROOT_FILES"
rec "sudo chown -R \$(whoami) $PREFIX"
else
ok "No root-owned files in $PREFIX"
fi
else
ok "npm prefix directory does not exist yet (no global installs made)"
fi
# ── 6. sudo npm in shell history ─────────────────────────────────────────────
header "6. Shell history — sudo npm usage"
SUDO_NPM_FOUND=false
for hfile in "$HOME/.bash_history" "$HOME/.zsh_history"; do
if [[ -f "$hfile" ]]; then
HITS=$(grep -c "sudo npm" "$hfile" 2>/dev/null || true)
if [[ "$HITS" -gt 0 ]]; then
warn "Found $HITS occurrence(s) of \"sudo npm\" in $hfile"
SUDO_NPM_FOUND=true
fi
fi
done
$SUDO_NPM_FOUND || ok "No \"sudo npm\" in shell history"
# ── 7. npm cache ownership ───────────────────────────────────────────────────
header "7. npm cache ownership"
CACHE_DIR=$(npm config get cache 2>/dev/null || echo "$HOME/.npm")
if [[ -d "$CACHE_DIR" ]]; then
ROOT_CACHE=$(find "$CACHE_DIR" -maxdepth 2 -user root 2>/dev/null | head -3 || true)
if [[ -n "$ROOT_CACHE" ]]; then
fail "Root-owned files in npm cache ($CACHE_DIR) — will cause EACCES errors"
rec "sudo chown -R \$(whoami) $CACHE_DIR"
else
ok "npm cache ($CACHE_DIR) is user-owned"
fi
else
ok "npm cache directory does not exist yet"
fi
# ── 8. Node version manager ───────────────────────────────────────────────────
header "8. Node version manager"
if command -v n &>/dev/null; then
N_PREFIX_VAL="${N_PREFIX:-}"
if [[ -z "$N_PREFIX_VAL" ]]; then
warn "n is installed but N_PREFIX is not set — n defaults to /usr/local (requires sudo)"
rec "Add to ~/.profile: export N_PREFIX=\$HOME/.n"
rec "Add to ~/.profile: export PATH=\$PATH:\$N_PREFIX/bin"
else
ok "n is installed, N_PREFIX=$N_PREFIX_VAL"
fi
elif [[ -s "$HOME/.nvm/nvm.sh" ]] || command -v nvm &>/dev/null 2>&1; then
ok "nvm is managing Node (sudo-free by design)"
elif command -v fnm &>/dev/null; then
ok "fnm is managing Node (sudo-free by design)"
else
ok "No Node version manager detected"
fi
# ── Summary ───────────────────────────────────────────────────────────────────
header "SUMMARY"
log "Scan completed at: $(date)"
log ""
if [[ $ISSUES -gt 0 ]]; then
printf "${RED}✗ %d issue(s) and %d warning(s) — see recommendations above${RESET}\n" "$ISSUES" "$WARNINGS"
send_telegram "⚠️ <b>npm sudo config issues</b>
Host: <code>${HOSTNAME}</code>
Issues: ${ISSUES} | Warnings: ${WARNINGS}
Run manually: bash check-npm-sudo-config.sh"
exit 1
elif [[ $WARNINGS -gt 0 ]]; then
printf "${YELLOW}⚠ Clean but %d warning(s) — see recommendations above${RESET}\n" "$WARNINGS"
exit 0
else
printf "${GREEN}✓ npm is correctly configured on %s${RESET}\n" "$HOSTNAME"
exit 0
fi

View File

@@ -31,6 +31,7 @@ fi
# ── Make scripts executable ────────────────────────────────────────────────────
chmod +x "$SCRIPT_DIR/npm-security-check.sh"
chmod +x "$SCRIPT_DIR/check-nextjs-rce.sh"
chmod +x "$SCRIPT_DIR/check-npm-sudo-config.sh"
# ── Create logs directory ──────────────────────────────────────────────────────
mkdir -p "$SCRIPT_DIR/logs"
@@ -38,7 +39,8 @@ mkdir -p "$SCRIPT_DIR/logs"
# ── Cron jobs ──────────────────────────────────────────────────────────────────
CRON_1="0 8 * * * $SCRIPT_DIR/npm-security-check.sh >> $SCRIPT_DIR/logs/npm-security-check-\$(date +\%Y\%m\%d).log 2>&1"
CRON_2="5 8 * * * $SCRIPT_DIR/check-nextjs-rce.sh >> $SCRIPT_DIR/logs/check-nextjs-rce-\$(date +\%Y\%m\%d).log 2>&1"
CRON_3="0 9 * * * find $SCRIPT_DIR/logs -name '*.log' -mtime +60 -delete"
CRON_3="10 8 * * * $SCRIPT_DIR/check-npm-sudo-config.sh >> $SCRIPT_DIR/logs/check-npm-sudo-config-\$(date +\%Y\%m\%d).log 2>&1"
CRON_4="0 9 * * * find $SCRIPT_DIR/logs -name '*.log' -mtime +60 -delete"
EXISTING=$(crontab -l 2>/dev/null || true)
@@ -56,10 +58,17 @@ else
echo "Cron job registered: check-nextjs-rce.sh daily at 08:05."
fi
if echo "$EXISTING" | grep -qF "check-npm-sudo-config.sh"; then
echo "Cron job for check-npm-sudo-config.sh already registered — skipping."
else
(crontab -l 2>/dev/null; echo "$CRON_3") | crontab -
echo "Cron job registered: check-npm-sudo-config.sh daily at 08:10."
fi
if echo "$EXISTING" | grep -qF "logs -name '*.log'"; then
echo "Log cleanup cron already registered — skipping."
else
(crontab -l 2>/dev/null; echo "$CRON_3") | crontab -
(crontab -l 2>/dev/null; echo "$CRON_4") | crontab -
echo "Cron job registered: log cleanup daily at 09:00 (60 day retention)."
fi
@@ -90,5 +99,6 @@ echo ""
echo "Running initial security scan..."
bash "$SCRIPT_DIR/npm-security-check.sh" >> "$SCRIPT_DIR/logs/npm-security-check-$(date +%Y%m%d).log" 2>&1 && echo "npm-security-check: done." || echo "npm-security-check: issues found — check Telegram."
bash "$SCRIPT_DIR/check-nextjs-rce.sh" >> "$SCRIPT_DIR/logs/check-nextjs-rce-$(date +%Y%m%d).log" 2>&1 && echo "check-nextjs-rce: done." || echo "check-nextjs-rce: issues found — check Telegram."
bash "$SCRIPT_DIR/check-npm-sudo-config.sh" >> "$SCRIPT_DIR/logs/check-npm-sudo-config-$(date +%Y%m%d).log" 2>&1 && echo "check-npm-sudo-config: done." || echo "check-npm-sudo-config: issues found — check Telegram."
echo ""
echo "Initial scan complete. Check Telegram for any alerts."