mirror of
https://github.com/ivuorinen/actions.git
synced 2026-01-26 11:34:00 +00:00
fix: security-trends fixes, docs, tweaks
This commit is contained in:
2
.github/workflows/action-security.yml
vendored
2
.github/workflows/action-security.yml
vendored
@@ -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'
|
||||
|
||||
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@@ -1,3 +1,5 @@
|
||||
---
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
|
||||
2
.github/workflows/pr-lint.yml
vendored
2
.github/workflows/pr-lint.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -1,3 +1,5 @@
|
||||
---
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
||||
name: Release
|
||||
|
||||
on:
|
||||
|
||||
4
.github/workflows/scorecard.yml
vendored
4
.github/workflows/scorecard.yml
vendored
@@ -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]
|
||||
|
||||
|
||||
2
.github/workflows/security-metrics.yml
vendored
2
.github/workflows/security-metrics.yml
vendored
@@ -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
|
||||
|
||||
233
.github/workflows/security-trends.yml
vendored
233
.github/workflows/security-trends.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/security.yml
vendored
2
.github/workflows/security.yml
vendored
@@ -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:
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -4,7 +4,7 @@ name: Stale
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
- cron: '0 8 * * *' # Every day at 08:00
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
4
.github/workflows/sync-labels.yml
vendored
4
.github/workflows/sync-labels.yml
vendored
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user