From aa505e1366de7fa0c82935f8407a4a0265f59fbc Mon Sep 17 00:00:00 2001 From: rhysd Date: Fri, 26 Apr 2024 18:55:42 +0900 Subject: [PATCH 1/4] generate set of outdated popular actions' specs --- scripts/generate-popular-actions/main.go | 24 +++++++++++++++---- .../testdata/go/fetched.go | 4 ++++ .../testdata/go/outdated.go | 6 +++++ .../testdata/go/skip_inputs_want.go | 4 ++++ .../testdata/go/skip_outputs_want.go | 4 ++++ .../testdata/go/want.go | 4 ++++ 6 files changed, 41 insertions(+), 5 deletions(-) diff --git a/scripts/generate-popular-actions/main.go b/scripts/generate-popular-actions/main.go index 19c64ad3e..e21ee6f91 100644 --- a/scripts/generate-popular-actions/main.go +++ b/scripts/generate-popular-actions/main.go @@ -180,10 +180,6 @@ func (g *gen) fetchRemote() (map[string]*actionlint.ActionMetadata, error) { close(done) return nil, f.err } - if isOutdatedRunner(f.meta.Runs.Using) { - g.log.Printf("Ignore outdated action %q since runner is %q", f.spec, f.meta.Runs.Using) - continue - } ret[f.spec] = f.meta } @@ -194,6 +190,10 @@ func (g *gen) fetchRemote() (map[string]*actionlint.ActionMetadata, error) { func (g *gen) writeJSONL(out io.Writer, actions map[string]*actionlint.ActionMetadata) error { enc := json.NewEncoder(out) for spec, meta := range actions { + if isOutdatedRunner(meta.Runs.Using) { + g.log.Printf("Ignore outdated action %q since runner is %q", spec, meta.Runs.Using) + continue + } j := actionOutput{spec, meta} if err := enc.Encode(&j); err != nil { return fmt.Errorf("could not encode action %q data into JSON: %w", spec, err) @@ -220,8 +220,14 @@ var PopularActions = map[string]*ActionMetadata{ } sort.Strings(specs) + outdated := []string{} for _, spec := range specs { meta := actions[spec] + if isOutdatedRunner(meta.Runs.Using) { + outdated = append(outdated, spec) + continue + } + fmt.Fprintf(b, "%q: {\n", spec) fmt.Fprintf(b, "Name: %q,\n", meta.Name) @@ -268,6 +274,14 @@ var PopularActions = map[string]*ActionMetadata{ fmt.Fprintln(b, "}") + fmt.Fprintln(b, `// OutdatedPopularActionSpecs is a spec set of known outdated popular actions. The word 'outdated' +// means that the runner used by the action is no longer available such as "node12". +var OutdatedPopularActionSpecs = map[string]struct{}{`) + for _, s := range outdated { + fmt.Fprintf(b, "%q: {},\n", s) + } + fmt.Fprintln(b, "}") + // Format the generated source with checking Go syntax gen := b.Bytes() src, err := format.Source(gen) @@ -279,7 +293,7 @@ var PopularActions = map[string]*ActionMetadata{ return fmt.Errorf("could not output generated Go source to stdout: %w", err) } - g.log.Printf("Wrote %d action metadata as Go", len(actions)) + g.log.Printf("Wrote %d action metadata and %d outdated action specs as Go", len(actions)-len(outdated), len(outdated)) return nil } diff --git a/scripts/generate-popular-actions/testdata/go/fetched.go b/scripts/generate-popular-actions/testdata/go/fetched.go index 896d40671..89ee6e166 100644 --- a/scripts/generate-popular-actions/testdata/go/fetched.go +++ b/scripts/generate-popular-actions/testdata/go/fetched.go @@ -28,3 +28,7 @@ var PopularActions = map[string]*ActionMetadata{ }, }, } + +// OutdatedPopularActionSpecs is a spec set of known outdated popular actions. The word 'outdated' +// means that the runner used by the action is no longer available such as "node12". +var OutdatedPopularActionSpecs = map[string]struct{}{} diff --git a/scripts/generate-popular-actions/testdata/go/outdated.go b/scripts/generate-popular-actions/testdata/go/outdated.go index 8923b9bc0..afcc450b2 100644 --- a/scripts/generate-popular-actions/testdata/go/outdated.go +++ b/scripts/generate-popular-actions/testdata/go/outdated.go @@ -5,3 +5,9 @@ package actionlint // PopularActions is data set of known popular actions. Keys are specs (owner/repo@ref) of actions // and values are their metadata. var PopularActions = map[string]*ActionMetadata{} + +// OutdatedPopularActionSpecs is a spec set of known outdated popular actions. The word 'outdated' +// means that the runner used by the action is no longer available such as "node12". +var OutdatedPopularActionSpecs = map[string]struct{}{ + "rhysd/action-setup-vim@v1.0.0": {}, +} diff --git a/scripts/generate-popular-actions/testdata/go/skip_inputs_want.go b/scripts/generate-popular-actions/testdata/go/skip_inputs_want.go index f44780976..8b77baf4e 100644 --- a/scripts/generate-popular-actions/testdata/go/skip_inputs_want.go +++ b/scripts/generate-popular-actions/testdata/go/skip_inputs_want.go @@ -13,3 +13,7 @@ var PopularActions = map[string]*ActionMetadata{ }, }, } + +// OutdatedPopularActionSpecs is a spec set of known outdated popular actions. The word 'outdated' +// means that the runner used by the action is no longer available such as "node12". +var OutdatedPopularActionSpecs = map[string]struct{}{} diff --git a/scripts/generate-popular-actions/testdata/go/skip_outputs_want.go b/scripts/generate-popular-actions/testdata/go/skip_outputs_want.go index 94c8c95a7..9b2e840f2 100644 --- a/scripts/generate-popular-actions/testdata/go/skip_outputs_want.go +++ b/scripts/generate-popular-actions/testdata/go/skip_outputs_want.go @@ -15,3 +15,7 @@ var PopularActions = map[string]*ActionMetadata{ SkipOutputs: true, }, } + +// OutdatedPopularActionSpecs is a spec set of known outdated popular actions. The word 'outdated' +// means that the runner used by the action is no longer available such as "node12". +var OutdatedPopularActionSpecs = map[string]struct{}{} diff --git a/scripts/generate-popular-actions/testdata/go/want.go b/scripts/generate-popular-actions/testdata/go/want.go index f8c3e7020..f4bb500e1 100644 --- a/scripts/generate-popular-actions/testdata/go/want.go +++ b/scripts/generate-popular-actions/testdata/go/want.go @@ -17,3 +17,7 @@ var PopularActions = map[string]*ActionMetadata{ }, }, } + +// OutdatedPopularActionSpecs is a spec set of known outdated popular actions. The word 'outdated' +// means that the runner used by the action is no longer available such as "node12". +var OutdatedPopularActionSpecs = map[string]struct{}{} From cfe8f9fec9fe58df5f677f7beef838b9f6a56284 Mon Sep 17 00:00:00 2001 From: rhysd Date: Fri, 26 Apr 2024 18:57:37 +0900 Subject: [PATCH 2/4] generate popular actions data for including outdated action specs --- popular_actions.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/popular_actions.go b/popular_actions.go index a1ea3adf0..6ad623ed6 100644 --- a/popular_actions.go +++ b/popular_actions.go @@ -3636,3 +3636,95 @@ var PopularActions = map[string]*ActionMetadata{ }, }, } + +// OutdatedPopularActionSpecs is a spec set of known outdated popular actions. The word 'outdated' +// means that the runner used by the action is no longer available such as "node12". +var OutdatedPopularActionSpecs = map[string]struct{}{ + "8398a7/action-slack@v1": {}, + "8398a7/action-slack@v2": {}, + "Azure/container-scan@v0": {}, + "JamesIves/github-pages-deploy-action@releases/v3": {}, + "ReactiveCircus/android-emulator-runner@v1": {}, + "Swatinem/rust-cache@v1": {}, + "actions-cool/issues-helper@v1": {}, + "actions-cool/issues-helper@v2": {}, + "actions-rs/audit-check@v1": {}, + "actions-rs/cargo@v1": {}, + "actions-rs/clippy-check@v1": {}, + "actions-rs/toolchain@v1": {}, + "actions/cache@v1": {}, + "actions/cache@v2": {}, + "actions/checkout@v2": {}, + "actions/delete-package-versions@v1": {}, + "actions/delete-package-versions@v2": {}, + "actions/download-artifact@v2": {}, + "actions/github-script@v1": {}, + "actions/github-script@v2": {}, + "actions/github-script@v3": {}, + "actions/github-script@v4": {}, + "actions/github-script@v5": {}, + "actions/labeler@v2": {}, + "actions/labeler@v3": {}, + "actions/setup-dotnet@v1": {}, + "actions/setup-go@v1": {}, + "actions/setup-go@v2": {}, + "actions/setup-java@v1": {}, + "actions/setup-java@v2": {}, + "actions/setup-node@v1": {}, + "actions/setup-node@v2": {}, + "actions/setup-python@v1": {}, + "actions/setup-python@v2": {}, + "actions/stale@v1": {}, + "actions/stale@v2": {}, + "actions/stale@v3": {}, + "actions/stale@v4": {}, + "actions/upload-artifact@v2": {}, + "aws-actions/configure-aws-credentials@v1": {}, + "azure/aks-set-context@v1": {}, + "azure/aks-set-context@v2": {}, + "codecov/codecov-action@v1": {}, + "codecov/codecov-action@v2": {}, + "dawidd6/action-send-mail@v2": {}, + "dessant/lock-threads@v2": {}, + "dessant/lock-threads@v3": {}, + "docker/build-push-action@v2": {}, + "docker/login-action@v1": {}, + "docker/metadata-action@v1": {}, + "docker/metadata-action@v2": {}, + "docker/metadata-action@v3": {}, + "docker/setup-buildx-action@v1": {}, + "docker/setup-qemu-action@v1": {}, + "dorny/paths-filter@v1": {}, + "github/codeql-action/analyze@v1": {}, + "github/codeql-action/autobuild@v1": {}, + "github/codeql-action/init@v1": {}, + "githubocto/flat@v1": {}, + "githubocto/flat@v2": {}, + "githubocto/flat@v3": {}, + "golangci/golangci-lint-action@v1": {}, + "golangci/golangci-lint-action@v2": {}, + "goreleaser/goreleaser-action@v1": {}, + "goreleaser/goreleaser-action@v2": {}, + "haskell/actions/setup@v1": {}, + "marvinpinto/action-automatic-releases@latest": {}, + "mikepenz/release-changelog-builder-action@v1": {}, + "mikepenz/release-changelog-builder-action@v2": {}, + "msys2/setup-msys2@v1": {}, + "nwtgck/actions-netlify@v1": {}, + "octokit/request-action@v1.x": {}, + "peter-evans/create-pull-request@v1": {}, + "peter-evans/create-pull-request@v2": {}, + "peter-evans/create-pull-request@v3": {}, + "preactjs/compressed-size-action@v1": {}, + "preactjs/compressed-size-action@v2": {}, + "pulumi/actions@v2": {}, + "pulumi/actions@v3": {}, + "ridedott/merge-me-action@v1": {}, + "shivammathur/setup-php@v1": {}, + "treosh/lighthouse-ci-action@v1": {}, + "treosh/lighthouse-ci-action@v2": {}, + "treosh/lighthouse-ci-action@v3": {}, + "treosh/lighthouse-ci-action@v7": {}, + "treosh/lighthouse-ci-action@v8": {}, + "wearerequired/lint-action@v1": {}, +} From 676cf1ed4a96111955338517a4c1dff986c0ea84 Mon Sep 17 00:00:00 2001 From: rhysd Date: Fri, 26 Apr 2024 19:11:08 +0900 Subject: [PATCH 3/4] detect use of outdated popular actions --- docs/checks.md | 4 ++-- rule_action.go | 4 ++++ testdata/err/github_script_untrusted_input.yaml | 2 +- testdata/err/outdated_popular_action.out | 2 ++ testdata/err/outdated_popular_action.yaml | 10 ++++++++++ testdata/examples/untrusted_input.yaml | 4 ++-- 6 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 testdata/err/outdated_popular_action.out create mode 100644 testdata/err/outdated_popular_action.yaml diff --git a/docs/checks.md b/docs/checks.md index a229070f5..6edd9fc6f 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -938,12 +938,12 @@ jobs: - name: Print pull request title # ERROR: Using the potentially untrusted input can cause script injection run: echo '${{ github.event.pull_request.title }}' - - uses: actions/stale@v4 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.TOKEN }} # This is OK because action input is not evaluated by shell stale-pr-message: ${{ github.event.pull_request.title }} was closed - - uses: actions/github-script@v4 + - uses: actions/github-script@v7 with: # ERROR: Using the potentially untrusted input can cause script injection script: console.log('${{ github.event.head_commit.author.name }}') diff --git a/rule_action.go b/rule_action.go index 9d0dcc7ff..51f12ea7f 100644 --- a/rule_action.go +++ b/rule_action.go @@ -362,6 +362,10 @@ func (rule *RuleAction) checkRepoAction(spec string, exec *ExecAction) { meta, ok := PopularActions[spec] if !ok { + if _, ok := OutdatedPopularActionSpecs[spec]; ok { + rule.Errorf(exec.Uses.Pos, "the runner of %q action is too old to run on GitHub Actions. update the action's version to fix this issue", spec) + return + } rule.Debug("This action is not found in popular actions data set: %s", spec) return } diff --git a/testdata/err/github_script_untrusted_input.yaml b/testdata/err/github_script_untrusted_input.yaml index 55abebfc7..8a5481483 100644 --- a/testdata/err/github_script_untrusted_input.yaml +++ b/testdata/err/github_script_untrusted_input.yaml @@ -6,7 +6,7 @@ jobs: comment: runs-on: ubuntu-latest steps: - - uses: actions/github-script@v4 + - uses: actions/github-script@v7 with: script: | github.issues.createComment({ diff --git a/testdata/err/outdated_popular_action.out b/testdata/err/outdated_popular_action.out new file mode 100644 index 000000000..b4fd474a4 --- /dev/null +++ b/testdata/err/outdated_popular_action.out @@ -0,0 +1,2 @@ +test.yaml:8:15: the runner of "actions/checkout@v2" action is too old to run on GitHub Actions. update the action's version to fix this issue [action] +test.yaml:10:15: the runner of "actions/stale@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue [action] diff --git a/testdata/err/outdated_popular_action.yaml b/testdata/err/outdated_popular_action.yaml new file mode 100644 index 000000000..173fcf811 --- /dev/null +++ b/testdata/err/outdated_popular_action.yaml @@ -0,0 +1,10 @@ +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + # ERROR: This version is outdated + - uses: actions/checkout@v2 + # ERROR: This version is outdated + - uses: actions/stale@v4 diff --git a/testdata/examples/untrusted_input.yaml b/testdata/examples/untrusted_input.yaml index 18c57bfde..08ffc2354 100644 --- a/testdata/examples/untrusted_input.yaml +++ b/testdata/examples/untrusted_input.yaml @@ -8,12 +8,12 @@ jobs: - name: Print pull request title # ERROR: Using the potentially untrusted input can cause script injection run: echo '${{ github.event.pull_request.title }}' - - uses: actions/stale@v4 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.TOKEN }} # This is OK because action input is not evaluated by shell stale-pr-message: ${{ github.event.pull_request.title }} was closed - - uses: actions/github-script@v4 + - uses: actions/github-script@v7 with: # ERROR: Using the potentially untrusted input can cause script injection script: console.log('${{ github.event.head_commit.author.name }}') From c49afc5231817ed6426fdfb37991ad6d3b03fe70 Mon Sep 17 00:00:00 2001 From: rhysd Date: Sat, 27 Apr 2024 21:39:35 +0900 Subject: [PATCH 4/4] explain outdated popular action checks in document --- docs/checks.md | 39 ++++++++++++++++++- .../detect_outdated_popular_actions.out | 1 + .../detect_outdated_popular_actions.yaml | 8 ++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 testdata/examples/detect_outdated_popular_actions.out create mode 100644 testdata/examples/detect_outdated_popular_actions.yaml diff --git a/docs/checks.md b/docs/checks.md index 6edd9fc6f..4de3a9a4a 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -28,6 +28,7 @@ List of checks: - [Action format in `uses:`](#check-action-format) - [Local action inputs validation at `with:`](#check-local-action-inputs) - [Popular action inputs validation at `with:`](#check-popular-action-inputs) +- [Outdated popular actions detection at `with:`](#detect-outdated-popular-actions) - [Shell name validation at `shell:`](#check-shell-names) - [Job ID and step ID uniqueness](#check-job-step-ids) - [Hardcoded credentials](#check-hardcoded-credentials) @@ -969,7 +970,7 @@ test.yaml:22:31: object filter extracts potentially untrusted properties "github | ^~~~~~~~~~~~~~~~~~~~ ``` -[Playground](https://rhysd.github.io/actionlint#eJyFkUFLAzEQhe/9FXMQ2gqJF085eRFBoRXsvWSzQ3drNrNmJi1S+t9NdkupSvUUknnzvcdLsB0aWCHLhIKBPnm/jviRysNkSxWbCYDkWzkBYgqsijBVKUhS3pbZMGLBnkcVgIIwgF9jG2SgwokK0orHk2wAGkDXEExvDgfYtNKkSuMOg+jLMHpYg+NxenZIjGzAOmkp8B2L9fiwuz+T9xllzrfshD0poXfMhsWK0UUU1qvly+Migy+kA0v1UXXIbDc4LvyfDfaWwXlirK+kHBmKXWx7+SvtqDDg8hZ51J42s98NNWjrtaOua0XbJA1FXXovNc1//MQTChRlXuNr7Qs9vy0Xs28Wt7qi+nNekF/IR69F) +[Playground](https://rhysd.github.io/actionlint#eJyFkUFLAzEQhe/9FXMQ2gqJRzGnXkRQaAV7L9ns0F3NZtbMpEVK/7vJbilVKZ5CMm++93gJtkMDa2SZUDDQJ+83ET9TeZi8U8VmAiD5Vk6AmAKrIkxVCpKUt2U2jFiw51EFoCAM4NfYBhmocKKCtOLxJBuABtA1BNObwwG2rTSp0rjDIPoyjB7W4Hicnh0SIxuwTloKfMdiPS52D2fyPqPM+ZadsCcl9IHZsFgxuojCer16eVxm8IV0YKk+qg6Z7RbHhf+zwd4yOE+M9ZWUI0Oxi20vi9391bSjwoDLW+RRe9rO/jbUoK03jrquFW2TNBR16b3UNP/1E08oUJR5ja+1L/T8tlrOfljc6orqr3lBfgPRHa9N) Since `${{ }}` placeholders are evaluated and replaced directly by GitHub Actions runtime, you need to use them carefully in inline scripts at `run:`. For example, if we have step as follows, @@ -1740,6 +1741,41 @@ So far, actionlint supports more than 100 popular actions The data set is embedd and were automatically collected by [a script][generate-popular-actions]. If you want more checks for other actions, please make a request [as an issue][issue-form]. + +## Outdated popular actions detection at `with:` + +Example input: + +```yaml +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + # ERROR: actions/checkout@v2 is using the outdated runner 'node12' + - uses: actions/checkout@v2 +``` + +Output: + +``` +test.yaml:8:15: the runner of "actions/checkout@v2" action is too old to run on GitHub Actions. update the action's version to fix this issue [action] + | +8 | - uses: actions/checkout@v2 + | ^~~~~~~~~~~~~~~~~~~ +``` + +[Playground](https://rhysd.github.io/actionlint#eJwlyjkOwCAMRNGeU8wFUKSUVLkKICSyyEYZO+fPVv3ifZWE4ewhbFqYAmCN9hY4XRj1Gby4mMcjv/YRrQ3+FxDhbEzI1VYVTrW3uqvbcs03dIgdzQ==) + +In addition to the checks for inputs of actions described in [the previous section](#check-popular-action-inputs), actionlint +reports an error when a popular action is 'outdated'. An action is outdated when the runner used by the action is no longer +supported by GitHub Actions runtime. For example, `node12` is no longer available so any actions can use `node12` runner. + +Note that this check doesn't report that the action version is up-to-date. For example, even if you use `actions/checkout@v3` and +newer version `actions/checkout@v4` is available, actionlint reports no error as long as `actions/checkout@v3` is not outdated. +If you want to keep actions used by your workflows up-to-date, consider to use [Dependabot][dependabot-doc]. + ## Shell name validation at `shell:` @@ -2778,6 +2814,7 @@ Note that `steps` in Composite action's metadata is not checked at this point. I [gh-hosted-runner]: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners [self-hosted-runner]: https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners [action-uses-doc]: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsuses +[dependabot-doc]: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot [credentials-doc]: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idcontainercredentials [actions-cache]: https://github.com/actions/cache [permissions-doc]: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token diff --git a/testdata/examples/detect_outdated_popular_actions.out b/testdata/examples/detect_outdated_popular_actions.out new file mode 100644 index 000000000..4ad04a9fb --- /dev/null +++ b/testdata/examples/detect_outdated_popular_actions.out @@ -0,0 +1 @@ +test.yaml:8:15: the runner of "actions/checkout@v2" action is too old to run on GitHub Actions. update the action's version to fix this issue [action] diff --git a/testdata/examples/detect_outdated_popular_actions.yaml b/testdata/examples/detect_outdated_popular_actions.yaml new file mode 100644 index 000000000..d6cf50b52 --- /dev/null +++ b/testdata/examples/detect_outdated_popular_actions.yaml @@ -0,0 +1,8 @@ +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + # ERROR: actions/checkout@v2 is using the outdated runner 'node12' + - uses: actions/checkout@v2