diff --git a/review-enrichment/src/analyzers/actions-pin.ts b/review-enrichment/src/analyzers/actions-pin.ts index 4f0c16e8e8..6aa9fdbe79 100644 --- a/review-enrichment/src/analyzers/actions-pin.ts +++ b/review-enrichment/src/analyzers/actions-pin.ts @@ -4,7 +4,7 @@ // Official actions/* + github/* are excluded (lowest risk, extremely common) to keep the signal high. Line-cited. import type { EnrichRequest, ActionPinFinding } from "../types.js"; -const USES_RE = /^\s*-?\s*uses:\s*["']?([\w.-]+\/[\w./-]+)@([^\s"'#]+)/; +const USES_RE = /^\s*-?\s*["']?uses["']?\s*:\s*["']?([\w.-]+\/[\w./-]+)@([^\s"'#]+)/; const FULL_SHA = /^[0-9a-f]{40}$/; const OFFICIAL = /^(actions|github)\//; const WORKFLOW_PATH = /^\.github\/workflows\/.+\.ya?ml$/; diff --git a/review-enrichment/test/enrichment.test.ts b/review-enrichment/test/enrichment.test.ts index 763bd0aefb..c186fbd516 100644 --- a/review-enrichment/test/enrichment.test.ts +++ b/review-enrichment/test/enrichment.test.ts @@ -433,6 +433,24 @@ test("scanWorkflowPins: flags unpinned third-party actions, skips official + SHA assert.equal(findings[0].line, 3); }); +test("scanWorkflowPins: flags unpinned third-party actions with YAML-equivalent uses keys", () => { + const patch = [ + "@@ -1,0 +1,3 @@", + "+ - uses : tj-actions/changed-files@v44", + "+ - \"uses\": third-party/action@main", + "+ - 'uses' : quoted/action@v1", + ].join("\n"); + const findings = scanWorkflowPins(".github/workflows/ci.yml", patch); + assert.deepEqual( + findings.map(({ action, ref, line }) => ({ action, ref, line })), + [ + { action: "tj-actions/changed-files", ref: "v44", line: 1 }, + { action: "third-party/action", ref: "main", line: 2 }, + { action: "quoted/action", ref: "v1", line: 3 }, + ], + ); +}); + test("scanActionPins: only scans .github/workflows/* files", async () => { const findings = await scanActionPins({ repoFullName: "o/r",