Skip to content

Commit 4d554a4

Browse files
feat(github): add Dependabot alert interface and enhance alert counting (#1997)
Co-authored-by: Tofik Hasanov <annexcies@gmail.com>
1 parent e54177a commit 4d554a4

2 files changed

Lines changed: 149 additions & 5 deletions

File tree

packages/integration-platform/src/manifests/github/checks/dependabot.ts

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
/**
22
* Dependabot Check
33
* Verifies that Dependabot security updates are enabled on repositories
4+
* and reports the count of open/closed security alerts.
45
*/
56

67
import { TASK_TEMPLATES } from '../../../task-mappings';
78
import type { IntegrationCheck } from '../../../types';
8-
import type { GitHubOrg, GitHubRepo } from '../types';
9+
import type { GitHubDependabotAlert, GitHubOrg, GitHubRepo } from '../types';
910
import { targetReposVariable } from '../variables';
1011

12+
interface AlertCounts {
13+
open: number;
14+
dismissed: number;
15+
fixed: number;
16+
total: number;
17+
bySeverity: {
18+
critical: number;
19+
high: number;
20+
medium: number;
21+
low: number;
22+
};
23+
}
24+
1125
export const dependabotCheck: IntegrationCheck = {
1226
id: 'dependabot_enabled',
1327
name: 'Dependabot Security Updates Enabled',
@@ -43,30 +57,128 @@ export const dependabotCheck: IntegrationCheck = {
4357

4458
ctx.log(`Checking ${repos.length} repositories for Dependabot`);
4559

60+
/**
61+
* Fetch Dependabot alerts for a repository and calculate counts
62+
*/
63+
const fetchAlertCounts = async (repoFullName: string): Promise<AlertCounts | null> => {
64+
try {
65+
const alerts = await ctx.fetchAllPages<GitHubDependabotAlert>(
66+
`/repos/${repoFullName}/dependabot/alerts`,
67+
);
68+
69+
const counts: AlertCounts = {
70+
open: 0,
71+
dismissed: 0,
72+
fixed: 0,
73+
total: alerts.length,
74+
bySeverity: { critical: 0, high: 0, medium: 0, low: 0 },
75+
};
76+
77+
for (const alert of alerts) {
78+
// Count by state
79+
if (alert.state === 'open') counts.open++;
80+
else if (alert.state === 'dismissed') counts.dismissed++;
81+
else if (alert.state === 'fixed') counts.fixed++;
82+
83+
// Count open alerts by severity
84+
if (alert.state === 'open') {
85+
const severity = alert.security_vulnerability?.severity ?? 'low';
86+
counts.bySeverity[severity]++;
87+
}
88+
}
89+
90+
return counts;
91+
} catch (error) {
92+
const errorStr = String(error);
93+
// 403 usually means Dependabot alerts are not enabled or no permission
94+
if (errorStr.includes('403') || errorStr.includes('Forbidden')) {
95+
ctx.log(`Cannot access Dependabot alerts for ${repoFullName} (permission denied)`);
96+
return null;
97+
}
98+
ctx.warn(`Failed to fetch Dependabot alerts for ${repoFullName}: ${errorStr}`);
99+
return null;
100+
}
101+
};
102+
103+
/**
104+
* Format alert counts for display
105+
*/
106+
const formatAlertSummary = (counts: AlertCounts): string => {
107+
const parts: string[] = [];
108+
109+
if (counts.open > 0) {
110+
const severityBreakdown: string[] = [];
111+
if (counts.bySeverity.critical > 0)
112+
severityBreakdown.push(`${counts.bySeverity.critical} critical`);
113+
if (counts.bySeverity.high > 0) severityBreakdown.push(`${counts.bySeverity.high} high`);
114+
if (counts.bySeverity.medium > 0)
115+
severityBreakdown.push(`${counts.bySeverity.medium} medium`);
116+
if (counts.bySeverity.low > 0) severityBreakdown.push(`${counts.bySeverity.low} low`);
117+
118+
parts.push(`${counts.open} open (${severityBreakdown.join(', ')})`);
119+
} else {
120+
parts.push('0 open');
121+
}
122+
123+
parts.push(`${counts.fixed} fixed`);
124+
parts.push(`${counts.dismissed} dismissed`);
125+
126+
return parts.join(', ');
127+
};
128+
46129
for (const repo of repos) {
47130
const dependabotStatus = repo.security_and_analysis?.dependabot_security_updates?.status;
48131

132+
// Fetch alert counts regardless of Dependabot status
133+
const alertCounts = await fetchAlertCounts(repo.full_name);
134+
49135
if (dependabotStatus === 'enabled') {
136+
const alertSummary = alertCounts
137+
? `\n\nAlert Summary: ${formatAlertSummary(alertCounts)}`
138+
: '';
139+
50140
ctx.pass({
51141
title: `Dependabot enabled on ${repo.name}`,
52-
description:
53-
'Dependabot security updates are enabled and will automatically create pull requests to fix vulnerable dependencies.',
142+
description: `Dependabot security updates are enabled and will automatically create pull requests to fix vulnerable dependencies.${alertSummary}`,
54143
resourceType: 'repository',
55144
resourceId: repo.full_name,
56145
evidence: {
57146
security_and_analysis: repo.security_and_analysis,
147+
...(alertCounts && {
148+
alerts: {
149+
open: alertCounts.open,
150+
fixed: alertCounts.fixed,
151+
dismissed: alertCounts.dismissed,
152+
total: alertCounts.total,
153+
open_by_severity: alertCounts.bySeverity,
154+
},
155+
}),
58156
checked_at: new Date().toISOString(),
59157
},
60158
});
61159
} else {
160+
const alertSummary = alertCounts
161+
? `\n\nAlert Summary: ${formatAlertSummary(alertCounts)}`
162+
: '';
163+
62164
ctx.fail({
63165
title: `Dependabot not enabled on ${repo.name}`,
64-
description:
65-
'Dependabot security updates are not enabled, leaving the repository vulnerable to known dependency exploits.',
166+
description: `Dependabot security updates are not enabled, leaving the repository vulnerable to known dependency exploits.${alertSummary}`,
66167
resourceType: 'repository',
67168
resourceId: repo.full_name,
68169
severity: 'medium',
69170
remediation: `1. Go to ${repo.html_url}/settings/security_analysis\n2. Enable "Dependabot security updates"\n3. Optionally enable "Dependabot version updates" for proactive updates`,
171+
evidence: alertCounts
172+
? {
173+
alerts: {
174+
open: alertCounts.open,
175+
fixed: alertCounts.fixed,
176+
dismissed: alertCounts.dismissed,
177+
total: alertCounts.total,
178+
open_by_severity: alertCounts.bySeverity,
179+
},
180+
}
181+
: undefined,
70182
});
71183
}
72184
}

packages/integration-platform/src/manifests/github/types.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,35 @@ export interface GitHubTreeEntry {
111111
size?: number;
112112
url: string;
113113
}
114+
115+
/**
116+
* Dependabot alert response
117+
* Returned by /repos/{owner}/{repo}/dependabot/alerts
118+
*/
119+
export interface GitHubDependabotAlert {
120+
number: number;
121+
state: 'open' | 'dismissed' | 'fixed';
122+
dependency: {
123+
package: {
124+
ecosystem: string;
125+
name: string;
126+
};
127+
manifest_path: string;
128+
};
129+
security_advisory: {
130+
ghsa_id: string;
131+
cve_id: string | null;
132+
summary: string;
133+
severity: 'low' | 'medium' | 'high' | 'critical';
134+
};
135+
security_vulnerability: {
136+
severity: 'low' | 'medium' | 'high' | 'critical';
137+
};
138+
created_at: string;
139+
updated_at: string;
140+
dismissed_at: string | null;
141+
dismissed_by: { login: string } | null;
142+
dismissed_reason: string | null;
143+
fixed_at: string | null;
144+
html_url: string;
145+
}

0 commit comments

Comments
 (0)