Skip to content

Commit e54177a

Browse files
feat(github): add advanced security status and enhance code scanning detection (#1995)
Co-authored-by: Tofik Hasanov <annexcies@gmail.com>
1 parent 6272146 commit e54177a

2 files changed

Lines changed: 195 additions & 31 deletions

File tree

‎packages/integration-platform/src/manifests/github/checks/sanitized-inputs.ts‎

Lines changed: 194 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
* Ensures repositories use a modern validation/sanitization library
55
* (Zod or Pydantic) and have automated static analysis (CodeQL) enabled.
66
* Supports monorepos by scanning all package.json/requirements.txt files.
7+
*
8+
* Code scanning detection supports:
9+
* - GitHub CodeQL default setup
10+
* - Custom CodeQL workflow files (.github/workflows/*.yml with codeql-action)
11+
* - Third-party SARIF uploaders
712
*/
813

914
import { TASK_TEMPLATES } from '../../../task-mappings';
@@ -21,6 +26,17 @@ const PY_VALIDATION_PACKAGES = ['pydantic'];
2126

2227
const TARGET_FILES = ['package.json', 'requirements.txt', 'pyproject.toml'];
2328

29+
// Patterns that indicate code scanning is configured in a workflow
30+
const CODE_SCANNING_PATTERNS = [
31+
'github/codeql-action/init',
32+
'github/codeql-action/analyze',
33+
'github/codeql-action/upload-sarif',
34+
'codeql-action/init',
35+
'codeql-action/analyze',
36+
'codeql-action/upload-sarif',
37+
'upload-sarif', // Generic SARIF upload
38+
];
39+
2440
interface GitHubFileResponse {
2541
content: string;
2642
encoding: 'base64' | 'utf-8';
@@ -32,6 +48,17 @@ interface ValidationMatch {
3248
file: string;
3349
}
3450

51+
type CodeScanningStatus =
52+
| {
53+
status: 'enabled';
54+
method: 'default-setup' | 'workflow';
55+
languages?: string[];
56+
workflow?: string;
57+
}
58+
| { status: 'not-configured' }
59+
| { status: 'permission-denied'; isPrivate: boolean }
60+
| { status: 'ghas-required' };
61+
3562
const decodeFile = (file: GitHubFileResponse): string => {
3663
if (!file?.content) return '';
3764
if (file.encoding === 'base64') {
@@ -160,18 +187,105 @@ export const sanitizedInputsCheck: IntegrationCheck = {
160187
return matches;
161188
};
162189

163-
const isCodeScanningEnabled = async (repoName: string) => {
190+
/**
191+
* Check if a workflow file contains code scanning configuration
192+
*/
193+
const hasCodeScanningInWorkflow = (content: string): boolean => {
194+
const lower = content.toLowerCase();
195+
return CODE_SCANNING_PATTERNS.some((pattern) => lower.includes(pattern.toLowerCase()));
196+
};
197+
198+
/**
199+
* Find workflow files that configure code scanning
200+
*/
201+
const findCodeScanningWorkflows = async (
202+
repoName: string,
203+
tree: GitHubTreeEntry[],
204+
): Promise<string[]> => {
205+
const workflowFiles = tree.filter(
206+
(entry) =>
207+
entry.type === 'blob' &&
208+
entry.path.startsWith('.github/workflows/') &&
209+
(entry.path.endsWith('.yml') || entry.path.endsWith('.yaml')),
210+
);
211+
212+
const codeScanningWorkflows: string[] = [];
213+
214+
for (const entry of workflowFiles) {
215+
const content = await fetchFile(repoName, entry.path);
216+
if (content && hasCodeScanningInWorkflow(content)) {
217+
codeScanningWorkflows.push(entry.path);
218+
}
219+
}
220+
221+
return codeScanningWorkflows;
222+
};
223+
224+
/**
225+
* Check code scanning status using multiple detection methods
226+
*/
227+
const getCodeScanningStatus = async ({
228+
repoName,
229+
tree,
230+
isPrivate,
231+
isGhasEnabled,
232+
}: {
233+
repoName: string;
234+
tree: GitHubTreeEntry[];
235+
isPrivate: boolean;
236+
isGhasEnabled: boolean;
237+
}): Promise<CodeScanningStatus> => {
238+
// First, try the default setup API
164239
try {
165240
const setup = await ctx.fetch<GitHubCodeScanningDefaultSetup>(
166241
`/repos/${repoName}/code-scanning/default-setup`,
167242
);
168-
return setup.state === 'configured'
169-
? { enabled: true, languages: setup.languages || [] }
170-
: { enabled: false, languages: setup.languages || [] };
243+
if (setup.state === 'configured') {
244+
return {
245+
status: 'enabled',
246+
method: 'default-setup',
247+
languages: setup.languages || [],
248+
};
249+
}
171250
} catch (error) {
172-
ctx.log(`Code scanning not configured for ${repoName}: ${String(error)}`);
173-
return { enabled: false };
251+
const errorStr = String(error);
252+
253+
// Check for 403 Forbidden - indicates permission issues or GHAS not enabled
254+
// Return early without checking workflows since we can't verify they're actually running
255+
if (errorStr.includes('403') || errorStr.includes('Forbidden')) {
256+
ctx.log(
257+
`Code scanning API returned 403 for ${repoName} (private: ${isPrivate}, ghas: ${isGhasEnabled})`,
258+
);
259+
260+
// For private repos, distinguish between GHAS not enabled vs permission issue
261+
if (isPrivate) {
262+
if (isGhasEnabled) {
263+
// GHAS is enabled but we still got 403 - it's a permission issue
264+
return { status: 'permission-denied', isPrivate };
265+
}
266+
// GHAS is not enabled - that's why we got 403
267+
return { status: 'ghas-required' };
268+
}
269+
270+
return { status: 'permission-denied', isPrivate };
271+
}
272+
273+
// Other errors - API might not be available, continue to check workflows
274+
ctx.log(`Code scanning default setup not available for ${repoName}: ${errorStr}`);
174275
}
276+
277+
// Fall back to checking for workflow files with code scanning
278+
// Only reached if API didn't return 403 (meaning we have access but default setup isn't on)
279+
const codeScanningWorkflows = await findCodeScanningWorkflows(repoName, tree);
280+
if (codeScanningWorkflows.length > 0) {
281+
return {
282+
status: 'enabled',
283+
method: 'workflow',
284+
workflow: codeScanningWorkflows[0],
285+
};
286+
}
287+
288+
return { status: 'not-configured' };
175289
};
176290

177291
for (const repoName of targetRepos) {
@@ -181,8 +295,18 @@ export const sanitizedInputsCheck: IntegrationCheck = {
181295
// Fetch the full tree to find all package.json/requirements.txt files
182296
const tree = await fetchRepoTree(repo.full_name, repo.default_branch);
183297
const validationMatches = await findValidationLibraries(repo.full_name, tree);
184-
const codeScanning = await isCodeScanningEnabled(repo.full_name);
185298

299+
// Check if GHAS is enabled (for private repos, this helps distinguish permission issues)
300+
const isGhasEnabled = repo.security_and_analysis?.advanced_security?.status === 'enabled';
301+
302+
const codeScanningStatus = await getCodeScanningStatus({
303+
repoName: repo.full_name,
304+
tree,
305+
isPrivate: repo.private,
306+
isGhasEnabled,
307+
});
308+
309+
// Report input validation results
186310
if (validationMatches.length > 0) {
187311
ctx.pass({
188312
title: `Input validation enabled in ${repo.name}`,
@@ -216,30 +340,69 @@ export const sanitizedInputsCheck: IntegrationCheck = {
216340
});
217341
}
218342

219-
if (codeScanning.enabled) {
220-
ctx.pass({
221-
title: `CodeQL scanning configured for ${repo.name}`,
222-
description: 'GitHub CodeQL default setup is enabled.',
223-
resourceType: 'repository',
224-
resourceId: repo.full_name,
225-
evidence: {
226-
repository: repo.full_name,
227-
codeScanning: 'CodeQL default setup',
228-
languages: codeScanning.languages,
229-
checkedAt: new Date().toISOString(),
230-
},
231-
});
232-
} else {
233-
ctx.fail({
234-
title: `Code scanning not enabled for ${repo.name}`,
235-
description:
236-
'GitHub CodeQL (or an equivalent static analysis tool) is not configured. Enable it to automatically detect insecure patterns.',
237-
resourceType: 'repository',
238-
resourceId: repo.full_name,
239-
severity: 'medium',
240-
remediation:
241-
'In the repository Security tab, enable CodeQL default setup (or add a custom workflow) to run on every push.',
242-
});
343+
// Report code scanning results based on status
344+
switch (codeScanningStatus.status) {
345+
case 'enabled': {
346+
const methodDescription =
347+
codeScanningStatus.method === 'default-setup'
348+
? 'GitHub CodeQL default setup is enabled.'
349+
: `Code scanning configured via workflow: ${codeScanningStatus.workflow}`;
350+
351+
ctx.pass({
352+
title: `CodeQL scanning configured for ${repo.name}`,
353+
description: methodDescription,
354+
resourceType: 'repository',
355+
resourceId: repo.full_name,
356+
evidence: {
357+
repository: repo.full_name,
358+
codeScanning: codeScanningStatus.method,
359+
...(codeScanningStatus.languages && { languages: codeScanningStatus.languages }),
360+
...(codeScanningStatus.workflow && { workflow: codeScanningStatus.workflow }),
361+
checkedAt: new Date().toISOString(),
362+
},
363+
});
364+
break;
365+
}
366+
367+
case 'ghas-required':
368+
ctx.fail({
369+
title: `Code scanning requires GitHub Advanced Security for ${repo.name}`,
370+
description:
371+
'This is a private repository. GitHub Advanced Security (GHAS) must be enabled before CodeQL can be configured. GHAS is a paid feature for private repositories.',
372+
resourceType: 'repository',
373+
resourceId: repo.full_name,
374+
severity: 'medium',
375+
remediation:
376+
'Enable GitHub Advanced Security in the repository settings (Settings → Code security and analysis → GitHub Advanced Security), then enable CodeQL.',
377+
});
378+
break;
379+
380+
case 'permission-denied':
381+
ctx.fail({
382+
title: `Cannot access code scanning configuration for ${repo.name}`,
383+
description:
384+
'The GitHub integration does not have permission to read code scanning configuration. This may be due to missing permissions or organization policies.',
385+
resourceType: 'repository',
386+
resourceId: repo.full_name,
387+
severity: 'medium',
388+
remediation:
389+
'Ensure the GitHub App has "Code scanning alerts: Read" permission. If this is an organization repository, check that organization policies allow access.',
390+
});
391+
break;
392+
393+
case 'not-configured':
394+
default:
395+
ctx.fail({
396+
title: `Code scanning not enabled for ${repo.name}`,
397+
description:
398+
'GitHub CodeQL (or an equivalent static analysis tool) is not configured. Enable it to automatically detect insecure patterns.',
399+
resourceType: 'repository',
400+
resourceId: repo.full_name,
401+
severity: 'medium',
402+
remediation:
403+
'In the repository Security tab, enable CodeQL default setup (or add a custom workflow) to run on every push.',
404+
});
405+
break;
243406
}
244407
}
245408
},

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface GitHubRepo {
1717
default_branch: string;
1818
owner: { login: string };
1919
security_and_analysis?: {
20+
advanced_security?: { status: 'enabled' | 'disabled' };
2021
dependabot_security_updates?: { status: 'enabled' | 'disabled' };
2122
secret_scanning?: { status: 'enabled' | 'disabled' };
2223
secret_scanning_push_protection?: { status: 'enabled' | 'disabled' };

0 commit comments

Comments
 (0)