joho/godotenv
A Go port of Ruby's dotenv library (Loads environment variables from .env files)
Healthy across all four use cases
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 7mo ago
- ✓32+ active contributors
- ✓Distributed ownership (top contributor 46% of recent commits)
Show all 7 evidence items →Show less
- ✓MIT licensed
- ✓CI configured
- ⚠Slowing — last commit 7mo ago
- ⚠No test directory detected
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/joho/godotenv)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/joho/godotenv on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: joho/godotenv
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/joho/godotenv shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 7mo ago
- 32+ active contributors
- Distributed ownership (top contributor 46% of recent commits)
- MIT licensed
- CI configured
- ⚠ Slowing — last commit 7mo ago
- ⚠ No test directory detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live joho/godotenv
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/joho/godotenv.
What it runs against: a local clone of joho/godotenv — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in joho/godotenv | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 228 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of joho/godotenv. If you don't
# have one yet, run these first:
#
# git clone https://github.com/joho/godotenv.git
# cd godotenv
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of joho/godotenv and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "joho/godotenv(\\.git)?\\b" \\
&& ok "origin remote is joho/godotenv" \\
|| miss "origin remote is not joho/godotenv (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "godotenv.go" \\
&& ok "godotenv.go" \\
|| miss "missing critical file: godotenv.go"
test -f "parser.go" \\
&& ok "parser.go" \\
|| miss "missing critical file: parser.go"
test -f "autoload/autoload.go" \\
&& ok "autoload/autoload.go" \\
|| miss "missing critical file: autoload/autoload.go"
test -f "cmd/godotenv/cmd.go" \\
&& ok "cmd/godotenv/cmd.go" \\
|| miss "missing critical file: cmd/godotenv/cmd.go"
test -f "godotenv_test.go" \\
&& ok "godotenv_test.go" \\
|| miss "missing critical file: godotenv_test.go"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 228 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~198d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/joho/godotenv"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
godotenv is a Go library that loads environment variables from .env files into the process environment at runtime, porting Ruby's dotenv behavior to Go. It parses KEY=VALUE syntax with support for comments, exports, multiline values, and variable substitution, letting developers manage configuration without hardcoding or setting OS-level env vars manually. Simple flat structure: godotenv.go and parser.go contain the core parsing logic and public API (Load, Read, Parse functions); godotenv_test.go alongside for tests; fixtures/ directory holds 8 .env test files covering edge cases; cmd/godotenv/cmd.go provides a CLI wrapper; autoload/autoload.go enables import-time auto-loading via init().
👥Who it's for
Go backend developers building 12-factor apps who need to load development/staging configuration from .env files without manually exporting shell variables; also DevOps engineers who want a CLI tool (cmd/godotenv) to load env files in CI/CD pipelines.
🌱Maturity & risk
Production-ready. The project has comprehensive test coverage (godotenv_test.go, fixtures/ with 8 different env file scenarios), active CI/CD via GitHub Actions (ci.yml, codeql-analysis.yml, release.yml), and targets Go 1.12+. No signs of abandonment but updates are infrequent—typical of a stable, feature-complete utility rather than an actively developing framework.
Low risk. Zero external dependencies (go.mod shows only the module itself), single-maintainer model (joho), but the codebase is small (~30KB of Go code) with straightforward logic. Main risk: if critical security issue discovered in .env parsing logic, response time depends on maintainer availability. Breaking changes unlikely given stable API surface.
Active areas of work
No specific recent activity visible in provided data. The project appears stable with no active PRs or milestone work mentioned. Updates likely occur reactively (bug reports, Go version compatibility) rather than through planned feature releases.
🚀Get running
git clone https://github.com/joho/godotenv && cd godotenv && go test ./... to run the full test suite. For CLI: go install ./cmd/godotenv or go install github.com/joho/godotenv/cmd/godotenv@latest (Go 1.17+).
Daily commands: go test ./... runs the test suite. go test -v ./... shows individual test cases. For the CLI: godotenv -f .env to load .env into the current shell (requires it to be eval'd) or use it in scripts.
🗺️Map of the codebase
godotenv.go— Core library entry point with Load, Read, and Unmarshal functions that define the public API for loading .env filesparser.go— Parsing logic for .env file syntax including variable expansion, quoting, and comment handling—essential for correct environment variable loadingautoload/autoload.go— Automatic loading of .env files during package initialization; critical for understanding zero-config usage patternscmd/godotenv/cmd.go— CLI entry point showing how the library is used as a standalone binary tool to load and execute commands with env varsgodotenv_test.go— Comprehensive test suite covering parsing, loading, and edge cases; reference for expected behavior and contribution patternsgo.mod— Minimal dependency declaration (Go 1.12+) showing the project's low external dependency footprint
🧩Components & responsibilities
- godotenv.Load / godotenv.Read (Go stdlib os, io) — Orchestrates loading and parsing of .env files; delegates parsing to parser.go and applies results to os.Environ
- Failure mode: Returns error if file not found, unreadable, or contains unparseable syntax; partial loads may succeed silently
- parser.go (Parse function) (Go strings, regexp) — Tokenizes .env file content, handles escaping, quotes, comments, and variable expansion; returns map[string]string
- Failure mode: Returns error on malformed syntax (e.g., unclosed quotes); incorrect variable substitution if ${VAR} references are cyclic or undefined
- autoload/autoload.go (Go init functions) — Automatic init-time loading; convenience wrapper that calls godotenv.Load without explicit user code
- Failure mode: Silently skips if .env not found; errors during load are logged but do not halt program startup
- cmd/godotenv CLI (Go flag, os.Exec) — Parses command-line arguments, loads .env, and spawns a subprocess with the loaded environment
- Failure mode: Returns non-zero exit code if .env load fails or child process fails; argument parsing errors print usage
🔀Data flow
.env file→os.ReadFile in godotenv.go— Raw file bytes read from diskRaw file bytes→parser.go Parse function— Content tokenized and parsed into key-value pairs with variable expansionParsed key-value map→os.Setenv in godotenv.go— Each key-value pair written into the process environmentProcess environment→Application code via os.Getenv— Application accesses environment variables normallyProcess environment→Child process via cmd/godotenv— CLI tool spawns subprocess inheriting loaded environment variables
🛠️How to make changes
Add support for a new .env syntax feature
- Define test cases in godotenv_test.go with fixture files in fixtures/ (
godotenv_test.go) - Add parsing logic to handle the new syntax (e.g., new quote type or operator) (
parser.go) - Update the Load or Read function if needed to expose new behavior (
godotenv.go) - Create a new fixture file to document the feature (
fixtures/yourfeature.env)
Extend the CLI with a new flag or command
- Add CLI argument parsing and logic (
cmd/godotenv/cmd.go) - Call existing godotenv.Load or godotenv.Read with the new parameter (
godotenv.go) - Document behavior with test cases if needed (
godotenv_test.go)
Add a new convenience loading mode
- Implement helper function in godotenv.go (e.g., LoadRequire, LoadWithDefaults) (
godotenv.go) - Optionally export from autoload/autoload.go for zero-config usage (
autoload/autoload.go) - Add test cases and fixture files (
godotenv_test.go)
🔧Why these technologies
- Go 1.12+ — Minimal language version enables broad compatibility while using standard library file I/O and environment APIs
- Standard library only (no external dependencies) — Keeps the library lightweight, zero-dependency, and easy to vendor; file I/O and env manipulation are core stdlib features
- Custom hand-written parser — Dotenv syntax is simple enough to parse without a formal parser generator; custom parser reduces bloat and gives full control over variable expansion semantics
⚖️Trade-offs already made
-
Single-pass parsing without AST
- Why: Minimizes memory footprint and parsing latency for typical .env files (usually <100KB)
- Consequence: Limited ability to detect complex syntax errors early or preserve source formatting; less extensible for future syntax additions
-
All variables set into process os.Environ() immediately
- Why: Simplest integration with Go standard library and shell-like semantics; matches Ruby dotenv behavior
- Consequence: No isolation or transaction support; side effects are immediate and hard to undo; not suitable for multi-tenant or sandboxed scenarios
-
No schema validation or type coercion
- Why: Environment variables are inherently strings; minimal assumptions keep the library general-purpose
- Consequence: Applications must parse and validate values themselves; no built-in defaults or type safety
🚫Non-goals (don't propose these)
- Does not provide Windows binary support guarantees (README explicitly disclaims this)
- Does not support hot-reloading or watching .env file changes
- Does not provide authentication or encryption for sensitive values in .env files
- Does not offer variable scoping or isolation beyond OS environment
- Does not handle hierarchical or profile-based environment configuration (e.g., .env.dev, .env.prod merging strategies)
⚠️Anti-patterns to avoid
- Silent failure on partial load (Medium) —
godotenv.go (Load function): If some variables fail to set but others succeed, no indication to caller which variables were actually loaded; can lead to subtle bugs where code assumes all vars are present - No cycle detection in variable expansion (Medium) —
parser.go: If .env contains cyclic variable references (e.g., FOO=${BAR}, BAR=${FOO}), parser may enter infinite loop or return incomplete values - Global state mutation (Low) —
godotenv.go, autoload/autoload.go: Load functions directly modify os.Environ() with no rollback or transaction support; unsuitable for unit tests that need isolation without manual cleanup
🪤Traps & gotchas
No hidden traps in typical usage. One non-obvious detail: Load() mutates os.Environ() directly via os.Setenv(), so tests must be isolated to avoid state leakage between test cases. The autoload package silently fails if .env doesn't exist (by design), so missing .env files in production won't panic but may cause uninitialized variables. Variable substitution (e.g., FOO=${BAR}) only resolves variables already in the environment, not those defined earlier in the same .env file.
🏗️Architecture
💡Concepts to learn
- .env file format / dotenv specification — Understanding the de facto standard syntax (KEY=VALUE, comments, exports, quoting rules) is essential to understanding what parser.go is trying to parse and why fixtures/ look the way they do
- State machine parsing — parser.go uses a character-by-character state machine to handle quoted strings, escapes, and line-end detection; knowing this pattern helps debug why certain .env syntax is unsupported
- Environment variable substitution / variable expansion — godotenv supports FOO=${BAR} syntax to reference other env vars; requires understanding scoping rules (only existing env vars resolve, not earlier .env lines by default)
- Blank imports / side effects in Go init() — The autoload/ package uses import _ to trigger code at startup with no explicit API call; crucial for understanding how 'magic' autoloading works
- os.Environ() mutation and test isolation — godotenv calls os.Setenv() which mutates the process-global environment; tests must avoid pollution between cases or use t.Setenv() (Go 1.17+)
- io.Reader interface and stream parsing — Parse() accepts io.Reader, letting godotenv work with files, HTTP responses, or strings; demonstrates Go's composition-via-interface pattern
🔗Related repos
bkeepers/dotenv— The original Ruby dotenv library that godotenv ports; reference implementation for expected .env syntax and behaviorkelseyhightower/envconfig— Alternative Go approach: instead of .env files, maps struct tags to environment variables; complementary to godotenv for config managementspf13/viper— Go config management framework supporting .env files plus YAML/JSON/TOML; heavier alternative to godotenv if you need multi-format supportcaarlos0/env— Lightweight Go library for parsing env vars into structs via tags; often used downstream of godotenv to deserialize loaded varsjoho/godotenv-sublime— Editor plugin by same author to syntax-highlight .env files in Sublime Text
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive test coverage for parser.go edge cases
The parser.go file handles the critical logic for parsing .env files, but the test fixtures (fixtures/*.env) suggest incomplete coverage. The repo has fixtures for comments, equals, exported, quoted, substitutions, and invalid cases, but godotenv_test.go likely lacks specific unit tests for malformed input, edge cases like empty values, multiline handling, and parser error scenarios. This would catch regressions and improve robustness.
- [ ] Review parser.go to identify untested code paths (edge cases like escaped characters, edge quotes, nested quotes)
- [ ] Add fixtures for missing cases (e.g., fixtures/multiline.env, fixtures/edge-cases.env)
- [ ] Extend godotenv_test.go with table-driven tests for each parser edge case and error condition
- [ ] Ensure error handling paths in parser.go are fully covered with negative test cases
Add benchmark tests for parser performance
As a library frequently used in application startup paths, parser performance matters. The repo lacks _test.go benchmark functions (BenchmarkParse*, BenchmarkLoad*). Adding benchmarks would help contributors identify performance regressions and allow the maintainer to make informed optimization decisions, especially as the parser complexity grows.
- [ ] Create fixtures/large.env with a realistic large .env file (1000+ variables)
- [ ] Add benchmark functions in godotenv_test.go (BenchmarkLoad, BenchmarkParse, BenchmarkSubstitutions)
- [ ] Run benchmarks locally to establish baseline performance metrics
- [ ] Document expected performance characteristics in README.md
Add CLI integration tests and expand cmd/godotenv functionality validation
The cmd/godotenv binary exists but there are no visible integration tests validating CLI behavior. The repo should test that the command-line tool correctly loads .env files, handles errors, supports flags, and works end-to-end. This is especially important since the CLI is mentioned as a primary use case in the README.
- [ ] Review cmd/godotenv/cmd.go to document all supported CLI flags and behaviors
- [ ] Create integration test file (cmd/godotenv/cmd_test.go or similar) with TestMain setup
- [ ] Add test cases for: loading valid .env, handling missing files, parsing errors, and passing variables to executed commands
- [ ] Update CI workflow (.github/workflows/ci.yml) to run CLI tests separately from library tests if needed
🌿Good first issues
- Add integration test for Windows .env line endings (CRLF vs LF). Fixtures currently tested on Unix; add a fixtures/windows.env with CRLF and verify parser.go handles it correctly.
- Document the autoload package behavior in README with a warning section: clarify that import _ "godotenv/autoload" silently does nothing if .env is missing, and explain error handling expectations.
- Add parser.go test coverage for edge case: variable substitution with missing env var (e.g., FOO=${UNDEFINED}). Current fixtures/ may not cover all substitution failure modes.
⭐Top contributors
Click to expand
Top contributors
- @joho — 46 commits
- @alexquick — 10 commits
- @mniak — 5 commits
- [@Takumasa Sakao](https://github.com/Takumasa Sakao) — 4 commits
- @dependabot[bot] — 3 commits
📝Recent commits
Click to expand
Recent commits
a2be92d— Fix typo inhasQuotePrefixreturn variable (#251) (parkerbxyz)3a7a190— fix: if a line contains multiple # characters, there will be issues w… (#238) (astak16)a7f6c4c— Re-add global env variable substitution (#227) (Envek)32e64fa— chore: fix typo (#231) (ALX99)7765d9d— Fix panic because of wrong function (#223) (lotusirous)383d64c— Update cmd.go (#221) (Adamsbite)e3b6eee— Bump actions/setup-go from 3 to 4 (#207) (dependabot[bot])193c9ab— Add whitespace tests. (#210) (cjyar)3fc4292— Fix bug where internal unquoted whitespace truncates values (#205) (joho)b311b26— Fix: ioutil.ReadAll() is deprecated, so removed it's dependency (#202) (dreygur)
🔒Security observations
The godotenv library is generally well-designed for its purpose, with no critical vulnerabilities identified in the static analysis. However, there are several medium and low-severity concerns related to input validation, injection risks, and best practices for handling environment variables. The primary risks lie in how consuming applications use this library rather than in the library itself. Key recommendations include: (1) strengthening input validation in the parser, (2) adding secret scanning to the CI pipeline, (3) documenting security best practices for users, and (4) protecting against symlink and path traversal attacks. The library would benefit from explicit security guidelines in the README about safe usage patterns.
- Medium · Potential Environment Variable Injection via .env File Parsing —
godotenv.go, parser.go. The godotenv library parses .env files and loads variables into the environment. If an application uses user-controlled input to construct .env file paths or content without validation, it could lead to injection attacks where malicious environment variables override critical settings. Fix: Validate and sanitize any user-controlled input used for .env file path construction. Implement strict file path validation to prevent directory traversal attacks. Document best practices for safe usage. - Medium · No Input Validation on Environment Variable Values —
parser.go. The parser.go file likely processes raw values from .env files without validation. Malformed or specially crafted .env files could potentially cause unexpected behavior if values are not properly escaped when used in shell contexts or SQL queries by consuming applications. Fix: Add comprehensive input validation and sanitization. Document that values loaded from .env files should be treated as untrusted input by consuming applications. Provide clear warnings about safe value usage patterns. - Low · Fixture Files May Contain Example Secrets —
fixtures/. Test fixture files (fixtures/*.env) may contain example credentials or sensitive values that could be accidentally committed with real data if developers copy-paste from fixtures into actual .env files. Fix: Ensure all fixture files contain only clearly marked dummy/example values. Add prominent comments in fixture files indicating they are for testing only. Consider using a naming convention like 'EXAMPLE_' prefix for all fixture variables. - Low · No Built-in Secret Scanning in CI Pipeline —
.github/workflows/. While the repository has CI workflows configured (.github/workflows/ci.yml, codeql-analysis.yml), there is no indication of secret scanning tools to detect accidentally committed credentials in .env files or test fixtures. Fix: Integrate secret scanning tools (e.g., git-secrets, TruffleHog, or native GitHub secret scanning) into the CI/CD pipeline. Add pre-commit hooks to prevent .env files with real secrets from being committed. - Low · Potential Symlink Attack via File Loading —
godotenv.go. If the Load/LoadFile functions don't validate against symlink attacks, a malicious symlink could be used to read arbitrary files on the system when loading .env files. Fix: Verify that file operations follow symlink-safe patterns. Use filepath.EvalSymlinks to detect and reject symlinks, or use appropriate file opening modes that prevent symlink traversal. Document security considerations for file path handling.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.