Advertisement

← claude-skills

deslop/scripts/scan_patterns.sh

8.5 KB Download file
#!/bin/bash
# scan_patterns.sh — Detect common antipatterns across a codebase
# Usage: bash scan_patterns.sh [directory]
#
# Scans for security issues, error handling gaps, config problems,
# and AI-specific code smells. Output is grouped by category.

DIR="${1:-.}"
echo "=== Pattern Scan: $DIR ==="
echo "Date: $(date -Iseconds)"
echo ""

# Exclude dirs
EXCL="--exclude-dir=node_modules --exclude-dir=.git --exclude-dir=__pycache__ --exclude-dir=venv --exclude-dir=.venv --exclude-dir=dist --exclude-dir=build --exclude-dir=vendor"

# ─── SECURITY ──────────────────────────────────────────────

echo "## SECURITY PATTERNS"
echo ""

echo "### Potential SQL injection (string concatenation in queries)"
grep -rn $EXCL \
    -e "SELECT.*+.*req\." \
    -e "INSERT.*+.*req\." \
    -e "UPDATE.*+.*req\." \
    -e "DELETE.*+.*req\." \
    -e 'f".*SELECT.*{' \
    -e 'f".*INSERT.*{' \
    -e 'f".*UPDATE.*{' \
    --include="*.js" --include="*.ts" --include="*.py" --include="*.rb" --include="*.java" --include="*.go" --include="*.php" \
    "$DIR" 2>/dev/null
echo ""

echo "### Potential command injection"
grep -rn $EXCL \
    -e "exec(" \
    -e "eval(" \
    -e "Function(" \
    -e "os\.system(" \
    -e "subprocess.*shell.*=.*True" \
    -e "child_process\.exec(" \
    --include="*.js" --include="*.ts" --include="*.py" --include="*.rb" \
    "$DIR" 2>/dev/null | grep -v "test\|spec\|mock"
echo ""

echo "### Hardcoded secrets (high-confidence patterns)"
grep -rn $EXCL \
    -e "sk-[a-zA-Z0-9]\{20,\}" \
    -e "pk_live_" \
    -e "ghp_[a-zA-Z0-9]\{36\}" \
    -e "AKIA[A-Z0-9]\{16\}" \
    -e "xox[bpas]-" \
    -e "ya29\." \
    --include="*.js" --include="*.ts" --include="*.py" --include="*.rb" --include="*.java" --include="*.go" --include="*.env" --include="*.yml" --include="*.yaml" --include="*.json" \
    "$DIR" 2>/dev/null | grep -v "node_modules\|example\|placeholder\|test"
echo ""

echo "### Hardcoded passwords/keys (medium-confidence — verify manually)"
grep -rn $EXCL \
    -e 'password\s*=\s*["\x27][^"\x27]\{3,\}' \
    -e 'api_key\s*=\s*["\x27][^"\x27]\{3,\}' \
    -e 'apiKey\s*=\s*["\x27][^"\x27]\{3,\}' \
    -e 'secret\s*=\s*["\x27][^"\x27]\{3,\}' \
    -e 'token\s*=\s*["\x27][^"\x27]\{3,\}' \
    --include="*.js" --include="*.ts" --include="*.py" --include="*.rb" --include="*.java" --include="*.go" \
    "$DIR" 2>/dev/null | grep -v "test\|mock\|example\|placeholder\|TODO\|process\.env\|os\.environ\|config\.\|getenv"
echo ""

# ─── ERROR HANDLING ────────────────────────────────────────

echo "## ERROR HANDLING PATTERNS"
echo ""

echo "### Empty catch blocks (JS/TS)"
grep -rn $EXCL "catch\s*([^)]*)\s*{" --include="*.js" --include="*.ts" --include="*.tsx" "$DIR" 2>/dev/null -A1 | grep -B1 "^\s*}" | grep "catch"
echo ""

echo "### Bare except (Python)"
grep -rn $EXCL "except:\s*$\|except\s*Exception\s*:" --include="*.py" "$DIR" 2>/dev/null
echo ""

echo "### Swallowed errors (catch with only console.log)"
grep -rn $EXCL "catch" --include="*.js" --include="*.ts" --include="*.tsx" "$DIR" 2>/dev/null -A3 | grep -B2 "console\.log\|console\.error" | grep -v "throw\|reject\|return.*error\|return.*Error"
echo ""

echo "### Unhandled promise chains (missing .catch)"
grep -rn $EXCL "\.then(" --include="*.js" --include="*.ts" "$DIR" 2>/dev/null | grep -v "\.catch\|await\|test\|spec"
echo ""

# ─── CONFIGURATION ─────────────────────────────────────────

echo "## CONFIGURATION PATTERNS"
echo ""

echo "### Hardcoded URLs in source"
grep -rn $EXCL "https\?://[a-zA-Z0-9]" \
    --include="*.js" --include="*.ts" --include="*.py" --include="*.rb" --include="*.java" --include="*.go" \
    "$DIR" 2>/dev/null | grep -v "test\|spec\|mock\|\.md\|comment\|localhost\|127\.0\.0\.1\|example\.com\|schema\.org\|w3\.org\|jsdelivr\|cdnjs\|unpkg\|googleapis"
echo ""

