### Output for unlocked-install example Source: https://jactionlint.jdx.dev/checks Shows the jactionlint output for the unlocked-install example, with two findings for 'npm install' and 'pip install -r'. ```text test.yaml:6:14: "npm install" resolves the dependencies again and may update the package-lock.json instead of failing when it is out of date. use `npm ci` [unlocked-install] | 6 | - run: npm install | ^~~ test.yaml:7:14: "pip install -r requirements.txt" installs a requirements file without hashes or constraints, so the transitive dependencies are resolved anew on every run. use a lock file with hashes (`pip-compile --generate-hashes`) and pass --require-hashes, or pin them with -c [unlocked-install] | 7 | - run: pip install -r requirements.txt | ^~~ ``` -------------------------------- ### Install specific version with download script Source: https://jactionlint.jdx.dev/install Pass the version as the 1st command line argument. Example installs v1.6.17. ```sh bash <(curl https://raw.githubusercontent.com/jdx/jactionlint/main/scripts/download-jactionlint.bash) 1.6.17 ``` -------------------------------- ### Install to custom directory with download script Source: https://jactionlint.jdx.dev/install Pass the directory path as the 2nd command line argument. Example installs the latest version to `/usr/bin`. ```sh bash <(curl https://raw.githubusercontent.com/jdx/jactionlint/main/scripts/download-jactionlint.bash) latest /usr/bin ``` -------------------------------- ### Example input for unlocked-install rule Source: https://jactionlint.jdx.dev/checks Shows a workflow with 'npm install' and 'pip install -r' that triggers the 'unlocked-install' rule under the 'pedantic' profile or when a package-lock.json exists. ```yaml on: push jobs: test: runs-on: ubuntu-latest steps: - run: npm install - run: pip install -r requirements.txt ``` -------------------------------- ### Installs without a lock file example output Source: https://jactionlint.jdx.dev/checks Output from jactionlint for the unlocked-install rule, showing the error message and the location of the offending line. ```text test.yaml:6:14: "cargo install" without --locked builds with the newest dependencies that match the crate instead of the ones in its Cargo.lock, so a new release of any dependency reaches the workflow. add --locked [unlocked-install] | 6 | - run: cargo install cargo-nextest | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` -------------------------------- ### unpinned-tools: Example input Source: https://jactionlint.jdx.dev/checks Example GitHub Actions workflow that triggers the `unpinned-tools` rule. It uses `hashicorp/setup-terraform` without setting `terraform_version`, and runs `pip install` and `go install` without exact versions. ```yaml on: push jobs: tools: runs-on: ubuntu-latest steps: - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 - run: pip install requests black==24.3.0 - run: go install golang.org/x/tools/cmd/stringer@latest ``` -------------------------------- ### Installs without a lock file example input Source: https://jactionlint.jdx.dev/checks Example workflow that runs 'cargo install cargo-nextest' without '--locked', which is reported by the unlocked-install rule. ```yaml on: push jobs: test: runs-on: ubuntu-latest steps: - run: cargo install cargo-nextest ``` -------------------------------- ### Install jactionlint with mise Source: https://jactionlint.jdx.dev/install Recommended way to install jactionlint. Use `mise use -g jactionlint` to install globally, or without `-g` to add it to the current project's `mise.toml`. `mise upgrade jactionlint` updates it. ```sh mise use -g jactionlint jactionlint --version ``` -------------------------------- ### Install jactionlint with mise Source: https://jactionlint.jdx.dev/ Install jactionlint globally using mise and verify the installation by checking the version. This command installs from the GitHub releases of the repository. ```sh mise use -g jactionlint jactionlint --version ``` -------------------------------- ### Output for self-hosted-runner example Source: https://jactionlint.jdx.dev/checks The actionlint output for the self-hosted-runner example, showing an info-level diagnostic at line 4. ```text test.yaml:4:15: info: job runs on a self-hosted runner (label "self-hosted"). self-hosted runners are hard to secure and should not run workflows of untrusted pull requests of a public repository [self-hosted-runner] | 4 | runs-on: [self-hosted, linux] | ^~~~~~~~~~~~ ``` -------------------------------- ### Example input YAML for syntax check Source: https://jactionlint.jdx.dev/checks This is the example input YAML file used to demonstrate the syntax check. It is incomplete and only shows the beginning of the file. ```yaml on: push ``` -------------------------------- ### Unsound prefix matches on names example input Source: https://jactionlint.jdx.dev/checks Example workflow using startsWith on github.actor, which can be bypassed by a username that starts with the prefix. ```yaml on: pull_request_target jobs: test: runs-on: ubuntu-latest steps: - run: ./trusted.sh if: startsWith(github.actor, 'jdx') ``` -------------------------------- ### Example input for adhoc-packages rule Source: https://jactionlint.jdx.dev/checks YAML workflow with steps that run 'npm install eslint@9.0.0' and 'gem install rake', which trigger the adhoc-packages rule. ```yaml on: push jobs: test: runs-on: ubuntu-latest steps: - run: npm install eslint@9.0.0 - run: gem install rake ``` -------------------------------- ### use-trusted-publishing: Example output Source: https://jactionlint.jdx.dev/checks Output from zizmor for the example input, showing warnings for using long-lived credentials instead of trusted publishing. The warnings suggest using `pypa/gh-action-pypi-publish` with `id-token: write` permission. ```text test.yaml:8:14: "twine upload" publishes to PyPI with the long-lived credential TWINE_PASSWORD. prefer trusted publishing with pypa/gh-action-pypi-publish and the permission "id-token: write" [use-trusted-publishing] | 8 | - run: twine upload dist/* | ^~~~~~~~~~~~~~~~~~~ test.yaml:13:21: action "pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc" publishes to PyPI but is given a password (input "password") instead of using trusted publishing. prefer trusted publishing with pypa/gh-action-pypi-publish and the permission "id-token: write" [use-trusted-publishing] | 13 | password: ${{ secrets.PYPI_TOKEN }} | ^~~ ``` -------------------------------- ### Output for adhoc-packages example Source: https://jactionlint.jdx.dev/checks Linter output showing two warnings for the adhoc-packages rule, with suggestions to use lock files and install commands like npm ci and bundle install. ```text test.yaml:6:14: command "npm install" installs a package outside of a lock file: its dependencies are resolved anew on every run, although its version is pinned. add the package to package.json and commit the package-lock.json and install with `npm ci` [adhoc-packages] | 6 | - run: npm install eslint@9.0.0 | ^~~~~~~~~~~~~~~~~~~~~~~~ test.yaml:7:14: command "gem install" installs a package outside of a lock file: its version and its dependencies are resolved anew on every run. add the package to a Gemfile and commit the Gemfile.lock and install with `bundle install` [adhoc-packages] | 7 | - run: gem install rake | ^~~~~~~~~~~~~~~~ ``` -------------------------------- ### Example input: actions/checkout without persist-credentials Source: https://jactionlint.jdx.dev/checks Example workflow showing a checkout step that triggers the artipacked rule and a corrected step with persist-credentials: false. ```yaml on: push jobs: test: runs-on: ubuntu-latest steps: # ERROR: the token stays in .git/config for the following steps - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - run: tar czf repo.tgz . # OK - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false path: other ``` -------------------------------- ### shellcheck integration for run: - Example input Source: https://jactionlint.jdx.dev/checks Example workflow with three run steps. The first step on ubuntu-latest uses bash and is checked by shellcheck. The second step on windows-latest uses PowerShell by default and is not checked. The third step on windows-latest explicitly sets 'shell: bash' and is checked. ```yaml on: push jobs: test: runs-on: ubuntu-latest steps: - run: echo $FOO test-win: runs-on: windows-latest steps: # Shell on Windows is PowerShell by default. # shellcheck is not run in this case. - run: echo $FOO # This script is run with bash due to 'shell:' configuration - run: echo $FOO shell: bash ``` -------------------------------- ### Install jactionlint v2 with Go Source: https://jactionlint.jdx.dev/v2-migration Installs the jactionlint v2 command-line tool using the Go module path `github.com/jdx/jactionlint/v2`. ```sh go install github.com/jdx/jactionlint/v2/cmd/jactionlint@latest ``` -------------------------------- ### Output for unpinned-tools example Source: https://jactionlint.jdx.dev/checks Linter output showing a warning for the unpinned-tools rule, indicating that the action installs the newest version of its tool because the 'version' input is not set. ```text test.yaml:6:15: action "aquasecurity/setup-trivy@e6c2c5e321ed9123bda567646e2f96565e34abe1" installs the newest version of its tool because the input "version" is not set. set "version" to an exact version [unpinned-tools] | 6 | - uses: aquasecurity/setup-trivy@e6c2c5e321ed9123bda567646e2f96565e34abe1 # v0.2.4 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` -------------------------------- ### Cyclic job dependencies detection Source: https://jactionlint.jdx.dev/checks Workflow with a cycle in needs: prepare -> build -> install -> prepare. The output reports the cycle as an error, preventing jobs from starting. ```yaml on: push jobs: prepare: needs: [build] runs-on: ubuntu-latest steps: - run: echo 'prepare' install: needs: [prepare] runs-on: ubuntu-latest steps: - run: echo 'install' build: needs: [install] runs-on: ubuntu-latest steps: - run: echo 'build' ``` ```text test.yaml:3:3: cyclic dependencies in "needs" job configurations are detected. detected cycle is "prepare" -> "build" -> "install" -> "prepare" [cyclic-job-needs] | 3 | prepare: | ^~~~~~~~ ``` -------------------------------- ### Generate initial configuration with --init-config Source: https://jactionlint.jdx.dev/config Run jactionlint --init-config to generate a default configuration file, then edit it with vim. The generated file is .github/jactionlint.yaml. ```sh jactionlint --init-config vim .github/jactionlint.yaml ``` -------------------------------- ### Example of unsound-prefix-match error output Source: https://jactionlint.jdx.dev/checks Shows the error message and location for a startsWith() call on github.actor that matches a prefix, which is unsafe because anyone can create an account with a name starting with 'jdx'. ```text test.yaml:7:13: startsWith(github.actor, "jdx") is also true for an account whose name only begins with "jdx", such as "jdx-evil", which anybody can create. compare the whole name with == or, for several names, test a list with contains(fromJSON('["a", "b"]'), value) [unsound-prefix-match] | 7 | if: startsWith(github.actor, 'jdx') | ^~~~~~~~~~~~~~~~~~~~~~~~ ``` -------------------------------- ### Example input for workflow and job names Source: https://jactionlint.jdx.dev/checks A minimal YAML workflow example showing a job named 'build' with a single step that runs 'make'. ```yaml on: push jobs: build: runs-on: ubuntu-latest steps: - run: make ``` -------------------------------- ### Download prebuilt binary with gh release download Source: https://jactionlint.jdx.dev/install Example for x86_64 Linux. Downloads an archive from the releases page, extracts it, and runs `jactionlint --version`. Prebuilt binaries are available for macOS, Linux, Windows, and FreeBSD; some targets are not tested. ```sh gh release download --repo jdx/jactionlint --pattern '*_linux_amd64.tar.gz' v1.7.12 tar xf jactionlint_1.7.12_linux_amd64.tar.gz ./jactionlint --version ``` -------------------------------- ### Output for secrets-outside-env example Source: https://jactionlint.jdx.dev/checks The warning message produced by jactionlint for the secrets-outside-env example. ```text test.yaml:8:24: warning: secret "API_KEY" is used by a job which has no "environment:", so it is a repository or organization secret exposed to every job. move it to an environment with protection rules and set "environment:" at the job [secrets-outside-env] | 8 | API_KEY: ${{ secrets.API_KEY }} | ^~~~~~~~~~~~~~~ ``` -------------------------------- ### Build jactionlint from source with go install Source: https://jactionlint.jdx.dev/install Requires recent Go toolchain (last two major versions supported). Install the latest stable version or the head of the main branch. ```sh # Install the latest stable version go install github.com/jdx/jactionlint/v2/cmd/jactionlint@latest # Install the head of the main branch go install github.com/jdx/jactionlint/v2/cmd/jactionlint@main ``` -------------------------------- ### Output for unredacted-secrets example Source: https://jactionlint.jdx.dev/checks The warning message produced by jactionlint for the unredacted-secrets example. ```text test.yaml:8:25: warning: secret "CREDENTIALS" is parsed with fromJSON(), and the runner does not redact the fields of a parsed secret from the logs. store each field in its own secret and reference it by name [unredacted-secrets] | 8 | PASSWORD: ${{ fromJSON(secrets.CREDENTIALS).password }} | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` -------------------------------- ### Example input for unpinned-tools rule Source: https://jactionlint.jdx.dev/checks YAML workflow using aquasecurity/setup-trivy action without setting the 'version' input, triggering the unpinned-tools rule. ```yaml on: push jobs: scan: runs-on: ubuntu-latest steps: - uses: aquasecurity/setup-trivy@e6c2c5e321ed9123bda567646e2f96565e34abe1 # v0.2.4 ``` -------------------------------- ### Output for overprovisioned-secrets example Source: https://jactionlint.jdx.dev/checks The warning message produced by jactionlint for the overprovisioned-secrets example. ```text test.yaml:8:31: warning: the whole "secrets" context is used, which exposes every secret to the runner even if only one is needed. reference each secret by its name, e.g. "secrets.NAME" [overprovisioned-secrets] | 8 | SECRETS: ${{ toJSON(secrets) }} | ^~~~~~~~ ``` -------------------------------- ### Lint output for the example workflow Source: https://jactionlint.jdx.dev/checks The output from actionlint for the example workflow, showing three errors from the `github-app` rule: missing `permission-*` input, `owner` without `repositories`, and `skip-token-revoke: true`. ```text test.yaml:8:15: no "permission-*" input is set, so the token gets every permission granted to the GitHub App. request only what the job needs, for instance "permission-contents: read" [github-app] | 8 | - uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test.yaml:17:18: "owner" without "repositories" issues a token with access to every repository of the owner where the app is installed. list the repositories the token is for in "repositories" [github-app] | 17 | owner: my-org | ^~~~~~ test.yaml:19:30: "skip-token-revoke: true" keeps the GitHub App token valid after the job ends. remove it so that the token is revoked in the post step [github-app] | 19 | skip-token-revoke: true | ^~~~ ```