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
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
ο»Ώ{
"scenario": "realistic-multilingual-defi-repo",
"commitHash": "a1b2c3d4e5f67890123456789abcdef012345678",
"rulePackVersion": "2.4.0",
"categories": [
{
"id": "SWC-107",
"taxonomy": "swc",
"name": "Reentrancy",
"description": "State change after external contract invocation",
"defaultSeverity": "critical"
},
{
"id": "SWC-101",
"taxonomy": "swc",
"name": "Integer Overflow and Underflow",
"description": "Unchecked arithmetic operations",
"defaultSeverity": "high"
},
{
"id": "CWE-89",
"taxonomy": "cwe",
"name": "SQL Injection",
"description": "Improper neutralization of special elements used in an SQL Command",
"defaultSeverity": "critical"
},
{
"id": "OWASP-A01",
"taxonomy": "owasp",
"name": "Broken Access Control",
"description": "Users acting outside of intended permissions",
"defaultSeverity": "high"
}
],
"mappings": [
{ "ruleId": "sol-reentrancy-eth", "categoryId": "SWC-107", "targetLanguages": ["solidity"] },
{ "ruleId": "sol-reentrancy-cross-fn", "categoryId": "SWC-107", "targetLanguages": ["solidity"] },
{ "ruleId": "sol-unchecked-math", "categoryId": "SWC-101", "targetLanguages": ["solidity"] },
{ "ruleId": "sql-param-check", "categoryId": "CWE-89", "targetLanguages": ["typescript", "rust"] },
{ "ruleId": "auth-rbac-guard", "categoryId": "OWASP-A01", "targetLanguages": ["typescript", "solidity"] }
],
"executedRuleIds": [
"sol-reentrancy-eth",
"sol-unchecked-math",
"sql-param-check",
"auth-rbac-guard"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
ο»Ώ{
"scenario": "regression-unmapped-and-partial-failure",
"commitHash": "deadbeefcafebabef00d123456789abcdef01234",
"rulePackVersion": "2.4.0-nightly",
"categories": [
{
"id": "SWC-107",
"taxonomy": "swc",
"name": "Reentrancy"
}
],
"mappings": [
{ "ruleId": "sol-reentrancy-eth", "categoryId": "SWC-107", "enabled": true },
{ "ruleId": "deprecated-rule-ignored", "categoryId": "SWC-107", "enabled": false }
],
"executedRuleIds": [
"sol-reentrancy-eth",
"unmapped-experimental-rule",
"third-party-custom-plugin-rule"
],
"unsupportedRules": [
{ "ruleId": "sol-inline-assembly-audit", "reason": "Repository does not contain EVM inline assembly blocks" }
]
}
5 changes: 5 additions & 0 deletions apps/api/src/rules/vulnerability-coverage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ο»Ώexport * from "./vulnerability-coverage-types";
export * from "./vulnerability-coverage-config";
export * from "./vulnerability-coverage-evaluator";
export * from "./vulnerability-coverage-store";
export * from "./vulnerability-coverage.service";
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
ο»Ώ/**
* Configuration surface for tracking rule coverage by vulnerability category (#1257).
*/

import { VulnerabilityTaxonomy } from "./vulnerability-coverage-types";

export interface RuleCoverageConfig {
/** Master switch. When false, coverage evaluation produces a disabled stub without failing scans. */
enabled: boolean;
/** Primary taxonomies enabled for tracking. */
enabledTaxonomies: VulnerabilityTaxonomy[];
/** Minimum required category coverage percentage (0-100) before triggering a quality warning. */
warningThresholdPercent: number;
/** Minimum required category coverage percentage (0-100) before failing a quality gate. */
strictGateThresholdPercent: number;
/** Whether to fail when rules without categories are detected. */
allowUncategorizedRules: boolean;
/** Maximum allowable identifier string lengths for tenantId / repoId. */
maxIdentifierLength: number;
/** How long coverage reports are retained in milliseconds (default: 90 days). */
retentionPeriodMs: number;
}

export const DEFAULT_RULE_COVERAGE_CONFIG: Readonly<RuleCoverageConfig> = Object.freeze({
enabled: true,
enabledTaxonomies: ["cwe", "swc", "owasp", "custom"],
warningThresholdPercent: 75,
strictGateThresholdPercent: 50,
allowUncategorizedRules: true,
maxIdentifierLength: 128,
retentionPeriodMs: 90 * 24 * 60 * 60 * 1000, // 90 days
});

export function resolveRuleCoverageConfig(
overrides?: Partial<RuleCoverageConfig>
): RuleCoverageConfig {
if (!overrides) {
return { ...DEFAULT_RULE_COVERAGE_CONFIG };
}

return {
enabled: overrides.enabled !== undefined ? Boolean(overrides.enabled) : DEFAULT_RULE_COVERAGE_CONFIG.enabled,
enabledTaxonomies: overrides.enabledTaxonomies
? [...overrides.enabledTaxonomies]
: [...DEFAULT_RULE_COVERAGE_CONFIG.enabledTaxonomies],
warningThresholdPercent:
typeof overrides.warningThresholdPercent === "number" &&
overrides.warningThresholdPercent >= 0 &&
overrides.warningThresholdPercent <= 100
? overrides.warningThresholdPercent
: DEFAULT_RULE_COVERAGE_CONFIG.warningThresholdPercent,
strictGateThresholdPercent:
typeof overrides.strictGateThresholdPercent === "number" &&
overrides.strictGateThresholdPercent >= 0 &&
overrides.strictGateThresholdPercent <= 100
? overrides.strictGateThresholdPercent
: DEFAULT_RULE_COVERAGE_CONFIG.strictGateThresholdPercent,
allowUncategorizedRules:
overrides.allowUncategorizedRules !== undefined
? Boolean(overrides.allowUncategorizedRules)
: DEFAULT_RULE_COVERAGE_CONFIG.allowUncategorizedRules,
maxIdentifierLength:
typeof overrides.maxIdentifierLength === "number" && overrides.maxIdentifierLength > 0
? overrides.maxIdentifierLength
: DEFAULT_RULE_COVERAGE_CONFIG.maxIdentifierLength,
retentionPeriodMs:
typeof overrides.retentionPeriodMs === "number" && overrides.retentionPeriodMs > 0
? overrides.retentionPeriodMs
: DEFAULT_RULE_COVERAGE_CONFIG.retentionPeriodMs,
};
}

export function loadRuleCoverageConfigFromEnv(): RuleCoverageConfig {
return resolveRuleCoverageConfig({
enabled: process.env.RULE_COVERAGE_ENABLED !== undefined ? process.env.RULE_COVERAGE_ENABLED === "true" : undefined,
warningThresholdPercent: process.env.RULE_COVERAGE_WARNING_THRESHOLD
? Number(process.env.RULE_COVERAGE_WARNING_THRESHOLD)
: undefined,
strictGateThresholdPercent: process.env.RULE_COVERAGE_GATE_THRESHOLD
? Number(process.env.RULE_COVERAGE_GATE_THRESHOLD)
: undefined,
allowUncategorizedRules:
process.env.RULE_COVERAGE_ALLOW_UNCATEGORIZED !== undefined
? process.env.RULE_COVERAGE_ALLOW_UNCATEGORIZED === "true"
: undefined,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
ο»Ώimport {
evaluateRuleCoverage,
computeCoverageFingerprint,
} from "./vulnerability-coverage-evaluator";
import { resolveRuleCoverageConfig } from "./vulnerability-coverage-config";
import realisticFixture from "./__fixtures__/realistic-repo-fixture.json";
import regressionFixture from "./__fixtures__/regression-case-fixture.json";

describe("Vulnerability Coverage Evaluator", () => {
const config = resolveRuleCoverageConfig();

it("produces deterministic coverage reports and fingerprints for identical inputs", () => {
const input = {
tenantId: "tenant-alpha",
repoId: "repo-defi-vault",
scanId: "scan-1001",
commitHash: realisticFixture.commitHash,
rulePackVersion: realisticFixture.rulePackVersion,
categories: realisticFixture.categories as any,
mappings: realisticFixture.mappings,
executedRuleIds: realisticFixture.executedRuleIds,
config,
};

const report1 = evaluateRuleCoverage(input);
const report2 = evaluateRuleCoverage(input);

expect(report1.fingerprint).toBe(report2.fingerprint);
expect(report1.overallCoveragePercent).toBe(report2.overallCoveragePercent);
expect(report1.totalExecutedRules).toBe(4);
expect(report1.totalRules).toBe(5);
expect(report1.overallCoveragePercent).toBe(80);
expect(report1.coveredCategoriesCount).toBe(4);
});

it("handles regression scenario with uncategorized rules and disabled mappings", () => {
const input = {
tenantId: "tenant-alpha",
repoId: "repo-edge-case",
scanId: "scan-1002",
commitHash: regressionFixture.commitHash,
rulePackVersion: regressionFixture.rulePackVersion,
categories: regressionFixture.categories as any,
mappings: regressionFixture.mappings,
executedRuleIds: regressionFixture.executedRuleIds,
unsupportedRules: regressionFixture.unsupportedRules,
config,
};

const report = evaluateRuleCoverage(input);

expect(report.totalRules).toBe(1); // disabled rule excluded
expect(report.totalExecutedRules).toBe(1);
expect(report.overallCoveragePercent).toBe(100);
expect(report.uncategorizedRules).toEqual([
"third-party-custom-plugin-rule",
"unmapped-experimental-rule",
]);
expect(report.skippedRules.length).toBe(1);
expect(report.skippedRules[0].reason).toContain("EVM inline assembly");
});

it("returns disabled stub when coverage tracking is switched off", () => {
const disabledConfig = resolveRuleCoverageConfig({ enabled: false });
const report = evaluateRuleCoverage({
tenantId: "tenant-alpha",
repoId: "repo-test",
scanId: "scan-1003",
commitHash: "abc",
rulePackVersion: "1.0",
categories: [],
mappings: [],
executedRuleIds: [],
config: disabledConfig,
});

expect(report.fingerprint).toBe("disabled");
expect(report.totalRules).toBe(0);
expect(report.overallCoveragePercent).toBe(100);
});
});
Loading
Loading