diff --git a/.github/workflows/code-quality-check.yml b/.github/workflows/code-quality-check.yml new file mode 100644 index 000000000..fa9bd1dc7 --- /dev/null +++ b/.github/workflows/code-quality-check.yml @@ -0,0 +1,910 @@ +name: Code Quality & Standards Check + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: [main, develop] + +permissions: + contents: read + pull-requests: write + issues: write + security-events: write + +jobs: + code-quality-analysis: + runs-on: ubuntu-latest + name: Code Quality Analysis + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history for better duplicate detection + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + # ============================================================ + # 0. GET CHANGED FILES (PR only) + # ============================================================ + - name: Get changed files + id: changed_files + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + // Paginate through all changed files (not just first 100) + const allFiles = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + per_page: 100 + }); + + const jsFiles = allFiles + .filter(f => f.status !== 'removed') + .filter(f => /\.(js|jsx)$/.test(f.filename)) + .map(f => f.filename) + .join('\n'); + + console.log(`Found ${jsFiles.split('\n').filter(Boolean).length} JS/JSX files changed in PR`); + console.log(`Files:\n${jsFiles}`); + + // Save to file for use in bash steps + require('fs').writeFileSync('/tmp/changed_files.txt', jsFiles); + + return jsFiles; + result-encoding: string + + # ============================================================ + # 1. DEPRECATED REACT FUNCTIONS CHECK + # ============================================================ + - name: Check for deprecated React functions + id: react_deprecated + continue-on-error: true + run: | + echo "🔍 Scanning for deprecated React lifecycle methods and patterns..." + + # Determine which files to scan + if [ "${{ github.event_name }}" = "pull_request" ] && [ -f /tmp/changed_files.txt ]; then + FILES=$(cat /tmp/changed_files.txt | tr '\n' ' ') + if [ -z "$FILES" ]; then + echo "No JS/JSX files changed in this PR" + echo "deprecated_found=false" >> $GITHUB_OUTPUT + exit 0 + fi + echo "Scanning only changed files in PR: $FILES" + SCAN_TARGET="$FILES" + else + echo "Scanning entire src/ directory (push to main/develop)" + SCAN_TARGET="src/" + fi + + # Scan for deprecated patterns in JS/JSX files + DEPRECATED_FOUND=false + DEPRECATED_DETAILS="" + + # Check for deprecated lifecycle methods + if echo "$SCAN_TARGET" | xargs grep -l "componentWillMount\|componentWillReceiveProps\|componentWillUpdate" 2>/dev/null; then + DEPRECATED_FOUND=true + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\n### Deprecated Lifecycle Methods Found:\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\`\`\`\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}$(echo "$SCAN_TARGET" | xargs grep -n "componentWillMount\|componentWillReceiveProps\|componentWillUpdate" 2>/dev/null | head -20)\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\`\`\`\n" + fi + + # Check for UNSAFE_ prefixed methods (still deprecated but explicit) + if echo "$SCAN_TARGET" | xargs grep -l "UNSAFE_componentWill" 2>/dev/null; then + DEPRECATED_FOUND=true + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\n### UNSAFE_ Methods Found:\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\`\`\`\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}$(echo "$SCAN_TARGET" | xargs grep -n "UNSAFE_componentWill" 2>/dev/null | head -20)\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\`\`\`\n" + fi + + # Check for React.createClass (very old) + if echo "$SCAN_TARGET" | xargs grep -l "React\.createClass\|createReactClass" 2>/dev/null; then + DEPRECATED_FOUND=true + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\n### React.createClass Usage Found:\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\`\`\`\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}$(echo "$SCAN_TARGET" | xargs grep -n "React\.createClass\|createReactClass" 2>/dev/null | head -10)\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\`\`\`\n" + fi + + # Check for string refs (deprecated) - Fixed regex pattern + if echo "$SCAN_TARGET" | xargs grep -E 'ref="[^"]+"' 2>/dev/null; then + DEPRECATED_FOUND=true + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\n### String Refs Found (use callback refs or createRef):\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\`\`\`\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}$(echo "$SCAN_TARGET" | xargs grep -nE 'ref="[^"]+"' 2>/dev/null | head -10)\n" + DEPRECATED_DETAILS="${DEPRECATED_DETAILS}\`\`\`\n" + fi + + if [ "$DEPRECATED_FOUND" = true ]; then + echo "deprecated_found=true" >> $GITHUB_OUTPUT + echo "deprecated_details<> $GITHUB_OUTPUT + echo -e "$DEPRECATED_DETAILS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + exit 1 + else + echo "✅ No deprecated React patterns found!" + echo "deprecated_found=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # ============================================================ + # 2. AI-GENERATED CODE DETECTION (VIBE CODE) + # ============================================================ + - name: Detect AI-generated code patterns + id: ai_detection + continue-on-error: true + run: | + echo "🤖 Scanning for AI-generated code markers..." + + # Determine which files to scan + if [ "${{ github.event_name }}" = "pull_request" ] && [ -f /tmp/changed_files.txt ]; then + FILES=$(cat /tmp/changed_files.txt | tr '\n' ' ') + if [ -z "$FILES" ]; then + echo "No JS/JSX files changed in this PR" + echo "ai_detected=false" >> $GITHUB_OUTPUT + exit 0 + fi + echo "Scanning only changed files in PR" + SCAN_TARGET="$FILES" + else + echo "Scanning entire src/ directory (push to main/develop)" + SCAN_TARGET="src/" + fi + + AI_MARKERS_FOUND=false + AI_DETAILS="" + + # Common AI-generated code patterns + # 1. Overly verbose comments typical of AI + if [ "${{ github.event_name }}" = "pull_request" ]; then + AI_VERBOSE_COMMENTS=$(echo "$SCAN_TARGET" | xargs grep -h "// This function\|// This method\|// Helper function to\|// Utility function\|// This will\|// This is used to" 2>/dev/null | wc -l) + else + AI_VERBOSE_COMMENTS=$(grep -r "// This function\|// This method\|// Helper function to\|// Utility function\|// This will\|// This is used to" \ + --include="*.js" --include="*.jsx" src/ 2>/dev/null | wc -l) + fi + + if [ "$AI_VERBOSE_COMMENTS" -gt 10 ]; then + AI_MARKERS_FOUND=true + AI_DETAILS="${AI_DETAILS}\n### âš ī¸ Excessive Verbose Comments ($AI_VERBOSE_COMMENTS found):\n" + AI_DETAILS="${AI_DETAILS}High number of AI-style verbose comments detected.\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + if [ "${{ github.event_name }}" = "pull_request" ]; then + AI_DETAILS="${AI_DETAILS}$(echo "$SCAN_TARGET" | xargs grep -n "// This function\|// This method\|// Helper function to" 2>/dev/null | head -10)\n" + else + AI_DETAILS="${AI_DETAILS}$(grep -rn "// This function\|// This method\|// Helper function to" \ + --include="*.js" --include="*.jsx" src/ 2>/dev/null | head -10)\n" + fi + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + fi + + # 2. Check for common AI placeholder text + if [ "${{ github.event_name }}" = "pull_request" ]; then + if echo "$SCAN_TARGET" | xargs grep -i "TODO: Implement\|FIXME: This is a placeholder\|// Add your code here\|// Your code here" 2>/dev/null; then + AI_MARKERS_FOUND=true + AI_DETAILS="${AI_DETAILS}\n### AI Placeholder Comments:\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + AI_DETAILS="${AI_DETAILS}$(echo "$SCAN_TARGET" | xargs grep -ni "TODO: Implement\|FIXME: This is a placeholder" 2>/dev/null | head -10)\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + fi + else + if grep -ri "TODO: Implement\|FIXME: This is a placeholder\|// Add your code here\|// Your code here" \ + --include="*.js" --include="*.jsx" src/ 2>/dev/null; then + AI_MARKERS_FOUND=true + AI_DETAILS="${AI_DETAILS}\n### AI Placeholder Comments:\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + AI_DETAILS="${AI_DETAILS}$(grep -rni "TODO: Implement\|FIXME: This is a placeholder" \ + --include="*.js" --include="*.jsx" src/ 2>/dev/null | head -10)\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + fi + fi + + # 3. Check for suspiciously perfect JSDoc patterns (scoped to PR files) + if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "$FILES" ]; then + JSDOC_COUNT=$(echo "$FILES" | xargs grep -h "/\*\*" 2>/dev/null | wc -l) + JSDOC_PARAM_COUNT=$(echo "$FILES" | xargs grep -h "@param\|@returns\|@throws" 2>/dev/null | wc -l) + else + JSDOC_COUNT=$(grep -r "/\*\*" --include="*.js" --include="*.jsx" src/ 2>/dev/null | wc -l) + JSDOC_PARAM_COUNT=$(grep -r "@param\|@returns\|@throws" --include="*.js" --include="*.jsx" src/ 2>/dev/null | wc -l) + fi + + if [ "$JSDOC_COUNT" -gt 100 ] && [ "$JSDOC_PARAM_COUNT" -gt 200 ]; then + AI_DETAILS="${AI_DETAILS}\n### â„šī¸ High JSDoc Usage:\n" + AI_DETAILS="${AI_DETAILS}Detected $JSDOC_COUNT JSDoc blocks with $JSDOC_PARAM_COUNT parameter docs.\n" + AI_DETAILS="${AI_DETAILS}This might indicate AI-generated documentation. Please verify quality.\n" + fi + + # 4. Check for overly descriptive variable names (AI tendency) - scoped to PR files + if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "$FILES" ]; then + if echo "$FILES" | xargs grep -E "const \w{30,}|let \w{30,}|function \w{30,}" 2>/dev/null; then + AI_MARKERS_FOUND=true + AI_DETAILS="${AI_DETAILS}\n### Overly Long Identifiers:\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + AI_DETAILS="${AI_DETAILS}$(echo "$FILES" | xargs grep -En "const \w{30,}|let \w{30,}|function \w{30,}" 2>/dev/null | head -5)\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + fi + else + if grep -rE "const \w{30,}|let \w{30,}|function \w{30,}" --include="*.js" --include="*.jsx" src/ 2>/dev/null; then + AI_MARKERS_FOUND=true + AI_DETAILS="${AI_DETAILS}\n### Overly Long Identifiers:\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + AI_DETAILS="${AI_DETAILS}$(grep -rEn "const \w{30,}|let \w{30,}|function \w{30,}" \ + --include="*.js" --include="*.jsx" src/ 2>/dev/null | head -5)\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + fi + fi + + # 5. Check for AI service markers (accidental inclusions) - scoped to PR files + if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "$FILES" ]; then + if echo "$FILES" | xargs grep -iE "ChatGPT|Claude|GPT-4|Copilot generated|AI generated" 2>/dev/null; then + AI_MARKERS_FOUND=true + AI_DETAILS="${AI_DETAILS}\n### 🚨 AI Service Mentions Found:\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + AI_DETAILS="${AI_DETAILS}$(echo "$FILES" | xargs grep -inE "ChatGPT|Claude|GPT-4|Copilot generated|AI generated" 2>/dev/null)\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + fi + else + if grep -riE "ChatGPT|Claude|GPT-4|Copilot generated|AI generated" --include="*.js" --include="*.jsx" src/ 2>/dev/null; then + AI_MARKERS_FOUND=true + AI_DETAILS="${AI_DETAILS}\n### 🚨 AI Service Mentions Found:\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + AI_DETAILS="${AI_DETAILS}$(grep -rniE "ChatGPT|Claude|GPT-4|Copilot generated|AI generated" \ + --include="*.js" --include="*.jsx" src/ 2>/dev/null)\n" + AI_DETAILS="${AI_DETAILS}\`\`\`\n" + fi + fi + + if [ "$AI_MARKERS_FOUND" = true ]; then + echo "ai_detected=true" >> $GITHUB_OUTPUT + echo "ai_details<> $GITHUB_OUTPUT + echo -e "$AI_DETAILS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + else + echo "✅ No obvious AI-generated code markers found!" + echo "ai_detected=false" >> $GITHUB_OUTPUT + fi + + # ============================================================ + # 3. DUPLICATE CODE DETECTION + # ============================================================ + - name: Install duplicate detection tool + run: npm install -g jscpd + + - name: Detect duplicate/copy-pasted code + id: duplicate_detection + continue-on-error: true + run: | + echo "🔎 Scanning for duplicate code blocks..." + + mkdir -p jscpd-report + + # Determine what to scan + if [ "${{ github.event_name }}" = "pull_request" ] && [ -f /tmp/changed_files.txt ]; then + FILES=$(cat /tmp/changed_files.txt | tr '\n' ' ') + if [ -z "$FILES" ]; then + echo "No JS/JSX files changed in this PR" + echo "duplicates_found=false" >> $GITHUB_OUTPUT + exit 0 + fi + echo "Scanning only changed files in PR for duplicates" + # Run duplicate detection on changed files only + jscpd $FILES --config .jscpd.json > jscpd-output.txt 2>&1 || true + else + echo "Scanning entire src/ directory for duplicates" + # Run duplicate detection on entire src/ + jscpd src/ --config .jscpd.json > jscpd-output.txt 2>&1 || true + fi + + # Check if duplicates were found + if [ -f "jscpd-report/jscpd-report.json" ]; then + DUPLICATE_COUNT=$(jq '.statistics.duplicates' jscpd-report/jscpd-report.json 2>/dev/null || echo "0") + DUPLICATE_PERCENTAGE=$(jq '.statistics.percentage' jscpd-report/jscpd-report.json 2>/dev/null || echo "0") + + if [ "$DUPLICATE_COUNT" -gt 0 ]; then + echo "duplicates_found=true" >> $GITHUB_OUTPUT + echo "duplicate_count=$DUPLICATE_COUNT" >> $GITHUB_OUTPUT + echo "duplicate_percentage=$DUPLICATE_PERCENTAGE" >> $GITHUB_OUTPUT + + # Extract top duplicates + DUPLICATE_SUMMARY=$(jq -r '.duplicates[:5] | .[] | "File: \(.firstFile.name):\(.firstFile.start)-\(.firstFile.end) duplicated in \(.secondFile.name):\(.secondFile.start)-\(.secondFile.end) (\(.lines) lines)"' \ + jscpd-report/jscpd-report.json 2>/dev/null || echo "Unable to parse duplicates") + + echo "duplicate_summary<> $GITHUB_OUTPUT + echo "$DUPLICATE_SUMMARY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "âš ī¸ Found $DUPLICATE_COUNT duplicate code blocks ($DUPLICATE_PERCENTAGE% duplication)" + else + echo "duplicates_found=false" >> $GITHUB_OUTPUT + echo "✅ No significant code duplication found!" + fi + else + echo "duplicates_found=false" >> $GITHUB_OUTPUT + echo "✅ No duplicate code detected!" + fi + + # ============================================================ + # 4. SECURITY & ETHICS CHECK + # ============================================================ + - name: Check for unethical code patterns + id: ethics_check + continue-on-error: true + run: | + echo "🔒 Scanning for security vulnerabilities and unethical patterns..." + + ETHICS_VIOLATIONS=false + ETHICS_DETAILS="" + + # 1. Check for hardcoded credentials/secrets (CRITICAL SECURITY CHECK) + echo "Checking for hardcoded secrets..." + HARDCODED_SECRETS_FOUND=false + HARDCODED_SECRETS_DETAILS="" + REDACTED_FINDINGS="" + + # Determine which files to scan + if [ "${{ github.event_name }}" = "pull_request" ] && [ -f /tmp/changed_files.txt ]; then + FILES=$(cat /tmp/changed_files.txt | tr '\n' ' ') + if [ -z "$FILES" ]; then + echo "No JS/JSX files changed in this PR - skipping secrets check" + else + echo "Scanning only changed files in PR for hardcoded secrets" + fi + else + echo "Scanning entire src/ directory for hardcoded secrets" + FILES="src/" + fi + + # Fixed regex pattern with -E flag for extended regex + # Matches both single and double quotes: password="value", password='value', api_key="value", etc. + # Also matches object property syntax: password: "value" + if [ -n "$FILES" ]; then + SECRET_PATTERN='(password|api_key|secret|token)\s*[:=]\s*["\047][^"\047]{3,}["\047]' + + # Run grep directly (no eval) and filter out empty strings + if [ "${{ github.event_name }}" = "pull_request" ]; then + # For PRs, use xargs with changed files + SCAN_RESULT=$(echo "$FILES" | xargs grep -En "$SECRET_PATTERN" 2>/dev/null | grep -v 'password\s*[:=]\s*["\047]["\047]' | grep -v 'token\s*[:=]\s*["\047]["\047]' || true) + else + # For pushes to main/develop, scan src/ recursively + SCAN_RESULT=$(grep -rEn "$SECRET_PATTERN" src/ 2>/dev/null | grep -v 'password\s*[:=]\s*["\047]["\047]' | grep -v 'token\s*[:=]\s*["\047]["\047]' || true) + fi + + if [ -n "$SCAN_RESULT" ]; then + ETHICS_VIOLATIONS=true + HARDCODED_SECRETS_FOUND=true + + # Capture findings (limit to 10) + FOUND_SECRETS=$(echo "$SCAN_RESULT" | head -10) + + # Redact actual secret values for security (handle both single and double quotes) + REDACTED_FINDINGS=$(echo "$FOUND_SECRETS" | sed -E 's/(password|api_key|secret|token)(\s*[:=]\s*["\047])[^"\047]+(["\047])/\1\2***REDACTED***\3/gi') + + ETHICS_DETAILS="${ETHICS_DETAILS}\n### 🚨 CRITICAL SECURITY VULNERABILITY: Hardcoded Credentials Found!\n" + ETHICS_DETAILS="${ETHICS_DETAILS}**âš ī¸ IMMEDIATE ACTION REQUIRED - SECURITY BREACH DETECTED âš ī¸**\n\n" + ETHICS_DETAILS="${ETHICS_DETAILS}Hardcoded credentials have been detected in the source code. This is a **CRITICAL SECURITY VULNERABILITY**.\n\n" + ETHICS_DETAILS="${ETHICS_DETAILS}**Location of Credentials (values REDACTED for security):**\n" + ETHICS_DETAILS="${ETHICS_DETAILS}```\n" + ETHICS_DETAILS="${ETHICS_DETAILS}${REDACTED_FINDINGS}\n" + ETHICS_DETAILS="${ETHICS_DETAILS}```\n\n" + ETHICS_DETAILS="${ETHICS_DETAILS}**IMMEDIATE ACTION REQUIRED:**\n" + ETHICS_DETAILS="${ETHICS_DETAILS}1. 🚨 **IMMEDIATELY REVOKE/ROTATE** all exposed credentials\n" + ETHICS_DETAILS="${ETHICS_DETAILS}2. 🔒 **DELETE** the exposed keys from your service provider\n" + ETHICS_DETAILS="${ETHICS_DETAILS}3. đŸ—‘ī¸ **REMOVE** hardcoded secrets from code immediately\n" + ETHICS_DETAILS="${ETHICS_DETAILS}4. 🔐 Use environment variables or GitHub Secrets instead\n" + ETHICS_DETAILS="${ETHICS_DETAILS}5. 📋 A security advisory will be created for this issue\n\n" + ETHICS_DETAILS="${ETHICS_DETAILS}**All repository members will be notified of this security issue.**\n\n" + + # Store REDACTED details for security advisory + HARDCODED_SECRETS_DETAILS="$REDACTED_FINDINGS" + fi + fi + + # Output hardcoded secrets info for later steps + echo "hardcoded_secrets_found=$HARDCODED_SECRETS_FOUND" >> $GITHUB_OUTPUT + + # Output hardcoded secrets info for later steps + echo "hardcoded_secrets_found=$HARDCODED_SECRETS_FOUND" >> $GITHUB_OUTPUT + if [ "$HARDCODED_SECRETS_FOUND" = true ]; then + echo "hardcoded_secrets_details<> $GITHUB_OUTPUT + echo "$HARDCODED_SECRETS_DETAILS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + # 2. Check for eval() usage (security risk) - scoped to PR files + if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "$FILES" ]; then + if echo "$FILES" | xargs grep "eval(" 2>/dev/null | grep -v "// "; then + ETHICS_VIOLATIONS=true + ETHICS_DETAILS="${ETHICS_DETAILS}\n### âš ī¸ eval() Usage Detected:\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + ETHICS_DETAILS="${ETHICS_DETAILS}$(echo "$FILES" | xargs grep -n "eval(" 2>/dev/null | grep -v "// " | head -10)\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + fi + else + if grep -r "eval(" --include="*.js" --include="*.jsx" src/ 2>/dev/null | grep -v "// "; then + ETHICS_VIOLATIONS=true + ETHICS_DETAILS="${ETHICS_DETAILS}\n### âš ī¸ eval() Usage Detected:\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + ETHICS_DETAILS="${ETHICS_DETAILS}$(grep -rn "eval(" --include="*.js" --include="*.jsx" src/ 2>/dev/null | grep -v "// " | head -10)\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + fi + fi + + # 3. Check for dangerous innerHTML usage without sanitization - scoped to PR files + if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "$FILES" ]; then + if echo "$FILES" | xargs grep -E "dangerouslySetInnerHTML|innerHTML\s*=" 2>/dev/null; then + ETHICS_VIOLATIONS=true + ETHICS_DETAILS="${ETHICS_DETAILS}\n### âš ī¸ Potentially Unsafe HTML Injection:\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + ETHICS_DETAILS="${ETHICS_DETAILS}$(echo "$FILES" | xargs grep -nE "dangerouslySetInnerHTML|innerHTML\s*=" 2>/dev/null | head -10)\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + ETHICS_DETAILS="${ETHICS_DETAILS}Make sure these are properly sanitized to prevent XSS attacks.\n" + fi + else + if grep -rE "dangerouslySetInnerHTML|innerHTML\s*=" --include="*.js" --include="*.jsx" src/ 2>/dev/null; then + ETHICS_VIOLATIONS=true + ETHICS_DETAILS="${ETHICS_DETAILS}\n### âš ī¸ Potentially Unsafe HTML Injection:\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + ETHICS_DETAILS="${ETHICS_DETAILS}$(grep -rnE "dangerouslySetInnerHTML|innerHTML\s*=" \ + --include="*.js" --include="*.jsx" src/ 2>/dev/null | head -10)\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + ETHICS_DETAILS="${ETHICS_DETAILS}Make sure these are properly sanitized to prevent XSS attacks.\n" + fi + fi + + # 4. Check for console.log in production code (not unethical but bad practice) + if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "$FILES" ]; then + CONSOLE_COUNT=$(echo "$FILES" | xargs grep -E "console\.(log|debug|info)" 2>/dev/null | wc -l) + else + CONSOLE_COUNT=$(grep -rE "console\.(log|debug|info)" --include="*.js" --include="*.jsx" src/ 2>/dev/null | wc -l) + fi + if [ "$CONSOLE_COUNT" -gt 100 ]; then + ETHICS_DETAILS="${ETHICS_DETAILS}\n### â„šī¸ Excessive Console Statements:\n" + ETHICS_DETAILS="${ETHICS_DETAILS}Found $CONSOLE_COUNT console.log statements. Consider using a proper logging library.\n" + fi + + # 5. Check for TODO/FIXME that might indicate incomplete security implementations - scoped to PR files + if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "$FILES" ]; then + if echo "$FILES" | xargs grep -iE "TODO.*security|FIXME.*auth|TODO.*sanitize|FIXME.*XSS" 2>/dev/null; then + ETHICS_VIOLATIONS=true + ETHICS_DETAILS="${ETHICS_DETAILS}\n### 🚨 Incomplete Security TODOs:\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + ETHICS_DETAILS="${ETHICS_DETAILS}$(echo "$FILES" | xargs grep -niE "TODO.*security|FIXME.*auth|TODO.*sanitize|FIXME.*XSS" 2>/dev/null)\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + fi + else + if grep -riE "TODO.*security|FIXME.*auth|TODO.*sanitize|FIXME.*XSS" \ + --include="*.js" --include="*.jsx" src/ 2>/dev/null; then + ETHICS_VIOLATIONS=true + ETHICS_DETAILS="${ETHICS_DETAILS}\n### 🚨 Incomplete Security TODOs:\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + ETHICS_DETAILS="${ETHICS_DETAILS}$(grep -rniE "TODO.*security|FIXME.*auth|TODO.*sanitize|FIXME.*XSS" \ + --include="*.js" --include="*.jsx" src/ 2>/dev/null)\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + fi + fi + + # 6. Check for disabled security features - scoped to PR files + if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "$FILES" ]; then + if echo "$FILES" | xargs grep -E "eslint-disable.*security|@ts-ignore.*security|skipLibCheck.*true" 2>/dev/null; then + ETHICS_VIOLATIONS=true + ETHICS_DETAILS="${ETHICS_DETAILS}\n### âš ī¸ Disabled Security Checks:\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + ETHICS_DETAILS="${ETHICS_DETAILS}$(echo "$FILES" | xargs grep -nE "eslint-disable.*security|@ts-ignore.*security" 2>/dev/null)\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + fi + else + if grep -rE "eslint-disable.*security|@ts-ignore.*security|skipLibCheck.*true" \ + --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" src/ 2>/dev/null; then + ETHICS_VIOLATIONS=true + ETHICS_DETAILS="${ETHICS_DETAILS}\n### âš ī¸ Disabled Security Checks:\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + ETHICS_DETAILS="${ETHICS_DETAILS}$(grep -rnE "eslint-disable.*security|@ts-ignore.*security" \ + --include="*.js" --include="*.jsx" src/ 2>/dev/null)\n" + ETHICS_DETAILS="${ETHICS_DETAILS}\`\`\`\n" + fi + fi + + if [ "$ETHICS_VIOLATIONS" = true ]; then + echo "ethics_violations=true" >> $GITHUB_OUTPUT + echo "ethics_details<> $GITHUB_OUTPUT + echo -e "$ETHICS_DETAILS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + else + echo "✅ No obvious security or ethical violations found!" + echo "ethics_violations=false" >> $GITHUB_OUTPUT + fi + + # ============================================================ + # 5. GENERATE SUMMARY REPORT + # ============================================================ + - name: Generate code quality report + if: always() + id: report + uses: actions/github-script@v7 + env: + DEPRECATED_FOUND: ${{ steps.react_deprecated.outputs.deprecated_found }} + DEPRECATED_DETAILS: ${{ steps.react_deprecated.outputs.deprecated_details }} + AI_DETECTED: ${{ steps.ai_detection.outputs.ai_detected }} + AI_DETAILS: ${{ steps.ai_detection.outputs.ai_details }} + DUPLICATES_FOUND: ${{ steps.duplicate_detection.outputs.duplicates_found }} + DUPLICATE_COUNT: ${{ steps.duplicate_detection.outputs.duplicate_count }} + DUPLICATE_PERCENTAGE: ${{ steps.duplicate_detection.outputs.duplicate_percentage }} + DUPLICATE_SUMMARY: ${{ steps.duplicate_detection.outputs.duplicate_summary }} + ETHICS_VIOLATIONS: ${{ steps.ethics_check.outputs.ethics_violations }} + ETHICS_DETAILS: ${{ steps.ethics_check.outputs.ethics_details }} + with: + script: | + const deprecatedFound = process.env.DEPRECATED_FOUND === 'true'; + const deprecatedDetails = process.env.DEPRECATED_DETAILS || ''; + const aiDetected = process.env.AI_DETECTED === 'true'; + const aiDetails = process.env.AI_DETAILS || ''; + const duplicatesFound = process.env.DUPLICATES_FOUND === 'true'; + const duplicateCount = process.env.DUPLICATE_COUNT || '0'; + const duplicatePercentage = process.env.DUPLICATE_PERCENTAGE || '0'; + const duplicateSummary = process.env.DUPLICATE_SUMMARY || ''; + const ethicsViolations = process.env.ETHICS_VIOLATIONS === 'true'; + const ethicsDetails = process.env.ETHICS_DETAILS || ''; + + const hasIssues = deprecatedFound || aiDetected || duplicatesFound || ethicsViolations; + + let report = `## 🔍 Code Quality Analysis Report\n\n`; + + if (!hasIssues) { + report += `### ✅ All Checks Passed!\n\n`; + report += `No code quality issues detected. Great work! 🎉\n\n`; + report += `- ✅ No deprecated React patterns\n`; + report += `- ✅ No obvious AI-generated code markers\n`; + report += `- ✅ No significant code duplication\n`; + report += `- ✅ No security or ethical violations\n`; + } else { + report += `### Issues Detected\n\n`; + + if (deprecatedFound) { + report += `## ❌ Deprecated React Patterns\n\n`; + report += `${deprecatedDetails}\n\n`; + report += `**Recommendation:** Migrate to modern React patterns (hooks, function components)\n\n`; + report += `---\n\n`; + } else { + report += `- ✅ No deprecated React patterns\n`; + } + + if (aiDetected) { + report += `## âš ī¸ AI-Generated Code Markers\n\n`; + report += `${aiDetails}\n\n`; + report += `**Recommendation:** Review AI-generated code for quality and correctness\n\n`; + report += `---\n\n`; + } else { + report += `- ✅ No obvious AI-generated code markers\n`; + } + + if (duplicatesFound) { + report += `## 🔄 Code Duplication Detected\n\n`; + report += `**Statistics:**\n`; + report += `- Duplicate blocks: ${duplicateCount}\n`; + report += `- Duplication percentage: ${duplicatePercentage}%\n\n`; + report += `**Top Duplicates:**\n\`\`\`\n${duplicateSummary}\n\`\`\`\n\n`; + report += `**Recommendation:** Refactor duplicated code into reusable functions/components\n\n`; + report += `---\n\n`; + } else { + report += `- ✅ No significant code duplication\n`; + } + + if (ethicsViolations) { + report += `## 🚨 Security & Ethics Violations\n\n`; + report += `${ethicsDetails}\n\n`; + report += `**Recommendation:** Address security issues immediately before merging\n\n`; + report += `---\n\n`; + } else { + report += `- ✅ No security or ethical violations\n`; + } + } + + report += `\n---\n`; + report += `*🤖 Automated code quality check performed by GitHub Actions*\n`; + report += `*Run ID: [${context.runId}](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})*\n`; + + // Post comment on PR if this is a pull request (skip for forks due to permissions) + if (context.payload.pull_request) { + const isFork = context.payload.pull_request.head.repo.fork || + context.payload.pull_request.head.repo.full_name !== `${context.repo.owner}/${context.repo.repo}`; + + if (!isFork) { + try { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: report + }); + } catch (error) { + console.warn('Failed to create comment (possibly due to permissions):', error.message); + // Don't fail the workflow if comment creation fails + } + } else { + console.log('Skipping PR comment for fork PR due to read-only token'); + } + } + + // Set output for subsequent steps + core.setOutput('has_issues', hasIssues); + core.setOutput('report', report); + + // Fail the check if critical issues found (deprecated or ethics violations) + if (deprecatedFound || ethicsViolations) { + core.setFailed('Critical code quality issues detected'); + } + + # ============================================================ + # 6. CREATE SECURITY ADVISORY FOR HARDCODED SECRETS + # ============================================================ + - name: Create security advisory for hardcoded secrets + if: steps.ethics_check.outputs.hardcoded_secrets_found == 'true' + id: security_advisory + uses: actions/github-script@v7 + env: + HARDCODED_SECRETS_DETAILS: ${{ steps.ethics_check.outputs.hardcoded_secrets_details }} + with: + script: | + const secretsDetails = process.env.HARDCODED_SECRETS_DETAILS || ''; + + try { + // Create a DRAFT security advisory (not public until fixed) + // This prevents premature disclosure before the PR is fixed + const advisory = await github.rest.securityAdvisories.createRepositoryAdvisory({ + owner: context.repo.owner, + repo: context.repo.repo, + summary: '🚨 CRITICAL: Hardcoded Credentials Detected in Source Code', + state: 'draft', // Keep private until fixed + description: `## CRITICAL SECURITY VULNERABILITY + + Hardcoded credentials have been detected in the source code during automated security scanning. + + ### Severity: CRITICAL + + ### Impact + Exposed credentials in source code can lead to: + - Unauthorized access to services + - Data breaches + - System compromise + - Financial loss + + ### Affected Locations + \`\`\` + ${secretsDetails} + \`\`\` + + ### IMMEDIATE ACTION REQUIRED + + 1. **🚨 IMMEDIATELY REVOKE/ROTATE** all exposed credentials + 2. **🔒 DELETE** the exposed keys from your service provider (API provider, database, etc.) + 3. **đŸ—‘ī¸ REMOVE** all hardcoded secrets from the codebase + 4. **🔐 MIGRATE** to using environment variables or GitHub Secrets + 5. **📋 AUDIT** git history to ensure secrets weren't committed previously + + ### Recommended Actions + + - Use GitHub Secrets for sensitive data in workflows + - Use environment variables for application secrets + - Add \`.env\` files to \`.gitignore\` + - Consider using a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.) + - Enable secret scanning in repository settings + + ### Detection Information + + - Detected by: Automated Code Quality Workflow + - PR: #${context.payload.pull_request?.number || 'N/A'} + - Commit: ${context.sha} + - Workflow Run: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId} + + **All repository members and collaborators have been notified.**`, + severity: 'critical', + cve_id: null, + vulnerabilities: [{ + package: { + ecosystem: 'other', + name: context.repo.repo + }, + vulnerable_version_range: '*', + patched_versions: 'none' + }], + cwe_ids: ['CWE-798'], // Use of Hard-coded Credentials + credits: [] + }); + + console.log(`Security advisory created: ${advisory.data.html_url}`); + core.setOutput('advisory_url', advisory.data.html_url); + core.setOutput('advisory_id', advisory.data.ghsa_id); + + return advisory.data.html_url; + } catch (error) { + console.error('Failed to create security advisory:', error); + console.log('Continuing workflow despite advisory creation failure...'); + // Don't fail the workflow if advisory creation fails + return null; + } + + - name: Notify all members about security issue + if: steps.ethics_check.outputs.hardcoded_secrets_found == 'true' + uses: actions/github-script@v7 + env: + ADVISORY_URL: ${{ steps.security_advisory.outputs.advisory_url }} + ADVISORY_ID: ${{ steps.security_advisory.outputs.advisory_id }} + with: + script: | + const advisoryUrl = process.env.ADVISORY_URL || 'N/A'; + const advisoryId = process.env.ADVISORY_ID || 'N/A'; + + // Get all collaborators + const collaborators = await github.rest.repos.listCollaborators({ + owner: context.repo.owner, + repo: context.repo.repo, + affiliation: 'all' + }); + + // Create mention string for all members + const mentions = collaborators.data.map(c => `@${c.login}`).join(' '); + + // Post urgent security notification + const securityMessage = `## 🚨 CRITICAL SECURITY ALERT 🚨 + + ${mentions} + + **HARDCODED CREDENTIALS DETECTED IN SOURCE CODE** + + A critical security vulnerability has been detected in PR #${context.payload.pull_request?.number || 'N/A'}. + Hardcoded credentials (passwords, API keys, tokens, or secrets) were found in the source code. + + ### Security Advisory Created + ${advisoryUrl !== 'N/A' ? `**Advisory:** ${advisoryUrl}` : 'Security advisory creation in progress...'} + ${advisoryId !== 'N/A' ? `**ID:** ${advisoryId}` : ''} + + ### IMMEDIATE ACTION REQUIRED + + 1. 🚨 **STOP** - Do not merge this PR + 2. 🔒 **REVOKE/ROTATE** all exposed credentials immediately + 3. đŸ—‘ī¸ **DELETE** exposed keys from service providers + 4. 🔐 **REMOVE** hardcoded secrets from code + 5. 📋 **REVIEW** git history for previous exposures + + ### What to do next + + - Check the security advisory for full details + - Review the workflow run: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId} + - Coordinate with the team to rotate all potentially exposed credentials + + **This is an automated security notification from the Code Quality Workflow.**`; + + // Post comment on PR + if (context.payload.pull_request) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: securityMessage + }); + } + + // Also create an issue for tracking + try { + // First create issue without assignees to avoid failures + const issueResponse = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `🚨 CRITICAL: Hardcoded Credentials Detected (${advisoryId})`, + body: securityMessage, + labels: ['security', 'critical', 'needs-immediate-attention'] + }); + + const issueNumber = issueResponse.data.number; + console.log(`Tracking issue created: #${issueNumber}`); + + // Try to assign collaborators individually to handle failures gracefully + const potentialAssignees = collaborators.data.slice(0, 10); + for (const collaborator of potentialAssignees) { + try { + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + assignees: [collaborator.login] + }); + console.log(`Assigned @${collaborator.login} to tracking issue`); + } catch (assigneeError) { + console.warn(`Failed to assign @${collaborator.login}:`, assigneeError.message || assigneeError); + // Continue with next assignee + } + } + } catch (error) { + console.error('Failed to create tracking issue:', error); + } + + - name: Update PR comment with security advisory link + if: steps.ethics_check.outputs.hardcoded_secrets_found == 'true' && steps.security_advisory.outputs.advisory_url + uses: actions/github-script@v7 + env: + ADVISORY_URL: ${{ steps.security_advisory.outputs.advisory_url }} + ADVISORY_ID: ${{ steps.security_advisory.outputs.advisory_id }} + with: + script: | + const advisoryUrl = process.env.ADVISORY_URL; + const advisoryId = process.env.ADVISORY_ID; + + if (!advisoryUrl || !context.payload.pull_request) { + return; + } + + // Find the bot's code quality comment + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number + }); + + const botComment = comments.data.find(comment => + comment.user.login === 'github-actions[bot]' && + comment.body.includes('Code Quality Analysis Report') + ); + + if (botComment) { + // Update the comment to add security advisory link + // Use append strategy to be more robust + let updatedBody = botComment.body; + + // Avoid adding advisory link multiple times + if (!updatedBody.includes(advisoryUrl)) { + // Try to find the security violations section + if (updatedBody.includes('## 🚨 Security & Ethics Violations')) { + updatedBody = updatedBody.replace( + /## 🚨 Security & Ethics Violations/, + `## 🚨 Security & Ethics Violations\n\n**🔗 Security Advisory Created (DRAFT):** [${advisoryId}](${advisoryUrl})\n*Advisory is in draft mode and private until issue is resolved.*\n` + ); + } else { + // If section not found, append to end + updatedBody += `\n\n---\n\n**🔗 Security Advisory Created (DRAFT):** [${advisoryId}](${advisoryUrl})\n*Advisory is in draft mode and private until issue is resolved.*`; + } + } + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: updatedBody + }); + } + + - name: Upload detailed reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: code-quality-reports + path: | + jscpd-report/ + jscpd-output.txt + retention-days: 30 + + - name: Summary + if: always() + run: | + echo "### 🔍 Code Quality Check Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.report.outputs.has_issues }}" = "true" ]; then + echo "âš ī¸ **Issues detected!** Review the report above." >> $GITHUB_STEP_SUMMARY + else + echo "✅ **All checks passed!** Your code looks great!" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "📊 Full report available in PR comments or workflow artifacts." >> $GITHUB_STEP_SUMMARY diff --git a/.jscpd.json b/.jscpd.json new file mode 100644 index 000000000..e12540a34 --- /dev/null +++ b/.jscpd.json @@ -0,0 +1,15 @@ +{ + "threshold": 5, + "minLines": 10, + "minTokens": 50, + "ignore": [ + "**/*.min.js", + "**/node_modules/**", + "**/build/**", + "**/dist/**", + "**/test/**" + ], + "reporters": ["console", "json"], + "format": ["javascript", "jsx"], + "output": "./jscpd-report" +}