diff --git a/.config/mise.dev.lock b/.config/mise.dev.lock index dd525655..61ba5da9 100644 --- a/.config/mise.dev.lock +++ b/.config/mise.dev.lock @@ -359,6 +359,53 @@ checksum = "sha256:9dc6e49ef47c7aa2e6a2844503d6cfa34e51a4d15dd78abef6e6de2fc10e5 url = "https://github.com/pvolok/dekit/releases/download/v0.9.6/mprocs-0.9.6-windows-x86_64.zip" url_api = "https://api.github.com/repos/pvolok/dekit/releases/assets/440298377" +[[tools.trivy]] +version = "0.74.0" +backend = "aqua:aquasecurity/trivy" +specifiers = ["latest"] + +[tools.trivy."platforms.linux-arm64"] +checksum = "sha256:b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5" +url = "https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-ARM64.tar.gz" +url_api = "https://api.github.com/repos/aquasecurity/trivy/releases/assets/514339429" +provenance = "cosign" + +[tools.trivy."platforms.linux-arm64-musl"] +checksum = "sha256:b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5" +url = "https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-ARM64.tar.gz" +url_api = "https://api.github.com/repos/aquasecurity/trivy/releases/assets/514339429" +provenance = "cosign" + +[tools.trivy."platforms.linux-x64"] +checksum = "sha256:2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a" +url = "https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-64bit.tar.gz" +url_api = "https://api.github.com/repos/aquasecurity/trivy/releases/assets/514339431" +provenance = "cosign" + +[tools.trivy."platforms.linux-x64-musl"] +checksum = "sha256:2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a" +url = "https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-64bit.tar.gz" +url_api = "https://api.github.com/repos/aquasecurity/trivy/releases/assets/514339431" +provenance = "cosign" + +[tools.trivy."platforms.macos-arm64"] +checksum = "sha256:1caada5e0e2091909357c7525d3aa76f4b660b13821bc143b190c7483e31cc11" +url = "https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_macOS-ARM64.tar.gz" +url_api = "https://api.github.com/repos/aquasecurity/trivy/releases/assets/514339464" +provenance = "cosign" + +[tools.trivy."platforms.macos-x64"] +checksum = "sha256:472816f6888dda689d075c30254d4210b4d1035acf365aa72332f584c2f60485" +url = "https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_macOS-64bit.tar.gz" +url_api = "https://api.github.com/repos/aquasecurity/trivy/releases/assets/514339388" +provenance = "cosign" + +[tools.trivy."platforms.windows-x64"] +checksum = "sha256:94c40e0696e4b907a74b7b2e1438d5d72ebaca83115817407f568a002d520842" +url = "https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_windows-64bit.zip" +url_api = "https://api.github.com/repos/aquasecurity/trivy/releases/assets/514339469" +provenance = "cosign" + [[tools.watchexec]] version = "2.7.2" backend = "aqua:watchexec/watchexec" diff --git a/.config/mise.dev.toml b/.config/mise.dev.toml index 950d6013..cc3ea00d 100644 --- a/.config/mise.dev.toml +++ b/.config/mise.dev.toml @@ -20,6 +20,8 @@ direnv = "latest" "github:peterldowns/localias" = "latest" "github:gitleaks/gitleaks" = "latest" +# filesystem vulnerabilities, misconfigurations, and secrets (`just dev_lint`) +trivy = "latest" "jq" = "latest" "mprocs" = "latest" "fd" = "latest" diff --git a/.config/trivy.yaml b/.config/trivy.yaml new file mode 100644 index 00000000..1bebcd57 --- /dev/null +++ b/.config/trivy.yaml @@ -0,0 +1,44 @@ +# https://trivy.dev/docs/latest/guide/references/configuration/config-file/ +# Passed to `trivy fs` from `just dev_lint` via `--config`. + +# Fail the lint when a reported finding matches the severity filter below. +exit-code: 1 + +# Drop progress and scanner logs so `just dev_lint` prints the report. +quiet: true + +# Gate on HIGH and CRITICAL. +severity: + - HIGH + - CRITICAL + +scan: + # mise owns upgrades; skip the extra version-check request on every lint. + skip-version-check: true + disable-telemetry: true + scanners: + - vuln + - misconfig + # gitleaks covers git history. Trivy covers the working tree with a different ruleset. + - secret + skip-dirs: + - tmp + - .git + - .venv + - .terraform + # vendored mise tool lockfiles, not this project's dependencies + - .config/mise/locks + - "**/node_modules" + +misconfiguration: + terraform: + # Lint this repo's modules, not third-party modules Terraform downloads. + exclude-downloaded-modules: true + +vulnerability: + # Advisories without a fix are not actionable in a lint gate. + ignore-unfixed: true + +pkg: + # CI installs dev dependencies, so they belong in the scan. + include-dev-deps: true diff --git a/.github/workflows/build_and_publish.yml b/.github/workflows/build_and_publish.yml index 909d55da..81133eec 100644 --- a/.github/workflows/build_and_publish.yml +++ b/.github/workflows/build_and_publish.yml @@ -61,6 +61,13 @@ jobs: fetch-depth: 0 - uses: ./.github/actions/common-setup timeout-minutes: 3 + - name: Cache Trivy databases + uses: actions/cache@v6 + with: + path: ~/.cache/trivy + key: ${{ runner.os }}-trivy-${{ hashFiles('.config/mise.dev.lock') }} + restore-keys: | + ${{ runner.os }}-trivy- - run: just dev_lint - uses: iloveitaly/github-action-localias@master with: diff --git a/README.md b/README.md index df5a2439..78c73fea 100644 --- a/README.md +++ b/README.md @@ -410,7 +410,7 @@ There are top-level commands for many of these (`clean`, `setup`, `dev`, etc) wh The more linting tools the better, as long as they are well maintained, useful, and add value. I think of linters as helpful teammates that let me know when I missed something. -This project implements many linting tools (including DB SQL linting!). This could cause developer friction at some point, but we'll see how this scales as the codebase complexity grows. +This project implements many linting tools (including DB SQL linting and Trivy filesystem scans for vulnerabilities, misconfigurations, and secrets). This could cause developer friction at some point, but we'll see how this scales as the codebase complexity grows. ### Test Database Cleaning diff --git a/infra/azure/deployment_state.tf b/infra/azure/deployment_state.tf index fd9dd8f4..0c1aa638 100644 --- a/infra/azure/deployment_state.tf +++ b/infra/azure/deployment_state.tf @@ -26,6 +26,7 @@ resource "azurerm_resource_group" "tfstate" { # IMPORTANT: (2) when bootstrapping a new azure account, this must be done second! # by default, tf stores state in the local filesystem. We use remote storage state to sync between # multiple devs and eliminate dependency on a single machine. +# trivy:ignore:AZU-0012 developers and CI read this account; default-deny without an IP allowlist blocks bootstrap resource "azurerm_storage_account" "tfstate" { name = local.tfstate.storage_account resource_group_name = azurerm_resource_group.tfstate.name diff --git a/infra/azure/dokku_vm.tf b/infra/azure/dokku_vm.tf index 25ce0e3e..0e13e35f 100644 --- a/infra/azure/dokku_vm.tf +++ b/infra/azure/dokku_vm.tf @@ -39,7 +39,8 @@ resource "azurerm_network_security_group" "vm" { location = local.location resource_group_name = azurerm_resource_group.main.name - # Allow SSH + # Allow SSH. Open to the internet so a fresh template VM is reachable; restrict this prefix before production use. + # trivy:ignore:AZU-0047 trivy:ignore:AZU-0050 security_rule { name = "SSH" priority = 1001 @@ -52,7 +53,8 @@ resource "azurerm_network_security_group" "vm" { destination_address_prefix = "*" } - # Allow HTTP + # Allow HTTP. Public ingress is required for the web app. + # trivy:ignore:AZU-0047 security_rule { name = "HTTP" priority = 1002 @@ -65,7 +67,8 @@ resource "azurerm_network_security_group" "vm" { destination_address_prefix = "*" } - # Allow HTTPS + # Allow HTTPS. Public ingress is required for the web app. + # trivy:ignore:AZU-0047 security_rule { name = "HTTPS" priority = 1003 diff --git a/just/dev.just b/just/dev.just index 6448b179..34093cdd 100644 --- a/just/dev.just +++ b/just/dev.just @@ -12,6 +12,7 @@ GENERATED_HOST_ENV := "env/host.sh" # we don't guard against _dev_only since this needs to be run to bootstrap the application dev_generate: _not_production dev_generate_hosts dev_generate_localias +# gitleaks (git history) and trivy (working tree). Policy for trivy lives in .config/trivy.yaml. dev_lint: # `--log-level=debug` for debugging # will report 0 commits scanned since it just outputs a massive diff and doesn't scan commits @@ -19,6 +20,8 @@ dev_lint: # TODO link justfiles too # GIT_CONFIG_GLOBAL=/dev/null gitleaks git --report-format json --report-path - | jq -r '.[].Fingerprint' | sort -t ':' -k2 > .gitleaksignore + trivy fs --config .config/trivy.yaml . + # start all of the services you need for development in a single terminal [script] [arg("open", long, help="open the development site in the browser", flag)] diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 146caa97..e4d2937b 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -4,6 +4,14 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + brace-expansion@1.1.14: 1.1.20 + brace-expansion@2.1.0: 2.1.6 + brace-expansion@5.0.6: 5.0.11 + js-yaml@4.1.1: 4.3.2 + js-yaml@4.2.0: 4.3.2 + shell-quote@1.8.4: 1.9.0 + importers: .: @@ -2938,15 +2946,15 @@ packages: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} - brace-expansion@1.1.14: - resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + brace-expansion@1.1.20: + resolution: {integrity: sha512-5/qk29RyvatwgzNMrvGjkIhKYzpJ0OUnPK+9TU+zS/CMJqzmb+UD0irWu42JfpUvO636fxd8IstgiydZRM/76A==} - brace-expansion@2.1.0: - resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@2.1.6: + resolution: {integrity: sha512-pi/WFpBvCBmB/NsURt7hW5sDvuOqvJaDMDai7CrlT9xoehzWRzo/dhQRKqOhpPvlnicVaTzHCbzGnnTgpvMTFw==} - brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.11: + resolution: {integrity: sha512-awigjhi6cLTh90bdw6+QJ9CtmJmyYhEIi70iCbc8Rozn04Fw9FeQIBjv/E22FFGuCGx1bLJyUfB64x/szUSXUg==} + engines: {node: 20 || >=22} browserslist@4.28.9: resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==} @@ -3885,12 +3893,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - - js-yaml@4.2.0: - resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true jsesc@3.1.0: @@ -4768,8 +4772,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.4: - resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} + shell-quote@1.9.0: + resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} engines: {node: '>= 0.4'} side-channel-list@1.0.1: @@ -5781,7 +5785,7 @@ snapshots: dependencies: '@jsdevtools/ono': 7.1.3 '@types/json-schema': 7.0.15 - js-yaml: 4.2.0 + js-yaml: 4.3.2 '@hey-api/openapi-ts@0.99.0(@typescript/typescript6@6.0.2)(magicast@0.5.4)': dependencies: @@ -7784,16 +7788,16 @@ snapshots: transitivePeerDependencies: - supports-color - brace-expansion@1.1.14: + brace-expansion@1.1.20: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.1.0: + brace-expansion@2.1.6: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.6: + brace-expansion@5.0.11: dependencies: balanced-match: 4.0.4 @@ -7945,7 +7949,7 @@ snapshots: cosmiconfig@8.3.6(@typescript/typescript6@6.0.2): dependencies: import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.2 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -8832,11 +8836,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - - js-yaml@4.2.0: + js-yaml@4.3.2: dependencies: argparse: 2.0.1 @@ -8872,7 +8872,7 @@ snapshots: launch-editor@2.13.2: dependencies: picocolors: 1.1.1 - shell-quote: 1.8.4 + shell-quote: 1.9.0 levn@0.4.1: dependencies: @@ -9047,15 +9047,15 @@ snapshots: minimatch@10.2.5: dependencies: - brace-expansion: 5.0.6 + brace-expansion: 5.0.11 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.14 + brace-expansion: 1.1.20 minimatch@9.0.9: dependencies: - brace-expansion: 2.1.0 + brace-expansion: 2.1.6 minipass@7.1.3: {} @@ -9743,7 +9743,7 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.4: {} + shell-quote@1.9.0: {} side-channel-list@1.0.1: dependencies: diff --git a/web/pnpm-workspace.yaml b/web/pnpm-workspace.yaml index 8779f297..8b4c3fe8 100644 --- a/web/pnpm-workspace.yaml +++ b/web/pnpm-workspace.yaml @@ -1,3 +1,12 @@ +# Pin transitive versions Trivy reports as HIGH with a fixed release. +overrides: + brace-expansion@1.1.14: 1.1.20 + brace-expansion@2.1.0: 2.1.6 + brace-expansion@5.0.6: 5.0.11 + js-yaml@4.1.1: 4.3.2 + js-yaml@4.2.0: 4.3.2 + shell-quote@1.8.4: 1.9.0 + allowBuilds: '@clerk/shared': true '@sentry/cli': true