Skip to content

Commit 558ae63

Browse files
committed
fix(github): classify 2fa check permission errors precisely
1 parent 592cea7 commit 558ae63

1 file changed

Lines changed: 69 additions & 5 deletions

File tree

packages/integration-platform/src/manifests/github/checks/two-factor-auth.ts

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,57 @@ interface GitHubOrgMember {
2121
const MAX_USERNAMES_IN_DESCRIPTION = 20;
2222
const MAX_USERNAMES_IN_EVIDENCE = 100;
2323

24-
const isOwnerPermissionError = (errorMsg: string): boolean => {
24+
const getHttpStatus = (error: unknown): number | null => {
25+
if (
26+
typeof error === 'object' &&
27+
error !== null &&
28+
'status' in error &&
29+
typeof (error as { status?: unknown }).status === 'number'
30+
) {
31+
return (error as { status: number }).status;
32+
}
33+
return null;
34+
};
35+
36+
const isOwnerPermissionError = (error: unknown, errorMsg: string): boolean => {
37+
const status = getHttpStatus(error);
2538
const lower = errorMsg.toLowerCase();
2639

27-
if (lower.includes('403') || lower.includes('forbidden')) return true;
40+
// GitHub documents 422 when 2fa_* filters are used in unsupported contexts.
41+
if (status === 422) return true;
42+
2843
if (lower.includes('must be an organization owner') || lower.includes('organization owners')) {
2944
return true;
3045
}
3146

32-
// GitHub documents 422 for this endpoint when filter constraints fail.
33-
if (lower.includes('422') || lower.includes('unprocessable') || lower.includes('validation failed')) {
47+
if (
48+
lower.includes('422') ||
49+
lower.includes('unprocessable') ||
50+
lower.includes('validation failed')
51+
) {
3452
return true;
3553
}
3654

3755
return false;
3856
};
3957

58+
const isSamlSsoError = (errorMsg: string): boolean => {
59+
const lower = errorMsg.toLowerCase();
60+
return lower.includes('saml') || lower.includes('single sign-on') || lower.includes('sso');
61+
};
62+
63+
const isRateLimitError = (error: unknown, errorMsg: string): boolean => {
64+
const status = getHttpStatus(error);
65+
const lower = errorMsg.toLowerCase();
66+
67+
return (
68+
status === 429 ||
69+
lower.includes('rate limit') ||
70+
lower.includes('abuse detection') ||
71+
(status === 403 && lower.includes('secondary rate limit'))
72+
);
73+
};
74+
4075
const formatUsernamesPreview = (members: GitHubOrgMember[]): string => {
4176
const preview = members.slice(0, MAX_USERNAMES_IN_DESCRIPTION).map((member) => `@${member.login}`);
4277
const remaining = members.length - preview.length;
@@ -106,8 +141,37 @@ export const twoFactorAuthCheck: IntegrationCheck = {
106141
} catch (error) {
107142
const errorMsg = error instanceof Error ? error.message : String(error);
108143

144+
if (isSamlSsoError(errorMsg)) {
145+
ctx.warn(`Cannot check 2FA for ${org.login}: SSO authorization is required.`);
146+
ctx.fail({
147+
title: `Cannot verify 2FA for ${org.login}`,
148+
description:
149+
'GitHub organization SSO authorization is required to access organization members.',
150+
resourceType: 'organization',
151+
resourceId: org.login,
152+
severity: 'medium',
153+
remediation:
154+
'Authorize this OAuth app for your organization SSO, then rerun the check.',
155+
});
156+
continue;
157+
}
158+
159+
if (isRateLimitError(error, errorMsg)) {
160+
ctx.warn(`Rate limit reached while checking 2FA for ${org.login}.`);
161+
ctx.fail({
162+
title: `Rate limited while checking ${org.login}`,
163+
description:
164+
'GitHub rate limits prevented completion of this 2FA check for the organization.',
165+
resourceType: 'organization',
166+
resourceId: org.login,
167+
severity: 'low',
168+
remediation: 'Wait for the GitHub rate limit to reset, then rerun the check.',
169+
});
170+
continue;
171+
}
172+
109173
// GitHub returns 422 when the caller is not an org owner for 2fa_* filters.
110-
if (isOwnerPermissionError(errorMsg)) {
174+
if (isOwnerPermissionError(error, errorMsg)) {
111175
ctx.warn(
112176
`Cannot check 2FA for ${org.login}: the account must be an organization owner to use the 2FA filter.`,
113177
);

0 commit comments

Comments
 (0)