diff --git a/.github/workflows/action-security.yml b/.github/workflows/action-security.yml index 7b9a911..a7fca5f 100644 --- a/.github/workflows/action-security.yml +++ b/.github/workflows/action-security.yml @@ -78,7 +78,7 @@ jobs: uses: aquasecurity/trivy-action@a11da62073708815958ea6d84f5650c78a3ef85b # master with: scan-type: 'fs' - security-checks: 'vuln,config,secret' + scanners: 'vuln,config,secret' format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 6d7e446..126edd9 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,3 +1,5 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: 'Dependency Review' on: [pull_request] diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 3239cc6..5914afd 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -64,7 +64,7 @@ jobs: - name: Checkout Code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }} + token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }} fetch-depth: 0 - name: MegaLinter diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 60137dd..f932b68 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,5 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: Release on: diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 1808a2a..3bd8975 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,7 +1,9 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: Scorecard on: schedule: - - cron: '0 2 * * 0' + - cron: '0 2 * * 0' # Run every Sunday at 2:00 AM push: branches: [main] diff --git a/.github/workflows/security-metrics.yml b/.github/workflows/security-metrics.yml index 91011f0..0d8efc4 100644 --- a/.github/workflows/security-metrics.yml +++ b/.github/workflows/security-metrics.yml @@ -8,7 +8,7 @@ on: types: - completed schedule: - - cron: '0 0 * * 0' # Weekly + - cron: '0 0 * * 0' # Every Sunday at 00:00 permissions: contents: read diff --git a/.github/workflows/security-trends.yml b/.github/workflows/security-trends.yml index 6fc0f6f..496ebf0 100644 --- a/.github/workflows/security-trends.yml +++ b/.github/workflows/security-trends.yml @@ -11,6 +11,7 @@ on: permissions: contents: read actions: read + pull-requests: read jobs: analyze-trends: @@ -38,97 +39,187 @@ jobs: script: | const fs = require('fs'); const path = require('path'); + const core = require('@actions/core'); - try { - // ... [previous code remains the same until report generation] + async function collectMetrics() { + const metrics = { + timestamp: new Date().toISOString(), + weekly: { + scans: 0, + vulnerabilities: { + critical: 0, + high: 0, + medium: 0, + low: 0 + }, + fixes: { + submitted: 0, + merged: 0 + }, + meanTimeToFix: null // Initialize as null instead of 0 + }, + history: [], + trends: { + vulnerabilities: { + trend: 'stable', + percentage: 0 + }, + fixRate: { + trend: 'stable', + percentage: 0 + } + } + }; - // Generate trend report - const report = generateTrendReport(trends); + try { + // Check if the artifacts exist + const files = fs.readdirSync('latest-results'); + const requiredFiles = ['vulnerability-summary.json', 'security-badge.json', 'security-report.md']; + const missingFiles = requiredFiles.filter(file => !files.includes(file)); + + if (missingFiles.length > 0) { + throw new Error(`Missing required files: ${missingFiles.join(', ')}`); + } - // Save report explicitly for next step - console.log('Writing trend report to file...'); - fs.writeFileSync('trend-report.md', report); - console.log('Trend report saved successfully'); + // Read the latest results + const vulnSummary = JSON.parse(fs.readFileSync('latest-results/vulnerability-summary.json', 'utf8')); + const securityBadge = JSON.parse(fs.readFileSync('latest-results/security-badge.json', 'utf8')); + const securityReport = fs.readFileSync('latest-results/security-report.md', 'utf8'); + + // Update metrics + metrics.weekly.vulnerabilities = vulnSummary.total; - // Save history - fs.writeFileSync(historyFile, JSON.stringify(history, null, 2)); + // Fetch history data if it exists + try { + const historyFile = 'security-metrics-history.json'; + if (fs.existsSync(historyFile)) { + const history = JSON.parse(fs.readFileSync(historyFile, 'utf8')); + metrics.history = history.slice(-12); // SΓ€ilytetÀÀn 12 viikon historia + } + } catch (error) { + console.log('No existing history found, starting fresh'); + } - // Generate and save chart - const chartData = generateChartData(history); - fs.writeFileSync('security-trends.svg', chartData); + // Collect PR metrics with security-fix label + const prs = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'all', + labels: ['security-fix'], + since: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString() + }); - // Set outputs for other steps - core.setOutput('has_vulnerabilities', - trends.critical.current > 0 || trends.high.current > 0); - core.setOutput('trend_status', - trends.critical.trend > 0 || trends.high.trend > 0 ? 'worsening' : 'improving'); - core.setOutput('report_path', 'trend-report.md'); + // Compute metrics + const securityFixes = prs.data.filter(pr => pr.labels.some(label => label.name === 'security-fix')); + metrics.weekly.fixes = { + submitted: securityFixes.length, + merged: securityFixes.filter(pr => pr.merged_at !== null).length, + automated: securityFixes.filter(pr => pr.labels.some(label => label.name === 'automated-pr')).length + }; - } catch (error) { - core.setFailed(`Failed to analyze trends: ${error.message}`); - throw error; + // Calculate trends + if (metrics.history.length > 0) { + const lastWeek = metrics.history[metrics.history.length - 1]; + const totalVulns = Object.values(metrics.weekly.vulnerabilities).reduce((a, b) => a + b, 0); + const lastWeekVulns = Object.values(lastWeek.weekly.vulnerabilities).reduce((a, b) => a + b, 0); + + metrics.trends.vulnerabilities = { + trend: totalVulns < lastWeekVulns ? 'improving' : totalVulns > lastWeekVulns ? 'worsening' : 'stable', + percentage: lastWeekVulns ? ((totalVulns - lastWeekVulns) / lastWeekVulns * 100).toFixed(1) : 0 + }; + } + + // Update history + metrics.history.push({ + timestamp: metrics.timestamp, + weekly: metrics.weekly + }); + + // Save metrics and history + fs.writeFileSync('security-metrics-history.json', JSON.stringify(metrics.history, null, 2)); + fs.writeFileSync('security-metrics.json', JSON.stringify(metrics, null, 2)); + + // Create the report + const report = generateEnhancedReport(metrics, securityReport); + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: 'πŸ“Š Weekly Security Metrics Report', + body: report, + labels: ['metrics', 'security'] + }); + + } catch (error) { + core.setFailed(`Failed to process security artifacts: ${error.message}`); + throw error; + } } - - name: Verify Report File - id: verify - shell: bash - run: | - if [ ! -f "trend-report.md" ]; then - echo "::error::Trend report file not found" - echo "exists=false" >> "$GITHUB_OUTPUT" - exit 1 - else - echo "exists=true" >> "$GITHUB_OUTPUT" - echo "size=$(stat -f%z trend-report.md)" >> "$GITHUB_OUTPUT" - fi + function generateEnhancedReport(metrics, securityReport) { + const formatTrend = (trend) => { + const icons = { + improving: 'πŸ“ˆ', + worsening: 'πŸ“‰', + stable: '➑️' + }; + return icons[trend] || '➑️'; + }; - - name: Create Trend Report Issue - if: | - github.event.workflow_run.conclusion == 'success' && - steps.verify.outputs.exists == 'true' - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - with: - script: | - try { - const fs = require('fs'); - const reportPath = 'trend-report.md'; + const formatDuration = (hours) => { + if (hours === null) return 'N/A'; + return `${hours} hours`; + }; - console.log('Reading trend report from:', reportPath); - console.log('File size:', '${{ steps.verify.outputs.size }}', 'bytes'); + // Luodaan trendikuvaaja ASCII-grafiikkana + const generateTrendGraph = (history, metric) => { + const values = history.map(h => Object.values(h.weekly[metric]).reduce((a, b) => a + b, 0)); + const max = Math.max(...values); + const min = Math.min(...values); + const range = max - min; + const height = 5; + + return values.map(v => { + const normalized = range ? Math.floor((v - min) / range * height) : 0; + return 'β–ˆ'.repeat(normalized) + 'β–‘'.repeat(height - normalized); + }).join(' '); + }; - if (!fs.existsSync(reportPath)) { - throw new Error('Trend report file not found despite verification'); - } + return `## πŸ“Š Weekly Security Metrics Report - const report = fs.readFileSync(reportPath, 'utf8'); - if (!report.trim()) { - throw new Error('Trend report file is empty'); - } + ### Timeline + - Report Generated: ${new Date().toISOString()} + - Period: Last 7 days - const hasVulnerabilities = '${{ steps.analyze.outputs.has_vulnerabilities }}' === 'true'; - const trendStatus = '${{ steps.analyze.outputs.trend_status }}'; + ### Vulnerability Trends ${formatTrend(metrics.trends.vulnerabilities.trend)} + \`\`\` + ${generateTrendGraph(metrics.history, 'vulnerabilities')} + \`\`\` + ${metrics.trends.vulnerabilities.trend !== 'stable' ? + `Change: ${metrics.trends.vulnerabilities.percentage}% from last week` : + 'No significant change from last week'} - const title = `πŸ“Š Security Trend Report - ${ - hasVulnerabilities ? - `⚠️ Vulnerabilities ${trendStatus}` : - 'βœ… No vulnerabilities' - }`; + ### Current Status + - Critical: ${metrics.weekly.vulnerabilities.critical} + - High: ${metrics.weekly.vulnerabilities.high} + - Medium: ${metrics.weekly.vulnerabilities.medium} + - Low: ${metrics.weekly.vulnerabilities.low} - await github.rest.issues.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: title, - body: report, - labels: ['security', 'metrics', hasVulnerabilities ? 'attention-required' : 'healthy'] - }); + ### Fix Metrics + - Security PRs Submitted: ${metrics.weekly.fixes.submitted} + - Automated Fixes: ${metrics.weekly.fixes.automated} + - Successfully Merged: ${metrics.weekly.fixes.merged} + - Mean Time to Fix: ${formatDuration(metrics.weekly.meanTimeToFix)} - console.log('Successfully created trend report issue'); + ### Detailed Reports + - [Full Security Report](${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) + - [Latest Scan Results](${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/security/advisories) - } catch (error) { - console.error('Failed to create trend report issue:', error); - core.setFailed(`Failed to create trend report issue: ${error.message}`); + > This report was automatically generated by the security metrics workflow.`; } + collectMetrics(); + - name: Cleanup if: always() shell: bash diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 857799c..3380d54 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -4,7 +4,7 @@ name: Security Checks on: schedule: - - cron: '0 0 * * *' # Run daily at midnight + - cron: '0 0 * * *' # Every day at 00:00 workflow_dispatch: pull_request: paths: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index a586fb9..7d36a01 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -4,7 +4,7 @@ name: Stale on: schedule: - - cron: '0 8 * * *' + - cron: '0 8 * * *' # Every day at 08:00 workflow_call: workflow_dispatch: diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index b244307..65374f4 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -11,7 +11,7 @@ on: - '.github/labels.yml' - '.github/workflows/sync-labels.yml' schedule: - - cron: '34 5 * * *' # 5:34 AM UTC every day + - cron: '34 5 * * *' # Run every day at 05:34 AM UTC workflow_call: workflow_dispatch: @@ -132,7 +132,7 @@ jobs: - name: Run Label Syncer id: sync - uses: micnncim/action-label-syncer@3abd5e6e7981d5a790c6f8a7494374bd8c74b9c6 + uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # v1.3.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/run.sh b/run.sh index 9552c1f..6b9b088 100755 --- a/run.sh +++ b/run.sh @@ -1,5 +1,29 @@ #!/usr/bin/env bash +# Error handling +set -euo pipefail + +# Log file +log_file="update_$(date +%Y%m%d_%H%M%S).log" +exec 1> >(tee -a "$log_file") 2>&1 + +# Error handling function +handle_error() { + echo "❌ Error on line $1" | tee -a "$log_file" + exit 1 +} +trap 'handle_error $LINENO' ERR + +echo "πŸš€ Aloitetaan pΓ€ivitys $(date)" + +# Check required tools +for cmd in npx sed find grep; do + if ! command -v $cmd &> /dev/null; then + echo "❌ Error: $cmd not found" | tee -a "$log_file" + exit 1 + fi +done + # Check if the OS is macOS or Linux if [[ $OSTYPE == "darwin"* ]]; then # macOS needs -i .bak because it doesn't support -i without arguments @@ -9,12 +33,15 @@ else SED_CMD="sed -i" fi +# Iterate over directories +echo "πŸ“‚ Iterating over directories..." find . -mindepth 1 -maxdepth 1 -type d | while read -r dir; do dir=${dir#./} action="./$dir/action.yml" # if action doesn't exist, skip if [ ! -f "$action" ]; then + echo "⏩ Skipping $dir - action.yml missing" continue fi @@ -25,40 +52,58 @@ find . -mindepth 1 -maxdepth 1 -type d | while read -r dir; do # if version doesn't exist, use 'main' if [ -z "$version" ]; then version="main" + echo "ℹ️ Version not set in $dir/action.yml, using 'main'" fi - echo "Updating $readme..." + echo "πŸ“ Updating $readme..." printf "# %s\n\n" "$repo" >"$readme" - echo "- Generating action documentation..." - npx --yes action-docs@latest \ + echo "πŸ“„ Generating action documentation..." + if ! npx --yes action-docs@latest \ --source="$action" \ --no-banner \ - --include-name-header >>"$readme" + --include-name-header >>"$readme"; then + echo "⚠️ Warning: action-docs failed in $dir directory" | tee -a "$log_file" + fi - echo "- Replacing placeholders in $readme..." + echo "πŸ”„ Replacing placeholders in $readme..." $SED_CMD "s|PROJECT|$repo|g; s|VERSION|$version|g; s|\*\*\*||g" "$readme" if [ -f "$readme.bak" ]; then rm "$readme.bak" - echo "- Removed $readme.bak" + echo "πŸ—‘οΈ Removed $readme.bak" fi done echo "" -echo "Running markdownlint..." -npx --yes markdownlint-cli --fix \ - --ignore "**/node_modules/**" "**/README.md" +echo "πŸ” Running markdownlint..." +if ! npx --yes markdownlint-cli --fix \ + --ignore "**/node_modules/**" "**/README.md"; then + echo "⚠️ Warning: markdownlint found issues" | tee -a "$log_file" +fi echo "" -echo "Running prettier..." -npx --yes prettier --write \ - "run.sh", "**/README.md" "**/action.yml" ".github/workflows/*.yml" +echo "✨ Running prettier..." +if ! npx --yes prettier --write \ + "run.sh" "**/README.md" "**/action.yml" ".github/workflows/*.yml"; then + echo "⚠️ Warning: prettier formatting failed" | tee -a "$log_file" +fi echo "" -echo "Running MegaLinter..." -npx --yes mega-linter-runner +echo "πŸ”Ž Running MegaLinter..." +if ! npx --yes mega-linter-runner; then + echo "⚠️ Warning: MegaLinter found issues" | tee -a "$log_file" +fi echo "" -echo "Done!" +# Summary report +echo "πŸ“Š Summary $(date):" +echo "- Log file: $log_file" +if [ -f "$log_file" ]; then + warnings=$(grep -c "⚠️ Warning" "$log_file" || true) + echo "- Warnings: $warnings" +fi +echo "- Status: βœ… Ready" + +echo "βœ… Ready!"