dotguard
Validate .env files against a documented .env.example, and keep real secrets out of git.
Almost every project has a .env.example that says which environment variables the app needs.
Almost every project also has that file drift out of date: a new variable gets added to the code
but not to the example, someone sets PORT=80O0, or a real API key slips into the example and
gets committed.
dotguard turns .env.example into a lightweight, human-readable schema and checks your
real .env against it, locally, in a pre-commit hook or in CI.
$ dotguard check --env examples/.env --example examples/.env.example
examples/.env:3 error DG004 PORT=80O0: expected a port number between 1 and 65535
examples/.env:4 error DG007 APP_ENV=prod is not one of: development, staging, production
examples/.env:6 error DG006 WORKERS=64 is above the @max of 32
examples/.env:7 error DG004 BASE_URL=localhost:8080: expected a URL with a scheme and host, e.g. https://example.com
examples/.env error DG001 DATABASE_URL is missing (documented in examples/.env.example:32)
examples/.env:8 error DG003 PAYMENTS_API_TOKEN is required but empty
examples/.env:10 warning DG008 VERBOSE is deprecated: use LOG_LEVEL=debug instead
examples/.env:11 warning DG002 OLD_FEATURE_FLAG is not documented in examples/.env.example
6 error(s), 2 warning(s)
Table of contents
- Why dotguard?
- Install
- Quick start
- Annotating
.env.example - Commands
- Output formats and exit codes
- Using dotguard in CI
- Using it from Python
.envsyntax supported- Roadmap
- Contributing
- License
Why dotguard?
| Zero dependencies | Pure Python standard library. Nothing to audit, nothing to break. |
| No new config file | The schema lives as comments in the .env.example you already have. |
| Catches real mistakes | Missing keys, typos, wrong types, values outside an allowed list or range. |
| Secret scanning | Flags real-looking AWS, GitHub, Slack, Stripe, Google and OpenAI-style keys, private keys and JWTs in files you commit. |
| Never leaks values | Keys marked @secret are printed as **** in every message and CI log. |
| CI-friendly | Stable rule codes, JSON output, GitHub Actions annotations, clear exit codes. |
Install
From PyPI (the package is called dotguard-cli; the command is dotguard):
pip install dotguard-cli
Or the latest code straight from GitHub:
pip install git+https://github.com/K70281095/dotguard.git
Or run it from a clone without installing:
git clone https://github.com/K70281095/dotguard.git
cd dotguard
PYTHONPATH=src python3 -m dotguard --help
Requires Python 3.9 or newer.
Quick start
-
Already have a
.envbut no example? Generate one. Sensitive-looking keys get@secretand every value is blanked, so the result is safe to commit:dotguard init # writes .env.example from .env dotguard init --keep-values # keep non-secret values as defaults
-
Document the rules by adding annotation comments (see below).
-
Check your environment:
dotguard check -
Make sure nobody commits a real secret in the example:
dotguard scan .env.example
A full, working example lives in examples/.
Annotating .env.example
Write annotations as comments directly above a key. Ordinary comments become the key's documentation. A blank line ends the block.
# Port the HTTP server listens on.
# @type port
# @required
PORT=8080
# Which deployment this is.
# @values development, staging, production
APP_ENV=development
# Number of background workers.
# @type int
# @min 1
# @max 32
WORKERS=4
# Postgres connection string.
# @type url
# @secret
DATABASE_URL=
# Sentry DSN. Leave empty to disable error reporting.
# @optional
SENTRY_DSN=
# @deprecated use LOG_LEVEL instead
# @optional
VERBOSE=
| Annotation | Meaning |
|---|---|
@type T |
Value must be of type T: string (default), int, float, bool, port, url, email. |
@required |
Value must not be empty. |
@optional |
Key may be absent from .env. Without it, every documented key must be present. |
@pattern REGEX |
Value must fully match the Python regular expression. |
@values a, b, c |
Value must be one of the listed choices. |
@min N / @max N |
Inclusive bounds for numeric types. |
@secret |
Never print the value; also makes scan treat the key as sensitive. |
@deprecated [reason] |
Warn when the key is still set. |
Full reference: docs/annotations.md. Every rule code (DG001…) is
explained in docs/rules.md.
Commands
dotguard check
dotguard check [--env .env] [--example .env.example] [--strict]
[--format text|json|github] [--fail-on-warning]
Checks every key documented in the example against the env file. With --strict, keys that
exist in .env but not in the example are errors instead of warnings, which helps keep the
example complete.
dotguard scan
dotguard scan [FILES ...] [--format text|json|github] [--fail-on-warning]
Scans files you intend to commit (default: .env.example) for:
- DS001 (error): well-known credential formats (AWS, GitHub, Slack, Stripe live keys, Google API keys, OpenAI-style keys, PEM private keys, JWTs).
- DS002 (warning): long, high-entropy values under sensitive-looking names
(
*_TOKEN,*_SECRET,*PASSWORD*,*API_KEY*, …) or keys marked@secret.
Obvious placeholders such as changeme, <your-token>, xxxx and ${VAR} are ignored.
dotguard diff
dotguard diff LEFT RIGHT [--values] [--format text|json|github] [--fail-on-warning]
Compares two environment files, for example staging and production, and reports keys that
exist in only one of them (DD001, DD002). With --values it also lists keys whose values
differ (DD003, warning). Values themselves are never printed, so the output is safe to paste
into a ticket or a CI log.
$ dotguard diff .env.staging .env.production --values
.env.staging:4 error DD001 FEATURE_X is set in .env.staging but missing from .env.production
.env.staging:2 warning DD003 DATABASE_URL has a different value in .env.production:2
dotguard init
dotguard init [--env .env] [--output .env.example] [--keep-values] [--force]
Creates a starter .env.example from an existing .env, guessing @type for ports, numbers,
booleans, URLs and emails and marking sensitive keys @secret. Secret values are never copied,
even with --keep-values. It refuses to overwrite an existing file unless you pass --force.
Output formats and exit codes
--format |
Use it for |
|---|---|
text (default) |
People. Colored when writing to a terminal; respects NO_COLOR and FORCE_COLOR. |
json |
Other tools. {"issues": [...], "summary": {"errors": N, "warnings": N}} |
github |
GitHub Actions. Issues appear as inline annotations on the pull request. |
| Exit code | Meaning |
|---|---|
0 |
No errors (warnings allowed unless --fail-on-warning). |
1 |
Problems found. |
2 |
Usage error: a file is missing or cannot be parsed. |
Using dotguard in CI
GitHub Actions
This repository is also a composite action:
- uses: actions/checkout@v5
- uses: K70281095/dotguard@v0.2.0
with:
command: scan
args: .env.example
Or install and run it yourself:
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- run: pip install git+https://github.com/K70281095/dotguard.git
- run: dotguard scan .env.example --format github
pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/K70281095/dotguard
rev: v0.2.0
hooks:
- id: dotguard-scan
More recipes, including GitLab CI and a plain git hook: docs/ci.md.
Using it from Python
import dotguard
env = dotguard.parse_file(".env")
example = dotguard.parse_file(".env.example")
for issue in dotguard.check(env, example, strict=True):
print(issue.code, issue.severity.value, issue.message)
for issue in dotguard.scan(example):
print("possible secret:", issue.key)
.env syntax supported
# comments on their own line
KEY=value
export KEY=value # "export" prefix is allowed
KEY=value # trailing comment # needs whitespace before "#"
URL=https://example.com/#anchor # "#" inside a value is kept
KEY="double \"quoted\"\nwith escapes" # \n \t \r \" \\ \$
KEY='single quoted, taken literally, even $HOME'
EMPTY=
# Quoted values can span several lines (handy for certificates):
CERT="-----BEGIN CERTIFICATE-----
MIIB...
-----END CERTIFICATE-----"
# References to keys defined earlier in the same file:
HOST=localhost
PORT=5432
DATABASE_URL=postgres://${HOST}:${PORT}/app # also $HOST
LOG_DIR=${LOG_ROOT:-/var/log}/app # default if unset or empty
LOG_FILE=${LOG_NAME-app.log} # default only if unset
PRICE="costs \$5" # \$ keeps a literal dollar sign
References work in unquoted and double-quoted values, never in single-quoted ones. A reference to
a key that is not defined above it expands to an empty string and check warns about it
(DG011). Values are never read from your shell's environment, so results are the same on every
machine.
Duplicate keys and malformed lines are reported with their file and line number.
Roadmap
-
${VAR}interpolation when checking values - Multi-line quoted values
- Publish to PyPI (as
dotguard-cli) -
dotguard diffto compare two environments (e.g. staging vs production keys) - Optional JSON Schema export
Ideas and pull requests are welcome. See the issues.
Contributing
Contributions of every size are welcome: bug reports, docs fixes, new secret patterns, new
@types. Please read CONTRIBUTING.md and our
Code of Conduct. To report a vulnerability, see SECURITY.md.
License
MIT © 2026 Kanhaiya Kumar
Metadata
Release files for dotguard-cli 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| dotguard_cli-0.2.0.tar.gz | 25.4 kB | With provenance | Details for dotguard_cli-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| dotguard_cli-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for dotguard_cli-0.2.0-py3-none-any.whl |
Total release size: 45.1 kB
Release files / dotguard_cli-0.2.0.tar.gz
| Download URL | dotguard_cli-0.2.0.tar.gz |
|---|---|
| Size | 25.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
30f2fd217453d37a3cc26c412006543dc36f9f6bf9a74c125a07fb9b12fde368
|
|
BLAKE2b-256 checksum How to use checksums |
28b3b7f1c532edf42443e9b088dc153fce660dbd94e8f23e9912270802563594
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logRelease files / dotguard_cli-0.2.0-py3-none-any.whl
| Download URL | dotguard_cli-0.2.0-py3-none-any.whl |
|---|---|
| Size | 19.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6d7be91b3c372de0803174919bc85b2fb15cef7cc92cf1d4ef21d7299b226392
|
|
BLAKE2b-256 checksum How to use checksums |
fada1789df815fd733f57d8ef2b004765e43cafa3485fcf112de7a70a10bedd2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logSecurity for dotguard-cli 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for dotguard_cli-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.