Add warning for short SHA refs in uses (#260)
This commit is contained in:
@@ -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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user