|
1 | 1 | import { afterEach, describe, expect, it, vi } from "vitest"; |
2 | 2 | import { |
| 3 | + RAW_CONTEXT_MAX_DIFF_CHARS, |
3 | 4 | recordConfiguredGateBlockerSignals, |
4 | 5 | type GateCheckPolicy, |
5 | 6 | } from "../../src/rules/advisory"; |
@@ -165,3 +166,80 @@ describe("recordConfiguredGateBlockerSignals (#8104)", () => { |
165 | 166 | ).resolves.toBeUndefined(); |
166 | 167 | }); |
167 | 168 | }); |
| 169 | + |
| 170 | +// ── #8130: bounded raw context in fired-event metadata (secret_leak permanently excluded) ─────────────────── |
| 171 | + |
| 172 | +describe("recordConfiguredGateBlockerSignals — raw context capture (#8130)", () => { |
| 173 | + it("SECURITY: secret_leak's fired event NEVER carries diff or rawSignal, even with confidence and detail present", async () => { |
| 174 | + const env = createTestEnv(); |
| 175 | + await recordConfiguredGateBlockerSignals( |
| 176 | + env, |
| 177 | + advisory([finding({ code: "secret_leak", severity: "critical", confidence: 0.99, detail: "AKIA... committed in config.ts" })]), |
| 178 | + {}, |
| 179 | + "owner/repo", |
| 180 | + 7, |
| 181 | + { aiReviewDiff: "+const key = 'AKIA-REAL-SECRET';" }, |
| 182 | + ); |
| 183 | + const [fired] = (await createSignalStore(env).queryRuleHistory("secret_leak", 0)).fired; |
| 184 | + expect(fired!.metadata).toEqual({ confidence: 0.99 }); |
| 185 | + expect(fired!.metadata).not.toHaveProperty("diff"); |
| 186 | + expect(fired!.metadata).not.toHaveProperty("rawSignal"); |
| 187 | + }); |
| 188 | + |
| 189 | + it("captures the AI review's diff (bounded to RAW_CONTEXT_MAX_DIFF_CHARS) for ai_consensus_defect", async () => { |
| 190 | + const env = createTestEnv(); |
| 191 | + const oversized = "d".repeat(RAW_CONTEXT_MAX_DIFF_CHARS + 5000); |
| 192 | + await recordConfiguredGateBlockerSignals( |
| 193 | + env, |
| 194 | + advisory([finding({ code: "ai_consensus_defect", confidence: 0.95 })]), |
| 195 | + blockAi, |
| 196 | + "owner/repo", |
| 197 | + 7, |
| 198 | + { aiReviewDiff: oversized }, |
| 199 | + ); |
| 200 | + const [fired] = (await createSignalStore(env).queryRuleHistory("ai_consensus_defect", 0)).fired; |
| 201 | + expect((fired!.metadata as { diff: string }).diff).toHaveLength(RAW_CONTEXT_MAX_DIFF_CHARS); |
| 202 | + expect((fired!.metadata as { confidence: number }).confidence).toBe(0.95); |
| 203 | + }); |
| 204 | + |
| 205 | + it("records no diff key for an AI code when the caller has no diff to thread", async () => { |
| 206 | + const env = createTestEnv(); |
| 207 | + await recordConfiguredGateBlockerSignals(env, advisory([finding({ code: "ai_review_split", confidence: 0.9 })]), blockAi, "owner/repo", 7); |
| 208 | + const [fired] = (await createSignalStore(env).queryRuleHistory("ai_review_split", 0)).fired; |
| 209 | + expect(fired!.metadata).toEqual({ confidence: 0.9 }); |
| 210 | + }); |
| 211 | + |
| 212 | + it("captures a non-diff-based code's own evaluated signal (its detail) as rawSignal — the audited fallback", async () => { |
| 213 | + const env = createTestEnv(); |
| 214 | + await recordConfiguredGateBlockerSignals( |
| 215 | + env, |
| 216 | + advisory([finding({ code: "missing_linked_issue", detail: "No linked issue reference found in the PR body." })]), |
| 217 | + blockLinked, |
| 218 | + "owner/repo", |
| 219 | + 7, |
| 220 | + { aiReviewDiff: "+irrelevant" }, |
| 221 | + ); |
| 222 | + const [fired] = (await createSignalStore(env).queryRuleHistory("missing_linked_issue", 0)).fired; |
| 223 | + expect(fired!.metadata).toEqual({ rawSignal: "No linked issue reference found in the PR body." }); |
| 224 | + }); |
| 225 | + |
| 226 | + it("records no metadata at all for a non-diff code with no confidence and an empty detail", async () => { |
| 227 | + const env = createTestEnv(); |
| 228 | + await recordConfiguredGateBlockerSignals(env, advisory([finding({ code: "missing_linked_issue", detail: "" })]), blockLinked, "owner/repo", 7); |
| 229 | + const [fired] = (await createSignalStore(env).queryRuleHistory("missing_linked_issue", 0)).fired; |
| 230 | + expect(fired!.metadata).toBeUndefined(); |
| 231 | + }); |
| 232 | + |
| 233 | + it("still skips linked_issue_scope_mismatch entirely (#8101's own site records it)", async () => { |
| 234 | + const env = createTestEnv(); |
| 235 | + await recordConfiguredGateBlockerSignals( |
| 236 | + env, |
| 237 | + advisory([finding({ code: "linked_issue_scope_mismatch" }), finding({ code: "missing_linked_issue" })]), |
| 238 | + { ...blockLinked, linkedIssueSatisfactionGateMode: "block" }, |
| 239 | + "owner/repo", |
| 240 | + 7, |
| 241 | + ); |
| 242 | + expect((await createSignalStore(env).queryRuleHistory("linked_issue_scope_mismatch", 0)).fired).toEqual([]); |
| 243 | + expect((await createSignalStore(env).queryRuleHistory("missing_linked_issue", 0)).fired).toHaveLength(1); |
| 244 | + }); |
| 245 | +}); |
0 commit comments