Add warning for short SHA refs in uses (#260)

This commit is contained in:
eric sciple
2025-12-22 08:34:29 -06:00
committed by GitHub
parent c0062e5287
commit 8b86b48961
2 changed files with 227 additions and 0 deletions
+31
View File
@@ -272,6 +272,31 @@ function validateCronExpression(diagnostics: Diagnostic[], token: StringToken):
}
}
// Matches a short SHA (7-8 hex characters) that looks like it should be a full SHA
const SHORT_SHA_PATTERN = /^[0-9a-f]{7,8}$/i;
const SHORT_SHA_DOCS_URL =
"https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions";
/**
* Checks if a ref looks like a short SHA and adds a warning if so.
* Returns true if a warning was added.
*/
function warnIfShortSha(diagnostics: Diagnostic[], token: StringToken, ref: string): boolean {
if (SHORT_SHA_PATTERN.test(ref)) {
diagnostics.push({
message: `The provided ref '${ref}' may be a shortened commit SHA. If so, please use the full 40-character commit SHA instead, as short SHAs are not supported.`,
severity: DiagnosticSeverity.Warning,
range: mapRange(token.range),
code: "short-sha-ref",
codeDescription: {
href: SHORT_SHA_DOCS_URL
}
});
return true;
}
return false;
}
/**
* Validates the format of a step's `uses` field.
*
@@ -343,6 +368,9 @@ function validateStepUsesFormat(diagnostics: Diagnostic[], token: StringToken):
});
return;
}
// Warn if ref looks like a short SHA
warnIfShortSha(diagnostics, token, gitRef);
}
function addStepUsesFormatError(diagnostics: Diagnostic[], token: StringToken): void {
@@ -501,6 +529,9 @@ function validateWorkflowUsesFormat(diagnostics: Diagnostic[], token: StringToke
addWorkflowUsesFormatError(diagnostics, token, "invalid workflow file name");
return;
}
// Warn if version looks like a short SHA
warnIfShortSha(diagnostics, token, version);
}
function addWorkflowUsesFormatError(diagnostics: Diagnostic[], token: StringToken, reason: string): void {
@@ -891,4 +891,200 @@ jobs:
});
});
});
describe("short SHA warnings", () => {
describe("step uses", () => {
it("warns on 7-char short SHA", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@a1b2c3d
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message:
"The provided ref 'a1b2c3d' may be a shortened commit SHA. If so, please use the full 40-character commit SHA instead, as short SHAs are not supported.",
severity: DiagnosticSeverity.Warning,
range: {
start: {line: 5, character: 12},
end: {line: 5, character: 36}
},
code: "short-sha-ref",
codeDescription: {
href: "https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions"
}
}
]);
});
it("warns on 8-char short SHA", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@a1b2c3d4
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message:
"The provided ref 'a1b2c3d4' may be a shortened commit SHA. If so, please use the full 40-character commit SHA instead, as short SHAs are not supported.",
severity: DiagnosticSeverity.Warning,
range: {
start: {line: 5, character: 12},
end: {line: 5, character: 37}
},
code: "short-sha-ref",
codeDescription: {
href: "https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions"
}
}
]);
});
it("does not warn on full SHA (40 chars)", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("does not warn on tag ref", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("does not warn on branch ref", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("does not warn on Docker action", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: docker://alpine:3.8
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("does not warn on local action", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: ./my-action
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
});
describe("workflow uses", () => {
it("warns on 7-char short SHA in reusable workflow", async () => {
const input = `on: push
jobs:
test:
uses: owner/repo/.github/workflows/ci.yml@a1b2c3d
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message:
"The provided ref 'a1b2c3d' may be a shortened commit SHA. If so, please use the full 40-character commit SHA instead, as short SHAs are not supported.",
severity: DiagnosticSeverity.Warning,
range: {
start: {line: 3, character: 10},
end: {line: 3, character: 53}
},
code: "short-sha-ref",
codeDescription: {
href: "https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions"
}
}
]);
});
it("warns on 8-char short SHA in reusable workflow", async () => {
const input = `on: push
jobs:
test:
uses: owner/repo/.github/workflows/ci.yml@a1b2c3d4
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message:
"The provided ref 'a1b2c3d4' may be a shortened commit SHA. If so, please use the full 40-character commit SHA instead, as short SHAs are not supported.",
severity: DiagnosticSeverity.Warning,
range: {
start: {line: 3, character: 10},
end: {line: 3, character: 54}
},
code: "short-sha-ref",
codeDescription: {
href: "https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions"
}
}
]);
});
it("does not warn on full SHA in reusable workflow", async () => {
const input = `on: push
jobs:
test:
uses: owner/repo/.github/workflows/ci.yml@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("does not warn on tag ref in reusable workflow", async () => {
const input = `on: push
jobs:
test:
uses: owner/repo/.github/workflows/ci.yml@v1
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("does not warn on local workflow", async () => {
const input = `on: push
jobs:
test:
uses: ./.github/workflows/ci.yml
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
});
});
});