Skip to main content

Scan codebases for environment variables, detect undocumented vars, and generate .env.example files

Project description

envsniff

envsniff

Stars Last Commit License Python

Scan codebases for environment variables, detect undocumented vars, and generate .env.example files — with or without AI-written descriptions.

No more "what env vars do I need to run this?" — envsniff finds them all and documents them for you.

envsniff usage demo


Table of Contents


Why envsniff

Most teams document environment variables manually — and it's always out of date. envsniff fixes this by reading the code directly.

Tool Gap
dotenv-linter Lints .env files only — does not scan source code
sync-dotenv Syncs .env.env.example, requires an existing .env
detect-secrets Security-focused; finds secrets, not documentation
env-checker Runtime validation, not a scanner

envsniff is the only tool that scans your source code, finds every os.getenv() / process.env.X / os.Getenv() call, and generates a documented .env.example from scratch.


Architecture

src/envsniff/
├── models.py           # Immutable dataclasses (SourceLocation, EnvVarFinding, ScanResult)
├── errors.py           # Exception hierarchy
├── config.py           # .envsniff.toml / pyproject.toml loader
├── scanner/
│   ├── engine.py       # Orchestrates walk → dispatch → deduplicate
│   ├── file_walker.py  # .gitignore-aware recursive walk (pathspec)
│   ├── registry.py     # Maps file extensions to plugins
│   ├── type_inferrer.py
│   └── plugins/        # One file per language
├── env_example/
│   ├── parser.py       # Reads existing .env.example preserving structure
│   ├── merger.py       # new / stale / existing classification
│   └── writer.py       # Atomic write (temp → rename)
├── describer/
│   ├── types.py        # Name → InferredType rules
│   ├── fallback.py     # Heuristic descriptions (no API needed)
│   ├── cache.py        # SHA-256 keyed JSON cache
│   └── ai.py           # Multi-provider AI descriptions (Anthropic, OpenAI, Gemini, Ollama)
├── hooks/
│   ├── precommit.py    # Staged-files-only scan
│   └── ci.py           # Full scan with JSON output
└── cli/
    ├── main.py         # Click commands: scan / generate / check
    └── formatters.py   # table / json / markdown renderers

How it works

flowchart TD
    A[Your codebase] --> B[File Walker]
    B -->|respects .gitignore| C[Scanner Registry]
    C --> D1[Python plugin\nos.getenv / os.environ]
    C --> D2[JavaScript plugin\nprocess.env.X]
    C --> D3[Go plugin\nos.Getenv]
    C --> D4[Shell plugin\n$VAR / $VARNAME]
    C --> D5[Docker plugin\nENV / ARG]
    D1 & D2 & D3 & D4 & D5 --> E[Scanner Engine\ndeduplication + type inference]
    E --> F{Command}
    F -->|scan| G[Formatted output\ntable / json / markdown]
    F -->|generate| H[.env.example Parser]
    H --> I[Merger\nnew / stale / existing]
    I --> J[AI Describer\noptional]
    J --> K[Atomic Writer\n.env.example]
    F -->|check| L{All vars documented?}
    L -->|yes| M[exit 0 ✓]
    L -->|no| N[exit 1 ✗\nlist undocumented vars]

The merge strategy

When running envsniff generate, existing human-written comments are always preserved:

Var state Action
In code + in .env.example Keep as-is (preserve human description)
In code, missing from .env.example Add with # Added by envsniff
In .env.example, not in code Comment out with # UNUSED (not found in codebase):

Install

macOS / Linux

pip install envsniff

Windows

pip install envsniff

Via npm / npx (any OS)

npx envsniff scan .

Local development (after cloning)

# macOS / Linux
git clone https://github.com/harish124/envsniff.git
cd envsniff
pip install -e .

# Windows
git clone https://github.com/harish124/envsniff.git
cd envsniff
pip install -e .

The -e flag installs in editable mode — changes to the source take effect immediately without reinstalling.


Usage

scan

Find all environment variables in a codebase:

envsniff scan .
envsniff scan ./src --format json
envsniff scan . --format md
envsniff scan . --exclude "tests/*" --exclude "*.sh"

Example output:

┌──────────────────┬─────────┬──────────┬─────────┬─────────────────┐
│ Name             │ Type    │ Required │ Default │ Locations       │
├──────────────────┼─────────┼──────────┼─────────┼─────────────────┤
│ DATABASE_URL     │ URL     │ yes      │ —       │ app.py:12       │
│ API_KEY          │ SECRET  │ yes      │ —       │ client.py:8     │
│ DEBUG            │ BOOLEAN │ no       │ false   │ settings.py:3   │
│ PORT             │ INTEGER │ no       │ 8080    │ server.py:1     │
└──────────────────┴─────────┴──────────┴─────────┴─────────────────┘
Scanned 42 files · Found 4 variables

generate

Create or update .env.example:

envsniff generate .
envsniff generate . --output .env.example
envsniff generate . --ai          # prompts for provider + model interactively
envsniff generate . --no-ai       # skip AI, use heuristic descriptions
envsniff generate . --ai --ai-provider openai --ai-model gpt-4o   # non-interactive

Example .env.example output:

# Added by envsniff
#
# Description: PostgreSQL/MySQL connection string
# Example:     postgres://user:pass@localhost/dbname
#
DATABASE_URL=

# Added by envsniff
#
# Description: Api API key
#
API_KEY=

# Added by envsniff
#
# Description: Enable debug mode (true/false)
# Example:     false
#
DEBUG=false

# Added by envsniff
#
# Description: Port number the server listens on
# Example:     8080
#
PORT=8080

Heuristic descriptions are always generated — no --ai flag needed. The # Example: line is omitted when no example value is known.

check

Use in CI or pre-commit to enforce documentation:

envsniff check .                  # exit 1 if undocumented vars exist
envsniff check . --fail-on-stale  # also exit 1 if stale vars in .env.example
envsniff check . --strict         # fail on any issue

Supported languages

Python JavaScript TypeScript Go Shell Docker

Language Patterns detected
Python os.getenv("X"), os.environ.get("X"), os.environ["X"]
JavaScript / TypeScript process.env.X, process.env["X"]
Go os.Getenv("X"), os.LookupEnv("X")
Shell $VAR, ${VAR} — skips local variables (VAR=value without export), shell specials ($$, $?, $1$9)
Dockerfile ENV VAR=value, ARG VAR=default

Technologies

Python Click tree--sitter Anthropic OpenAI Google Gemini Ollama

Library Purpose
Click CLI framework
tree-sitter AST-based source code parsing per language
questionary Interactive terminal prompts with arrow-key navigation
pathspec .gitignore-aware file walking
anthropic Anthropic Claude AI descriptions
openai OpenAI / Grok / Perplexity AI descriptions
google-generativeai Google Gemini AI descriptions
ollama Local Ollama AI descriptions

AI descriptions

All AI providers are bundled with envsniff — no extra install step. Pass --ai and you will be prompted to choose a provider and model interactively:

$ envsniff generate . --ai

? Which AI provider?
  1. Anthropic (Claude)
❯ 2. OpenAI (GPT)
  3. Google Gemini
  4. Ollama (local)

  Tip: check the official openai documentation for available models.
? Which model? (e.g. gpt-4o-mini): gpt-4o

Use arrow keys to select a provider, Enter to confirm. Model name is required — leaving it blank will error.

Or skip the prompts by passing flags directly (useful for CI):

envsniff generate . --ai --ai-provider openai --ai-model gpt-4o

Set the API key for your chosen provider before running:

Provider API key env var Notes
Anthropic ANTHROPIC_API_KEY Default provider
OpenAI OPENAI_API_KEY Also works for Grok, Perplexity via OPENAI_BASE_URL
Gemini GEMINI_API_KEY
Ollama Local, no key needed
  • Batches up to 20 vars per API call
  • Caches results in ~/.cache/envsniff/descriptions.json — API is never called twice for the same var
  • Falls back to heuristic descriptions if the provider is unavailable

Privacy

envsniff scrubs code snippets before sending anything to the AI provider. Default values (the second argument in calls like os.environ.get("KEY", "value")) are stripped using regex so only the variable name and call structure are transmitted:

# What's in your code:
db = os.environ.get("DATABASE_URL", "postgres://user:secret@prod/db")

# What the AI receives:
os.environ.get("DATABASE_URL")

What is never sent: .env file contents, actual secret values, or any string literals used as defaults.

Disclaimer — known limitation: The scrubbing works on single-line calls. If a default value spans multiple lines, the regex cannot reliably detect it and that line may be sent as-is:

# Multi-line call — default value on its own line may not be scrubbed:
url = os.environ.get(
    "DATABASE_URL",
    "postgres://user:secret@prod/db"   # ← this line could be sent
)

This is an inherent limitation of line-by-line text scrubbing. The fix would require full AST-level analysis, which is not currently implemented. To be safe, avoid hardcoding real secrets as default values in your source code — use placeholder strings like "postgres://localhost/mydb" instead.


Pre-commit hook

Add to .pre-commit-config.yaml:

repos:
  - repo: https://github.com/harish124/envsniff
    rev: v0.1.0
    hooks:
      - id: envsniff-check

This blocks commits that introduce undocumented environment variables.


GitHub Action

Add envsniff to any repository in seconds. On every pull request it scans your code, updates .env.example, and commits the result automatically — no manual step needed.

# .github/workflows/envsniff.yml
name: Sync .env.example

on:
  pull_request:
    branches: [main, master]

jobs:
  envsniff:
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }}
          fetch-depth: 0

      - uses: harish124/envsniff@v1
        with:
          path: "."
          commit: "true" # auto-commit updated .env.example
          fail-on-drift: "false" # set "true" to block merges on undocumented vars
          commit-message: "chore: sync .env.example [skip ci]"

Action inputs

Input Default Description
path . Directory to scan
commit true Commit updated .env.example back to the PR branch
fail-on-drift false Block the merge when new undocumented variables are found
commit-message chore: sync .env.example Commit message for the auto-commit
python-version 3.12 Python version used to install envsniff

Action outputs

Output Description
new-vars Comma-separated list of newly found variables not yet in .env.example
stale-vars Comma-separated list of variables in .env.example no longer in code
scanned-files Number of source files scanned
drift-detected true when new undocumented variables were found

Requires: Repository Settings → Actions → General → Workflow permissions → Read and write permissions

A ready-to-copy workflow file is available at .github/examples/envsniff.yml.


CI integration

# .github/workflows/ci.yml
- name: Check env vars are documented
  run: envsniff check . --strict

For machine-readable output in CI logs:

envsniff check . --format json
status: fail
new_vars[1]: NEW_SECRET
stale_vars[0]:
scanned_files: 42

Configuration

Create .envsniff.toml in your project root (or add [tool.envsniff] to pyproject.toml):

[tool.envsniff]
exclude = ["tests/*", "scripts/*", "*.sh"]
output = ".env.example"
ai = false
ai_provider = "anthropic"   # anthropic | openai | gemini | ollama
ai_model = ""               # leave empty to be prompted, or set a specific model

Output formats

scan and check support --format table (default), --format json, and --format md.

JSON — for scripting and CI:

findings[1]:
  - name: DATABASE_URL
    inferred_type: URL
    is_required: true
    default_value: null
    language: python
    locations[1]{file,line}:
      app.py,12
scanned_files: 42
errors[0]:

Markdown — for documentation:

| Name         | Type    | Required | Default |
| ------------ | ------- | -------- | ------- |
| DATABASE_URL | URL     | yes      | —       |
| DEBUG        | BOOLEAN | no       | false   |

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

envsniff-0.1.0.tar.gz (40.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

envsniff-0.1.0-py3-none-any.whl (50.7 kB view details)

Uploaded Python 3

File details

Details for the file envsniff-0.1.0.tar.gz.

File metadata

  • Download URL: envsniff-0.1.0.tar.gz
  • Upload date:
  • Size: 40.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.14.3 Darwin/24.6.0

File hashes

Hashes for envsniff-0.1.0.tar.gz
Algorithm Hash digest
SHA256 84140c994446e29acb860d4a3afa391c60b9ed65fa77bbe01de24730cbfafdbd
MD5 c7ab350b76e4c2fdb29400cf4de4756c
BLAKE2b-256 2776aa2caed4525c6b67a9fd21d634ab620b86a0bfaaac0a05878d5258b5ced4

See more details on using hashes here.

File details

Details for the file envsniff-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: envsniff-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 50.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.14.3 Darwin/24.6.0

File hashes

Hashes for envsniff-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f7af56ddb1db6b32a9a1250a294a5cd6b3c37c6a1b0246a1e1de805633034b92
MD5 0d9057b991af6829518268b46ba21917
BLAKE2b-256 65bbb0192499bff14c7fb1817929060e2309f6ebdd6843e8a20c245f47d7a05a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page