echo "### process.env / os.environ without fallback"
grep -rn $EXCL "process\.env\.\w\+" --include="*.js" --include="*.ts" "$DIR" 2>/dev/null | grep -v "||.*\|??\|process\.env\.NODE_ENV"
grep -rn $EXCL "os\.environ\[" --include="*.py" "$DIR" 2>/dev/null | grep -v "\.get(\|os\.getenv"
echo ""

echo "### Missing lock files"
for lockfile in "package-lock.json" "yarn.lock" "pnpm-lock.yaml" "Pipfile.lock" "poetry.lock" "Cargo.lock" "go.sum" "Gemfile.lock"; do
    if [ -f "$DIR/package.json" ] || [ -f "$DIR/requirements.txt" ] || [ -f "$DIR/Cargo.toml" ] || [ -f "$DIR/go.mod" ] || [ -f "$DIR/Gemfile" ]; then
        if [ ! -f "$DIR/$lockfile" ] && [ "$lockfile" != "package-lock.json" ] 2>/dev/null; then
            continue
        fi
        if [ -f "$DIR/package.json" ] && [ ! -f "$DIR/package-lock.json" ] && [ ! -f "$DIR/yarn.lock" ] && [ ! -f "$DIR/pnpm-lock.yaml" ]; then
            echo "  MISSING_LOCK: No JS lock file found (package-lock.json, yarn.lock, or pnpm-lock.yaml)"
            break
        fi
    fi
done
echo ""

# ─── AI-SPECIFIC SMELLS ───────────────────────────────────

echo "## AI-SPECIFIC CODE SMELLS"
echo ""

echo "### TODO/FIXME/HACK comments (may indicate incomplete AI-generated code)"
grep -rn $EXCL "TODO\|FIXME\|HACK\|XXX\|TEMP\|WORKAROUND\|PLACEHOLDER" \
    --include="*.js" --include="*.ts" --include="*.py" --include="*.rb" --include="*.java" --include="*.go" \
    "$DIR" 2>/dev/null
echo ""

echo "### Placeholder/dummy data in non-test files"
grep -rn $EXCL "lorem\|placeholder\|dummy\|fake_\|mock_\|sample_data\|test_data" \
    --include="*.js" --include="*.ts" --include="*.py" --include="*.rb" --include="*.java" --include="*.go" \
    "$DIR" 2>/dev/null | grep -v "test\|spec\|__test__\|__mock__\|fixture"
echo ""

echo "### TypeScript escape hatches"
grep -rn $EXCL "@ts-ignore\|@ts-nocheck\|@ts-expect-error\|as any\|: any" \
    --include="*.ts" --include="*.tsx" \
    "$DIR" 2>/dev/null
echo ""

echo "### Python type suppression"
grep -rn $EXCL "# type: ignore\|# noqa\|# pylint: disable" \
    --include="*.py" \
    "$DIR" 2>/dev/null
echo ""

# ─── PERFORMANCE ───────────────────────────────────────────

echo "## PERFORMANCE PATTERNS"
echo ""

echo "### Synchronous I/O in async codebases (JS/TS)"
grep -rn $EXCL "readFileSync\|writeFileSync\|execSync\|accessSync\|existsSync\|mkdirSync" \
    --include="*.js" --include="*.ts" \
    "$DIR" 2>/dev/null | grep -v "test\|spec\|config\|script\|build\|webpack\|vite\|rollup"
echo ""

echo "### Full lodash import (should use specific imports)"
grep -rn $EXCL "from ['\"]lodash['\"]$\|require(['\"]lodash['\"])" \
    --include="*.js" --include="*.ts" \
    "$DIR" 2>/dev/null
echo ""

# ─── SUMMARY ───────────────────────────────────────────────

echo ""
echo "## FILE STATISTICS"
echo ""

total_files=$(find "$DIR" -type f \( -name "*.js" -o -name "*.ts" -o -name "*.tsx" -o -name "*.jsx" -o -name "*.py" -o -name "*.rb" -o -name "*.java" -o -name "*.go" -o -name "*.php" \) 2>/dev/null | grep -v node_modules | grep -v __pycache__ | grep -v venv | grep -v dist | grep -v build | wc -l)
total_lines=$(find "$DIR" -type f \( -name "*.js" -o -name "*.ts" -o -name "*.tsx" -o -name "*.jsx" -o -name "*.py" -o -name "*.rb" -o -name "*.java" -o -name "*.go" -o -name "*.php" \) 2>/dev/null | grep -v node_modules | grep -v __pycache__ | grep -v venv | grep -v dist | grep -v build | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}')
test_files=$(find "$DIR" -type f \( -name "*.test.*" -o -name "*.spec.*" -o -name "test_*" -o -name "*_test.*" \) 2>/dev/null | grep -v node_modules | wc -l)

echo "  Source files: $total_files"
echo "  Total lines: $total_lines"
echo "  Test files: $test_files"

if [ -f "$DIR/package.json" ]; then
    dep_count=$(node -e "const p=require('./$DIR/package.json'); console.log(Object.keys(p.dependencies||{}).length)" 2>/dev/null || echo "?")
    devdep_count=$(node -e "const p=require('./$DIR/package.json'); console.log(Object.keys(p.devDependencies||{}).length)" 2>/dev/null || echo "?")
    echo "  Dependencies: $dep_count (+ $devdep_count dev)"
fi

echo ""
echo "=== Pattern scan complete ==="