@oddessentials/odd-ai-reviewers 1.10.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/dist/agents/control_flow/types.d.ts +1 -1
- package/dist/agents/control_flow/types.js +1 -1
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +2 -0
- package/dist/agents/index.js.map +1 -1
- package/dist/benchmark/adapter.d.ts.map +1 -1
- package/dist/benchmark/adapter.js +4 -2
- package/dist/benchmark/adapter.js.map +1 -1
- package/dist/cache/store.d.ts.map +1 -1
- package/dist/cache/store.js +26 -2
- package/dist/cache/store.js.map +1 -1
- package/dist/cli/commands/local-review.d.ts +13 -2
- package/dist/cli/commands/local-review.d.ts.map +1 -1
- package/dist/cli/commands/local-review.js +164 -33
- package/dist/cli/commands/local-review.js.map +1 -1
- package/dist/cli/execution-plan.d.ts +118 -0
- package/dist/cli/execution-plan.d.ts.map +1 -0
- package/dist/cli/execution-plan.js +260 -0
- package/dist/cli/execution-plan.js.map +1 -0
- package/dist/config/schemas.d.ts +103 -21
- package/dist/config/schemas.d.ts.map +1 -1
- package/dist/config/schemas.js +177 -10
- package/dist/config/schemas.js.map +1 -1
- package/dist/config.d.ts +8 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -6
- package/dist/config.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +79 -8
- package/dist/main.js.map +1 -1
- package/dist/phases/execute.d.ts +3 -0
- package/dist/phases/execute.d.ts.map +1 -1
- package/dist/phases/execute.js +17 -5
- package/dist/phases/execute.js.map +1 -1
- package/dist/phases/index.d.ts +1 -1
- package/dist/phases/index.d.ts.map +1 -1
- package/dist/phases/index.js +1 -1
- package/dist/phases/index.js.map +1 -1
- package/dist/phases/report.d.ts +28 -4
- package/dist/phases/report.d.ts.map +1 -1
- package/dist/phases/report.js +86 -36
- package/dist/phases/report.js.map +1 -1
- package/dist/report/ado.d.ts +2 -1
- package/dist/report/ado.d.ts.map +1 -1
- package/dist/report/ado.js +9 -5
- package/dist/report/ado.js.map +1 -1
- package/dist/report/finding-validator.d.ts +1 -4
- package/dist/report/finding-validator.d.ts.map +1 -1
- package/dist/report/finding-validator.js +23 -54
- package/dist/report/finding-validator.js.map +1 -1
- package/dist/report/framework-pattern-filter.d.ts +1 -1
- package/dist/report/framework-pattern-filter.d.ts.map +1 -1
- package/dist/report/framework-pattern-filter.js +114 -99
- package/dist/report/framework-pattern-filter.js.map +1 -1
- package/dist/report/github.d.ts +2 -1
- package/dist/report/github.d.ts.map +1 -1
- package/dist/report/github.js +9 -5
- package/dist/report/github.js.map +1 -1
- package/dist/report/terminal.d.ts +42 -4
- package/dist/report/terminal.d.ts.map +1 -1
- package/dist/report/terminal.js +36 -8
- package/dist/report/terminal.js.map +1 -1
- package/dist/report/user-suppressions.d.ts +74 -0
- package/dist/report/user-suppressions.d.ts.map +1 -0
- package/dist/report/user-suppressions.js +264 -0
- package/dist/report/user-suppressions.js.map +1 -0
- package/dist/security-logger.d.ts +1 -1
- package/dist/security-logger.js +1 -1
- package/package.json +7 -6
- package/dist/__tests__/hermetic-setup.d.ts +0 -55
- package/dist/__tests__/hermetic-setup.d.ts.map +0 -1
- package/dist/__tests__/hermetic-setup.js +0 -62
- package/dist/__tests__/hermetic-setup.js.map +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-Configurable Suppressions (FR-022)
|
|
3
|
+
*
|
|
4
|
+
* Stage 1.25 in the finding pipeline: applies user-defined suppression rules
|
|
5
|
+
* from .ai-review.yml configuration. Runs after semantic validation (Stage 1)
|
|
6
|
+
* and before framework convention filter (Stage 1.5).
|
|
7
|
+
*
|
|
8
|
+
* Security: In CI mode, rules are loaded from the BASE branch config to prevent
|
|
9
|
+
* attackers from smuggling suppressions into fork PRs.
|
|
10
|
+
*/
|
|
11
|
+
import type { Finding } from '../agents/types.js';
|
|
12
|
+
import { type SuppressionRule, type Suppressions } from '../config/schemas.js';
|
|
13
|
+
export interface SuppressionMatchResult {
|
|
14
|
+
finding: Finding;
|
|
15
|
+
rule: SuppressionRule;
|
|
16
|
+
ruleIndex: number;
|
|
17
|
+
}
|
|
18
|
+
export interface UserSuppressionResult {
|
|
19
|
+
/** Findings that passed all suppression rules */
|
|
20
|
+
filtered: Finding[];
|
|
21
|
+
/** Findings that were suppressed with their matching rule */
|
|
22
|
+
suppressed: SuppressionMatchResult[];
|
|
23
|
+
/** Per-rule match count (keyed by rule index) */
|
|
24
|
+
matchCounts: Map<number, number>;
|
|
25
|
+
}
|
|
26
|
+
export type SuppressionMode = 'ci' | 'local';
|
|
27
|
+
export interface BreadthViolation {
|
|
28
|
+
ruleIndex: number;
|
|
29
|
+
reason: string;
|
|
30
|
+
matchCount: number;
|
|
31
|
+
limit: number;
|
|
32
|
+
hasOverride: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Filter findings through user-defined suppression rules.
|
|
36
|
+
* First matching rule wins (no multi-rule accumulation).
|
|
37
|
+
*/
|
|
38
|
+
export declare function filterUserSuppressions(findings: Finding[], rules: SuppressionRule[]): UserSuppressionResult;
|
|
39
|
+
/**
|
|
40
|
+
* Check breadth limits on suppression match counts.
|
|
41
|
+
* Returns violations that should cause failures (CI) or warnings (local).
|
|
42
|
+
*/
|
|
43
|
+
export declare function checkBreadthLimits(rules: SuppressionRule[], matchCounts: Map<number, number>): BreadthViolation[];
|
|
44
|
+
/**
|
|
45
|
+
* Check if a breadth-override rule is authorized to suppress error-severity findings.
|
|
46
|
+
* Returns violations for unauthorized error-severity suppressions.
|
|
47
|
+
*/
|
|
48
|
+
export declare function checkErrorSeverityOverrides(rules: SuppressionRule[], suppressedResults: SuppressionMatchResult[], securityOverrideAllowlist: string[]): BreadthViolation[];
|
|
49
|
+
/**
|
|
50
|
+
* Enforce breadth limits based on mode.
|
|
51
|
+
* In CI mode: throws Error for violations.
|
|
52
|
+
* In local mode: logs warnings only.
|
|
53
|
+
*/
|
|
54
|
+
export declare function enforceBreadthLimits(rules: SuppressionRule[], result: UserSuppressionResult, mode: SuppressionMode, securityOverrideAllowlist: string[]): void;
|
|
55
|
+
/**
|
|
56
|
+
* Build suppression match count summary for JSON output.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildSuppressionSummary(rules: SuppressionRule[], matchCounts: Map<number, number>): {
|
|
59
|
+
reason: string;
|
|
60
|
+
matched: number;
|
|
61
|
+
}[];
|
|
62
|
+
/**
|
|
63
|
+
* Load suppressions from the base branch config via `git show`.
|
|
64
|
+
*
|
|
65
|
+
* Security (FR-022): In CI mode, suppression rules MUST be loaded from the
|
|
66
|
+
* BASE branch configuration only, never from the PR branch. This prevents
|
|
67
|
+
* attackers from smuggling suppressions into fork PRs to hide vulnerabilities.
|
|
68
|
+
*
|
|
69
|
+
* @param repoPath - Path to the git repository
|
|
70
|
+
* @param baseRef - Base branch ref (e.g., 'origin/main', a SHA)
|
|
71
|
+
* @returns Parsed suppressions, or empty defaults if base has no config/suppressions
|
|
72
|
+
*/
|
|
73
|
+
export declare function loadBaseBranchSuppressions(repoPath: string, baseRef: string): Suppressions;
|
|
74
|
+
//# sourceMappingURL=user-suppressions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-suppressions.d.ts","sourceRoot":"","sources":["../../src/report/user-suppressions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAsB,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AASnG,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,iDAAiD;IACjD,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,6DAA6D;IAC7D,UAAU,EAAE,sBAAsB,EAAE,CAAC;IACrC,iDAAiD;IACjD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB;AA4CD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,eAAe,EAAE,GACvB,qBAAqB,CAqCvB;AASD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EAAE,EACxB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,gBAAgB,EAAE,CAuBpB;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,eAAe,EAAE,EACxB,iBAAiB,EAAE,sBAAsB,EAAE,EAC3C,yBAAyB,EAAE,MAAM,EAAE,GAClC,gBAAgB,EAAE,CA8BpB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,eAAe,EAAE,EACxB,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE,eAAe,EACrB,yBAAyB,EAAE,MAAM,EAAE,GAClC,IAAI,CA0DN;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,eAAe,EAAE,EACxB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CAUvC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAsD1F"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-Configurable Suppressions (FR-022)
|
|
3
|
+
*
|
|
4
|
+
* Stage 1.25 in the finding pipeline: applies user-defined suppression rules
|
|
5
|
+
* from .ai-review.yml configuration. Runs after semantic validation (Stage 1)
|
|
6
|
+
* and before framework convention filter (Stage 1.5).
|
|
7
|
+
*
|
|
8
|
+
* Security: In CI mode, rules are loaded from the BASE branch config to prevent
|
|
9
|
+
* attackers from smuggling suppressions into fork PRs.
|
|
10
|
+
*/
|
|
11
|
+
import { execFileSync } from 'child_process';
|
|
12
|
+
import { parse as parseYaml } from 'yaml';
|
|
13
|
+
import { minimatch } from 'minimatch';
|
|
14
|
+
import { SuppressionsSchema } from '../config/schemas.js';
|
|
15
|
+
import { ConfigError, ConfigErrorCode } from '../types/errors.js';
|
|
16
|
+
import { SafeGitRefHelpers } from '../types/branded.js';
|
|
17
|
+
import { isOk } from '../types/result.js';
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Matching Logic
|
|
20
|
+
// =============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Test if a suppression rule matches a finding.
|
|
23
|
+
* All specified criteria must match (AND logic).
|
|
24
|
+
*/
|
|
25
|
+
function ruleMatchesFinding(rule, finding) {
|
|
26
|
+
// Rule ID: glob match
|
|
27
|
+
if (rule.rule !== undefined) {
|
|
28
|
+
const ruleId = finding.ruleId ?? '';
|
|
29
|
+
if (!minimatch(ruleId, rule.rule))
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// Message: anchored regex (validated at config load: must start with ^ and end with $)
|
|
33
|
+
if (rule.message !== undefined) {
|
|
34
|
+
try {
|
|
35
|
+
// SAFETY: rule.message is validated by SuppressionRuleSchema to be fully anchored (^...$).
|
|
36
|
+
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
37
|
+
const regex = new RegExp(rule.message);
|
|
38
|
+
if (!regex.test(finding.message))
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Invalid regex — treat as non-match (validated at config time)
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// File: glob match
|
|
47
|
+
if (rule.file !== undefined) {
|
|
48
|
+
const filePath = finding.file ?? '';
|
|
49
|
+
if (!minimatch(filePath, rule.file))
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// Severity: exact match
|
|
53
|
+
if (rule.severity !== undefined) {
|
|
54
|
+
if (finding.severity !== rule.severity)
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Filter findings through user-defined suppression rules.
|
|
61
|
+
* First matching rule wins (no multi-rule accumulation).
|
|
62
|
+
*/
|
|
63
|
+
export function filterUserSuppressions(findings, rules) {
|
|
64
|
+
if (rules.length === 0) {
|
|
65
|
+
return { filtered: [...findings], suppressed: [], matchCounts: new Map() };
|
|
66
|
+
}
|
|
67
|
+
const filtered = [];
|
|
68
|
+
const suppressed = [];
|
|
69
|
+
const matchCounts = new Map();
|
|
70
|
+
// Initialize counts
|
|
71
|
+
for (let i = 0; i < rules.length; i++) {
|
|
72
|
+
matchCounts.set(i, 0);
|
|
73
|
+
}
|
|
74
|
+
for (const finding of findings) {
|
|
75
|
+
let wasSuppressed = false;
|
|
76
|
+
for (let i = 0; i < rules.length; i++) {
|
|
77
|
+
const rule = rules[i];
|
|
78
|
+
if (ruleMatchesFinding(rule, finding)) {
|
|
79
|
+
suppressed.push({ finding, rule, ruleIndex: i });
|
|
80
|
+
matchCounts.set(i, (matchCounts.get(i) ?? 0) + 1);
|
|
81
|
+
wasSuppressed = true;
|
|
82
|
+
// Diagnostics go to stderr to avoid corrupting JSON/SARIF stdout output
|
|
83
|
+
console.error(`[router] [user-suppression] Suppressed: ${finding.file}:${finding.line ?? '?'} — rule: "${rule.reason}"`);
|
|
84
|
+
break; // First matching rule wins
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!wasSuppressed) {
|
|
88
|
+
filtered.push(finding);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { filtered, suppressed, matchCounts };
|
|
92
|
+
}
|
|
93
|
+
// =============================================================================
|
|
94
|
+
// Breadth Enforcement
|
|
95
|
+
// =============================================================================
|
|
96
|
+
const DEFAULT_BREADTH_LIMIT = 20;
|
|
97
|
+
const OVERRIDE_BREADTH_LIMIT = 200;
|
|
98
|
+
/**
|
|
99
|
+
* Check breadth limits on suppression match counts.
|
|
100
|
+
* Returns violations that should cause failures (CI) or warnings (local).
|
|
101
|
+
*/
|
|
102
|
+
export function checkBreadthLimits(rules, matchCounts) {
|
|
103
|
+
const violations = [];
|
|
104
|
+
for (let i = 0; i < rules.length; i++) {
|
|
105
|
+
const rule = rules[i];
|
|
106
|
+
const count = matchCounts.get(i) ?? 0;
|
|
107
|
+
if (count === 0)
|
|
108
|
+
continue;
|
|
109
|
+
const hasOverride = rule.breadth_override === true;
|
|
110
|
+
const limit = hasOverride ? OVERRIDE_BREADTH_LIMIT : DEFAULT_BREADTH_LIMIT;
|
|
111
|
+
if (count > limit) {
|
|
112
|
+
violations.push({
|
|
113
|
+
ruleIndex: i,
|
|
114
|
+
reason: rule.reason,
|
|
115
|
+
matchCount: count,
|
|
116
|
+
limit,
|
|
117
|
+
hasOverride,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return violations;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if a breadth-override rule is authorized to suppress error-severity findings.
|
|
125
|
+
* Returns violations for unauthorized error-severity suppressions.
|
|
126
|
+
*/
|
|
127
|
+
export function checkErrorSeverityOverrides(rules, suppressedResults, securityOverrideAllowlist) {
|
|
128
|
+
const violations = [];
|
|
129
|
+
// Group suppressed findings by rule index
|
|
130
|
+
const suppressedByRule = new Map();
|
|
131
|
+
for (const result of suppressedResults) {
|
|
132
|
+
const existing = suppressedByRule.get(result.ruleIndex) ?? [];
|
|
133
|
+
existing.push(result);
|
|
134
|
+
suppressedByRule.set(result.ruleIndex, existing);
|
|
135
|
+
}
|
|
136
|
+
for (let i = 0; i < rules.length; i++) {
|
|
137
|
+
const rule = rules[i];
|
|
138
|
+
if (!rule.breadth_override)
|
|
139
|
+
continue;
|
|
140
|
+
const ruleSuppressions = suppressedByRule.get(i) ?? [];
|
|
141
|
+
const hasErrorSeverity = ruleSuppressions.some((s) => s.finding.severity === 'error');
|
|
142
|
+
if (hasErrorSeverity && !securityOverrideAllowlist.includes(rule.reason)) {
|
|
143
|
+
violations.push({
|
|
144
|
+
ruleIndex: i,
|
|
145
|
+
reason: rule.reason,
|
|
146
|
+
matchCount: ruleSuppressions.length,
|
|
147
|
+
limit: DEFAULT_BREADTH_LIMIT,
|
|
148
|
+
hasOverride: true,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return violations;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Enforce breadth limits based on mode.
|
|
156
|
+
* In CI mode: throws Error for violations.
|
|
157
|
+
* In local mode: logs warnings only.
|
|
158
|
+
*/
|
|
159
|
+
export function enforceBreadthLimits(rules, result, mode, securityOverrideAllowlist) {
|
|
160
|
+
const breadthViolations = checkBreadthLimits(rules, result.matchCounts);
|
|
161
|
+
const errorSeverityViolations = checkErrorSeverityOverrides(rules, result.suppressed, securityOverrideAllowlist);
|
|
162
|
+
const allViolations = [...breadthViolations, ...errorSeverityViolations];
|
|
163
|
+
if (allViolations.length === 0) {
|
|
164
|
+
// Log overrides that are within limits
|
|
165
|
+
for (let i = 0; i < rules.length; i++) {
|
|
166
|
+
const rule = rules[i];
|
|
167
|
+
const count = result.matchCounts.get(i) ?? 0;
|
|
168
|
+
if (rule.breadth_override && count > DEFAULT_BREADTH_LIMIT) {
|
|
169
|
+
console.error(`[router] [user-suppression] Broad suppression override: '${rule.reason}' approved by ${rule.approved_by ?? 'unknown'} — matched ${count} findings`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (mode === 'local') {
|
|
175
|
+
for (const v of allViolations) {
|
|
176
|
+
console.warn(`[router] [user-suppression] Warning: Suppression rule '${v.reason}' matched ${v.matchCount} findings (limit: ${v.limit})`);
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// CI mode: fail on violations — error-severity violations take precedence
|
|
181
|
+
// FR-022: Breadth violations produce exit code 2 (config_error) via ConfigError
|
|
182
|
+
if (errorSeverityViolations.length > 0) {
|
|
183
|
+
const v = errorSeverityViolations[0];
|
|
184
|
+
throw new ConfigError(`Breadth override on rule '${v.reason}' cannot suppress error-severity findings — add to security_override_allowlist to authorize`, ConfigErrorCode.INVALID_VALUE, { field: 'suppressions.rules' });
|
|
185
|
+
}
|
|
186
|
+
const firstViolation = allViolations[0];
|
|
187
|
+
if (firstViolation.hasOverride) {
|
|
188
|
+
throw new ConfigError(`Suppression rule '${firstViolation.reason}' matched ${firstViolation.matchCount} findings (limit: ${firstViolation.limit}). Override limit exceeded.`, ConfigErrorCode.INVALID_VALUE, { field: 'suppressions.rules' });
|
|
189
|
+
}
|
|
190
|
+
throw new ConfigError(`Suppression rule '${firstViolation.reason}' matched ${firstViolation.matchCount} findings (limit: ${firstViolation.limit}). Add \`breadth_override: true\` to this rule to allow broad suppression.`, ConfigErrorCode.INVALID_VALUE, { field: 'suppressions.rules' });
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Build suppression match count summary for JSON output.
|
|
194
|
+
*/
|
|
195
|
+
export function buildSuppressionSummary(rules, matchCounts) {
|
|
196
|
+
const summary = [];
|
|
197
|
+
for (let i = 0; i < rules.length; i++) {
|
|
198
|
+
const rule = rules[i];
|
|
199
|
+
const count = matchCounts.get(i) ?? 0;
|
|
200
|
+
if (count > 0) {
|
|
201
|
+
summary.push({ reason: rule.reason, matched: count });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return summary;
|
|
205
|
+
}
|
|
206
|
+
// =============================================================================
|
|
207
|
+
// Base-Branch Suppression Loading (CI Security)
|
|
208
|
+
// =============================================================================
|
|
209
|
+
/**
|
|
210
|
+
* Load suppressions from the base branch config via `git show`.
|
|
211
|
+
*
|
|
212
|
+
* Security (FR-022): In CI mode, suppression rules MUST be loaded from the
|
|
213
|
+
* BASE branch configuration only, never from the PR branch. This prevents
|
|
214
|
+
* attackers from smuggling suppressions into fork PRs to hide vulnerabilities.
|
|
215
|
+
*
|
|
216
|
+
* @param repoPath - Path to the git repository
|
|
217
|
+
* @param baseRef - Base branch ref (e.g., 'origin/main', a SHA)
|
|
218
|
+
* @returns Parsed suppressions, or empty defaults if base has no config/suppressions
|
|
219
|
+
*/
|
|
220
|
+
export function loadBaseBranchSuppressions(repoPath, baseRef) {
|
|
221
|
+
const emptySuppressions = {
|
|
222
|
+
rules: [],
|
|
223
|
+
disable_matchers: [],
|
|
224
|
+
security_override_allowlist: [],
|
|
225
|
+
};
|
|
226
|
+
// Defense-in-depth: validate baseRef before passing to git
|
|
227
|
+
const refResult = SafeGitRefHelpers.parse(baseRef);
|
|
228
|
+
if (!isOk(refResult)) {
|
|
229
|
+
console.warn(`[router] [user-suppression] Invalid base ref, skipping suppression loading: ${baseRef}`);
|
|
230
|
+
return emptySuppressions;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const configContent = execFileSync('git', ['show', `${refResult.value}:.ai-review.yml`], {
|
|
234
|
+
cwd: repoPath,
|
|
235
|
+
encoding: 'utf-8',
|
|
236
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
237
|
+
timeout: 10000,
|
|
238
|
+
});
|
|
239
|
+
const parsed = parseYaml(configContent);
|
|
240
|
+
if (!parsed || typeof parsed !== 'object' || !('suppressions' in parsed)) {
|
|
241
|
+
console.error('[router] [user-suppression] Base branch config has no suppressions section');
|
|
242
|
+
return emptySuppressions;
|
|
243
|
+
}
|
|
244
|
+
const result = SuppressionsSchema.safeParse(parsed['suppressions']);
|
|
245
|
+
if (!result.success) {
|
|
246
|
+
console.warn(`[router] [user-suppression] Base branch suppressions invalid, ignoring: ${result.error.message}`);
|
|
247
|
+
return emptySuppressions;
|
|
248
|
+
}
|
|
249
|
+
console.error(`[router] [user-suppression] Loaded ${result.data.rules.length} suppression rule(s) from base branch (${baseRef})`);
|
|
250
|
+
return result.data;
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
// git show fails when config doesn't exist on base branch — expected
|
|
254
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
255
|
+
if (message.includes('does not exist') || message.includes('fatal:')) {
|
|
256
|
+
console.error('[router] [user-suppression] No .ai-review.yml on base branch — no suppressions active');
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
console.warn(`[router] [user-suppression] Failed to load base branch config: ${message}`);
|
|
260
|
+
}
|
|
261
|
+
return emptySuppressions;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=user-suppressions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-suppressions.js","sourceRoot":"","sources":["../../src/report/user-suppressions.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Consistent structured format across all events
|
|
11
11
|
*
|
|
12
12
|
* @see FR-021, FR-022, FR-023, FR-024
|
|
13
|
-
* @see specs/006-quality-enforcement/contracts/security-event.ts
|
|
13
|
+
* @see specs/archive/006-quality-enforcement/contracts/security-event.ts
|
|
14
14
|
*/
|
|
15
15
|
import { z } from 'zod';
|
|
16
16
|
/**
|
package/dist/security-logger.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Consistent structured format across all events
|
|
11
11
|
*
|
|
12
12
|
* @see FR-021, FR-022, FR-023, FR-024
|
|
13
|
-
* @see specs/006-quality-enforcement/contracts/security-event.ts
|
|
13
|
+
* @see specs/archive/006-quality-enforcement/contracts/security-event.ts
|
|
14
14
|
*/
|
|
15
15
|
import { createHash } from 'crypto';
|
|
16
16
|
import { z } from 'zod';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oddessentials/odd-ai-reviewers",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "AI-powered code review CLI - run locally or in CI/CD pipelines",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -32,19 +32,19 @@
|
|
|
32
32
|
"homepage": "https://github.com/oddessentials/odd-ai-reviewers#readme",
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@actions/cache": "5.0.5",
|
|
35
|
-
"@anthropic-ai/sdk": "0.
|
|
35
|
+
"@anthropic-ai/sdk": "0.78.0",
|
|
36
36
|
"@octokit/rest": "^22.0.1",
|
|
37
37
|
"commander": "^14.0.3",
|
|
38
38
|
"minimatch": "^10.2.4",
|
|
39
|
-
"openai": "^6.
|
|
39
|
+
"openai": "^6.29.0",
|
|
40
40
|
"typescript": "5.9.3",
|
|
41
41
|
"yaml": "^2.8.2",
|
|
42
42
|
"zod": "^4.3.6"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/node": "25.
|
|
46
|
-
"@vitest/coverage-v8": "4.0
|
|
47
|
-
"vitest": "^4.0
|
|
45
|
+
"@types/node": "25.5.0",
|
|
46
|
+
"@vitest/coverage-v8": "4.1.0",
|
|
47
|
+
"vitest": "^4.1.0"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=22.0.0"
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"test:coverage": "vitest run --coverage",
|
|
59
59
|
"test:ci": "vitest run --reporter=json --outputFile=test-results.json",
|
|
60
60
|
"test:ci:coverage": "vitest run --coverage --reporter=json --reporter=dot --outputFile=test-results.json",
|
|
61
|
+
"test:ci-thresholds": "CI=true vitest run --coverage",
|
|
61
62
|
"typecheck": "tsc --noEmit"
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hermetic Test Setup Utilities
|
|
3
|
-
*
|
|
4
|
-
* Shared test infrastructure for deterministic, isolated tests.
|
|
5
|
-
* Located in __tests__/ directory which is classified as test code
|
|
6
|
-
* by dependency-cruiser, allowing legitimate vitest imports.
|
|
7
|
-
*
|
|
8
|
-
* Provides:
|
|
9
|
-
* - Frozen time (no wall-clock dependencies)
|
|
10
|
-
* - Deterministic teardown
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* import { describe, it, beforeEach, afterEach } from 'vitest';
|
|
15
|
-
* import {
|
|
16
|
-
* FROZEN_TIMESTAMP,
|
|
17
|
-
* setupHermeticTest,
|
|
18
|
-
* teardownHermeticTest,
|
|
19
|
-
* } from '../hermetic-setup.js';
|
|
20
|
-
*
|
|
21
|
-
* describe('MyFeature', () => {
|
|
22
|
-
* beforeEach(() => setupHermeticTest());
|
|
23
|
-
* afterEach(() => teardownHermeticTest());
|
|
24
|
-
*
|
|
25
|
-
* it('works with frozen time', () => {
|
|
26
|
-
* expect(new Date().toISOString()).toBe(FROZEN_TIMESTAMP);
|
|
27
|
-
* });
|
|
28
|
-
* });
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
/**
|
|
32
|
-
* Frozen test timestamp - use consistently across all hermetic tests
|
|
33
|
-
*/
|
|
34
|
-
export declare const FROZEN_TIMESTAMP = "2026-01-29T00:00:00.000Z";
|
|
35
|
-
export declare const FROZEN_DATE: Date;
|
|
36
|
-
/**
|
|
37
|
-
* Setup hermetic test environment
|
|
38
|
-
*
|
|
39
|
-
* Configures:
|
|
40
|
-
* - Frozen system time to FROZEN_TIMESTAMP
|
|
41
|
-
*
|
|
42
|
-
* Call this in beforeEach()
|
|
43
|
-
*/
|
|
44
|
-
export declare function setupHermeticTest(): void;
|
|
45
|
-
/**
|
|
46
|
-
* Teardown hermetic test environment
|
|
47
|
-
*
|
|
48
|
-
* Restores:
|
|
49
|
-
* - Real system time
|
|
50
|
-
* - All mocks
|
|
51
|
-
*
|
|
52
|
-
* Call this in afterEach()
|
|
53
|
-
*/
|
|
54
|
-
export declare function teardownHermeticTest(): void;
|
|
55
|
-
//# sourceMappingURL=hermetic-setup.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hermetic-setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/hermetic-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH;;GAEG;AACH,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AAC3D,eAAO,MAAM,WAAW,MAA6B,CAAC;AAEtD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hermetic Test Setup Utilities
|
|
3
|
-
*
|
|
4
|
-
* Shared test infrastructure for deterministic, isolated tests.
|
|
5
|
-
* Located in __tests__/ directory which is classified as test code
|
|
6
|
-
* by dependency-cruiser, allowing legitimate vitest imports.
|
|
7
|
-
*
|
|
8
|
-
* Provides:
|
|
9
|
-
* - Frozen time (no wall-clock dependencies)
|
|
10
|
-
* - Deterministic teardown
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* import { describe, it, beforeEach, afterEach } from 'vitest';
|
|
15
|
-
* import {
|
|
16
|
-
* FROZEN_TIMESTAMP,
|
|
17
|
-
* setupHermeticTest,
|
|
18
|
-
* teardownHermeticTest,
|
|
19
|
-
* } from '../hermetic-setup.js';
|
|
20
|
-
*
|
|
21
|
-
* describe('MyFeature', () => {
|
|
22
|
-
* beforeEach(() => setupHermeticTest());
|
|
23
|
-
* afterEach(() => teardownHermeticTest());
|
|
24
|
-
*
|
|
25
|
-
* it('works with frozen time', () => {
|
|
26
|
-
* expect(new Date().toISOString()).toBe(FROZEN_TIMESTAMP);
|
|
27
|
-
* });
|
|
28
|
-
* });
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
import { vi } from 'vitest';
|
|
32
|
-
/**
|
|
33
|
-
* Frozen test timestamp - use consistently across all hermetic tests
|
|
34
|
-
*/
|
|
35
|
-
export const FROZEN_TIMESTAMP = '2026-01-29T00:00:00.000Z';
|
|
36
|
-
export const FROZEN_DATE = new Date(FROZEN_TIMESTAMP);
|
|
37
|
-
/**
|
|
38
|
-
* Setup hermetic test environment
|
|
39
|
-
*
|
|
40
|
-
* Configures:
|
|
41
|
-
* - Frozen system time to FROZEN_TIMESTAMP
|
|
42
|
-
*
|
|
43
|
-
* Call this in beforeEach()
|
|
44
|
-
*/
|
|
45
|
-
export function setupHermeticTest() {
|
|
46
|
-
vi.useFakeTimers();
|
|
47
|
-
vi.setSystemTime(FROZEN_DATE);
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Teardown hermetic test environment
|
|
51
|
-
*
|
|
52
|
-
* Restores:
|
|
53
|
-
* - Real system time
|
|
54
|
-
* - All mocks
|
|
55
|
-
*
|
|
56
|
-
* Call this in afterEach()
|
|
57
|
-
*/
|
|
58
|
-
export function teardownHermeticTest() {
|
|
59
|
-
vi.useRealTimers();
|
|
60
|
-
vi.restoreAllMocks();
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=hermetic-setup.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hermetic-setup.js","sourceRoot":"","sources":["../../src/__tests__/hermetic-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE5B;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAC3D,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,gBAAgB,CAAC,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB;IAC/B,EAAE,CAAC,aAAa,EAAE,CAAC;IACnB,EAAE,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB;IAClC,EAAE,CAAC,aAAa,EAAE,CAAC;IACnB,EAAE,CAAC,eAAe,EAAE,CAAC;AACvB,CAAC"}
|