🔍 Quality & Security Checks #172
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: 🔍 Quality & Security Checks | |
on: | |
push: | |
branches: [main, develop] | |
pull_request: | |
branches: [main, develop] | |
schedule: | |
# Run daily at 2 AM UTC | |
- cron: "0 2 * * *" | |
workflow_dispatch: | |
env: | |
NODE_VERSION: "20" | |
jobs: | |
# Documentation Quality Check | |
documentation-check: | |
name: 📚 Documentation Quality | |
runs-on: ubuntu-latest | |
outputs: | |
docs-status: ${{ steps.docs-check.outputs.status }} | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ env.NODE_VERSION }} | |
cache: "npm" | |
- name: Install dependencies | |
run: | | |
# Clean install to prevent rollup optional dependency issues | |
rm -rf node_modules package-lock.json | |
npm install | |
- name: Check documentation completeness | |
id: docs-check | |
run: | | |
echo "Checking documentation completeness..." | |
# Required documentation files | |
required_docs=( | |
"README.md" | |
"frontend/CHANGELOG.md" | |
"frontend/docs/website-overview.md" | |
"frontend/docs/security-architecture.md" | |
"frontend/docs/design-system.md" | |
"frontend/docs/brand-guidelines.md" | |
"frontend/docs/company-info.md" | |
) | |
missing_docs=() | |
for doc in "${required_docs[@]}"; do | |
if [[ ! -f "$doc" ]]; then | |
missing_docs+=("$doc") | |
fi | |
done | |
if [[ ${#missing_docs[@]} -gt 0 ]]; then | |
echo "❌ Missing documentation files:" | |
printf '%s\n' "${missing_docs[@]}" | |
echo "status=failed" >> $GITHUB_OUTPUT | |
exit 1 | |
fi | |
echo "✅ All required documentation files present" | |
echo "status=passed" >> $GITHUB_OUTPUT | |
- name: Validate markdown format | |
run: | | |
npx prettier --check "*.md" "frontend/docs/**/*.md" || { | |
echo "❌ Markdown formatting issues found" | |
exit 1 | |
} | |
echo "✅ Markdown formatting is correct" | |
- name: Check for broken links in documentation | |
run: | | |
# Check for broken internal links | |
echo "Checking for broken internal documentation links..." | |
# Find all markdown files and check internal links | |
find . -name "*.md" -type f | while read -r file; do | |
echo "Checking $file..." | |
grep -n '\[.*\](\./' "$file" | while IFS: read -r line_num link; do | |
link_path=$(echo "$link" | sed -n 's/.*](\([^)]*\)).*/\1/p') | |
if [[ -n "$link_path" && ! -f "$link_path" ]]; then | |
echo "❌ Broken link in $file:$line_num -> $link_path" | |
exit 1 | |
fi | |
done | |
done | |
echo "✅ No broken internal links found" | |
# Lint and Code Quality Check | |
lint-check: | |
name: 🔧 Lint & Code Quality | |
runs-on: ubuntu-latest | |
outputs: | |
lint-status: ${{ steps.lint-check.outputs.status }} | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ env.NODE_VERSION }} | |
cache: "npm" | |
- name: Install dependencies | |
run: | | |
# Clean install to prevent rollup optional dependency issues | |
rm -rf node_modules package-lock.json | |
npm install | |
- name: Run ESLint | |
id: lint-check | |
run: | | |
echo "Running ESLint..." | |
npm run lint 2>&1 | tee lint-output.txt | |
if [[ ${PIPESTATUS[0]} -eq 0 ]]; then | |
echo "✅ ESLint passed" | |
echo "status=passed" >> $GITHUB_OUTPUT | |
else | |
echo "❌ ESLint failed" | |
echo "status=failed" >> $GITHUB_OUTPUT | |
exit 1 | |
fi | |
- name: Check TypeScript compilation | |
run: | | |
echo "Checking TypeScript compilation..." | |
npx tsc --noEmit -p frontend/tsconfig.json || { | |
echo "❌ TypeScript compilation failed" | |
exit 1 | |
} | |
echo "✅ TypeScript compilation successful" | |
- name: Run Prettier format check | |
run: | | |
echo "Checking code formatting..." | |
npm run format:check || { | |
echo "❌ Code formatting issues found" | |
exit 1 | |
} | |
echo "✅ Code formatting is correct" | |
# Build Status Check | |
build-check: | |
name: 🏗️ Build & Test | |
runs-on: ubuntu-latest | |
outputs: | |
build-status: ${{ steps.build-check.outputs.status }} | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ env.NODE_VERSION }} | |
cache: "npm" | |
- name: Install dependencies | |
run: | | |
# Clean install to prevent rollup optional dependency issues | |
rm -rf node_modules package-lock.json | |
npm install | |
- name: Run tests | |
run: | | |
echo "Running tests..." | |
npm test || { | |
echo "❌ Tests failed" | |
exit 1 | |
} | |
echo "✅ All tests passed" | |
- name: Build application | |
id: build-check | |
run: | | |
echo "Building application..." | |
npm run build 2>&1 | tee build-output.txt | |
if [[ ${PIPESTATUS[0]} -eq 0 ]]; then | |
echo "✅ Build successful" | |
echo "status=passed" >> $GITHUB_OUTPUT | |
# Check build size | |
build_size=$(du -sh build/ | cut -f1) | |
echo "📦 Build size: $build_size" | |
else | |
echo "❌ Build failed" | |
echo "status=failed" >> $GITHUB_OUTPUT | |
exit 1 | |
fi | |
- name: Upload build artifacts | |
if: success() | |
uses: actions/upload-artifact@v4 | |
with: | |
name: build-artifacts | |
path: build/ | |
retention-days: 7 | |
# Security Vulnerability Check | |
security-check: | |
name: 🔒 Security Scan | |
runs-on: ubuntu-latest | |
outputs: | |
security-status: ${{ steps.security-check.outputs.status }} | |
vulnerabilities: ${{ steps.security-check.outputs.vulnerabilities }} | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ env.NODE_VERSION }} | |
cache: "npm" | |
- name: Install dependencies | |
run: | | |
# Clean install to prevent rollup optional dependency issues | |
rm -rf node_modules package-lock.json | |
npm install | |
- name: Run npm audit | |
id: security-check | |
run: | | |
echo "Running security audit..." | |
# Run npm audit and capture output | |
if npm audit --audit-level=moderate --json > audit-results.json 2>&1; then | |
echo "✅ No security vulnerabilities found" | |
echo "status=passed" >> $GITHUB_OUTPUT | |
echo "vulnerabilities=none" >> $GITHUB_OUTPUT | |
else | |
echo "❌ Security vulnerabilities detected" | |
# Parse vulnerabilities | |
vulnerabilities=$(cat audit-results.json | jq -r '.vulnerabilities | to_entries[] | select(.value.severity == "high" or .value.severity == "critical") | "\(.key): \(.value.severity) - \(.value.title)"' | head -10) | |
if [[ -n "$vulnerabilities" ]]; then | |
echo "status=failed" >> $GITHUB_OUTPUT | |
echo "vulnerabilities<<EOF" >> $GITHUB_OUTPUT | |
echo "$vulnerabilities" >> $GITHUB_OUTPUT | |
echo "EOF" >> $GITHUB_OUTPUT | |
else | |
echo "status=warning" >> $GITHUB_OUTPUT | |
echo "vulnerabilities=low-moderate" >> $GITHUB_OUTPUT | |
fi | |
fi | |
- name: Upload audit results | |
if: always() | |
uses: actions/upload-artifact@v4 | |
with: | |
name: security-audit-results | |
path: audit-results.json | |
retention-days: 30 | |
# Dependency Check | |
dependency-check: | |
name: 📦 Dependency Analysis | |
runs-on: ubuntu-latest | |
outputs: | |
dependency-status: ${{ steps.dep-check.outputs.status }} | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
node-version: ${{ env.NODE_VERSION }} | |
cache: "npm" | |
- name: Check for outdated dependencies | |
id: dep-check | |
run: | | |
echo "Checking for outdated dependencies..." | |
# Get outdated packages | |
outdated_output=$(npm outdated --json 2>/dev/null || echo "{}") | |
if [[ "$outdated_output" == "{}" ]]; then | |
echo "✅ All dependencies are up to date" | |
echo "status=passed" >> $GITHUB_OUTPUT | |
else | |
echo "⚠️ Some dependencies are outdated" | |
echo "$outdated_output" | jq -r 'to_entries[] | "\(.key): \(.value.current) -> \(.value.wanted)"' | |
echo "status=warning" >> $GITHUB_OUTPUT | |
fi | |
# Create GitHub Issues for Security Vulnerabilities | |
create-security-issues: | |
name: 📋 Create Security Issues | |
runs-on: ubuntu-latest | |
needs: [security-check] | |
if: needs.security-check.outputs.security-status == 'failed' | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Create security issue | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
// Enhanced duplicate prevention logic: | |
// - Checks both open and recently closed issues (last 7 days) | |
// - Prevents spam from repeated workflow runs | |
// - Uses specific labels and title patterns for precise matching | |
const vulnerabilities = `${{ needs.security-check.outputs.vulnerabilities }}`; | |
const issueBody = ` | |
## 🚨 Security Vulnerabilities Detected | |
**Detected on:** ${new Date().toISOString()} | |
**Workflow Run:** [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
### Vulnerabilities Found: | |
\`\`\` | |
${vulnerabilities} | |
\`\`\` | |
### Recommended Actions: | |
1. **Update Dependencies:** Run \`npm update\` to update packages to secure versions | |
2. **Review Audit:** Run \`npm audit\` locally for detailed vulnerability information | |
3. **Apply Fixes:** Use \`npm audit fix\` to automatically apply available fixes | |
4. **Manual Review:** For vulnerabilities that can't be auto-fixed, consider: | |
- Finding alternative packages | |
- Updating to newer versions manually | |
- Applying security patches | |
### Safe Remediation Steps: | |
\`\`\`bash | |
# 1. Check current vulnerabilities | |
npm audit | |
# 2. Try automatic fixes first | |
npm audit fix | |
# 3. For remaining issues, update specific packages | |
npm update [package-name] | |
# 4. Test the application | |
npm test | |
npm run build | |
\`\`\` | |
### Testing Checklist: | |
- [ ] All tests pass | |
- [ ] Application builds successfully | |
- [ ] Core functionality works (contact forms, job applications, admin panel) | |
- [ ] Security features remain intact | |
- [ ] No breaking changes introduced | |
**Priority:** High - Please address these security vulnerabilities promptly. | |
`; | |
// Check if a similar issue already exists (open or recently closed) | |
const sevenDaysAgo = new Date(); | |
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); | |
const existingOpenIssues = await github.rest.issues.listForRepo({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
labels: ['security', 'vulnerability'], | |
state: 'open' | |
}); | |
const existingClosedIssues = await github.rest.issues.listForRepo({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
labels: ['security', 'vulnerability'], | |
state: 'closed', | |
since: sevenDaysAgo.toISOString() | |
}); | |
const allRelevantIssues = [...existingOpenIssues.data, ...existingClosedIssues.data]; | |
const securityIssueExists = allRelevantIssues.some(issue => | |
issue.title.includes('Security Vulnerabilities Detected') | |
); | |
if (!securityIssueExists) { | |
await github.rest.issues.create({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
title: '🚨 Security Vulnerabilities Detected - Immediate Action Required', | |
body: issueBody, | |
labels: ['security', 'vulnerability', 'high-priority'] | |
}); | |
console.log('Created new security vulnerability issue'); | |
} else { | |
console.log('Security issue already exists or was recently closed, skipping creation'); | |
} | |
# Update README badges | |
update-badges: | |
name: 🏷️ Update Status Badges | |
runs-on: ubuntu-latest | |
needs: | |
[ | |
documentation-check, | |
lint-check, | |
build-check, | |
security-check, | |
dependency-check, | |
] | |
if: always() && github.ref == 'refs/heads/main' | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
with: | |
token: ${{ secrets.GITHUB_TOKEN }} | |
- name: Update README badges | |
run: | | |
# Determine status colors | |
docs_status="${{ needs.documentation-check.outputs.docs-status }}" | |
lint_status="${{ needs.lint-check.outputs.lint-status }}" | |
build_status="${{ needs.build-check.outputs.build-status }}" | |
security_status="${{ needs.security-check.outputs.security-status }}" | |
dependency_status="${{ needs.dependency-check.outputs.dependency-status }}" | |
# Function to get color based on status | |
get_color() { | |
case "$1" in | |
"passed") echo "brightgreen" ;; | |
"warning") echo "yellow" ;; | |
"failed") echo "red" ;; | |
*) echo "lightgrey" ;; | |
esac | |
} | |
# Generate badge URLs | |
docs_color=$(get_color "$docs_status") | |
lint_color=$(get_color "$lint_status") | |
build_color=$(get_color "$build_status") | |
security_color=$(get_color "$security_status") | |
dependency_color=$(get_color "$dependency_status") | |
# Create replacement badges | |
BUILD_BADGE="[](https://github.com/${{ github.repository }}/actions)" | |
QUALITY_BADGE="[](https://github.com/${{ github.repository }}/actions)" | |
SECURITY_BADGE="[](https://github.com/${{ github.repository }}/actions)" | |
# Use sed to replace the badges section | |
# This targets the specific three-line section in the header | |
# Only update if the current badges are different from the new ones | |
if ! grep -q "Build-${build_status}" README.md || ! grep -q "Code%20Quality-${lint_status}" README.md || ! grep -q "Security-${security_status}" README.md; then | |
sed -i.bak -E " | |
/^\[\!\[Build Status\]/ { | |
s|.*|$BUILD_BADGE| | |
n | |
s|.*|$QUALITY_BADGE| | |
n | |
s|.*|$SECURITY_BADGE| | |
} | |
" README.md | |
# Clean up backup file | |
rm -f README.md.bak | |
echo "✅ Badges updated successfully" | |
else | |
echo "ℹ️ Badges are already up to date" | |
fi | |
- name: Commit updated badges | |
run: | | |
git config --local user.email "[email protected]" | |
git config --local user.name "GitHub Action" | |
if git diff --quiet; then | |
echo "No badge updates needed" | |
else | |
# Check if there was already a badge update today | |
last_badge_commit=$(git log --since="24 hours ago" --grep="Update quality & security status badges" --oneline | head -1) | |
if [ -z "$last_badge_commit" ]; then | |
echo "Creating daily badge update commit" | |
git add README.md | |
git commit -m "🏷️ Update quality & security status badges [skip ci]" | |
git pull --rebase origin main | |
git push | |
else | |
echo "Badge update already done today, skipping commit to reduce git history pollution" | |
fi | |
fi |