Skip to main content

dotguard

Validate .env files against a documented .env.example, and keep real secrets out of git.

CI Python Dependencies License: MIT

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?

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

  1. Already have a .env but no example? Generate one. Sensitive-looking keys get @secret and 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
    
  2. Document the rules by adding annotation comments (see below).

  3. Check your environment:

    dotguard check
    
  4. 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 diff to 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)

Source distribution for dotguard-cli 0.2.0
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)

Table of built distributions (wheels) for dotguard-cli 0.2.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page