Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/init-action-post.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 18 additions & 1 deletion lib/init-action.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions src/workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,65 @@ test("getWorkflowErrors() should not report a warning if there is a workflow_cal
t.deepEqual(...errorCodes(errors, []));
});

test("getWorkflowErrors() should report a warning if different versions of the CodeQL Action are used", async (t) => {
const errors = await getWorkflowErrors(
yaml.load(`
name: "CodeQL"
on:
push:
branches: [main]
jobs:
analyze:
steps:
- uses: github/codeql-action/init@v2
- uses: github/codeql-action/analyze@v3
`) as Workflow,
await getCodeQLForTesting(),
);

t.deepEqual(
...errorCodes(errors, [WorkflowErrors.InconsistentActionVersion]),
);
});

test("getWorkflowErrors() should not report a warning if the same versions of the CodeQL Action are used", async (t) => {
const errors = await getWorkflowErrors(
yaml.load(`
name: "CodeQL"
on:
push:
branches: [main]
jobs:
analyze:
steps:
- uses: github/codeql-action/init@v3
- uses: github/codeql-action/analyze@v3
`) as Workflow,
await getCodeQLForTesting(),
);

t.deepEqual(...errorCodes(errors, []));
});

test("getWorkflowErrors() should not report a warning involving versions of other actions", async (t) => {
const errors = await getWorkflowErrors(
yaml.load(`
name: "CodeQL"
on:
push:
branches: [main]
jobs:
analyze:
steps:
- uses: actions/checkout@v5
- uses: github/codeql-action/init@v3
`) as Workflow,
await getCodeQLForTesting(),
);

t.deepEqual(...errorCodes(errors, []));
});

test("getCategoryInputOrThrow returns category for simple workflow with category", (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
t.is(
Expand Down
24 changes: 24 additions & 0 deletions src/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function toCodedErrors(errors: {
export const WorkflowErrors = toCodedErrors({
MissingPushHook: `Please specify an on.push hook to analyze and see code scanning alerts from the default branch on the Security tab.`,
CheckoutWrongHead: `git checkout HEAD^2 is no longer necessary. Please remove this step as Code Scanning recommends analyzing the merge commit for best results.`,
InconsistentActionVersion: `Not all workflow steps that use \`github/codeql-action\` actions use the same version. Please ensure that all such steps use the same version to avoid compatibility issues.`,
});

/**
Expand Down Expand Up @@ -161,6 +162,29 @@ export async function getWorkflowErrors(
}
}

// Check that all `github/codeql-action` steps use the same ref, i.e. the same version.
// Mixing different versions of the actions can lead to unpredictable behaviour.
const codeqlStepRefs: string[] = [];
for (const job of Object.values(doc?.jobs || {})) {
if (Array.isArray(job.steps)) {
for (const step of job.steps) {
if (step.uses?.startsWith("github/codeql-action/")) {
const parts = step.uses.split("@");
if (parts.length >= 2) {
Comment on lines +172 to +173
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: don't we have this kind of parsing elsewhere already?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have

  const format = new RegExp(
    "(?<owner>[^/]+)/(?<repo>[^/]+)/(?<path>[^@]+)@(?<ref>.*)",
  );

in getRemoteConfig, but I thought I'd keep this simple.

codeqlStepRefs.push(parts[parts.length - 1]);
Copy link

Copilot AI Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic assumes that splitting on '@' will always produce the version as the last part, but this may not handle edge cases correctly. For example, if step.uses is 'github/codeql-action/init@v2@extra', this would extract 'extra' instead of 'v2@extra'. Consider using parts.slice(1).join('@') to handle multiple '@' characters properly.

Suggested change
codeqlStepRefs.push(parts[parts.length - 1]);
codeqlStepRefs.push(parts.slice(1).join("@"));

Copilot uses AI. Check for mistakes.

}
}
}
}
}

if (
codeqlStepRefs.length > 0 &&
!codeqlStepRefs.every((ref) => ref === codeqlStepRefs[0])
) {
errors.push(WorkflowErrors.InconsistentActionVersion);
}

// If there is no push trigger, we will not be able to analyze the default branch.
// So add a warning to the user to add a push trigger.
// If there is a workflow_call trigger, we don't need a push trigger since we assume
Expand Down
Loading