diff --git a/.github/workflows/pr-security-check.yml b/.github/workflows/pr-security-check.yml new file mode 100644 index 000000000..f796f6492 --- /dev/null +++ b/.github/workflows/pr-security-check.yml @@ -0,0 +1,171 @@ +name: PR Security Check + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [main] + paths: + - 'src/**' + - 'public/**' + - 'package.json' + - 'package-lock.json' + - 'scripts/**' + - '.github/workflows/**' + # Also run on pushes to feature branches + push: + branches-ignore: + - main + - develop + - gh-pages + - deploy + paths: + - 'src/**' + - 'public/**' + - 'package.json' + - 'package-lock.json' + - 'scripts/**' + - '.github/workflows/**' + +permissions: + contents: read + pull-requests: write + +jobs: + security-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: '20' + cache: 'npm' + + - name: Get PR information + id: pr_info + uses: actions/github-script@v8 + with: + script: | + let prNumber = null; + + if (context.payload.pull_request) { + // Direct PR event + prNumber = context.payload.pull_request.number; + } else if (context.eventName === 'push') { + // Push event - find associated PR + const branchName = context.ref.replace('refs/heads/', ''); + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${context.repo.owner}:${branchName}` + }); + + if (prs.length > 0) { + prNumber = prs[0].number; + } + } + + console.log(`PR Number: ${prNumber}`); + core.setOutput('pr_number', prNumber || ''); + + return prNumber; + + - name: Install dependencies + run: npm ci --legacy-peer-deps + continue-on-error: false + + - name: Run security checks + id: security_check + continue-on-error: true + run: | + echo "šŸ”’ Running comprehensive security checks..." + + # Run the security check script + node scripts/run-security-checks.js + + # Capture exit code + exit_code=$? + echo "security_exit_code=$exit_code" >> $GITHUB_OUTPUT + + # Always continue to format the comment even if checks fail + exit 0 + + - name: Format security comment + id: format_comment + if: always() + run: | + echo "šŸ“ Formatting security check results..." + + # Check if results file exists + if [ -f "security-check-results.json" ]; then + node scripts/format-security-comment.js + echo "comment_available=true" >> $GITHUB_OUTPUT + else + echo "āš ļø Security check results not found" + echo "comment_available=false" >> $GITHUB_OUTPUT + fi + + - name: Update PR comment + if: always() && steps.pr_info.outputs.pr_number != '' && steps.format_comment.outputs.comment_available == 'true' + continue-on-error: true + run: | + python3 scripts/manage-security-comment.py \ + --token "${{ secrets.GITHUB_TOKEN }}" \ + --repo "${{ github.repository }}" \ + --pr "${{ steps.pr_info.outputs.pr_number }}" \ + --comment-file "security-comment.md" + + - name: Set status check + if: always() && steps.pr_info.outputs.pr_number != '' + uses: actions/github-script@v8 + with: + script: | + const exitCode = '${{ steps.security_check.outputs.security_exit_code }}'; + const prNumber = '${{ steps.pr_info.outputs.pr_number }}'; + + // Read results to get more details + const fs = require('fs'); + let summary = 'Security checks completed'; + let conclusion = 'success'; + + try { + if (fs.existsSync('security-check-results.json')) { + const results = JSON.parse(fs.readFileSync('security-check-results.json', 'utf8')); + const s = results.summary; + + if (s.failed > 0) { + conclusion = 'failure'; + summary = `Security issues found: ${s.failed} failed, ${s.warned} warnings`; + } else if (s.warned > 0) { + conclusion = 'neutral'; + summary = `Security warnings: ${s.warned} warnings, ${s.passed} passed`; + } else { + conclusion = 'success'; + summary = `All security checks passed: ${s.passed} checks`; + } + } + } catch (e) { + console.error('Error reading results:', e); + } + + console.log(`Security check conclusion: ${conclusion}`); + console.log(`Summary: ${summary}`); + + // Don't fail the workflow for warnings + if (conclusion === 'failure') { + core.setFailed(summary); + } + + - name: Upload security results as artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: security-check-results + path: | + security-check-results.json + security-comment.md + retention-days: 30 diff --git a/.gitignore b/.gitignore index a82a97aac..3a862517e 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,10 @@ test-pr-feedback-improvements.md # Deployment structure (generated by CI/CD) sgex/public/404-complex-backup.html scripts/__pycache__/ + +# Security check results (generated by CI/CD) +security-check-results.json +security-comment.md +eslint-results.json +audit_results.txt +audit_results.json diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 1bd58688e..fc52a7239 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -212,6 +212,7 @@ The following additional workflows provide build quality, security, and feedback ### Code Quality & Compliance Workflows - **Framework Compliance Check**: [`.github/workflows/framework-compliance.yml`](.github/workflows/framework-compliance.yml) - Validates page framework standards and TypeScript compliance - **Dependency Security Check**: [`.github/workflows/dependency-security.yml`](.github/workflows/dependency-security.yml) - Scans for security vulnerabilities in dependencies +- **PR Security Check**: [`.github/workflows/pr-security-check.yml`](.github/workflows/pr-security-check.yml) - Comprehensive security checks on every PR build (npm audit, outdated dependencies, ESLint security, security headers, license compliance, secret scanning, framework compliance) ### Workflow Configuration Details All workflows include: diff --git a/MERGE_CONFLICT_ANALYSIS.md b/MERGE_CONFLICT_ANALYSIS.md index f5cf23cd5..ebe2fb4d1 100644 --- a/MERGE_CONFLICT_ANALYSIS.md +++ b/MERGE_CONFLICT_ANALYSIS.md @@ -1,132 +1,3 @@ -# Merge Conflict Analysis - Fix 404 Errors for Assets PR - -**Generated:** 2025-10-11 -**Branch:** copilot/fix-404-errors-for-assets -**Target:** main - ---- - -## Conflict Summary - -**Conflicting File:** `src/services/helpContentService.js` - -**Conflict Type:** Content modification conflict -**Severity:** Low - Simple path reference conflict -**Resolution Complexity:** Simple string replacement - ---- - -## Conflict Details - -### What Changed in This PR Branch - -Changed all badge icon paths from **absolute paths** to **relative paths** to fix 404 errors in feature branch deployments: - -```javascript -// BEFORE (in this PR branch at commit 66f083c) -badge: 'cat-paw-info-icon.svg' // Line 11 -badge: 'cat-paw-workflow-icon.svg' // Line 59 -badge: 'cat-paw-lock-icon.svg' // Line 93 -badge: 'cat-paw-bug-icon.svg' // Line ~150+ -// ... and 13 more badge references -``` - -### What Changed in Main Branch - -Main branch retained the **absolute paths** with `/sgex/` prefix: - -```javascript -// CURRENT (in main branch at commit 09cfe1f) -badge: '/sgex/cat-paw-info-icon.svg' // Line 11 -badge: '/sgex/cat-paw-workflow-icon.svg' // Line 59 -badge: '/sgex/cat-paw-lock-icon.svg' // Line 93 -badge: '/sgex/cat-paw-bug-icon.svg' // Line ~150+ -// ... and 13 more badge references -``` - -### Why This Conflict Exists - -Both branches modified the same badge path lines: -- **This PR branch:** Changed absolute paths (`/sgex/cat-paw-*.svg`) to relative paths (`cat-paw-*.svg`) -- **Main branch:** Added new badge references or modified help content while keeping absolute paths - -Git cannot automatically merge because it doesn't know which version to keep. - ---- - -## Resolution Options - -### Option 1: Accept This PR's Changes (Recommended) āœ… - -**What it does:** Keep all relative badge paths from this PR branch -**Why recommended:** Fixes the 404 errors in feature branch deployments - -**Benefits:** -- āœ… Fixes 404 errors in all deployment scenarios (local, main, feature branches) -- āœ… Simpler and more portable code -- āœ… No hardcoded path prefixes -- āœ… Browser resolves paths relative to current page URL -- āœ… Already tested and working in this PR - -**How to resolve:** -```bash -# Keep all changes from this PR branch -git checkout --ours src/services/helpContentService.js -git add src/services/helpContentService.js -``` - -**Then verify:** Check if any new badge references were added in main that need the relative path pattern applied. - ---- - -### Option 2: Accept Main's Changes - -**What it does:** Keep absolute badge paths from main branch -**Why NOT recommended:** Will reintroduce 404 errors in feature branch deployments - -**Drawbacks:** -- āŒ 404 errors will return in feature branch deployments -- āŒ Absolute paths don't work across different deployment contexts -- āŒ Undoes the fix this PR provides - -**How to resolve:** -```bash -# Keep main branch version (NOT RECOMMENDED) -git checkout --theirs src/services/helpContentService.js -git add src/services/helpContentService.js -``` - ---- - -### Option 3: Manual Merge (Most Thorough) šŸ”§ - -**What it does:** Carefully merge both changes, keeping relative paths while preserving any new content from main - -**When to use:** If main branch added new help topics or badge references - -**How to resolve:** -1. Open `src/services/helpContentService.js` in a text editor -2. Look for conflict markers: `<<<<<<<`, `=======`, `>>>>>>>` -3. For each badge path conflict: - - Keep the **relative path** version (without `/sgex/` prefix) - - Verify it matches pattern: `badge: 'cat-paw-*.svg'` -4. For any new content in main: - - Keep the new content - - But ensure any badge paths use relative format -5. Remove all conflict markers -6. Save the file -7. Test the changes -8. Stage the file: `git add src/services/helpContentService.js` - -**Example resolution:** -```javascript -<<<<<<< HEAD (this PR branch) -badge: 'cat-paw-icon.svg' -======= -badge: '/sgex/cat-paw-icon.svg' ->>>>>>> origin/main - -// RESOLVED: Keep relative path from PR branch badge: 'cat-paw-icon.svg' ``` @@ -277,3 +148,4 @@ If you need help resolving this conflict, please: 4. If still unclear, ask for clarification with specific questions about the conflict **Note:** This is a straightforward conflict with a clear recommended resolution. The relative path approach is tested and working in this PR. + diff --git a/README.md b/README.md index c226f5a46..5111605e6 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,20 @@ const user = validateAndCast('GitHubUser', userData); The project uses `eslint-plugin-jsx-a11y` to enforce accessibility best practices. See [docs/accessibility-linting.md](docs/accessibility-linting.md) for detailed information about accessibility rules and how to fix common issues. +### Security Checks + +The project includes comprehensive automated security checks that run on every PR build. These checks include: + +- **NPM Audit** - Scans for known vulnerabilities in dependencies +- **Outdated Dependencies** - Identifies packages needing updates +- **ESLint Security Rules** - Detects security issues in code +- **Security Headers** - Verifies security header configuration +- **License Compliance** - Checks for restrictive licenses +- **Secret Scanning** - Detects hardcoded secrets +- **Framework Compliance** - Ensures security best practices + +See [docs/security-checks.md](docs/security-checks.md) for detailed information about security checks and how to interpret results. + ### Troubleshooting If you encounter build or installation issues: diff --git a/docs/SECURITY_CHECK_IMPLEMENTATION.md b/docs/SECURITY_CHECK_IMPLEMENTATION.md new file mode 100644 index 000000000..4fce3d6c9 --- /dev/null +++ b/docs/SECURITY_CHECK_IMPLEMENTATION.md @@ -0,0 +1,242 @@ +# PR Security Check System - Implementation Summary + +## Issue Addressed +Improve security reporting on PR builds to provide: +- Condensed output +- Button/badge style (like GH Pages deployment workflow) +- Update existing comments instead of creating new ones +- Additional security checks beyond just npm audit + +## Solution Overview + +Implemented a comprehensive security check system that runs automatically on every PR build, featuring: + +1. **7 Different Security Checks** (vs. just 1 in the original) +2. **Condensed, Visual UI** with badges and color-coded status +3. **Automatic Comment Updates** (no duplicate comments) +4. **Actionable Recommendations** for fixing issues + +## Files Created + +### Core Scripts +- **`scripts/run-security-checks.js`** (14KB) + - Executes all 7 security checks + - Outputs structured JSON results + - Exit code indicates overall status + +- **`scripts/format-security-comment.js`** (7KB) + - Reads JSON results + - Formats as condensed PR comment + - Uses shields.io badges for visual appeal + +- **`scripts/manage-security-comment.py`** (5KB) + - Posts/updates PR comments + - Uses comment marker to identify and update existing comments + - Prevents duplicate comments + +### Workflow +- **`.github/workflows/pr-security-check.yml`** (5KB) + - Triggers on PR events and feature branch pushes + - Runs security checks after dependency installation + - Posts formatted results as PR comment + - Sets workflow status based on findings + +### Documentation +- **`docs/security-checks.md`** (8KB) + - Comprehensive guide to all security checks + - How to run locally + - Configuration and troubleshooting + - Best practices + +- **`docs/security-check-examples.md`** (6KB) + - Visual examples of different report states + - Comparison with old format + - Shows clean, warning, and failure states + +### Updates to Existing Files +- **`scripts/manage-pr-comment.py`** + - Added 'security-check' to ALLOWED_STAGES + - Added stage handler for security check results + +- **`.gitignore`** + - Added patterns for generated security files + - Prevents committing temporary check results + +- **`DEPLOYMENT.md`** + - Added PR Security Check workflow to documentation + +- **`README.md`** + - Added Security Checks section + - Links to security documentation + +## Security Checks Implemented + +### 1. NPM Audit šŸ” +- Scans dependencies for known vulnerabilities +- Reports by severity (Critical, High, Moderate, Low) +- Recommendation: Run `npm audit fix` + +### 2. Outdated Dependencies šŸ“¦ +- Identifies packages behind current versions +- Flags packages multiple major versions behind +- Helps prevent using packages with known issues + +### 3. ESLint Security Rules šŸ”’ +- Checks for security-related linting issues +- Detects `no-eval`, `no-dangerous-*` violations +- Catches potential code injection risks + +### 4. Security Headers šŸ›”ļø +- Verifies CSP, X-Frame-Options, etc. +- Checks both source and build artifacts +- Ensures protection against XSS, clickjacking + +### 5. License Compliance āš–ļø +- Scans for GPL, AGPL, LGPL licenses +- Flags packages with restrictive licenses +- Helps avoid legal compliance issues + +### 6. Secret Scanning šŸ” +- Detects potential hardcoded secrets +- Looks for API keys, tokens, passwords +- Finds AWS credentials, private keys +- **CRITICAL** - any findings must be addressed immediately + +### 7. Framework Compliance āœ… +- Ensures components follow project standards +- Validates security-related best practices +- Uses existing compliance scripts + +## Visual Design + +### Status Indicators +- 🟢 **Green** - Check passed +- 🟔 **Yellow** - Warnings detected +- šŸ”“ **Red** - Critical issues found +- šŸ”µ **Blue** - Informational findings +- ⚪ **White** - Check skipped + +### Badge Style +Uses shields.io badges matching GH Pages workflow: +``` +![Security Status](https://img.shields.io/badge/Security%20Status-SECURE-brightgreen?style=flat-square) +``` + +### Comment Format +```markdown +## šŸ”’ Security Check Report + +![Security Status](badge) + +**🟢 5 passed • 🟔 1 warnings** + +### Security Checks +[Table with all checks] + +### šŸ” Action Items +[Expandable details for issues] + +--- +### āœ… Security Status: CLEAN +``` + +## Workflow Integration + +### Triggers +- Pull request opened, synchronized, or reopened +- Pushes to feature branches +- Only when relevant files change (src/, package.json, etc.) + +### Process +1. Checkout code +2. Install dependencies (`npm ci --legacy-peer-deps`) +3. Run comprehensive security checks +4. Format results as PR comment +5. Update or create PR comment +6. Set workflow status (pass/warning/fail) +7. Upload results as workflow artifact + +### Behavior +- āœ… **Success** - All checks passed +- āš ļø **Neutral** - Warnings but not critical +- āŒ **Failure** - Critical security issues + +## Key Improvements vs Original + +| Feature | Original | Improved | +|---------|----------|----------| +| Number of checks | 1 (npm audit only) | 7 comprehensive checks | +| Output format | Verbose text | Condensed table | +| Visual design | Plain text with āœ… | Badges + colored circles | +| Comment behavior | Creates new comment | Updates existing comment | +| Action items | Generic message | Specific recommendations | +| Status reporting | Pass/Fail | Pass/Warn/Fail/Info/Skip | +| Expandable details | No | Yes (collapsible sections) | +| Local testing | Not mentioned | Fully documented | + +## Testing Results + +Tested locally on current repository: +``` +Summary: 5 passed, 1 warnings, 0 failed, 0 skipped + +āœ… NPM Audit - No vulnerabilities found +ā„¹ļø Outdated Dependencies - 9 outdated packages (0 major versions behind) +āœ… ESLint Security - No security-related linting issues +āš ļø Security Headers - Some headers missing in source (build not available) +āœ… License Compliance - No problematic licenses detected +āœ… Secret Scanning - No potential secrets detected in code +āœ… Framework Compliance - Framework compliance checks passed +``` + +## Usage + +### Automatic (via PR) +- Security checks run automatically on every PR +- Comment appears/updates with results +- No manual intervention needed + +### Manual (local testing) +```bash +# Install dependencies +npm ci --legacy-peer-deps + +# Run security checks +node scripts/run-security-checks.js + +# View formatted results +node scripts/format-security-comment.js +cat security-comment.md +``` + +## Future Enhancements + +Possible additions mentioned in documentation: +- [ ] GitHub CodeQL integration +- [ ] OWASP Dependency-Check +- [ ] Docker image scanning +- [ ] SAST tools integration +- [ ] Supply chain security checks +- [ ] Automated dependency updates +- [ ] Security scorecard + +## Documentation + +All documentation is in place: +- **Main guide**: `docs/security-checks.md` +- **Visual examples**: `docs/security-check-examples.md` +- **README section**: Links to security docs +- **DEPLOYMENT.md**: Updated with new workflow + +## Conclusion + +The new security check system provides comprehensive, automated security scanning on every PR build with a clean, condensed UI that matches the existing GH Pages workflow style. It addresses all requirements from the original issue: + +āœ… Condensed output (table format) +āœ… Button/badge style (shields.io badges + colored circles) +āœ… Updates existing comment (comment marker system) +āœ… Multiple security checks (7 different checks) +āœ… Actionable recommendations (specific guidance for each issue) +āœ… Consistent with project style (matches GH Pages workflow) + +The system is ready for use on PR builds and will automatically provide security feedback to developers. diff --git a/docs/additional-security-tools.md b/docs/additional-security-tools.md new file mode 100644 index 000000000..1015a85fb --- /dev/null +++ b/docs/additional-security-tools.md @@ -0,0 +1,344 @@ +# Additional Security Scanning Tools for SGeX Workbench + +This document provides recommendations for additional security scanning tools that can be integrated into the PR security check system. + +## Currently Implemented + +The current security check system includes: +1. NPM Audit - Dependency vulnerabilities +2. Outdated Dependencies - Version freshness +3. ESLint Security - Code security issues +4. Security Headers - Configuration validation +5. License Compliance - Restrictive licenses +6. Secret Scanning - Hardcoded credentials +7. Framework Compliance - Best practices + +## Recommended Additional Tools + +### 1. OWASP Dependency-Check + +**What it does:** Identifies known vulnerabilities in project dependencies using the National Vulnerability Database (NVD). + +**Why use it:** More comprehensive than npm audit alone, checks against broader vulnerability databases. + +**Implementation:** +```bash +# Install +npm install -g @owasp/dependency-check + +# Run check +dependency-check --project "SGeX" --scan . --format JSON --out ./dependency-check-report.json +``` + +**Integration Priority:** HIGH - Complements npm audit with additional vulnerability sources + +**Estimated effort:** 2-4 hours + +--- + +### 2. Snyk + +**What it does:** Advanced vulnerability scanning with fix suggestions, license checks, and container scanning. + +**Why use it:** Industry-leading tool with extensive vulnerability database and automated fix PRs. + +**Implementation:** +```bash +# Install +npm install -g snyk + +# Authenticate (requires account) +snyk auth + +# Run test +snyk test --json > snyk-report.json +``` + +**Integration Priority:** HIGH - Free tier available, excellent developer experience + +**Estimated effort:** 3-5 hours (includes account setup) + +--- + +### 3. GitHub CodeQL + +**What it does:** Semantic code analysis to find security vulnerabilities in source code. + +**Why use it:** Deep static analysis, finds logic errors and security issues npm audit can't detect. + +**Implementation:** +```yaml +# Add to .github/workflows/pr-security-check.yml +- name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: javascript + +- name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 +``` + +**Integration Priority:** VERY HIGH - Native GitHub integration, free for public repos + +**Estimated effort:** 2-3 hours + +--- + +### 4. Semgrep + +**What it does:** Fast, customizable static analysis tool for finding bugs and security issues. + +**Why use it:** Lightweight alternative to CodeQL, easy to customize rules for project-specific patterns. + +**Implementation:** +```bash +# Install +npm install -g @semgrep/cli + +# Run with security rules +semgrep --config=auto --json --output=semgrep-report.json +``` + +**Integration Priority:** MEDIUM - Good balance of speed and accuracy + +**Estimated effort:** 2-4 hours + +--- + +### 5. TruffleHog + +**What it does:** Advanced secret scanning that searches git history for accidentally committed secrets. + +**Why use it:** More thorough than basic regex patterns, scans entire git history. + +**Implementation:** +```bash +# Install +pip install trufflehog + +# Scan repository +trufflehog git file://. --json > trufflehog-report.json +``` + +**Integration Priority:** MEDIUM - Current secret scanning is basic regex-based + +**Estimated effort:** 2-3 hours + +--- + +### 6. GitGuardian + +**What it does:** Real-time secret detection with policy enforcement. + +**Why use it:** Prevents secrets from being committed, not just detecting after the fact. + +**Implementation:** +```bash +# Install pre-commit hook +npm install -g @gitguardian/ggshield + +# Configure in .github/workflows +ggshield secret scan ci +``` + +**Integration Priority:** MEDIUM - Requires API key, but has free tier + +**Estimated effort:** 3-4 hours + +--- + +### 7. SonarQube / SonarCloud + +**What it does:** Comprehensive code quality and security analysis platform. + +**Why use it:** Industry standard for continuous code quality inspection, tracks technical debt. + +**Implementation:** +```yaml +# SonarCloud GitHub Action +- name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} +``` + +**Integration Priority:** LOW-MEDIUM - More suited for mature projects, requires setup + +**Estimated effort:** 4-8 hours (includes configuration) + +--- + +### 8. npm-audit-resolver + +**What it does:** Interactive tool to resolve npm audit issues with better UX than `npm audit fix`. + +**Why use it:** Helps manage and track exceptions for known vulnerabilities. + +**Implementation:** +```bash +# Install +npm install -g npm-audit-resolver + +# Create audit exceptions +npm-audit-resolver --audit-report +``` + +**Integration Priority:** LOW - Nice to have, mainly for workflow improvement + +**Estimated effort:** 1-2 hours + +--- + +### 9. Retire.js + +**What it does:** Scanner detecting use of vulnerable JavaScript libraries. + +**Why use it:** Checks for known vulnerabilities in client-side JavaScript dependencies. + +**Implementation:** +```bash +# Install +npm install -g retire + +# Scan +retire --js --path . --outputformat json --outputpath retire-report.json +``` + +**Integration Priority:** MEDIUM - Useful for React applications + +**Estimated effort:** 2-3 hours + +--- + +### 10. Trivy + +**What it does:** Comprehensive vulnerability scanner for containers, filesystems, and git repositories. + +**Why use it:** All-in-one scanner, includes npm audit functionality plus OS package scanning. + +**Implementation:** +```bash +# Install +curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh + +# Scan +trivy fs --format json --output trivy-report.json . +``` + +**Integration Priority:** LOW-MEDIUM - Overkill for current needs, better for containerized apps + +**Estimated effort:** 3-5 hours + +--- + +### 11. Socket Security + +**What it does:** Detects supply chain attacks in dependencies, analyzes package behavior. + +**Why use it:** Emerging threat detection for malicious packages and suspicious behavior. + +**Implementation:** +```bash +# GitHub Action available +- uses: SocketDev/socket-security-action@v1 + with: + token: ${{ secrets.SOCKET_TOKEN }} +``` + +**Integration Priority:** LOW - Newer tool, addresses emerging threats + +**Estimated effort:** 2-3 hours + +--- + +### 12. Checkmarx KICS + +**What it does:** Infrastructure as Code security scanner (YAML, JSON, Terraform, etc.). + +**Why use it:** Scans workflow files and configuration for security issues. + +**Implementation:** +```bash +# Install +docker pull checkmarx/kics:latest + +# Scan +docker run -v $(pwd):/path checkmarx/kics:latest scan -p /path --output-path results +``` + +**Integration Priority:** LOW - More relevant if using IaC heavily + +**Estimated effort:** 3-4 hours + +--- + +## Recommended Implementation Plan + +### Phase 1: Quick Wins (2-4 weeks) +1. **GitHub CodeQL** - Best ROI, native integration +2. **Snyk** - Comprehensive vulnerability scanning +3. **TruffleHog** - Enhanced secret detection + +### Phase 2: Enhanced Coverage (4-6 weeks) +4. **Semgrep** - Custom security rules +5. **Retire.js** - JavaScript library vulnerabilities +6. **OWASP Dependency-Check** - Additional vulnerability sources + +### Phase 3: Advanced Features (6-8 weeks) +7. **GitGuardian** - Pre-commit secret prevention +8. **SonarCloud** - Code quality tracking +9. **Socket Security** - Supply chain attack detection + +## Integration Considerations + +### Performance Impact +- Running all tools would significantly increase build time +- Consider running some tools only on main branch or scheduled basis +- Use caching to speed up repeated scans + +### False Positives +- More tools = more potential false positives +- Implement suppression/exception mechanisms +- Maintain a `.securityignore` or similar file + +### Cost Considerations +- Most tools have free tiers for open source projects +- Some require API keys or accounts +- GitHub CodeQL is free for public repos + +### Workflow Structure +```yaml +# Suggested approach: Separate workflows by priority +jobs: + critical-checks: # Fast, runs on every PR + - npm audit + - CodeQL + - Secret scanning + + extended-checks: # Slower, runs on main or scheduled + - Snyk + - OWASP Dependency-Check + - SonarCloud +``` + +## Summary + +**Immediate Recommendations:** +1. **GitHub CodeQL** - Add semantic code analysis (highest priority) +2. **Snyk** - Enhanced vulnerability detection with fixes +3. **TruffleHog** - Better secret scanning + +**Long-term Enhancements:** +- Semgrep for custom security patterns +- SonarCloud for code quality tracking +- Socket Security for supply chain attacks + +All recommended tools integrate well with GitHub Actions and most offer free tiers for open source projects. + +## References + +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) +- [GitHub Security Features](https://github.com/features/security) +- [npm Security Best Practices](https://docs.npmjs.com/security) +- [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework) diff --git a/docs/codeql-detailed-explanation.md b/docs/codeql-detailed-explanation.md new file mode 100644 index 000000000..631a38075 --- /dev/null +++ b/docs/codeql-detailed-explanation.md @@ -0,0 +1,473 @@ +# GitHub CodeQL - Detailed Explanation + +## What is CodeQL? + +CodeQL is GitHub's semantic code analysis engine that treats code as data, allowing you to query it to find security vulnerabilities, bugs, and logic errors that traditional tools miss. + +## What Does CodeQL Test? + +### 1. Language Coverage for SGeX Workbench + +CodeQL supports the following languages used in this project: + +#### JavaScript/TypeScript (Primary Focus) +- **React Components** - All `.js` and `.jsx` files in `src/` +- **Node.js Scripts** - Build scripts, test utilities, CI/CD scripts +- **Service Files** - GitHub API integrations, caching services +- **Utility Functions** - Helper functions, validators, formatters + +#### Specifically for SGeX: +- **React App** (`src/**/*.js`, `src/**/*.jsx`) +- **Node Scripts** (`scripts/**/*.js`) +- **Build Configuration** (`package.json`, webpack configs) +- **Test Files** (`**/*.test.js`) + +### 2. Types of Security Issues CodeQL Detects + +#### A. Injection Vulnerabilities +**What it tests:** +- **SQL Injection** - Dynamic SQL queries with unsanitized input +- **Command Injection** - Shell commands with user input (e.g., `child_process.exec()`) +- **Code Injection** - `eval()`, `Function()` constructor with untrusted data +- **Path Traversal** - File operations with unsanitized paths +- **XSS (Cross-Site Scripting)** - Unsafe DOM manipulation, `dangerouslySetInnerHTML` + +**Example findings in React:** +```javascript +// āŒ BAD - CodeQL would flag this +function UserProfile({ username }) { + return
; +} + +// āœ… GOOD +function UserProfile({ username }) { + return
{username}
; // React auto-escapes +} +``` + +**Example findings in Node scripts:** +```javascript +// āŒ BAD - CodeQL would flag this +const { exec } = require('child_process'); +exec(`git commit -m "${userMessage}"`); // Command injection risk + +// āœ… GOOD +execSync('git', ['commit', '-m', userMessage]); // Parameterized +``` + +#### B. Authentication & Authorization +**What it tests:** +- **Missing authentication checks** - Unprotected API endpoints +- **Weak password policies** - Hardcoded credentials +- **Insecure token storage** - localStorage without encryption +- **Session management issues** - Token expiration, refresh logic + +**Example findings in SGeX:** +```javascript +// āŒ BAD - CodeQL would flag this +const token = "ghp_hardcoded_token_12345"; // Hardcoded secret + +// āœ… GOOD +const token = process.env.GITHUB_TOKEN; // Environment variable +``` + +#### C. Data Flow Analysis +**What it tests:** +- **Tainted data flow** - User input reaching sensitive operations +- **Information disclosure** - Sensitive data in logs or error messages +- **Insecure data transmission** - Unencrypted sensitive data +- **Memory leaks** - Improper cleanup of sensitive data + +**Example findings:** +```javascript +// āŒ BAD - CodeQL tracks tainted data flow +function processUserInput(input) { + console.log(`Processing: ${input}`); // Logs might contain sensitive data + localStorage.setItem('userData', input); // Unencrypted storage +} + +// āœ… GOOD +function processUserInput(input) { + const sanitized = sanitizeInput(input); + console.log('Processing user data'); // No sensitive info logged + encryptedStorage.setItem('userData', sanitized); +} +``` + +#### D. React-Specific Security Issues +**What it tests:** +- **Unsafe lifecycle methods** - `componentWillMount` with side effects +- **Props validation** - Missing PropTypes or TypeScript types +- **Key prop issues** - Using array indices as keys (potential XSS) +- **Ref misuse** - Direct DOM manipulation bypassing React +- **State mutation** - Direct state modification (security context) + +**Example findings:** +```javascript +// āŒ BAD - CodeQL would flag this +class UserList extends React.Component { + render() { + return this.props.users.map((user, index) => ( +
+ )); + } +} + +// āœ… GOOD +function UserList({ users }) { + return users.map(user => ( +
{user.bio}
+ )); +} +``` + +#### E. API & Network Security +**What it tests:** +- **Unvalidated redirects** - Open redirect vulnerabilities +- **CORS misconfigurations** - Overly permissive origins +- **Insecure HTTP** - Missing HTTPS enforcement +- **Missing rate limiting** - API abuse potential +- **Improper error handling** - Information leakage in errors + +**Example findings in GitHub API calls:** +```javascript +// āŒ BAD - CodeQL would flag this +async function fetchRepo(url) { + const response = await fetch(url); // No validation + return response.json(); +} + +// āœ… GOOD +async function fetchRepo(repoName) { + const url = `https://api.github.com/repos/${validateRepoName(repoName)}`; + const response = await fetch(url, { headers: authHeaders }); + if (!response.ok) throw new Error('Fetch failed'); + return response.json(); +} +``` + +#### F. Cryptography Issues +**What it tests:** +- **Weak algorithms** - MD5, SHA1 for security purposes +- **Insecure random** - `Math.random()` for security tokens +- **Hardcoded keys** - Encryption keys in source code +- **Improper encryption** - ECB mode, no IV + +**Example findings:** +```javascript +// āŒ BAD - CodeQL would flag this +function generateToken() { + return Math.random().toString(36); // Weak randomness +} + +// āœ… GOOD +function generateToken() { + return crypto.randomBytes(32).toString('hex'); // Cryptographically secure +} +``` + +### 3. CodeQL Query Categories + +CodeQL organizes checks into severity levels: + +#### Critical Severity +- Remote code execution vulnerabilities +- SQL injection in critical paths +- Authentication bypass +- Sensitive data exposure + +#### High Severity +- XSS vulnerabilities +- Path traversal +- Insecure deserialization +- Weak cryptography + +#### Medium Severity +- Information disclosure +- Missing input validation +- Insecure defaults +- Exception handling issues + +#### Low Severity +- Code quality issues with security implications +- Deprecated APIs with security risks +- Missing security headers recommendations + +### 4. What CodeQL Tests in SGeX Workbench Specifically + +#### React Application (`src/` directory) +``` +Tested Files: +ā”œā”€ā”€ src/App.js +ā”œā”€ā”€ src/components/**/*.js +│ ā”œā”€ā”€ Authentication components (PATLogin.js) +│ ā”œā”€ā”€ Editor components (BPMN, DMN editors) +│ ā”œā”€ā”€ Dashboard components +│ └── Helper components +ā”œā”€ā”€ src/services/**/*.js +│ ā”œā”€ā”€ githubService.js (API calls, authentication) +│ ā”œā”€ā”€ repositoryCacheService.js (data storage) +│ └── bugReportService.js (user input handling) +└── src/utils/**/*.js + └── Helper functions, validators + +Security Focus Areas: +1. GitHub API authentication and token handling +2. User input sanitization in bug reports +3. Repository URL validation +4. Local storage usage (cache, tokens) +5. DOM manipulation in editors +6. File upload/download operations +7. Markdown rendering (potential XSS) +8. Query parameter parsing +``` + +#### Node.js Scripts (`scripts/` directory) +``` +Tested Files: +ā”œā”€ā”€ scripts/run-security-checks.js +ā”œā”€ā”€ scripts/check-framework-compliance.js +ā”œā”€ā”€ scripts/manage-pr-comment.py (if JS version exists) +└── Build and deployment scripts + +Security Focus Areas: +1. Command injection in shell commands +2. File system operations (path traversal) +3. Environment variable handling +4. External process execution +5. Dependency resolution +6. Configuration file parsing +``` + +#### GitHub Actions Workflows (`.github/workflows/`) +``` +Tested Files: +ā”œā”€ā”€ .github/workflows/branch-deployment.yml +ā”œā”€ā”€ .github/workflows/pr-security-check.yml +└── Other workflow files + +Security Focus Areas: +1. Secret exposure in logs +2. Privilege escalation +3. Workflow injection +4. Insecure artifact handling +5. Third-party action security +``` + +## How CodeQL Works + +### 1. Code as Data +CodeQL creates a database representing your codebase: +``` +Source Code → Parse → Abstract Syntax Tree (AST) → CodeQL Database +``` + +### 2. Query Execution +Runs predefined security queries against the database: +``` +CodeQL Queries → Pattern Matching → Vulnerability Detection → Results +``` + +### 3. Data Flow Analysis +Tracks how data moves through your application: +``` +User Input → Function Calls → Operations → Sinks (dangerous operations) +``` + +**Example trace:** +```javascript +// CodeQL traces this data flow: +1. [SOURCE] const userInput = req.query.name +2. [STEP] const message = `Hello ${userInput}` +3. [STEP] const html = `
${message}
` +4. [SINK] element.innerHTML = html // āš ļø XSS vulnerability +``` + +## Implementation for SGeX Workbench + +### Option 1: Separate Workflow (Recommended) + +Create `.github/workflows/codeql-analysis.yml`: + +```yaml +name: "CodeQL Security Analysis" + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + schedule: + - cron: '0 0 * * 1' # Weekly Monday at midnight + +jobs: + analyze: + name: Analyze JavaScript/TypeScript + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: javascript + queries: security-extended, security-and-quality + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:javascript" +``` + +### Option 2: Integrate into Existing Security Check + +Add to `.github/workflows/pr-security-check.yml`: + +```yaml +- name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: javascript + +- name: Install dependencies + run: npm ci --legacy-peer-deps + +- name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 +``` + +## CodeQL Results Format + +### 1. GitHub Security Tab +Results appear in: `https://github.com/litlfred/sgex/security/code-scanning` + +### 2. PR Annotations +Inline comments on the exact lines with vulnerabilities + +### 3. JSON Output +```json +{ + "results": [ + { + "ruleId": "js/sql-injection", + "level": "error", + "message": { + "text": "This query depends on a user-provided value." + }, + "locations": [{ + "physicalLocation": { + "artifactLocation": { + "uri": "src/services/githubService.js" + }, + "region": { + "startLine": 42, + "startColumn": 15 + } + } + }] + } + ] +} +``` + +## Comparison: CodeQL vs Current Security Checks + +| Feature | Current Checks | CodeQL | +|---------|---------------|--------| +| **Scope** | Dependencies, configs | Source code logic | +| **Language** | Package metadata | JavaScript/TypeScript AST | +| **Depth** | Surface-level | Deep semantic analysis | +| **React Support** | āœ… ESLint rules | āœ… React-specific queries | +| **Node.js Support** | āœ… npm audit | āœ… Node.js-specific queries | +| **Data Flow** | āŒ No | āœ… Yes - traces tainted data | +| **False Positives** | Low | Medium (but more accurate) | +| **Runtime** | ~30 seconds | ~5-10 minutes | +| **Cost** | Free | Free for public repos | + +## Benefits for SGeX Workbench + +1. **Complements npm audit** - Finds logic bugs npm audit can't detect +2. **React security** - Detects XSS in component rendering +3. **GitHub API security** - Validates token handling, API calls +4. **Node script safety** - Prevents command injection in build scripts +5. **Data flow tracking** - Ensures user input is sanitized +6. **Native integration** - Built into GitHub, no external services + +## Example Findings CodeQL Might Report + +Based on SGeX codebase: + +### 1. GitHub Token Exposure +```javascript +// Location: src/services/githubService.js +// Severity: CRITICAL +console.log(`Using token: ${token}`); // Token in logs +``` + +### 2. Unsafe DOM Manipulation +```javascript +// Location: src/components/BPMNEditor.js +// Severity: HIGH +element.innerHTML = userProvidedBPMN; // XSS risk +``` + +### 3. Command Injection +```javascript +// Location: scripts/deployment.js +// Severity: CRITICAL +exec(`git checkout ${branchName}`); // Unsanitized branch name +``` + +### 4. Path Traversal +```javascript +// Location: src/services/fileService.js +// Severity: HIGH +fs.readFile(`./dak/${userFilename}`); // No path validation +``` + +### 5. Insecure Random +```javascript +// Location: src/utils/helpers.js +// Severity: MEDIUM +const sessionId = Math.random().toString(); // Weak randomness +``` + +## Recommended Configuration + +For SGeX Workbench, use: + +```yaml +queries: security-extended, security-and-quality +``` + +This includes: +- All standard security queries +- Additional quality checks with security implications +- React-specific patterns +- Node.js-specific patterns + +## Timeline & Effort + +- **Initial Setup:** 30 minutes +- **First Scan:** 5-10 minutes (one-time) +- **Subsequent Scans:** 2-5 minutes +- **Reviewing Results:** 1-2 hours (initial triage) +- **Fixing Issues:** Variable (depends on findings) + +## Conclusion + +CodeQL provides deep semantic analysis of JavaScript/React/Node.js code, finding security vulnerabilities that dependency scanners miss. It's particularly valuable for SGeX Workbench because: + +1. **React Security** - Validates component rendering, props, state management +2. **GitHub Integration** - Analyzes GitHub API usage and authentication +3. **Build Scripts** - Secures Node.js scripts for deployment and CI/CD +4. **Data Flow** - Tracks user input from GitHub through the application +5. **Free for Public Repos** - No cost, native GitHub integration + +It complements (not replaces) the existing npm audit and security header checks by focusing on code logic rather than dependency vulnerabilities. diff --git a/docs/security-check-examples.md b/docs/security-check-examples.md new file mode 100644 index 000000000..ffdb3d607 --- /dev/null +++ b/docs/security-check-examples.md @@ -0,0 +1,193 @@ +# Security Check Report - Visual Examples + +This document shows visual examples of the security check report format that appears on pull requests. + +## Example 1: All Checks Passing (Clean) + +``` +šŸ”’ Security Check Report + +![Security Status](brightgreen badge: SECURE) + +🟢 7 passed + +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Check │ Status │ Details │ +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ NPM Audit │ 🟢 │ No vulnerabilities found │ +│ Outdated Dependencies │ 🟢 │ All dependencies up to date │ +│ ESLint Security │ 🟢 │ No security issues │ +│ Security Headers │ 🟢 │ Properly configured │ +│ License Compliance │ 🟢 │ No problematic licenses │ +│ Secret Scanning │ 🟢 │ No secrets detected │ +│ Framework Compliance │ 🟢 │ Checks passed │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + +--- + +āœ… Security Status: CLEAN + +All security checks passed successfully. Your changes maintain the security +posture of the project. + +Last checked: Sat, 11 Oct 2025 12:28:23 GMT +``` + +## Example 2: With Warnings + +``` +šŸ”’ Security Check Report + +![Security Status](yellow badge: WARNINGS) + +🟢 5 passed • 🟔 1 warnings • šŸ”µ 1 info + +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Check │ Status │ Details │ +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ NPM Audit │ 🟢 │ No vulnerabilities found │ +│ Outdated Dependencies │ šŸ”µ │ 9 outdated packages │ +│ ESLint Security │ 🟢 │ No security issues │ +│ Security Headers │ 🟔 │ Some headers missing in source │ +│ License Compliance │ 🟢 │ No problematic licenses │ +│ Secret Scanning │ 🟢 │ No secrets detected │ +│ Framework Compliance │ 🟢 │ Checks passed │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + +šŸ” Action Items + +ā–¼ āš ļø Security Headers - Some security headers missing in source + + Details: + [Specific details about missing headers] + + Recommendation: Ensure all security headers are properly defined + +--- + +āš ļø Security Status: WARNINGS + +Some security warnings were detected. Please review the action items above. + +Last checked: Sat, 11 Oct 2025 12:28:23 GMT +``` + +## Example 3: Critical Issues Found + +``` +šŸ”’ Security Check Report + +![Security Status](red badge: ISSUES FOUND) + +🟢 4 passed • 🟔 1 warnings • šŸ”“ 2 failed + +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Check │ Status │ Details │ +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ NPM Audit │ šŸ”“ │ 5 vulnerabilities (2 critical) │ +│ Outdated Dependencies │ 🟔 │ 12 outdated packages │ +│ ESLint Security │ 🟢 │ No security issues │ +│ Security Headers │ 🟢 │ Properly configured │ +│ License Compliance │ 🟢 │ No problematic licenses │ +│ Secret Scanning │ šŸ”“ │ 2 potential secrets found │ +│ Framework Compliance │ 🟢 │ Checks passed │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + +šŸ” Action Items + +ā–¼ āŒ NPM Audit - 5 vulnerabilities found (Critical: 2, High: 3) + + Details: + - Critical: 2 + - High: 3 + - Moderate: 0 + - Low: 0 + + Recommendation: Run `npm audit fix` to automatically fix vulnerabilities + +ā–¼ āŒ Secret Scanning - 2 potential secrets found + + Potential secrets found in: + - `src/config/api.js` (API Keys) + - `src/utils/auth.js` (Tokens) + + Recommendation: Review flagged files and remove any hardcoded secrets + +--- + +āŒ Security Status: ACTION REQUIRED + +Security issues were detected that need to be addressed before merging. + +Last checked: Sat, 11 Oct 2025 12:28:23 GMT +``` + +## Key Features + +### Condensed Format +- Single badge showing overall status (green/yellow/red) +- Compact table showing all checks at a glance +- Expandable details for issues + +### Button/Badge Style (like GH Pages) +- Uses shields.io badges for status +- Color-coded status indicators (šŸŸ¢šŸŸ”šŸ”“šŸ”µāšŖ) +- Styled buttons for actions + +### Update Existing Comment +- Comment is updated in place on each build +- Uses comment marker for identification +- Preserves workflow history + +### Multiple Security Checks +1. **NPM Audit** - Dependency vulnerabilities +2. **Outdated Dependencies** - Version freshness +3. **ESLint Security** - Code security issues +4. **Security Headers** - Header configuration +5. **License Compliance** - License risks +6. **Secret Scanning** - Hardcoded secrets +7. **Framework Compliance** - Best practices + +### Status Levels +- 🟢 **Pass** - No issues found +- 🟔 **Warning** - Non-critical issues +- šŸ”“ **Fail** - Critical issues +- šŸ”µ **Info** - Informational findings +- ⚪ **Skip** - Check not applicable + +## Integration + +The security check runs automatically on: +- Pull request events (opened, synchronize, reopened) +- Pushes to feature branches + +Workflow: `.github/workflows/pr-security-check.yml` + +## Comparison to Old Format + +### OLD (from issue description): +``` +šŸ”’ Dependency Security Check Results +āœ… **No vulnerabilities found!** + +All dependencies have been scanned and no security vulnerabilities were detected. + +``` +found 0 vulnerabilities +``` + +--- + +### āœ… Security Status: CLEAN +Your changes maintain the security posture of th +``` + +### NEW (improved): +- āœ… Condensed format with table +- āœ… Button/badge style with shields.io +- āœ… Updates existing comment +- āœ… 7 different security checks (not just npm audit) +- āœ… Color-coded status circles +- āœ… Expandable details sections +- āœ… Clear action items with recommendations +- āœ… Consistent formatting with GH Pages workflow diff --git a/docs/security-check-next-steps.md b/docs/security-check-next-steps.md new file mode 100644 index 000000000..805837cd5 --- /dev/null +++ b/docs/security-check-next-steps.md @@ -0,0 +1,221 @@ +# Security Check Integration - Next Steps + +## Completed in This PR āœ… + +### Task 1: Remove Duplicate npm Audit āœ… +**COMPLETED** - Removed duplicate npm audit from `code-quality.yml` + +**Changes Made:** +- Removed entire `security-audit` job from `.github/workflows/code-quality.yml` +- Updated `success-summary` job to only depend on `framework-compliance` +- Added note in summary linking to PR Security Check workflow +- **Impact:** Saves ~2 minutes per PR, eliminates duplicate comments + +### Task 2: Extract Common Comment Management āœ… +**COMPLETED** - Created shared comment management module + +**New Files:** +- `scripts/lib/pr-comment-manager.js` - Shared PR comment management class +- `scripts/lib/pr-comment-manager.test.js` - Test suite with 11 tests + +**Updated Files:** +- `.github/workflows/code-quality.yml` - Now uses shared comment manager + +**Implementation:** +```javascript +const PRCommentManager = require('./scripts/lib/pr-comment-manager.js'); +const manager = new PRCommentManager(github, context, ''); +await manager.updateOrCreateComment(prNumber, commentBody); +``` + +**Impact:** +- DRY principle applied +- Consistent behavior across workflows +- Easier to maintain (single source of truth) +- Fully tested (11 passing tests) + +## What's in This PR + +### Complete Security Check System +- New workflow: `.github/workflows/pr-security-check.yml` +- Security check system with 7 comprehensive checks +- Improved PR comment formatting with badges and tables +- 25 unit tests for security checks +- 11 unit tests for shared comment manager +- **Total: 36 passing tests** + +### Optimized Code Quality Workflow +- Removed duplicate npm audit (now only in security check) +- Uses shared PR comment manager +- Links to security check workflow for audit results +- Faster execution (one less job) + +### Shared Infrastructure +- Common PR comment management module +- Reusable across all workflows +- Consistent comment update behavior +- Well-tested and documented + +## Follow-up Tasks (Future PRs) + +### Task 3: Add Tests for Framework Compliance (Priority: Medium) + +**Problem:** Framework compliance has no automated tests + +**Solution:** Add Jest tests similar to security check tests + +**New File:** `scripts/check-framework-compliance.test.js` + +**Test Coverage:** +```javascript +describe('Framework Compliance Checker', () => { + describe('Component Analysis', () => { + it('should detect PageLayout usage'); + it('should validate pageName uniqueness'); + it('should check for framework hooks'); + }); + + describe('Compliance Scoring', () => { + it('should calculate compliance score'); + it('should categorize as compliant/partial/non-compliant'); + }); +}); +``` + +**Impact:** +- Prevents regressions +- Documents expected behavior +- Easier refactoring +- **Estimated time:** 4-6 hours + +### Task 4: Unified Report Format (Priority: Low) + +**Problem:** Different comment formats across workflows + +**Solution:** Standardize on shields.io badges + tables format + +**Changes:** +1. Update framework compliance comment to use shields.io badges +2. Use HTML tables for compliance results +3. Add expandable details sections +4. Match security check visual style + +**Impact:** +- Consistent user experience +- Professional appearance +- Easier to scan results +- **Estimated time:** 3-4 hours + +## Long-term Vision (Future Sprints) + +### Unified Code Quality & Security Workflow + +**Goal:** Single workflow for all code quality and security checks + +**Structure:** +```yaml +name: Code Quality & Security + +jobs: + security-checks: + name: Security Analysis + # Runs: npm audit, secrets, headers, licenses + + framework-compliance: + name: Code Structure + # Runs: page framework, profile compliance + + code-quality: + name: Linting & Types + # Runs: eslint, typescript, prettier + + summary: + name: Quality Report + needs: [security-checks, framework-compliance, code-quality] + # Creates single unified comment with tabs/sections +``` + +**Comment Format:** +```markdown +## šŸ“Š Code Quality & Security Report + +### šŸ”’ Security Checks +[Shields.io status badge] +[Expandable table with results] + +### šŸ—ļø Framework Compliance +[Shields.io status badge] +[Expandable table with results] + +### šŸ” Code Quality +[Shields.io status badge] +[Expandable table with results] + +--- +Overall Status: āœ… All checks passed +``` + +**Benefits:** +- Single point of truth +- Easier to understand overall quality +- Faster feedback loop +- Better developer experience + +**Estimated Effort:** 2-3 days +**Priority:** Low (nice to have, not urgent) + +## Decision Matrix + +| Task | Priority | Effort | Impact | When | +|------|----------|--------|--------|------| +| Remove duplicate npm audit | Medium | 30 min | High | Next PR | +| Extract common comment logic | Low | 2-3 hrs | Medium | Sprint 2 | +| Add compliance tests | Medium | 4-6 hrs | High | Sprint 2 | +| Unified report format | Low | 3-4 hrs | Medium | Sprint 3 | +| Unified workflow | Low | 2-3 days | High | Future | + +## Recommendation + +**For This PR:** +- āœ… Merge as-is +- āœ… Document overlap in PR description +- āœ… Create follow-up issues for tasks above + +**Next Steps:** +1. Merge this PR +2. Create GitHub issue: "Remove duplicate npm audit from code-quality.yml" +3. Create GitHub issue: "Add tests for framework compliance checker" +4. Monitor both systems in parallel for 1-2 weeks +5. Remove duplication once new system proves stable + +## Questions to Answer + +Before implementing follow-up tasks: + +1. **Should framework compliance be integrated into security check?** + - Pro: Single comprehensive check system + - Con: Framework compliance is different concern (structure vs security) + - Decision: Keep separate for now, consider consolidation later + +2. **Which system should be the "primary" for npm audit?** + - Answer: New security check system (better formatting, more comprehensive) + - Action: Remove from code-quality.yml + +3. **Should we consolidate workflows now or later?** + - Answer: Later, after new system proves stable + - Reason: Avoid breaking changes, test in production first + +## Success Metrics + +After implementing follow-up tasks: + +- āœ… No duplicate checks running +- āœ… Single npm audit per PR +- āœ… Consistent comment format +- āœ… Test coverage > 80% for all checks +- āœ… Faster PR build times +- āœ… Better developer experience + +## Conclusion + +The security check system is ready to merge. The overlap with framework compliance is minimal and intentional. Follow-up tasks will optimize the system further, but the current implementation adds significant value without breaking existing functionality. diff --git a/docs/security-checks.md b/docs/security-checks.md new file mode 100644 index 000000000..ff807b65b --- /dev/null +++ b/docs/security-checks.md @@ -0,0 +1,247 @@ +# Security Check System + +The SGeX Workbench implements a comprehensive security check system that automatically runs on every pull request build to ensure code quality and security standards are maintained. + +## Overview + +The security check system consists of three main components: + +1. **Security Check Script** (`scripts/run-security-checks.js`) - Executes multiple security checks +2. **Comment Formatter** (`scripts/format-security-comment.js`) - Formats results as GitHub PR comment +3. **Comment Manager** (`scripts/manage-security-comment.py`) - Updates PR comments with results + +## Security Checks Performed + +### 1. NPM Audit šŸ” +**What it checks:** Scans all npm dependencies for known security vulnerabilities using the npm audit database. + +**Severity levels:** Critical, High, Moderate, Low + +**Action items:** +- Run `npm audit fix` to automatically fix vulnerabilities +- For vulnerabilities requiring manual intervention, update affected packages or add overrides + +### 2. Outdated Dependencies šŸ“¦ +**What it checks:** Identifies packages that are outdated, especially those with major version updates available. + +**Why it matters:** Outdated packages may contain security vulnerabilities that have been fixed in newer versions. + +**Action items:** +- Review outdated packages and update where possible +- Prioritize packages that are multiple major versions behind + +### 3. ESLint Security Rules šŸ”’ +**What it checks:** Runs ESLint with focus on security-related rules including: +- `no-eval` - Prevents dangerous eval() usage +- `no-dangerous-*` - Prevents dangerous HTML/DOM operations +- Security-specific ESLint plugins + +**Action items:** +- Fix ESLint errors related to security +- Review and address security warnings + +### 4. Security Headers šŸ›”ļø +**What it checks:** Verifies that security headers are properly configured in the application: +- Content Security Policy (CSP) +- X-Frame-Options +- X-Content-Type-Options +- Referrer Policy +- Permissions Policy + +**Action items:** +- Ensure all required security headers are present in `public/index.html` +- Review and update CSP directives as needed + +### 5. License Compliance āš–ļø +**What it checks:** Scans dependencies for restrictive licenses (GPL, AGPL, LGPL) that may have legal implications. + +**Why it matters:** Some licenses require source code disclosure or have other restrictions. + +**Action items:** +- Review packages with restrictive licenses +- Consider alternative packages with more permissive licenses +- Ensure compliance with license requirements + +### 6. Secret Scanning šŸ” +**What it checks:** Scans source code for potential hardcoded secrets including: +- API keys +- Authentication tokens +- Passwords +- Private keys +- AWS credentials + +**Critical:** Any detected secrets should be removed immediately and rotated. + +**Action items:** +- Remove any hardcoded secrets from source code +- Use environment variables or secret management systems +- Rotate any exposed credentials + +### 7. Framework Compliance āœ… +**What it checks:** Ensures components follow the project's framework standards and security best practices. + +**Action items:** +- Review framework compliance issues +- Update components to follow project standards + +## PR Comment Format + +The security check system posts a condensed, easy-to-read comment on pull requests with the following structure: + +```markdown +## šŸ”’ Security Check Report + +![Security Status](badge) + +**🟢 5 passed • 🟔 1 warnings • šŸ”“ 0 failed** + +### Security Checks + +| Check | Status | Details | +|-------|--------|---------| +| NPM Audit | 🟢 | No vulnerabilities found | +| Outdated Dependencies | šŸ”µ | 9 outdated packages | +| ESLint Security | 🟢 | No security issues | +| Security Headers | 🟔 | Some headers missing | +| License Compliance | 🟢 | No problematic licenses | +| Secret Scanning | 🟢 | No secrets detected | +| Framework Compliance | 🟢 | Checks passed | + +### šŸ” Action Items +[Expandable details for each failed/warned check] + +--- +### āœ… Security Status: CLEAN +``` + +### Status Indicators + +- 🟢 **Pass** - Check completed successfully with no issues +- 🟔 **Warning** - Check found non-critical issues that should be reviewed +- šŸ”“ **Fail** - Check found critical issues that must be addressed +- šŸ”µ **Info** - Check completed with informational findings +- ⚪ **Skip** - Check was skipped (e.g., build artifacts not available) + +## Workflow Integration + +The security checks run automatically via the `.github/workflows/pr-security-check.yml` workflow: + +**Triggers:** +- Pull request events (opened, synchronize, reopened) targeting main +- Pushes to feature branches + +**Process:** +1. Checkout code and install dependencies +2. Run comprehensive security checks +3. Format results as PR comment +4. Update or create comment on PR +5. Set workflow status based on results + +**Status behavior:** +- āœ… **Success** - All checks passed +- āš ļø **Neutral** - Warnings detected but not critical +- āŒ **Failure** - Critical security issues found + +## Running Locally + +You can run security checks locally before pushing code: + +```bash +# Install dependencies +npm ci --legacy-peer-deps + +# Run security checks +node scripts/run-security-checks.js + +# View formatted results +node scripts/format-security-comment.js +cat security-comment.md +``` + +## Configuration + +### Adding New Security Checks + +To add a new security check: + +1. Add a new check function in `scripts/run-security-checks.js`: +```javascript +function checkNewSecurity() { + console.log('šŸ” Running new security check...'); + + // Perform check logic + + return { + name: 'Check Name', + id: 'check-id', + status: 'pass', // or 'fail', 'warn', 'info', 'skip' + severity: 'none', // or 'critical', 'high', 'moderate', 'low' + summary: 'Description of results', + details: { + // Additional details object + }, + recommendation: 'What to do if issues found' + }; +} +``` + +2. Add the check to the main execution: +```javascript +checks.push(checkNewSecurity()); +``` + +3. (Optional) Add custom formatting in `scripts/format-security-comment.js` if needed + +### Customizing Check Behavior + +You can adjust check behavior by modifying the check functions: + +- **Thresholds**: Adjust what counts as a failure vs warning +- **Patterns**: Modify regex patterns for secret scanning +- **Severity mapping**: Change how issues are classified + +## Best Practices + +1. **Review all security warnings** - Even warnings can indicate potential issues +2. **Don't ignore secret scanning alerts** - These are critical security risks +3. **Keep dependencies updated** - Regularly update to get security fixes +4. **Test security headers** - Verify headers work correctly in deployed environment +5. **Document exceptions** - If you must use a flagged pattern, document why + +## Troubleshooting + +### Check fails unexpectedly +- Ensure all dependencies are installed: `npm ci --legacy-peer-deps` +- Check that the build directory exists if needed +- Review the full output in the workflow logs + +### Comment not updating +- Verify the PR has proper permissions +- Check that the GitHub token has `pull-requests: write` permission +- Look for errors in the "Update PR comment" step + +### False positives +- Review the check logic in the security check script +- Consider adjusting thresholds or patterns +- Document why a finding is not a real issue + +## Additional Resources + +- [npm audit documentation](https://docs.npmjs.com/cli/v8/commands/npm-audit) +- [OWASP Security Guidelines](https://owasp.org/) +- [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) +- [GitHub Security Best Practices](https://docs.github.com/en/code-security) + +## Future Enhancements + +Potential additions to the security check system: + +- [ ] Integration with GitHub CodeQL +- [ ] OWASP Dependency-Check +- [ ] Docker image scanning (when containerized) +- [ ] SAST (Static Application Security Testing) tools +- [ ] Supply chain security checks +- [ ] Automated dependency updates via Dependabot +- [ ] Security scorecard integration + +For a comprehensive guide to additional security scanning tools (OWASP, Snyk, CodeQL, Semgrep, TruffleHog, and more), see [Additional Security Tools Guide](additional-security-tools.md). diff --git a/docs/security-framework-overlap-analysis.md b/docs/security-framework-overlap-analysis.md new file mode 100644 index 000000000..3f8bfdd7f --- /dev/null +++ b/docs/security-framework-overlap-analysis.md @@ -0,0 +1,244 @@ +# Security Check vs Framework Compliance - Overlap Analysis + +## Executive Summary + +After analyzing the existing framework compliance system in `.github/workflows/code-quality.yml` and comparing it with the new security check system in `.github/workflows/pr-security-check.yml`, there are **significant overlaps** in implementation patterns but **different purposes and scope**. + +## Key Findings + +### Overlapping Functionality + +1. **PR Comment Management Pattern** + - Both use identical comment update/create logic + - Both search for existing comments by content marker + - Both update existing comments instead of creating duplicates + - Both use GitHub Actions `actions/github-script@v8` for comment management + +2. **Check Execution Pattern** + - Both run Node.js-based check scripts + - Both capture output and exit codes + - Both format results for display + - Both run on PR events (opened, synchronize, reopened) + +3. **Error Handling** + - Both use `continue-on-error` for checks + - Both create error comments when checks fail + - Both have separate steps to determine final pass/fail status + +### Key Differences + +| Aspect | Framework Compliance | Security Check | +|--------|---------------------|----------------| +| **Purpose** | Code structure and pattern compliance | Security vulnerability detection | +| **Scope** | Page components, hooks, framework usage | Dependencies, secrets, headers, licenses | +| **Tools** | Custom JS script (`check-framework-compliance.js`) | Multiple tools (npm audit, eslint, custom scanners) | +| **Comment Format** | Plain text with emoji | Shields.io badges, HTML tables, expandable details | +| **Update Method** | Direct GitHub API (inline JavaScript) | Python script wrapper (`manage-security-comment.py`) | +| **Workflow File** | `code-quality.yml` | `pr-security-check.yml` | + +## Detailed Overlap Analysis + +### 1. Comment Finding & Updating Logic + +**Framework Compliance** (code-quality.yml lines 365-393): +```javascript +const existingComment = comments.find(comment => + comment.body.includes('Page Framework Compliance Check Results') +); + +if (existingComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body: commentBody + }); +} else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: commentBody + }); +} +``` + +**Security Check** (manage-security-comment.py lines 46-62 + 74-100): +```python +def get_existing_comment(self) -> Optional[Dict[str, Any]]: + comments = response.json() + for comment in comments: + if self.COMMENT_MARKER in comment.get('body', ''): + return comment + return None + +def update_or_create_comment(self, security_comment: str) -> bool: + existing = self.get_existing_comment() + if existing: + # Update existing + response = requests.patch(url, headers=self.headers, json=payload) + else: + # Create new + response = requests.post(url, headers=self.headers, json=payload) +``` + +**Analysis:** Nearly identical logic, just implemented in different languages (JavaScript inline vs Python module). + +### 2. Security Audit Overlap + +**Existing** (code-quality.yml lines 39-76): +```yaml +jobs: + security-audit: + name: Dependency Security Check + steps: + - name: Run security audit + run: | + audit_output=$(npm audit 2>&1) + audit_exit_code=$? + npm audit --json > audit_results.json +``` + +**New** (pr-security-check.yml + run-security-checks.js): +```javascript +function runNpmAudit() { + const result = execCommand('npm audit --json'); + let auditData = JSON.parse(result.output || '{}'); + // ... parsing and formatting +} +``` + +**Analysis:** Both run `npm audit`, but: +- Old: Simple shell script, basic text output +- New: JavaScript module with structured JSON output, integrated with 6 other checks + +## Recommendations + +### Option 1: Consolidate into Single Security Check Workflow (Recommended) + +**Benefits:** +- Single source of truth for security checks +- Consistent comment format across all checks +- Easier to maintain +- Better user experience (one consolidated report) + +**Implementation:** +1. Integrate framework compliance check into `run-security-checks.js` as 8th check +2. Deprecate security-audit job from `code-quality.yml` +3. Keep framework compliance in `code-quality.yml` for now (backward compatibility) +4. Update security check workflow to run on same triggers as code-quality + +### Option 2: Keep Separate but Align Comment Management + +**Benefits:** +- Clear separation of concerns +- Easier to understand what each workflow does +- Can run independently + +**Implementation:** +1. Extract common comment management logic to shared script +2. Use same comment marker pattern across both +3. Ensure comments don't conflict (different headers) + +### Option 3: Merge Workflows Entirely + +**Benefits:** +- Single "Code Quality & Security" workflow +- All checks in one place +- Single PR comment with multiple sections + +**Implementation:** +1. Merge `pr-security-check.yml` into `code-quality.yml` +2. Add security check job alongside existing jobs +3. Create unified summary job +4. Single consolidated comment + +## Specific Overlap: npm audit + +The most significant overlap is the npm audit functionality: + +**Current State:** +- `code-quality.yml` runs npm audit independently +- `pr-security-check.yml` runs npm audit as part of comprehensive check + +**Recommendation:** +Remove npm audit from `code-quality.yml` since the new security check provides: +- Better formatting (shields.io badges, tables) +- Integration with other security checks +- More detailed vulnerability breakdown +- Structured JSON output +- Better comment management (Python module vs inline JavaScript) + +## Implementation Pattern Reuse + +The new security check system demonstrates better practices: + +1. **Modular Design:** + - `run-security-checks.js` - Check execution + - `format-security-comment.js` - Formatting + - `manage-security-comment.py` - Comment management + +2. **Better Separation:** + - Check logic separate from formatting + - Formatting separate from GitHub API interaction + - Testable modules (25 unit tests) + +3. **Enhanced UI:** + - Shields.io badges for visual status + - HTML tables for compact display + - Expandable details for failures + - Color-coded status circles + +**Recommendation:** Consider refactoring framework compliance to use similar patterns. + +## Migration Path + +### Phase 1: Short-term (This PR) +- āœ… Deploy new security check system +- āš ļø Keep both systems running (no breaking changes) +- Document the overlap in PR description + +### Phase 2: Medium-term (Next sprint) +- Remove npm audit from `code-quality.yml` +- Update code-quality to reference security check for audit results +- Add link from framework compliance comment to security check comment + +### Phase 3: Long-term (Future enhancement) +- Consolidate all checks into unified "Code Quality & Security" workflow +- Single consolidated PR comment with tabbed sections +- Shared comment management library +- Unified reporting format + +## Conclusion + +There is **significant overlap** in: +- PR comment management patterns +- npm audit functionality +- GitHub Actions workflow structure + +However, the systems serve **different purposes**: +- Framework Compliance: Code structure and patterns +- Security Check: Security vulnerabilities and risks + +**Recommended Action:** +1. Accept this PR as-is (adds value without breaking existing functionality) +2. Create follow-up issue to remove npm audit duplication from code-quality.yml +3. Consider longer-term consolidation of comment management logic + +## Files with Overlapping Functionality + +| Functionality | Framework Compliance | Security Check | +|---------------|---------------------|----------------| +| PR Comment Management | `.github/workflows/code-quality.yml` (lines 365-393) | `scripts/manage-security-comment.py` | +| NPM Audit | `.github/workflows/code-quality.yml` (lines 39-76) | `scripts/run-security-checks.js` (runNpmAudit) | +| Check Execution | Inline in workflow | Modular scripts | +| Comment Formatting | Basic text + emoji | Badges + tables + expandable details | + +## Test Coverage Comparison + +| System | Tests | Coverage | +|--------|-------|----------| +| Framework Compliance | Manual testing only | None documented | +| Security Check | 25 unit tests | Security summary, comment formatting, status display | + +**Recommendation:** Add tests for framework compliance using similar patterns from security check tests. diff --git a/scripts/format-security-comment.js b/scripts/format-security-comment.js new file mode 100755 index 000000000..172e37551 --- /dev/null +++ b/scripts/format-security-comment.js @@ -0,0 +1,216 @@ +#!/usr/bin/env node +/** + * Format security check results into a condensed PR comment + * + * This script reads the security check results JSON and formats it + * into a GitHub-styled comment with buttons/badges similar to the + * GH Pages build/deploy workflow. + */ + +const fs = require('fs'); +const path = require('path'); + +/** + * Get status emoji and color + */ +function getStatusDisplay(status, severity) { + switch (status) { + case 'pass': + return { emoji: 'āœ…', color: 'brightgreen', circle: '🟢' }; + case 'fail': + if (severity === 'critical') return { emoji: 'āŒ', color: 'critical', circle: 'šŸ”“' }; + if (severity === 'high') return { emoji: 'āŒ', color: 'red', circle: 'šŸ”“' }; + return { emoji: 'āŒ', color: 'orange', circle: '🟠' }; + case 'warn': + return { emoji: 'āš ļø', color: 'yellow', circle: '🟔' }; + case 'info': + return { emoji: 'ā„¹ļø', color: 'blue', circle: 'šŸ”µ' }; + case 'skip': + return { emoji: 'ā­ļø', color: 'lightgrey', circle: '⚪' }; + default: + return { emoji: 'ā“', color: 'lightgrey', circle: '⚪' }; + } +} + +/** + * Generate badge URL + */ +function generateBadgeUrl(label, message, color) { + const encodedLabel = encodeURIComponent(label); + const encodedMessage = encodeURIComponent(message); + return `https://img.shields.io/badge/${encodedLabel}-${encodedMessage}-${color}?style=flat-square`; +} + +/** + * Format security check results as PR comment + */ +function formatSecurityComment(results) { + const { timestamp, summary, checks } = results; + + // Overall status badge + let overallColor = 'brightgreen'; + let overallLabel = 'SECURE'; + + if (summary.overallStatus === 'fail') { + overallColor = summary.overallSeverity === 'critical' ? 'critical' : 'red'; + overallLabel = 'ISSUES FOUND'; + } else if (summary.overallStatus === 'warn') { + overallColor = 'yellow'; + overallLabel = 'WARNINGS'; + } + + const overallBadge = generateBadgeUrl('Security Status', overallLabel, overallColor); + + // Build comment + let comment = `## šŸ”’ Security Check Report\n\n`; + comment += `![Security Status](${overallBadge})\n\n`; + + // Summary line with circles + const summaryParts = []; + if (summary.passed > 0) summaryParts.push(`🟢 ${summary.passed} passed`); + if (summary.warned > 0) summaryParts.push(`🟔 ${summary.warned} warnings`); + if (summary.failed > 0) summaryParts.push(`šŸ”“ ${summary.failed} failed`); + if (summary.skipped > 0) summaryParts.push(`⚪ ${summary.skipped} skipped`); + + comment += `**${summaryParts.join(' • ')}**\n\n`; + + // Individual check results in compact format + comment += `### Security Checks\n\n`; + comment += `\n`; + comment += `\n`; + + checks.forEach(check => { + const display = getStatusDisplay(check.status, check.severity); + const statusBadge = generateBadgeUrl(check.name, check.status.toUpperCase(), display.color); + + comment += ``; + comment += ``; + comment += ``; + comment += ``; + comment += `\n`; + }); + + comment += `
CheckStatusDetails
${check.name}${display.circle}${check.summary}
\n\n`; + + // Detailed findings for failed/warned checks + const criticalChecks = checks.filter(c => c.status === 'fail' || c.status === 'warn'); + + if (criticalChecks.length > 0) { + comment += `### šŸ” Action Items\n\n`; + + criticalChecks.forEach(check => { + const display = getStatusDisplay(check.status, check.severity); + comment += `
\n`; + comment += `${display.emoji} ${check.name} - ${check.summary}\n\n`; + + if (check.details && Object.keys(check.details).length > 0) { + comment += `**Details:**\n`; + const details = check.details; + + // Format details based on check type + if (check.id === 'npm-audit' && details.total > 0) { + comment += `- Critical: ${details.critical}\n`; + comment += `- High: ${details.high}\n`; + comment += `- Moderate: ${details.moderate}\n`; + comment += `- Low: ${details.low}\n`; + } else if (check.id === 'outdated-deps' && details.criticalPackages?.length > 0) { + comment += `Major version updates needed for:\n`; + details.criticalPackages.forEach(pkg => { + comment += `- \`${pkg}\`\n`; + }); + } else if (check.id === 'eslint-security' && details.securityIssues > 0) { + comment += `- Security issues: ${details.securityIssues}\n`; + comment += `- Errors: ${details.errors}\n`; + comment += `- Warnings: ${details.warnings}\n`; + } else if (check.id === 'secret-scan' && details.findings?.length > 0) { + comment += `Potential secrets found in:\n`; + details.findings.forEach(finding => { + comment += `- \`${finding.file}\` (${finding.type})\n`; + }); + } else if (check.id === 'license-check' && details.issues?.length > 0) { + comment += `Packages with restrictive licenses:\n`; + details.issues.forEach(issue => { + comment += `- \`${issue.name}\` (${issue.license})\n`; + }); + } + } + + if (check.recommendation) { + comment += `\n**Recommendation:** ${check.recommendation}\n`; + } + + comment += `\n
\n\n`; + }); + } + + // Footer with additional info + comment += `---\n\n`; + + if (summary.overallStatus === 'pass') { + comment += `### āœ… Security Status: CLEAN\n\n`; + comment += `All security checks passed successfully. Your changes maintain the security posture of the project.\n`; + } else if (summary.overallStatus === 'warn') { + comment += `### āš ļø Security Status: WARNINGS\n\n`; + comment += `Some security warnings were detected. Please review the action items above.\n`; + } else { + comment += `### āŒ Security Status: ACTION REQUIRED\n\n`; + comment += `Security issues were detected that need to be addressed before merging.\n`; + } + + comment += `\n*Last checked: ${new Date(timestamp).toUTCString()}*\n`; + + return comment; +} + +/** + * Generate condensed status for PR comment marker + */ +function generateCondensedStatus(results) { + const { summary } = results; + + // Create compact status line + const parts = []; + if (summary.passed > 0) parts.push(`${summary.passed}āœ…`); + if (summary.warned > 0) parts.push(`${summary.warned}āš ļø`); + if (summary.failed > 0) parts.push(`${summary.failed}āŒ`); + + return parts.join(' '); +} + +/** + * Main execution + */ +function main() { + const resultsPath = process.argv[2] || path.join(process.cwd(), 'security-check-results.json'); + + if (!fs.existsSync(resultsPath)) { + console.error(`Error: Security check results not found at ${resultsPath}`); + console.error('Run run-security-checks.js first to generate results.'); + process.exit(1); + } + + const results = JSON.parse(fs.readFileSync(resultsPath, 'utf8')); + const comment = formatSecurityComment(results); + + // Output formatted comment + console.log(comment); + + // Also write to file for easy consumption + const outputPath = path.join(process.cwd(), 'security-comment.md'); + fs.writeFileSync(outputPath, comment); + + console.error(`\nāœ… Formatted comment written to: ${outputPath}`); +} + +// Run if called directly +if (require.main === module) { + main(); +} + +module.exports = { + formatSecurityComment, + generateCondensedStatus, + getStatusDisplay, + generateBadgeUrl, + main +}; diff --git a/scripts/format-security-comment.test.js b/scripts/format-security-comment.test.js new file mode 100644 index 000000000..6d3daa60b --- /dev/null +++ b/scripts/format-security-comment.test.js @@ -0,0 +1,381 @@ +/** + * Tests for format-security-comment.js + * + * Tests the security comment formatter that creates condensed PR comments + * with badges and status indicators. + */ + +const fs = require('fs'); +const path = require('path'); + +// Mock fs +jest.mock('fs'); + +describe('Format Security Comment', () => { + let module; + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + + // Suppress console output during tests + global.console = { + ...console, + log: jest.fn(), + error: jest.fn() + }; + + // Load the module fresh for each test + module = require('./format-security-comment.js'); + }); + + describe('getStatusDisplay', () => { + it('should return correct display for pass status', () => { + const display = module.getStatusDisplay('pass', 'none'); + + expect(display.emoji).toBe('āœ…'); + expect(display.color).toBe('brightgreen'); + expect(display.circle).toBe('🟢'); + }); + + it('should return correct display for critical fail status', () => { + const display = module.getStatusDisplay('fail', 'critical'); + + expect(display.emoji).toBe('āŒ'); + expect(display.color).toBe('critical'); + expect(display.circle).toBe('šŸ”“'); + }); + + it('should return correct display for high fail status', () => { + const display = module.getStatusDisplay('fail', 'high'); + + expect(display.emoji).toBe('āŒ'); + expect(display.color).toBe('red'); + expect(display.circle).toBe('šŸ”“'); + }); + + it('should return correct display for warn status', () => { + const display = module.getStatusDisplay('warn', 'moderate'); + + expect(display.emoji).toBe('āš ļø'); + expect(display.color).toBe('yellow'); + expect(display.circle).toBe('🟔'); + }); + + it('should return correct display for info status', () => { + const display = module.getStatusDisplay('info', 'low'); + + expect(display.emoji).toBe('ā„¹ļø'); + expect(display.color).toBe('blue'); + expect(display.circle).toBe('šŸ”µ'); + }); + + it('should return correct display for skip status', () => { + const display = module.getStatusDisplay('skip', 'none'); + + expect(display.emoji).toBe('ā­ļø'); + expect(display.color).toBe('lightgrey'); + expect(display.circle).toBe('⚪'); + }); + + it('should handle unknown status', () => { + const display = module.getStatusDisplay('unknown', 'none'); + + expect(display.emoji).toBe('ā“'); + expect(display.color).toBe('lightgrey'); + expect(display.circle).toBe('⚪'); + }); + }); + + describe('generateBadgeUrl', () => { + it('should generate correct shields.io badge URL', () => { + const url = module.generateBadgeUrl('Security Status', 'SECURE', 'brightgreen'); + + expect(url).toContain('https://img.shields.io/badge/'); + expect(url).toContain('Security%20Status'); + expect(url).toContain('SECURE'); + expect(url).toContain('brightgreen'); + expect(url).toContain('style=flat-square'); + }); + + it('should properly encode special characters', () => { + const url = module.generateBadgeUrl('Test & Check', 'Pass/Fail', 'green'); + + expect(url).toContain('Test%20%26%20Check'); + expect(url).toContain('Pass%2FFail'); + }); + }); + + describe('formatSecurityComment', () => { + it('should format comment for all passing checks', () => { + const results = { + timestamp: '2025-01-01T00:00:00.000Z', + summary: { + overallStatus: 'pass', + overallSeverity: 'none', + passed: 7, + warned: 0, + failed: 0, + skipped: 0, + info: 0, + total: 7 + }, + checks: [ + { + name: 'NPM Audit', + id: 'npm-audit', + status: 'pass', + severity: 'none', + summary: 'No vulnerabilities found', + details: {}, + recommendation: null + } + ] + }; + + const comment = module.formatSecurityComment(results); + + expect(comment).toContain('## šŸ”’ Security Check Report'); + expect(comment).toContain('![Security Status]'); + expect(comment).toContain('SECURE'); + expect(comment).toContain('🟢 7 passed'); + expect(comment).toContain('### āœ… Security Status: CLEAN'); + expect(comment).not.toContain('Action Items'); + }); + + it('should format comment for checks with warnings', () => { + const results = { + timestamp: '2025-01-01T00:00:00.000Z', + summary: { + overallStatus: 'warn', + overallSeverity: 'moderate', + passed: 5, + warned: 1, + failed: 0, + skipped: 1, + info: 0, + total: 7 + }, + checks: [ + { + name: 'Security Headers', + id: 'security-headers', + status: 'warn', + severity: 'moderate', + summary: 'Some headers missing', + details: {}, + recommendation: 'Add missing security headers' + } + ] + }; + + const comment = module.formatSecurityComment(results); + + expect(comment).toContain('WARNINGS'); + expect(comment).toContain('🟢 5 passed'); + expect(comment).toContain('🟔 1 warnings'); + expect(comment).toContain('⚪ 1 skipped'); + expect(comment).toContain('### šŸ” Action Items'); + expect(comment).toContain('### āš ļø Security Status: WARNINGS'); + }); + + it('should format comment for checks with failures', () => { + const results = { + timestamp: '2025-01-01T00:00:00.000Z', + summary: { + overallStatus: 'fail', + overallSeverity: 'critical', + passed: 4, + warned: 1, + failed: 2, + skipped: 0, + info: 0, + total: 7 + }, + checks: [ + { + name: 'NPM Audit', + id: 'npm-audit', + status: 'fail', + severity: 'critical', + summary: '5 vulnerabilities found', + details: { + total: 5, + critical: 2, + high: 3 + }, + recommendation: 'Run npm audit fix' + }, + { + name: 'Secret Scanning', + id: 'secret-scan', + status: 'fail', + severity: 'critical', + summary: '2 secrets found', + details: { + findings: [ + { file: 'src/config.js', type: 'API Keys' } + ] + }, + recommendation: 'Remove hardcoded secrets' + } + ] + }; + + const comment = module.formatSecurityComment(results); + + expect(comment).toContain('ISSUES%20FOUND'); // URL encoded in badge + expect(comment).toContain('🟢 4 passed'); + expect(comment).toContain('🟔 1 warnings'); + expect(comment).toContain('šŸ”“ 2 failed'); + expect(comment).toContain('### šŸ” Action Items'); + expect(comment).toContain('### āŒ Security Status: ACTION REQUIRED'); + }); + + it('should include expandable details for npm audit failures', () => { + const results = { + timestamp: '2025-01-01T00:00:00.000Z', + summary: { + overallStatus: 'fail', + overallSeverity: 'high', + passed: 6, + warned: 0, + failed: 1, + skipped: 0, + info: 0, + total: 7 + }, + checks: [ + { + name: 'NPM Audit', + id: 'npm-audit', + status: 'fail', + severity: 'high', + summary: '10 vulnerabilities found', + details: { + total: 10, + critical: 0, + high: 5, + moderate: 3, + low: 2 + }, + recommendation: 'Run npm audit fix' + } + ] + }; + + const comment = module.formatSecurityComment(results); + + expect(comment).toContain('
'); + expect(comment).toContain(''); + expect(comment).toContain('NPM Audit'); + expect(comment).toContain('Critical: 0'); + expect(comment).toContain('High: 5'); + expect(comment).toContain('Moderate: 3'); + expect(comment).toContain('Low: 2'); + expect(comment).toContain('Run npm audit fix'); + }); + + it('should include timestamp in footer', () => { + const results = { + timestamp: '2025-01-01T12:34:56.789Z', + summary: { + overallStatus: 'pass', + overallSeverity: 'none', + passed: 7, + warned: 0, + failed: 0, + skipped: 0, + info: 0, + total: 7 + }, + checks: [] + }; + + const comment = module.formatSecurityComment(results); + + expect(comment).toContain('Last checked:'); + expect(comment).toMatch(/\d{4}/); // Contains year + }); + }); + + describe('generateCondensedStatus', () => { + it('should generate compact status string', () => { + const results = { + summary: { + passed: 5, + warned: 1, + failed: 0 + } + }; + + const status = module.generateCondensedStatus(results); + + expect(status).toContain('5āœ…'); + expect(status).toContain('1āš ļø'); + expect(status).not.toContain('0āŒ'); + }); + + it('should handle all zeros', () => { + const results = { + summary: { + passed: 0, + warned: 0, + failed: 0 + } + }; + + const status = module.generateCondensedStatus(results); + + expect(status).toBe(''); + }); + }); + + describe('Table formatting', () => { + it('should create proper HTML table structure', () => { + const results = { + timestamp: '2025-01-01T00:00:00.000Z', + summary: { + overallStatus: 'pass', + overallSeverity: 'none', + passed: 2, + warned: 0, + failed: 0, + skipped: 0, + info: 0, + total: 2 + }, + checks: [ + { + name: 'Check One', + id: 'check-1', + status: 'pass', + severity: 'none', + summary: 'All good', + details: {}, + recommendation: null + }, + { + name: 'Check Two', + id: 'check-2', + status: 'pass', + severity: 'none', + summary: 'Also good', + details: {}, + recommendation: null + } + ] + }; + + const comment = module.formatSecurityComment(results); + + expect(comment).toContain(''); + expect(comment).toContain('
'); + expect(comment).toContain('CheckStatusDetails'); + expect(comment).toContain('Check One'); + expect(comment).toContain('Check Two'); + expect(comment).toContain('All good'); + expect(comment).toContain('Also good'); + }); + }); +}); diff --git a/scripts/lib/pr-comment-manager.js b/scripts/lib/pr-comment-manager.js new file mode 100644 index 000000000..72d3b9abb --- /dev/null +++ b/scripts/lib/pr-comment-manager.js @@ -0,0 +1,105 @@ +/** + * Shared PR Comment Management for GitHub Actions + * + * Provides common functionality for finding and updating PR comments + * with consistent behavior across different workflows. + */ + +class PRCommentManager { + constructor(github, context, commentMarker) { + this.github = github; + this.context = context; + this.commentMarker = commentMarker; + } + + /** + * Find existing comment with the given marker + * @param {number} prNumber - Pull request number + * @returns {Promise} - Existing comment or null + */ + async findExistingComment(prNumber) { + try { + const { data: comments } = await this.github.rest.issues.listComments({ + owner: this.context.repo.owner, + repo: this.context.repo.repo, + issue_number: prNumber, + }); + + return comments.find(comment => + comment.body && comment.body.includes(this.commentMarker) + ) || null; + } catch (error) { + console.error('Error finding existing comment:', error); + return null; + } + } + + /** + * Update existing comment or create new one + * @param {number} prNumber - Pull request number + * @param {string} body - Comment body with marker included + * @returns {Promise} - Success status + */ + async updateOrCreateComment(prNumber, body) { + try { + const existing = await this.findExistingComment(prNumber); + + if (existing) { + // Update existing comment + console.log(`Updating existing comment (ID: ${existing.id})`); + await this.github.rest.issues.updateComment({ + owner: this.context.repo.owner, + repo: this.context.repo.repo, + comment_id: existing.id, + body: body + }); + console.log('āœ… Comment updated successfully'); + } else { + // Create new comment + console.log('Creating new comment'); + await this.github.rest.issues.createComment({ + owner: this.context.repo.owner, + repo: this.context.repo.repo, + issue_number: prNumber, + body: body + }); + console.log('āœ… Comment created successfully'); + } + + return true; + } catch (error) { + console.error('Error updating/creating comment:', error); + return false; + } + } + + /** + * Delete comment if it exists + * @param {number} prNumber - Pull request number + * @returns {Promise} - Success status + */ + async deleteCommentIfExists(prNumber) { + try { + const existing = await this.findExistingComment(prNumber); + + if (existing) { + console.log(`Deleting comment (ID: ${existing.id})`); + await this.github.rest.issues.deleteComment({ + owner: this.context.repo.owner, + repo: this.context.repo.repo, + comment_id: existing.id + }); + console.log('āœ… Comment deleted successfully'); + return true; + } + + console.log('No comment found to delete'); + return true; + } catch (error) { + console.error('Error deleting comment:', error); + return false; + } + } +} + +module.exports = PRCommentManager; diff --git a/scripts/lib/pr-comment-manager.test.js b/scripts/lib/pr-comment-manager.test.js new file mode 100644 index 000000000..212d39a5a --- /dev/null +++ b/scripts/lib/pr-comment-manager.test.js @@ -0,0 +1,201 @@ +/** + * Tests for shared PR Comment Manager + */ + +const PRCommentManager = require('./pr-comment-manager'); + +describe('PRCommentManager', () => { + let mockGithub; + let mockContext; + let manager; + + beforeEach(() => { + mockGithub = { + rest: { + issues: { + listComments: jest.fn(), + updateComment: jest.fn(), + createComment: jest.fn(), + deleteComment: jest.fn(), + }, + }, + }; + + mockContext = { + repo: { + owner: 'test-owner', + repo: 'test-repo', + }, + }; + + manager = new PRCommentManager( + mockGithub, + mockContext, + '' + ); + }); + + describe('findExistingComment', () => { + it('should find existing comment with marker', async () => { + const mockComments = [ + { id: 1, body: 'Some other comment' }, + { id: 2, body: '\nTest comment' }, + { id: 3, body: 'Another comment' }, + ]; + + mockGithub.rest.issues.listComments.mockResolvedValue({ + data: mockComments, + }); + + const result = await manager.findExistingComment(123); + + expect(result).toEqual(mockComments[1]); + expect(mockGithub.rest.issues.listComments).toHaveBeenCalledWith({ + owner: 'test-owner', + repo: 'test-repo', + issue_number: 123, + }); + }); + + it('should return null if no comment with marker exists', async () => { + mockGithub.rest.issues.listComments.mockResolvedValue({ + data: [ + { id: 1, body: 'Some comment' }, + { id: 2, body: 'Another comment' }, + ], + }); + + const result = await manager.findExistingComment(123); + + expect(result).toBeNull(); + }); + + it('should handle API errors gracefully', async () => { + mockGithub.rest.issues.listComments.mockRejectedValue( + new Error('API Error') + ); + + const result = await manager.findExistingComment(123); + + expect(result).toBeNull(); + }); + }); + + describe('updateOrCreateComment', () => { + it('should update existing comment', async () => { + const existingComment = { + id: 42, + body: '\nOld content', + }; + + mockGithub.rest.issues.listComments.mockResolvedValue({ + data: [existingComment], + }); + + mockGithub.rest.issues.updateComment.mockResolvedValue({}); + + const newBody = '\nNew content'; + const result = await manager.updateOrCreateComment(123, newBody); + + expect(result).toBe(true); + expect(mockGithub.rest.issues.updateComment).toHaveBeenCalledWith({ + owner: 'test-owner', + repo: 'test-repo', + comment_id: 42, + body: newBody, + }); + expect(mockGithub.rest.issues.createComment).not.toHaveBeenCalled(); + }); + + it('should create new comment if none exists', async () => { + mockGithub.rest.issues.listComments.mockResolvedValue({ + data: [], + }); + + mockGithub.rest.issues.createComment.mockResolvedValue({}); + + const newBody = '\nNew content'; + const result = await manager.updateOrCreateComment(123, newBody); + + expect(result).toBe(true); + expect(mockGithub.rest.issues.createComment).toHaveBeenCalledWith({ + owner: 'test-owner', + repo: 'test-repo', + issue_number: 123, + body: newBody, + }); + expect(mockGithub.rest.issues.updateComment).not.toHaveBeenCalled(); + }); + + it('should return false on error', async () => { + mockGithub.rest.issues.listComments.mockResolvedValue({ + data: [], + }); + + mockGithub.rest.issues.createComment.mockRejectedValue( + new Error('API Error') + ); + + const result = await manager.updateOrCreateComment( + 123, + '\nContent' + ); + + expect(result).toBe(false); + }); + }); + + describe('deleteCommentIfExists', () => { + it('should delete existing comment', async () => { + const existingComment = { + id: 42, + body: '\nContent', + }; + + mockGithub.rest.issues.listComments.mockResolvedValue({ + data: [existingComment], + }); + + mockGithub.rest.issues.deleteComment.mockResolvedValue({}); + + const result = await manager.deleteCommentIfExists(123); + + expect(result).toBe(true); + expect(mockGithub.rest.issues.deleteComment).toHaveBeenCalledWith({ + owner: 'test-owner', + repo: 'test-repo', + comment_id: 42, + }); + }); + + it('should return true if no comment exists', async () => { + mockGithub.rest.issues.listComments.mockResolvedValue({ + data: [], + }); + + const result = await manager.deleteCommentIfExists(123); + + expect(result).toBe(true); + expect(mockGithub.rest.issues.deleteComment).not.toHaveBeenCalled(); + }); + + it('should return false on error', async () => { + const existingComment = { + id: 42, + body: '\nContent', + }; + + mockGithub.rest.issues.listComments.mockResolvedValue({ + data: [existingComment], + }); + + mockGithub.rest.issues.deleteComment.mockRejectedValue( + new Error('API Error') + ); + + const result = await manager.deleteCommentIfExists(123); + + expect(result).toBe(false); + }); + }); +}); diff --git a/scripts/manage-pr-comment.py b/scripts/manage-pr-comment.py index ab700e744..20d0119e0 100755 --- a/scripts/manage-pr-comment.py +++ b/scripts/manage-pr-comment.py @@ -36,7 +36,7 @@ class PRCommentManager: # Allowed stages to prevent injection ALLOWED_STAGES = { 'started', 'setup', 'building', 'deploying', 'verifying', - 'success', 'failure', 'pages-built' + 'success', 'failure', 'pages-built', 'security-check' } def __init__(self, token: str, repo: str, pr_number: int, action_id: Optional[str] = None, @@ -446,6 +446,45 @@ def build_comment_body(self, stage: str, data: Dict[str, Any], existing_timeline Build Logs""" timeline_entry = f"- **{timestamp}** - 🟢 {step_link} - Complete" + elif stage == 'security-check': + # Security check stage - read the security comment from file + security_comment_path = data.get('security_comment_path', 'security-comment.md') + security_comment = "" + + try: + import os + if os.path.exists(security_comment_path): + with open(security_comment_path, 'r') as f: + security_comment = f.read() + else: + security_comment = "Security check results not available" + except Exception as e: + security_comment = f"Error reading security check results: {str(e)}" + + # Extract summary from security comment + status_icon = "🟢" + status_text = "Security checks passed" + if "ISSUES FOUND" in security_comment or "ACTION REQUIRED" in security_comment: + status_icon = "šŸ”“" + status_text = "Security issues detected" + elif "WARNINGS" in security_comment: + status_icon = "🟔" + status_text = "Security warnings" + + status_line = f"

