From 8a3bc317ae3933a5c9c10b768d56f9b22234f957 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 27 Jul 2026 09:30:28 +0000 Subject: [PATCH 1/2] feat: add safe dotenv linting Signed-off-by: Gregor Zeitlinger --- .github/renovate-tracked-deps.json | 5 +++ .github/renovate.json5 | 1 + README.md | 17 +++++----- default.json | 1 + docs/linters.md | 17 +++++----- docs/linters/dotenv-linter.md | 32 +++++++++++++++++++ mise.toml | 1 + src/main.rs | 1 + src/registry/checks.rs | 24 ++++++++++++++ tests/cases/dotenv-linter/auto-fix/files/.env | 1 + .../dotenv-linter/auto-fix/files/mise.toml | 2 ++ tests/cases/dotenv-linter/auto-fix/test.toml | 20 ++++++++++++ tests/cases/dotenv-linter/clean/files/.env | 2 ++ .../cases/dotenv-linter/clean/files/mise.toml | 2 ++ tests/cases/dotenv-linter/clean/test.toml | 3 ++ 15 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 docs/linters/dotenv-linter.md create mode 100644 tests/cases/dotenv-linter/auto-fix/files/.env create mode 100644 tests/cases/dotenv-linter/auto-fix/files/mise.toml create mode 100644 tests/cases/dotenv-linter/auto-fix/test.toml create mode 100644 tests/cases/dotenv-linter/clean/files/.env create mode 100644 tests/cases/dotenv-linter/clean/files/mise.toml create mode 100644 tests/cases/dotenv-linter/clean/test.toml diff --git a/.github/renovate-tracked-deps.json b/.github/renovate-tracked-deps.json index 295c0200..12a86e0b 100644 --- a/.github/renovate-tracked-deps.json +++ b/.github/renovate-tracked-deps.json @@ -16,6 +16,10 @@ "packageName": "biomejs/biome", "datasource": "github-tags" }, + "dotenv-linter": { + "packageName": "dotenv-linter/dotenv-linter", + "datasource": "github-releases" + }, "editorconfig-checker": { "packageName": "editorconfig-checker/editorconfig-checker", "datasource": "github-releases" @@ -109,6 +113,7 @@ "aqua:jonwiggins/xmloxide", "aqua:owenlamont/ryl", "biome", + "dotenv-linter", "dotnet", "editorconfig-checker", "go", diff --git a/.github/renovate.json5 b/.github/renovate.json5 index efa4a3d9..ff719a80 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -60,6 +60,7 @@ "aqua:jonwiggins/xmloxide", "aqua:owenlamont/ryl", "biome", + "dotenv-linter", "editorconfig-checker", "golangci-lint", "google-java-format", diff --git a/README.md b/README.md index fa06ae3b..f4df8b4e 100644 --- a/README.md +++ b/README.md @@ -158,14 +158,15 @@ For Flint contributor workflow and local testing tips, see ### Files / Formats -| Name | Linter | Formatter | -| -------- | ------------------------------------------ | ---------------------------------------------- | -| JSON | [`biome`](docs/linters/biome.md) | [`biome-format`](docs/linters/biome-format.md) | -| Markdown | [`rumdl`](docs/linters/rumdl.md) | [`rumdl`](docs/linters/rumdl.md) | -| Shell | [`shellcheck`](docs/linters/shellcheck.md) | [`shfmt`](docs/linters/shfmt.md) | -| TOML | — | [`taplo`](docs/linters/taplo.md) | -| XML | [`xmllint`](docs/linters/xmllint.md) | — | -| YAML | [`ryl`](docs/linters/ryl.md) | [`ryl`](docs/linters/ryl.md) | +| Name | Linter | Formatter | +| -------- | ------------------------------------------------ | ------------------------------------------------ | +| Dotenv | [`dotenv-linter`](docs/linters/dotenv-linter.md) | [`dotenv-linter`](docs/linters/dotenv-linter.md) | +| JSON | [`biome`](docs/linters/biome.md) | [`biome-format`](docs/linters/biome-format.md) | +| Markdown | [`rumdl`](docs/linters/rumdl.md) | [`rumdl`](docs/linters/rumdl.md) | +| Shell | [`shellcheck`](docs/linters/shellcheck.md) | [`shfmt`](docs/linters/shfmt.md) | +| TOML | — | [`taplo`](docs/linters/taplo.md) | +| XML | [`xmllint`](docs/linters/xmllint.md) | — | +| YAML | [`ryl`](docs/linters/ryl.md) | [`ryl`](docs/linters/ryl.md) | ### Tooling / CI diff --git a/default.json b/default.json index aa0a26c5..b1af6f01 100644 --- a/default.json +++ b/default.json @@ -24,6 +24,7 @@ "aqua:jonwiggins/xmloxide", "aqua:owenlamont/ryl", "biome", + "dotenv-linter", "editorconfig-checker", "golangci-lint", "google-java-format", diff --git a/docs/linters.md b/docs/linters.md index 114df62c..44af0853 100644 --- a/docs/linters.md +++ b/docs/linters.md @@ -22,14 +22,15 @@ page with its behavior, configuration, and examples. ### Files / Formats -| Name | Linter | Formatter | -| -------- | ------------------------------------- | ----------------------------------------- | -| JSON | [`biome`](linters/biome.md) | [`biome-format`](linters/biome-format.md) | -| Markdown | [`rumdl`](linters/rumdl.md) | [`rumdl`](linters/rumdl.md) | -| Shell | [`shellcheck`](linters/shellcheck.md) | [`shfmt`](linters/shfmt.md) | -| TOML | — | [`taplo`](linters/taplo.md) | -| XML | [`xmllint`](linters/xmllint.md) | — | -| YAML | [`ryl`](linters/ryl.md) | [`ryl`](linters/ryl.md) | +| Name | Linter | Formatter | +| -------- | ------------------------------------------- | ------------------------------------------- | +| Dotenv | [`dotenv-linter`](linters/dotenv-linter.md) | [`dotenv-linter`](linters/dotenv-linter.md) | +| JSON | [`biome`](linters/biome.md) | [`biome-format`](linters/biome-format.md) | +| Markdown | [`rumdl`](linters/rumdl.md) | [`rumdl`](linters/rumdl.md) | +| Shell | [`shellcheck`](linters/shellcheck.md) | [`shfmt`](linters/shfmt.md) | +| TOML | — | [`taplo`](linters/taplo.md) | +| XML | [`xmllint`](linters/xmllint.md) | — | +| YAML | [`ryl`](linters/ryl.md) | [`ryl`](linters/ryl.md) | ### Tooling / CI diff --git a/docs/linters/dotenv-linter.md b/docs/linters/dotenv-linter.md new file mode 100644 index 00000000..4833acaf --- /dev/null +++ b/docs/linters/dotenv-linter.md @@ -0,0 +1,32 @@ +# `dotenv-linter` + + + + +| | | +| -------- | --------------------------------------------------------------- | +| Project | [dotenv-linter](https://github.com/dotenv-linter/dotenv-linter) | +| Fix | yes | +| Binary | `dotenv-linter` | +| Scope | [files](../linters.md#scope-files) | +| Patterns | `.env .env.* *.env` | + + + +`dotenv-linter` checks and safely formats dotenv environment files without +printing their values. + +Flint checks only explicit `.env`-style files: `.env`, `.env.*`, and files +ending in `.env`. It passes those file paths rather than a directory, so +unrelated YAML, Compose, and application configuration files are never scanned. + +Both check and fix mode disable dotenv-linter's update check, avoiding +unexpected network access. Fix mode also uses `--no-backup`, so it does not +leave secret-bearing backup files behind: + +```bash +flint run --fix dotenv-linter +``` + +The fixer is serialized with other Flint formatters that may own the same file. +Do not commit secret-bearing `.env` files. diff --git a/mise.toml b/mise.toml index d774b8d2..423491db 100644 --- a/mise.toml +++ b/mise.toml @@ -13,6 +13,7 @@ actionlint = "1.7.12" "aqua:jonwiggins/xmloxide" = "0.4.3" "aqua:owenlamont/ryl" = "0.21.0" biome = "2.5.4" +dotenv-linter = "4.0.0" editorconfig-checker = "3.8.0" golangci-lint = "2.12.2" google-java-format = "1.35.0" diff --git a/src/main.rs b/src/main.rs index 353016a1..95d7ead1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1361,6 +1361,7 @@ zizmor zizmor active fast yes Audit Gi hadolint hadolint missing fast no Lint Dockerfiles Dockerfile Dockerfile.* *.dockerfile xmllint xmllint missing fast no Validate XML files are well-formed *.xml typos typos active fast yes Check for common spelling mistakes * +dotenv-linter dotenv-linter missing fast yes Lint dotenv environment files without printing their values .env .env.* *.env editorconfig-checker ec active fast no Check files comply with EditorConfig settings * golangci-lint golangci-lint missing fast no Lint Go code; uses --new-from-rev to scope analysis to changed code *.go ruff ruff active fast yes Lint Python code *.py diff --git a/src/registry/checks.rs b/src/registry/checks.rs index abbbafa0..09220879 100644 --- a/src/registry/checks.rs +++ b/src/registry/checks.rs @@ -26,6 +26,8 @@ const GOFMT_URL: &str = "https://pkg.go.dev/cmd/gofmt"; const GOLANGCI_LINT_URL: &str = "https://golangci-lint.run/"; const GOLANGCI_LINT_CONFIG_URL: &str = "https://golangci-lint.run/usage/configuration/"; const GOOGLE_JAVA_FORMAT_URL: &str = "https://github.com/google/google-java-format"; +const DOTENV_LINTER_URL: &str = "https://github.com/dotenv-linter/dotenv-linter"; +const DOTENV_LINTER_CONFIG_URL: &str = "https://dotenv-linter.github.io/"; const HADOLINT_URL: &str = "https://github.com/hadolint/hadolint"; const HADOLINT_CONFIG_URL: &str = "https://github.com/hadolint/hadolint?tab=readme-ov-file#configure"; @@ -357,6 +359,27 @@ fn check_typos() -> Check { .mise_tool("typos") } +fn check_dotenv_linter() -> Check { + Check::files( + "dotenv-linter", + "dotenv-linter check --plain --skip-updates {FILES}", + &[".env", ".env.*", "*.env"], + ) + .fix("dotenv-linter fix --plain --skip-updates --no-backup {FILES}") + .mise_tool("dotenv-linter") + .project_url(DOTENV_LINTER_URL) + .config_doc_url(DOTENV_LINTER_CONFIG_URL) + .overview( + OverviewSection::FilesFormats, + "Dotenv", + OverviewRole::Both, + Some("Environment-file syntax and consistency"), + ) + .desc("Lint dotenv environment files without printing their values") + .formatter() + .style() +} + fn check_editorconfig_checker() -> Check { // Defer to formatters that enforce line length — those are the ones // that conflict with ec's max_line_length editorconfig check. @@ -713,6 +736,7 @@ pub fn builtin() -> Vec { check_hadolint(), check_xmllint(), check_typos(), + check_dotenv_linter(), check_editorconfig_checker(), check_golangci_lint(), check_ruff(), diff --git a/tests/cases/dotenv-linter/auto-fix/files/.env b/tests/cases/dotenv-linter/auto-fix/files/.env new file mode 100644 index 00000000..b4088bb7 --- /dev/null +++ b/tests/cases/dotenv-linter/auto-fix/files/.env @@ -0,0 +1 @@ +bad line diff --git a/tests/cases/dotenv-linter/auto-fix/files/mise.toml b/tests/cases/dotenv-linter/auto-fix/files/mise.toml new file mode 100644 index 00000000..d96574ef --- /dev/null +++ b/tests/cases/dotenv-linter/auto-fix/files/mise.toml @@ -0,0 +1,2 @@ +[tools] +"dotenv-linter" = "4.0.0" diff --git a/tests/cases/dotenv-linter/auto-fix/test.toml b/tests/cases/dotenv-linter/auto-fix/test.toml new file mode 100644 index 00000000..2eb53985 --- /dev/null +++ b/tests/cases/dotenv-linter/auto-fix/test.toml @@ -0,0 +1,20 @@ +[expected] +args = "run --full --fix dotenv-linter" +exit = 1 +stderr = ''' +flint: fixed: dotenv-linter — commit before pushing +''' + +[expected.files] +".env" = "PORT=8080\n" + +[fake_bins] +dotenv-linter = ''' +#!/bin/sh +set -eu +case "$1" in + check) exit 1 ;; + fix) printf 'PORT=8080\n' > .env ;; + *) exit 1 ;; +esac +''' diff --git a/tests/cases/dotenv-linter/clean/files/.env b/tests/cases/dotenv-linter/clean/files/.env new file mode 100644 index 00000000..701b0b0b --- /dev/null +++ b/tests/cases/dotenv-linter/clean/files/.env @@ -0,0 +1,2 @@ +NAME=flint +PORT=8080 diff --git a/tests/cases/dotenv-linter/clean/files/mise.toml b/tests/cases/dotenv-linter/clean/files/mise.toml new file mode 100644 index 00000000..d96574ef --- /dev/null +++ b/tests/cases/dotenv-linter/clean/files/mise.toml @@ -0,0 +1,2 @@ +[tools] +"dotenv-linter" = "4.0.0" diff --git a/tests/cases/dotenv-linter/clean/test.toml b/tests/cases/dotenv-linter/clean/test.toml new file mode 100644 index 00000000..4c25aa9d --- /dev/null +++ b/tests/cases/dotenv-linter/clean/test.toml @@ -0,0 +1,3 @@ +[expected] +args = "run --full dotenv-linter" +exit = 0 From d4fdb07fb365d58495dd80c5f3677ce640a5c719 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 27 Jul 2026 18:14:55 +0200 Subject: [PATCH 2/2] Update docs/linters/dotenv-linter.md Co-authored-by: Martin Costello --- docs/linters/dotenv-linter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/linters/dotenv-linter.md b/docs/linters/dotenv-linter.md index b379d919..6199e447 100644 --- a/docs/linters/dotenv-linter.md +++ b/docs/linters/dotenv-linter.md @@ -17,7 +17,7 @@ printing their values. > [!WARNING] -> Do not commit secret-bearing `.env` files. +> Do not commit `.env` files containing secrets to git. Flint checks only explicit `.env`-style files: `.env`, `.env.*`, and files ending in `.env`. It passes those file paths rather than a directory, so