Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/__tests__/sarif.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,31 @@ describe('toSarif()', () => {
expect(sarif.runs[0].tool.driver.rules[0].id).toBe(RuleId.NO_AUTH);
});

it('rules carry a GitHub "security-severity" property', () => {
const finding = makeFinding({ ruleId: RuleId.NO_AUTH, severity: Severity.CRITICAL });
const sarif = toSarif(makeReport({ findings: [finding] }));
expect(sarif.runs[0].tool.driver.rules[0].properties).toEqual({
'security-severity': '9.0',
});
});

it('security-severity score maps to GitHub severity bands', () => {
const cases: Array<[Severity, string]> = [
[Severity.CRITICAL, '9.0'], // >= 9.0 → critical
[Severity.HIGH, '8.0'], // 7.0–8.9 → high
[Severity.MEDIUM, '5.0'], // 4.0–6.9 → medium
[Severity.LOW, '2.0'], // < 4.0 → low
[Severity.INFO, '0.0'],
];
for (const [severity, score] of cases) {
const finding = makeFinding({ ruleId: RuleId.NO_AUTH, severity });
const sarif = toSarif(makeReport({ findings: [finding] }));
expect(sarif.runs[0].tool.driver.rules[0].properties).toEqual({
'security-severity': score,
});
}
});

it('configPath is included as artifact URI when provided', () => {
const finding = makeFinding({ ruleId: RuleId.NO_AUTH, severity: Severity.CRITICAL });
const sarif = toSarif(makeReport({ findings: [finding] }), './mcp-config.json');
Expand Down
28 changes: 28 additions & 0 deletions src/sarif.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface SarifRule {
name: string;
shortDescription: { text: string };
helpUri?: string;
properties?: { 'security-severity': string };
}

interface SarifResult {
Expand Down Expand Up @@ -67,6 +68,32 @@ function severityToLevel(severity: Severity): 'error' | 'warning' | 'note' {
}
}

/**
* Map a Severity to a GitHub Code Scanning `security-severity` score.
*
* GitHub reads this numeric string (0.0–10.0) from each rule's `properties`
* bag to assign the Critical/High/Medium/Low badge in the Security tab and to
* drive severity-based alert filtering and branch-protection rules. Without it,
* GitHub can only rank by SARIF `level`, so CRITICAL and HIGH (both `error`)
* become indistinguishable. The bands follow GitHub's documented thresholds:
* 9.0+ → critical, 7.0–8.9 → high, 4.0–6.9 → medium, <4.0 → low.
*/
function severityToScore(severity: Severity): string {
switch (severity) {
case Severity.CRITICAL:
return '9.0';
case Severity.HIGH:
return '8.0';
case Severity.MEDIUM:
return '5.0';
case Severity.LOW:
return '2.0';
case Severity.INFO:
default:
return '0.0';
}
}

/**
* Convert a SecurityReport to a minimal SARIF 2.1.0 document.
*
Expand All @@ -83,6 +110,7 @@ export function toSarif(report: SecurityReport, configPath?: string): SarifOutpu
name: finding.title,
shortDescription: { text: finding.title },
helpUri: finding.docsUrl,
properties: { 'security-severity': severityToScore(finding.severity) },
});
}
}
Expand Down
Loading