šŸ”’ Security Check Status: {status_text.title()} {status_icon}

" + next_step = f"**Status:** Security scan completed" + + # Include the full security report in the actions section + actions = f"""

šŸ”’ Security Report

+ +{security_comment} + +

šŸ”— Quick Actions

+ +Build Logs""" + + timeline_entry = f"- **{timestamp}** - {status_icon} Security Check - {status_text}" + elif stage == 'success': status_line = "

šŸš€ Deployment Status: Successfully Deployed 🟢

" status_icon = "🟢" diff --git a/scripts/manage-security-comment.py b/scripts/manage-security-comment.py new file mode 100755 index 000000000..1a309ac3d --- /dev/null +++ b/scripts/manage-security-comment.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +""" +Security Check Comment Manager for GitHub Actions + +This script manages security check comments on pull requests, +integrating with the run-security-checks.js and format-security-comment.js scripts. +""" + +import argparse +import json +import os +import sys +from typing import Dict, Optional, Any + +try: + import requests +except ImportError: + print("Error: requests library not found. Install with: pip install requests", file=sys.stderr) + sys.exit(1) + + +class SecurityCheckCommentManager: + """Manages security check comments on PRs.""" + + COMMENT_MARKER = "" + + def __init__(self, token: str, repo: str, pr_number: int): + """ + Initialize the security check comment manager. + + Args: + token: GitHub token for authentication + repo: Repository in format 'owner/repo' + pr_number: Pull request number + """ + self.token = token + self.owner, self.repo_name = repo.split('/') + self.pr_number = pr_number + self.api_base = "https://api.github.com" + self.headers = { + "Authorization": f"token {token}", + "Accept": "application/vnd.github.v3+json", + "Content-Type": "application/json" + } + + def get_existing_comment(self) -> Optional[Dict[str, Any]]: + """Find existing security check comment on the PR.""" + url = f"{self.api_base}/repos/{self.owner}/{self.repo_name}/issues/{self.pr_number}/comments" + + try: + response = requests.get(url, headers=self.headers, timeout=30) + response.raise_for_status() + + comments = response.json() + for comment in comments: + if self.COMMENT_MARKER in comment.get('body', ''): + return comment + + return None + except requests.exceptions.RequestException as e: + print(f"Error fetching comments: {e}", file=sys.stderr) + return None + + def create_comment_body(self, security_comment: str) -> str: + """Create the full comment body with marker.""" + return f"""{self.COMMENT_MARKER} +{security_comment} + +--- + +*This security check is automatically run on every PR build. [Learn more about our security checks](../blob/main/docs/security.md)* +""" + + def update_or_create_comment(self, security_comment: str) -> bool: + """Update existing comment or create new one.""" + try: + comment_body = self.create_comment_body(security_comment) + existing = self.get_existing_comment() + + if existing: + # Update existing comment + url = f"{self.api_base}/repos/{self.owner}/{self.repo_name}/issues/comments/{existing['id']}" + payload = {"body": comment_body} + + response = requests.patch(url, headers=self.headers, json=payload, timeout=30) + response.raise_for_status() + + print(f"āœ… Updated security check comment on PR #{self.pr_number}") + return True + else: + # Create new comment + url = f"{self.api_base}/repos/{self.owner}/{self.repo_name}/issues/{self.pr_number}/comments" + payload = {"body": comment_body} + + response = requests.post(url, headers=self.headers, json=payload, timeout=30) + response.raise_for_status() + + print(f"āœ… Created security check comment on PR #{self.pr_number}") + return True + + except requests.exceptions.RequestException as e: + print(f"Error updating comment: {e}", file=sys.stderr) + return False + + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser(description='Manage security check comments on PRs') + parser.add_argument('--token', required=True, help='GitHub token') + parser.add_argument('--repo', required=True, help='Repository (owner/repo)') + parser.add_argument('--pr', type=int, required=True, help='Pull request number') + parser.add_argument('--comment-file', default='security-comment.md', + help='Path to formatted security comment file') + + args = parser.parse_args() + + # Read the formatted security comment + if not os.path.exists(args.comment_file): + print(f"Error: Comment file not found: {args.comment_file}", file=sys.stderr) + print("Run run-security-checks.js and format-security-comment.js first.", file=sys.stderr) + sys.exit(1) + + with open(args.comment_file, 'r') as f: + security_comment = f.read() + + # Create manager and update comment + manager = SecurityCheckCommentManager( + token=args.token, + repo=args.repo, + pr_number=args.pr + ) + + success = manager.update_or_create_comment(security_comment) + sys.exit(0 if success else 1) + + +if __name__ == '__main__': + main() diff --git a/scripts/run-security-checks.js b/scripts/run-security-checks.js new file mode 100755 index 000000000..b37214a13 --- /dev/null +++ b/scripts/run-security-checks.js @@ -0,0 +1,541 @@ +#!/usr/bin/env node +/** + * Comprehensive Security Check Script for PR Builds + * + * This script runs multiple security checks and outputs results in a + * structured JSON format that can be consumed by the PR comment manager. + * + * Checks performed: + * 1. NPM Audit - Dependency vulnerabilities + * 2. Outdated Dependencies - Security risk from old packages + * 3. ESLint Security Rules - Code security issues + * 4. Security Headers - Verification of security headers + * 5. License Compliance - Check for problematic licenses + * 6. Git Secrets Scan - Detect hardcoded secrets + * 7. Framework Compliance - Security-related framework checks + */ + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +/** + * Execute a command and return the result + */ +function execCommand(command, options = {}) { + try { + const output = execSync(command, { + encoding: 'utf8', + stdio: 'pipe', + ...options + }); + return { success: true, output, error: null }; + } catch (error) { + return { + success: false, + output: error.stdout || '', + error: error.stderr || error.message + }; + } +} + +/** + * Run NPM audit check + */ +function runNpmAudit() { + console.log('šŸ” Running npm audit...'); + + const result = execCommand('npm audit --json'); + let auditData = { vulnerabilities: { total: 0 } }; + + try { + auditData = JSON.parse(result.output || '{}'); + } catch (e) { + console.error('Failed to parse npm audit JSON:', e.message); + } + + const vulns = auditData.metadata?.vulnerabilities || auditData.vulnerabilities || {}; + const total = vulns.total || 0; + const critical = vulns.critical || 0; + const high = vulns.high || 0; + const moderate = vulns.moderate || 0; + const low = vulns.low || 0; + + return { + name: 'NPM Audit', + id: 'npm-audit', + status: total === 0 ? 'pass' : (critical > 0 || high > 0) ? 'fail' : 'warn', + severity: total === 0 ? 'none' : (critical > 0) ? 'critical' : (high > 0) ? 'high' : (moderate > 0) ? 'moderate' : 'low', + summary: total === 0 + ? 'No vulnerabilities found' + : `${total} vulnerabilities found (Critical: ${critical}, High: ${high}, Moderate: ${moderate}, Low: ${low})`, + details: { + total, + critical, + high, + moderate, + low + }, + recommendation: total > 0 ? 'Run `npm audit fix` to automatically fix vulnerabilities' : null + }; +} + +/** + * Check for outdated dependencies + */ +function checkOutdatedDependencies() { + console.log('šŸ” Checking outdated dependencies...'); + + const result = execCommand('npm outdated --json'); + let outdatedData = {}; + + try { + outdatedData = JSON.parse(result.output || '{}'); + } catch (e) { + // npm outdated returns non-zero exit code when there are outdated packages + // but we still want to parse the JSON + } + + const outdatedCount = Object.keys(outdatedData).length; + const criticalPackages = Object.entries(outdatedData) + .filter(([name, info]) => { + // Check if the package is significantly outdated (major version behind) + const current = info.current || ''; + const latest = info.latest || ''; + const currentMajor = parseInt(current.split('.')[0]) || 0; + const latestMajor = parseInt(latest.split('.')[0]) || 0; + return latestMajor > currentMajor; + }) + .map(([name]) => name); + + return { + name: 'Outdated Dependencies', + id: 'outdated-deps', + status: outdatedCount === 0 ? 'pass' : criticalPackages.length > 0 ? 'warn' : 'info', + severity: criticalPackages.length > 0 ? 'moderate' : 'low', + summary: outdatedCount === 0 + ? 'All dependencies are up to date' + : `${outdatedCount} outdated packages (${criticalPackages.length} major versions behind)`, + details: { + total: outdatedCount, + criticalPackages: criticalPackages.slice(0, 5) // Limit to first 5 + }, + recommendation: outdatedCount > 0 + ? 'Review outdated packages and update where possible' + : null + }; +} + +/** + * Run ESLint security rules + */ +function runEslintSecurityCheck() { + console.log('šŸ” Running ESLint security checks...'); + + // Check if eslint is available + const eslintPath = path.join(process.cwd(), 'node_modules', '.bin', 'eslint'); + if (!fs.existsSync(eslintPath)) { + return { + name: 'ESLint Security', + id: 'eslint-security', + status: 'skip', + severity: 'none', + summary: 'ESLint not configured', + details: {}, + recommendation: null + }; + } + + // Run ESLint and save output to file to avoid command line buffer issues + const outputFile = path.join(process.cwd(), 'eslint-results.json'); + const result = execCommand(`${eslintPath} src --format json --output-file ${outputFile}`, { cwd: process.cwd() }); + + let eslintData = []; + try { + if (fs.existsSync(outputFile)) { + const content = fs.readFileSync(outputFile, 'utf8'); + eslintData = JSON.parse(content); + // Clean up temp file + fs.unlinkSync(outputFile); + } + } catch (e) { + console.error('Failed to parse ESLint JSON:', e.message); + // Try to parse from stdout as fallback + try { + eslintData = JSON.parse(result.output || '[]'); + } catch (e2) { + // Give up + } + } + + // Count security-related warnings/errors + let securityIssues = 0; + let errorCount = 0; + let warningCount = 0; + + eslintData.forEach(file => { + file.messages?.forEach(msg => { + // Look for security-related rules + if (msg.ruleId?.includes('security') || + msg.ruleId?.includes('no-eval') || + msg.ruleId?.includes('no-dangerous')) { + securityIssues++; + } + if (msg.severity === 2) errorCount++; + if (msg.severity === 1) warningCount++; + }); + }); + + return { + name: 'ESLint Security', + id: 'eslint-security', + status: securityIssues === 0 ? 'pass' : errorCount > 0 ? 'fail' : 'warn', + severity: errorCount > 0 ? 'high' : securityIssues > 0 ? 'moderate' : 'none', + summary: securityIssues === 0 + ? 'No security-related linting issues' + : `${securityIssues} security-related issues found`, + details: { + securityIssues, + errors: errorCount, + warnings: warningCount + }, + recommendation: securityIssues > 0 ? 'Review and fix ESLint security warnings' : null + }; +} + +/** + * Check security headers implementation + */ +function checkSecurityHeaders() { + console.log('šŸ” Checking security headers...'); + + const headerScript = path.join(process.cwd(), 'scripts', 'verify-security-headers.sh'); + + if (!fs.existsSync(headerScript)) { + return { + name: 'Security Headers', + id: 'security-headers', + status: 'skip', + severity: 'none', + summary: 'Security headers verification script not found', + details: {}, + recommendation: null + }; + } + + // Check if build directory exists (needed for full verification) + const buildDir = path.join(process.cwd(), 'build'); + if (!fs.existsSync(buildDir)) { + // Check source files only + const indexHtml = path.join(process.cwd(), 'public', 'index.html'); + if (!fs.existsSync(indexHtml)) { + return { + name: 'Security Headers', + id: 'security-headers', + status: 'skip', + severity: 'none', + summary: 'Build artifacts not available for verification', + details: {}, + recommendation: null + }; + } + + // Just verify source files have the meta tags + const content = fs.readFileSync(indexHtml, 'utf8'); + const hasCSP = content.includes('Content-Security-Policy'); + const hasXFrame = content.includes('X-Frame-Options'); + + if (hasCSP && hasXFrame) { + return { + name: 'Security Headers', + id: 'security-headers', + status: 'pass', + severity: 'none', + summary: 'Security headers defined in source (build verification skipped)', + details: { sourceOnly: true }, + recommendation: null + }; + } else { + return { + name: 'Security Headers', + id: 'security-headers', + status: 'warn', + severity: 'moderate', + summary: 'Some security headers missing in source', + details: { hasCSP, hasXFrame, sourceOnly: true }, + recommendation: 'Ensure all security headers are properly defined' + }; + } + } + + const result = execCommand(`bash ${headerScript}`); + + // Parse the output to check for success + const output = result.output || ''; + const hasErrors = output.includes('ERROR') || output.includes('āŒ'); + const hasWarnings = output.includes('WARNING') || output.includes('āš ļø'); + + return { + name: 'Security Headers', + id: 'security-headers', + status: hasErrors ? 'fail' : hasWarnings ? 'warn' : 'pass', + severity: hasErrors ? 'high' : hasWarnings ? 'moderate' : 'none', + summary: hasErrors + ? 'Security headers configuration has errors' + : hasWarnings + ? 'Security headers have warnings' + : 'Security headers properly configured', + details: { + hasErrors, + hasWarnings + }, + recommendation: hasErrors || hasWarnings + ? 'Review security headers implementation' + : null + }; +} + +/** + * Check license compliance + */ +function checkLicenseCompliance() { + console.log('šŸ” Checking license compliance...'); + + const result = execCommand('npm ls --json --all'); + let depsData = {}; + + try { + depsData = JSON.parse(result.output || '{}'); + } catch (e) { + console.error('Failed to parse npm ls JSON:', e.message); + } + + // List of problematic licenses (GPL, AGPL require disclosure) + const problematicLicenses = ['GPL', 'AGPL', 'LGPL']; + const issues = []; + + function checkDependency(dep, name) { + const license = dep.license || ''; + if (problematicLicenses.some(l => license.includes(l))) { + issues.push({ name, license }); + } + } + + // Check dependencies recursively (limit depth for performance) + function walkDeps(deps, depth = 0) { + if (!deps || depth > 2) return; + Object.entries(deps).forEach(([name, dep]) => { + checkDependency(dep, name); + if (dep.dependencies) { + walkDeps(dep.dependencies, depth + 1); + } + }); + } + + if (depsData.dependencies) { + walkDeps(depsData.dependencies); + } + + return { + name: 'License Compliance', + id: 'license-check', + status: issues.length === 0 ? 'pass' : 'warn', + severity: issues.length > 0 ? 'low' : 'none', + summary: issues.length === 0 + ? 'No problematic licenses detected' + : `${issues.length} packages with restrictive licenses`, + details: { + issues: issues.slice(0, 5) // Limit to first 5 + }, + recommendation: issues.length > 0 + ? 'Review packages with GPL/AGPL licenses for compliance' + : null + }; +} + +/** + * Scan for potential secrets in code + */ +function scanForSecrets() { + console.log('šŸ” Scanning for potential secrets...'); + + // Simple regex patterns for common secrets + const patterns = [ + { name: 'API Keys', regex: /['"](api[_-]?key|apikey)['"]:\s*['"][^'"]{20,}['"]/ }, + { name: 'Tokens', regex: /['"](token|auth[_-]?token)['"]:\s*['"][^'"]{20,}['"]/ }, + { name: 'Passwords', regex: /(password|passwd|pwd)['"]?\s*[:=]\s*['"][^'"]+['"]/ }, + { name: 'Private Keys', regex: /-----BEGIN (RSA |EC )?PRIVATE KEY-----/ }, + { name: 'AWS Keys', regex: /AKIA[0-9A-Z]{16}/ } + ]; + + let findings = []; + + // Scan src directory + const srcDir = path.join(process.cwd(), 'src'); + if (fs.existsSync(srcDir)) { + const files = execCommand('find src -type f \\( -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \\)'); + const fileList = (files.output || '').trim().split('\n').filter(Boolean); + + fileList.slice(0, 50).forEach(file => { // Limit to 50 files for performance + try { + const content = fs.readFileSync(file, 'utf8'); + patterns.forEach(pattern => { + if (pattern.regex.test(content)) { + findings.push({ file, type: pattern.name }); + } + }); + } catch (e) { + // Skip files that can't be read + } + }); + } + + return { + name: 'Secret Scanning', + id: 'secret-scan', + status: findings.length === 0 ? 'pass' : 'fail', + severity: findings.length > 0 ? 'critical' : 'none', + summary: findings.length === 0 + ? 'No potential secrets detected in code' + : `${findings.length} potential secrets found`, + details: { + findings: findings.slice(0, 3) // Limit to first 3 + }, + recommendation: findings.length > 0 + ? 'Review flagged files and remove any hardcoded secrets' + : null + }; +} + +/** + * Run framework compliance checks (security-related) + */ +function checkFrameworkCompliance() { + console.log('šŸ” Checking framework compliance...'); + + const complianceScript = path.join(process.cwd(), 'scripts', 'check-framework-compliance.js'); + + if (!fs.existsSync(complianceScript)) { + return { + name: 'Framework Compliance', + id: 'framework-compliance', + status: 'skip', + severity: 'none', + summary: 'Framework compliance script not found', + details: {}, + recommendation: null + }; + } + + const result = execCommand(`node ${complianceScript}`); + const hasErrors = !result.success || result.output?.includes('FAILED'); + + return { + name: 'Framework Compliance', + id: 'framework-compliance', + status: hasErrors ? 'warn' : 'pass', + severity: hasErrors ? 'low' : 'none', + summary: hasErrors + ? 'Some framework compliance issues detected' + : 'Framework compliance checks passed', + details: {}, + recommendation: hasErrors ? 'Review framework compliance report' : null + }; +} + +/** + * Generate security summary badge + */ +function generateSecuritySummary(checks) { + const failed = checks.filter(c => c.status === 'fail').length; + const warned = checks.filter(c => c.status === 'warn').length; + const passed = checks.filter(c => c.status === 'pass').length; + const skipped = checks.filter(c => c.status === 'skip').length; + const info = checks.filter(c => c.status === 'info').length; + + let overallStatus = 'pass'; + let overallSeverity = 'none'; + + if (failed > 0) { + overallStatus = 'fail'; + // Find highest severity + const severities = checks.filter(c => c.status === 'fail').map(c => c.severity); + if (severities.includes('critical')) overallSeverity = 'critical'; + else if (severities.includes('high')) overallSeverity = 'high'; + else if (severities.includes('moderate')) overallSeverity = 'moderate'; + else overallSeverity = 'low'; + } else if (warned > 0) { + overallStatus = 'warn'; + const severities = checks.filter(c => c.status === 'warn').map(c => c.severity); + if (severities.includes('high')) overallSeverity = 'high'; + else if (severities.includes('moderate')) overallSeverity = 'moderate'; + else overallSeverity = 'low'; + } + + return { + overallStatus, + overallSeverity, + passed, + warned, + failed, + skipped, + info, + total: checks.length + }; +} + +/** + * Main execution + */ +function main() { + console.log('šŸ”’ Starting comprehensive security checks...\n'); + + const checks = []; + + // Run all security checks + checks.push(runNpmAudit()); + checks.push(checkOutdatedDependencies()); + checks.push(runEslintSecurityCheck()); + checks.push(checkSecurityHeaders()); + checks.push(checkLicenseCompliance()); + checks.push(scanForSecrets()); + checks.push(checkFrameworkCompliance()); + + const summary = generateSecuritySummary(checks); + + // Output results as JSON + const results = { + timestamp: new Date().toISOString(), + summary, + checks + }; + + // Write to file for consumption by other scripts + const outputPath = path.join(process.cwd(), 'security-check-results.json'); + fs.writeFileSync(outputPath, JSON.stringify(results, null, 2)); + + console.log('\nāœ… Security checks complete!'); + console.log(`Results written to: ${outputPath}`); + console.log(`\nSummary: ${summary.passed} passed, ${summary.warned} warnings, ${summary.failed} failed, ${summary.skipped} skipped`); + + // Exit with non-zero if there are failures + process.exit(summary.failed > 0 ? 1 : 0); +} + +// Run if called directly +if (require.main === module) { + main(); +} + +module.exports = { + main, + execCommand, + runNpmAudit, + checkOutdatedDependencies, + runEslintSecurityCheck, + checkSecurityHeaders, + checkLicenseCompliance, + scanForSecrets, + checkFrameworkCompliance, + generateSecuritySummary +}; diff --git a/scripts/run-security-checks.test.js b/scripts/run-security-checks.test.js new file mode 100644 index 000000000..7fb4c2648 --- /dev/null +++ b/scripts/run-security-checks.test.js @@ -0,0 +1,131 @@ +/** + * Tests for run-security-checks.js + * + * Tests the comprehensive security check script focusing on + * the security summary generation and result structure validation. + */ + +describe('Security Check Script', () => { + let module; + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + + // Suppress console output during tests + global.console = { + ...console, + log: jest.fn(), + error: jest.fn() + }; + + // Load the module fresh for each test + module = require('./run-security-checks.js'); + }); + + describe('Security Summary Generation', () => { + it('should calculate correct overall status for all passing checks', () => { + const checks = [ + { status: 'pass', severity: 'none' }, + { status: 'pass', severity: 'none' }, + { status: 'pass', severity: 'none' } + ]; + + const summary = module.generateSecuritySummary(checks); + + expect(summary.overallStatus).toBe('pass'); + expect(summary.overallSeverity).toBe('none'); + expect(summary.passed).toBe(3); + expect(summary.failed).toBe(0); + expect(summary.warned).toBe(0); + }); + + it('should set overall status to fail when any check fails', () => { + const checks = [ + { status: 'pass', severity: 'none' }, + { status: 'fail', severity: 'critical' }, + { status: 'pass', severity: 'none' } + ]; + + const summary = module.generateSecuritySummary(checks); + + expect(summary.overallStatus).toBe('fail'); + expect(summary.overallSeverity).toBe('critical'); + expect(summary.passed).toBe(2); + expect(summary.failed).toBe(1); + }); + + it('should set overall status to warn when checks have warnings but no failures', () => { + const checks = [ + { status: 'pass', severity: 'none' }, + { status: 'warn', severity: 'moderate' }, + { status: 'pass', severity: 'none' } + ]; + + const summary = module.generateSecuritySummary(checks); + + expect(summary.overallStatus).toBe('warn'); + expect(summary.overallSeverity).toBe('moderate'); + expect(summary.passed).toBe(2); + expect(summary.warned).toBe(1); + expect(summary.failed).toBe(0); + }); + + it('should prioritize critical severity over high', () => { + const checks = [ + { status: 'fail', severity: 'high' }, + { status: 'fail', severity: 'critical' } + ]; + + const summary = module.generateSecuritySummary(checks); + + expect(summary.overallSeverity).toBe('critical'); + }); + + it('should prioritize high severity over moderate', () => { + const checks = [ + { status: 'warn', severity: 'moderate' }, + { status: 'warn', severity: 'high' } + ]; + + const summary = module.generateSecuritySummary(checks); + + expect(summary.overallSeverity).toBe('high'); + }); + + it('should count skipped and info status checks', () => { + const checks = [ + { status: 'pass', severity: 'none' }, + { status: 'skip', severity: 'none' }, + { status: 'info', severity: 'low' } + ]; + + const summary = module.generateSecuritySummary(checks); + + expect(summary.passed).toBe(1); + expect(summary.skipped).toBe(1); + expect(summary.info).toBe(1); + expect(summary.total).toBe(3); + }); + }); + + describe('Module Exports', () => { + it('should export required functions', () => { + expect(module).toHaveProperty('main'); + expect(module).toHaveProperty('generateSecuritySummary'); + expect(module).toHaveProperty('runNpmAudit'); + expect(module).toHaveProperty('checkOutdatedDependencies'); + expect(module).toHaveProperty('runEslintSecurityCheck'); + expect(module).toHaveProperty('checkSecurityHeaders'); + expect(module).toHaveProperty('checkLicenseCompliance'); + expect(module).toHaveProperty('scanForSecrets'); + expect(module).toHaveProperty('checkFrameworkCompliance'); + }); + + it('should export functions as callable', () => { + expect(typeof module.generateSecuritySummary).toBe('function'); + expect(typeof module.runNpmAudit).toBe('function'); + expect(typeof module.checkOutdatedDependencies).toBe('function'); + }); + }); +});