diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 4139fa85a..a2bab2477 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -104,6 +104,7 @@ actions: enabled: # enabled actions inherited from github.com/trunk-io/configs plugin + - terraform-docs - linter-test-helper - npm-check-pre-push - remove-release-snapshots diff --git a/README.md b/README.md index 77915b32d..e55a36f21 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ trunk check enable {linter} | Dart | [dart] | | Docker | [hadolint], [checkov] | | Dotenv | [dotenv-linter] | -| GitHub | [actionlint] | +| GitHub | [actionlint], [zizmor] | | Go | [gofmt], [gofumpt], [goimports], [gokart], [golangci-lint], [golines], [semgrep] | | GraphQL | [graphql-schema-linter], [prettier] | | HAML | [haml-lint] | @@ -81,7 +81,7 @@ trunk check enable {linter} | Ruby | [brakeman], [rubocop], [rufo], [semgrep], [standardrb] | | Rust | [clippy], [rustfmt] | | Scala | [scalafmt] | -| Security | [checkov], [dustilock], [grype], [nancy], [osv-scanner], [snyk], [tfsec], [trivy], [trufflehog], [terrascan] | +| Security | [checkov], [dustilock], [grype], [nancy], [osv-scanner], [snyk], [tfsec], [trivy], [trufflehog], [terrascan], [zizmor] | | SQL | [sqlfluff], [sqlfmt], [sql-formatter], [squawk] | | SVG | [svgo] | | Swift | [stringslint], [swiftlint], [swiftformat] | @@ -203,6 +203,7 @@ trunk check enable {linter} [yamlfmt]: https://github.com/google/yamlfmt#readme [yamllint]: https://trunk.io/linters/yaml/yamllint [yapf]: https://github.com/google/yapf#readme +[zizmor]: https://docs.zizmor.sh/
diff --git a/linters/zizmor/plugin.yaml b/linters/zizmor/plugin.yaml new file mode 100644 index 000000000..ae97864ef --- /dev/null +++ b/linters/zizmor/plugin.yaml @@ -0,0 +1,40 @@ +version: 0.1 +tools: + definitions: + - name: zizmor + runtime: python + package: zizmor + shims: [zizmor] + known_good_version: 1.24.1 +lint: + definitions: + - name: zizmor + files: [github-workflow] + tools: [zizmor] + description: Static analysis for GitHub Actions + commands: + - name: lint + output: sarif + run: zizmor --format=sarif ${target} + success_codes: [0] + batch: true + cache_results: true + is_security: true + direct_configs: + - zizmor.yml + - zizmor.yaml + - .github/zizmor.yml + - .github/zizmor.yaml + suggest_if: config_present + environment: + - name: GH_TOKEN + value: ${env.GH_TOKEN} + optional: true + - name: GITHUB_TOKEN + value: ${env.GITHUB_TOKEN} + optional: true + issue_url_format: https://docs.zizmor.sh/audits/ + known_good_version: 1.24.1 + version_command: + parse_regex: ${semver} + run: zizmor --version diff --git a/linters/zizmor/test_data/bad.in.yaml b/linters/zizmor/test_data/bad.in.yaml new file mode 100644 index 000000000..b8b8d7a91 --- /dev/null +++ b/linters/zizmor/test_data/bad.in.yaml @@ -0,0 +1,13 @@ +name: bad + +on: + pull_request_target: + +permissions: write-all + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: echo "${{ github.event.pull_request.title }}" diff --git a/linters/zizmor/test_data/online/mismatched_version_comment.in.yaml b/linters/zizmor/test_data/online/mismatched_version_comment.in.yaml new file mode 100644 index 000000000..2efe65773 --- /dev/null +++ b/linters/zizmor/test_data/online/mismatched_version_comment.in.yaml @@ -0,0 +1,15 @@ +name: mismatched version comment + +on: + push: + +permissions: {} + +jobs: + test: + permissions: {} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 + with: + persist-credentials: false diff --git a/linters/zizmor/test_data/zizmor_v1.24.1_CUSTOM.check.shot b/linters/zizmor/test_data/zizmor_v1.24.1_CUSTOM.check.shot new file mode 100644 index 000000000..8395f6df9 --- /dev/null +++ b/linters/zizmor/test_data/zizmor_v1.24.1_CUSTOM.check.shot @@ -0,0 +1,111 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Testing linter zizmor test CUSTOM 1`] = ` +{ + "issues": [ + { + "code": "zizmor/unpinned-uses", + "column": "15", + "file": ".github/workflows/bad.in.yaml", + "isSecurity": true, + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://docs.zizmor.sh/audits/", + "level": "LEVEL_HIGH", + "line": "12", + "linter": "zizmor", + "message": "unpinned action reference", + "ranges": [ + { + "filePath": ".github/workflows/bad.in.yaml", + "length": "19", + "offset": "129", + }, + ], + "targetType": "github-workflow", + }, + { + "code": "zizmor/artipacked", + "column": "9", + "file": ".github/workflows/bad.in.yaml", + "isSecurity": true, + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://docs.zizmor.sh/audits/", + "level": "LEVEL_MEDIUM", + "line": "12", + "linter": "zizmor", + "message": "credential persistence through GitHub Actions artifacts", + "ranges": [ + { + "filePath": ".github/workflows/bad.in.yaml", + "length": "25", + "offset": "123", + }, + ], + "targetType": "github-workflow", + }, + { + "code": "zizmor/template-injection", + "column": "24", + "file": ".github/workflows/bad.in.yaml", + "isSecurity": true, + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://docs.zizmor.sh/audits/", + "level": "LEVEL_HIGH", + "line": "13", + "linter": "zizmor", + "message": "code injection via template expansion", + "ranges": [ + { + "filePath": ".github/workflows/bad.in.yaml", + "length": "31", + "offset": "172", + }, + ], + "targetType": "github-workflow", + }, + { + "code": "zizmor/dangerous-triggers", + "column": "1", + "file": ".github/workflows/bad.in.yaml", + "isSecurity": true, + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://docs.zizmor.sh/audits/", + "level": "LEVEL_HIGH", + "line": "3", + "linter": "zizmor", + "message": "use of fundamentally insecure workflow trigger", + "ranges": [ + { + "filePath": ".github/workflows/bad.in.yaml", + "length": "26", + "offset": "11", + }, + ], + "targetType": "github-workflow", + }, + ], + "lintActions": [ + { + "command": "lint", + "fileGroupName": "github-workflow", + "linter": "zizmor", + "paths": [ + ".github/workflows/bad.in.yaml", + ], + "verb": "TRUNK_VERB_CHECK", + }, + { + "command": "lint", + "fileGroupName": "github-workflow", + "linter": "zizmor", + "paths": [ + ".github/workflows/bad.in.yaml", + ], + "upstream": true, + "verb": "TRUNK_VERB_CHECK", + }, + ], + "taskFailures": [], + "unformattedFiles": [], +} +`; diff --git a/linters/zizmor/test_data/zizmor_v1.24.1_version_comment.check.shot b/linters/zizmor/test_data/zizmor_v1.24.1_version_comment.check.shot new file mode 100644 index 000000000..b9d03ac24 --- /dev/null +++ b/linters/zizmor/test_data/zizmor_v1.24.1_version_comment.check.shot @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Testing linter zizmor test version_comment 1`] = ` +{ + "issues": [ + { + "code": "zizmor/ref-version-mismatch", + "column": "75", + "file": ".github/workflows/mismatched_version_comment.in.yaml", + "isSecurity": true, + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://docs.zizmor.sh/audits/", + "level": "LEVEL_MEDIUM", + "line": "13", + "linter": "zizmor", + "message": "action's hash pin has mismatched or missing version comment", + "ranges": [ + { + "filePath": ".github/workflows/mismatched_version_comment.in.yaml", + "length": "6", + "offset": "210", + }, + ], + "targetType": "github-workflow", + }, + ], + "lintActions": [ + { + "command": "lint", + "fileGroupName": "github-workflow", + "linter": "zizmor", + "paths": [ + ".github/workflows/mismatched_version_comment.in.yaml", + ], + "verb": "TRUNK_VERB_CHECK", + }, + { + "command": "lint", + "fileGroupName": "github-workflow", + "linter": "zizmor", + "paths": [ + ".github/workflows/mismatched_version_comment.in.yaml", + ], + "upstream": true, + "verb": "TRUNK_VERB_CHECK", + }, + ], + "taskFailures": [], + "unformattedFiles": [], +} +`; diff --git a/linters/zizmor/zizmor.test.ts b/linters/zizmor/zizmor.test.ts new file mode 100644 index 000000000..0a0ba33b9 --- /dev/null +++ b/linters/zizmor/zizmor.test.ts @@ -0,0 +1,35 @@ +import * as fs from "fs"; +import * as path from "path"; +import { customLinterCheckTest } from "tests"; +import { TrunkLintDriver } from "tests/driver"; +import { TEST_DATA } from "tests/utils"; + +// zizmor only runs on files under .github/workflows. +const moveWorkflowFiles = + (sourceDir = TEST_DATA) => + async (driver: TrunkLintDriver) => { + fs.readdirSync(path.resolve(driver.getSandbox(), sourceDir), { withFileTypes: true }) + .filter((file) => file.isFile()) + .forEach((file) => { + driver.moveFile(path.join(sourceDir, file.name), path.join(".github/workflows", file.name)); + }); + await driver.gitDriver?.add(".").commit("moved"); + }; + +const skipIfMissingGitHubToken = () => { + if (!process.env.GH_TOKEN && !process.env.GITHUB_TOKEN) { + console.log("Skipping zizmor online audit test because GH_TOKEN and GITHUB_TOKEN are not set."); + return true; + } + return false; +}; + +customLinterCheckTest({ linterName: "zizmor", args: ".github", preCheck: moveWorkflowFiles() }); + +customLinterCheckTest({ + linterName: "zizmor", + testName: "version_comment", + args: ".github", + preCheck: moveWorkflowFiles(path.join(TEST_DATA, "online")), + skipTestIf: skipIfMissingGitHubToken, +}); diff --git a/tools/pnpm/plugin.yaml b/tools/pnpm/plugin.yaml index f52438f4e..47411c4e8 100644 --- a/tools/pnpm/plugin.yaml +++ b/tools/pnpm/plugin.yaml @@ -1,8 +1,22 @@ version: 0.1 downloads: - name: pnpm - executable: true downloads: + - os: + linux: linux + macos: darwin + cpu: + x86_64: x64 + arm_64: arm64 + url: https://github.com/pnpm/pnpm/releases/download/v${version}/pnpm-${os}-${cpu}.tar.gz + version: ">=11.0.0" + - os: + windows: win32 + cpu: + x86_64: x64 + arm_64: arm64 + url: https://github.com/pnpm/pnpm/releases/download/v${version}/pnpm-${os}-${cpu}.zip + version: ">=11.0.0" - os: linux: linuxstatic macos: macos @@ -10,17 +24,19 @@ downloads: x86_64: x64 arm_64: arm64 url: https://github.com/pnpm/pnpm/releases/download/v${version}/pnpm-${os}-${cpu} + version: <11.0.0 - os: windows: win cpu: x86_64: x64 arm_64: arm64 url: https://github.com/pnpm/pnpm/releases/download/v${version}/pnpm-${os}-${cpu}.exe + version: <11.0.0 tools: definitions: - name: pnpm - known_good_version: 8.6.1 + known_good_version: 11.0.9 download: pnpm shims: - name: pnpm diff --git a/tools/pnpm/pnpm.test.ts b/tools/pnpm/pnpm.test.ts index d83a309d9..7f67ba8ba 100644 --- a/tools/pnpm/pnpm.test.ts +++ b/tools/pnpm/pnpm.test.ts @@ -1,5 +1,11 @@ import { makeToolTestConfig, toolTest } from "tests"; +toolTest({ + toolName: "pnpm", + toolVersion: "11.0.9", + testConfigs: [makeToolTestConfig({ command: ["pnpm", "--version"], expectedOut: "11.0.9" })], +}); + toolTest({ toolName: "pnpm", toolVersion: "8.6.1",