npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, πŸ‘‹, I’m Ryan HefnerΒ  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you πŸ™

Β© 2026 – Pkg Stats / Ryan Hefner

@dotenc/cli

v0.15.2

Published

πŸ” Git-native encrypted environments powered by your SSH keys

Readme

dotenc β€” animated terminal demo showing a Node.js app, dotenc env edit development in nano, and dotenc dev injecting the encrypted greeting

NPM Version Github License NPM Downloads CI Codecov

πŸ” Git-native encrypted environments powered by your SSH keys

Features

  • πŸ”’ Uses the battle-tested AES-256-GCM encryption algorithm
  • πŸ”‘ Uses your existing SSH keys - no extra key management
  • πŸš€ Secure command running with on-the-fly decryption
  • ✍️ Easy and secure environment variable editing
  • 🌍 Supports multiple and extensible environments
  • πŸ‘€ Personal encrypted environments per developer
  • πŸ”„ Automatic data key rotation on edits and access changes
  • πŸ” Readable local Git diffs and redacted pull-request reviews
  • πŸ›‘οΈ Supports both RSA and Ed25519 SSH keys
  • πŸ€– Ready for the AI era β€” check out the official dotenc skill

Getting Started

dotenc init --name alice  # pick your SSH key and create personal.alice
dotenc env edit personal.alice  # add your personal secrets
dotenc dev npm start   # run with your encrypted env

Encrypted .env.personal.alice.enc committed. No external services. Uses your existing SSH keys. Done.

Installation

curl -fsSL https://dotenc.org/install.sh | sh

The installer selects a supported package manager for your system. To review the script first or install manually with APT, RPM, APK, AUR, Homebrew, Scoop, npm, a standalone binary, or an OCI image, see the installation guide.

Table of Contents

Why?

Managing secrets and environment variables is critical for any modern application, but most solutions rely on third-party services and web dashboards. dotenc was created to solve these problems:

  • No Vendor Lock-In: Your secrets and keys live in your codebase and your repository. You're never tied to a third-party provider or forced to migrate if pricing or policies change.
  • Improved Security: Eliminate the risk of exposing secrets to external services. All encryption and decryption happen locally, and private keys never leave your machine.
  • Zero Key Management: You already have SSH keys. dotenc uses them directly - no custom key generation, no extra files cluttering your home directory, no new workflows to learn.
  • Better Developer Experience: No more juggling environment variables in a web UI or struggling to keep them in sync across branches. Everything is managed alongside your code, with simple CLI commands and full Git integration.
  • Seamless Collaboration: Onboard or revoke team members with a single command. Grant or remove access per environment, and let Git handle the rest.
  • Reviewable in Git: Grants and revocations change tracked files, so normal repository review and history show access changes under your Git hosting and branch-protection policy.
  • PR-Safe Environment Changes: Environment variable updates live in the same pull request as your feature code. No more "merge β†’ broken build β†’ patch env β†’ rebuild" workflow.

Security Model

  • Each environment has its own randomly generated 256-bit data key.
  • Data keys are encrypted per-user using their SSH public key.
  • dotenc uses AES-256-GCM for authenticated encryption.
  • Your repository alone is not enough to decrypt secrets.
  • Access can be revoked at any time.

AES-GCM detects changes to a ciphertext under its data key; it does not prove who authored a complete replacement envelope. Repository writers are trusted, with Git permissions, review, and history providing the authorship layer.

For a detailed breakdown of the cryptographic design, key material handling, threat model, and vulnerability reporting, see SECURITY.md.

How It Works

  1. dotenc detects your existing SSH keys in ~/.ssh/ (Ed25519 or RSA);
  2. Your public key is derived and stored in the project (.dotenc/john.pub);
  3. A unique data key is generated for each environment;
  4. The data key is encrypted with each authorized public key;
  5. Environment variables are encrypted using the data key with AES-256-GCM;
  6. Encrypted files (.env.*.enc) are committed to your repository;
  7. When running commands, variables are decrypted on-the-fly using your SSH private key.

By default, filesystem SSH private keys stay in ~/.ssh/ and dotenc reads them in place. A persistent passwordless or 1Password-backed local copy is created only through the explicitly confirmed opt-in flows documented below; provider keys otherwise enter dotenc process memory only for the current operation.

Project Structure

After setup, your project will look like:

.
β”œβ”€β”€ .dotenc/
β”‚   β”œβ”€β”€ alice.pub
β”‚   β”œβ”€β”€ bob.pub
β”‚   └── ...
β”œβ”€β”€ .env.personal.alice.enc
β”œβ”€β”€ .env.production.enc
└── .env.development.enc

Encrypted files are committed to Git. Public keys are stored inside .dotenc/. Each developer can have a personal encrypted environment in the reserved personal.<profile> namespace (for example, .env.personal.alice.enc). Public-key filenames are display aliases only; profile access is matched by key fingerprint.

Readable local Git diffs

dotenc β€” animated terminal demo showing an encrypted environment rendered as a readable local Git diff

How does Git diff work?

Seamlessly. dotenc connects encrypted environment files to Git's native diff pipeline. When an authorized developer runs git diff, dotenc decrypts both revisions locally with their SSH key and shows the change that mattersβ€”a variable added, removed, or updatedβ€”instead of a wall of regenerated ciphertext.

Only encrypted .env.*.enc files are stored and committed. The readable view exists only on the developer's machine, so local reviews stay clear without a manual decrypt-and-re-encrypt workflowβ€”even though every edit rotates the data key and rewrites the ciphertext.

dotenc init configures this automatically. Run it once in every clone. When it detects an existing dotenc project, it configures the clone-local Git driver without changing keys, environments, or access rules.

The driver is limited to .env.*.enc, and clone-local textconv caching is disabled so Git does not persist decrypted output. dotenc init currently writes the bare command dotenc textconv, so Git resolves dotenc from your machine's PATH. Pinning a stable installed executable path is deferred; until then, local executable resolution remains part of the trusted developer-machine boundary.

Optionally, you can also enable redacted diffs in pull requests.

Basic Usage

Setup

dotenc init

In a new project, this interactively guides you through the setup process:

  1. Scanning your ~/.ssh/ directory for SSH keys (Ed25519, RSA, etc.);
  2. Prompting for your username (defaults to your system username);
  3. Letting you choose which SSH key to use;
  4. Deriving the public key and storing it in .dotenc/ (e.g., .dotenc/alice.pub);
  5. Creating encrypted development and personal environments (for example, .env.development.enc and .env.personal.alice.enc).

In an existing clone, dotenc init only enables the local Git diff integration. It does not prompt for an identity or recreate keys and environments, so it is safe to run after every clone.

No keys to generate. If you already have an SSH key (and you probably do), you're ready to go.

If you don't have an SSH key yet, just run ssh-keygen first - you'll want one anyway.

Creating a new environment

dotenc env create [environment]

This command creates a new encrypted environment file under the specified name (e.g., .env.development.enc). Your personal.<profile> environment is created automatically during init. Environment names may contain letters, numbers, dots (.), hyphens (-), and underscores (_).

New and rewritten environments use format version 2, which binds the logical environment name to the ciphertext. Existing version 1 envelopes remain readable, but renaming a version 2 file requires decrypting and re-encrypting it under the new name.

In a monorepo, cd to the target directory first, then run dotenc env create.

Renaming an environment

dotenc env rename <source> <destination> [--all-layers] [--yes]

This decrypts and re-encrypts the content under the destination name rather than moving the encrypted file. The default targets the current directory; --all-layers targets the effective ancestor chain. See the personal-profile migration guidance below for its safety and recovery semantics.

Listing environments

dotenc env list

Lists encrypted environments in the current directory. Use --all to recursively list all environments across the project tree. Use --json for machine-readable output ({ "environments": [...] }).

Editing an environment

dotenc env edit [environment]

Opens your system's default editor to modify the specified environment. To set a custom editor, use the dotenc config editor command. It will take precedence over your system's default editor. In a monorepo, cd to the directory containing the file before running dotenc env edit.

Example:

dotenc config editor vim

Currently supported dotenc config key: editor. You can include editor arguments, for example: dotenc config editor "code --wait". The configured editor is trusted local input. Shell metacharacters are rejected and the command is launched without a shell, but editor-specific arguments can still enable editor-native behavior. On POSIX, dotenc enforces mode 0700 on ~/.dotenc/ and 0600 on its config.json when reading or writing it. On Windows, home-configuration persistence fails closed because the runtime cannot safely prevent reparse-point replacement; use EDITOR or VISUAL, with notepad as the platform fallback.

Run commands on an environment

For development, the dev command always loads the required shared development environment and, when available, one accessible personal.<profile> overlay:

dotenc dev <command> [...args]

Example:

dotenc dev node app.js

Personal profiles are discovered only along the effective ancestor chain (or only in the current directory with --local-only). Access is tested by the recipient fingerprint against your available private keys; .dotenc/*.pub filenames are human-readable aliases and do not choose a namespaced personal.<profile> profile.

  • One accessible profile is selected automatically.
  • Several accessible profiles prompt in an interactive terminal. In non-interactive use, pass --profile <name>.
  • --profile alice means the personal.alice environment.
  • No personal environment is a healthy state: dev runs with development only.
  • A requested missing/inaccessible profile, or discovered profiles with no accessible candidate, warns and continues with development only. Add --strict to make that personal-profile failure fatal.
  • development is always required, even without --strict.
dotenc dev --profile alice bun run start
dotenc dev --profile alice --strict bun run start

The personal.* namespace is a breaking replacement for legacy personal environments named directly after a key alias. When no namespaced personal profile is selected, dotenc dev may print a read-only warning about an accessible environment that matches the former convention. The warning is only a migration hint: dev never auto-loads or modifies the candidate, because an unprefixed environment may be legitimate. This legacy hint requires the exact .dotenc/<name>.pub alias to exist and validate, its fingerprint to be a recipient in every effective .env.<name>.enc layer, and every layer to decrypt and authenticate successfully. For example, this remains valid:

dotenc run -e alice bun run test

Migrate a personal environment in the current directory explicitly:

dotenc env rename alice personal.alice
dotenc dev --profile alice bun run test

For a profile layered from the project root through the current directory, migrate every source layer in that effective chain with one reviewed operation:

dotenc env rename alice personal.alice --all-layers

Use --all-layers from a nested directory even when the only source is a single ancestor layer; the default command looks only in the current directory.

The command shows the source and destination paths and asks for confirmation. A non-interactive caller must pass --yes; that flag skips only the prompt, not validation, exclusive destination creation, or verification. Without --all-layers, env rename follows other single-environment commands and acts only in the current directory. --all-layers does not scan unrelated sibling packages.

This is a cryptographic rename, not a filesystem move. dotenc decrypts the source, emits a version 2 destination using personal.alice as AAD and a fresh ciphertext IV, and preserves the recipient entries exactly. Every destination inode is created inside a private same-parent quarantine, published without overwrite by a hard link, and parsed and decrypted under its new name. Its private recovery link remains until source cleanup is complete. Before deleting any source inode, dotenc moves every source into a private same-parent quarantine, verifies its identity, content, and permissions, and re-verifies all destinations. Before removing each destination recovery link, dotenc proves the live path still names the verified inode or restores that inode exclusively. A live source path recreated concurrently is never overwritten or deleted by cleanup.

For --all-layers, all destinations are verified before source cleanup starts, but multiple filesystem operations cannot be atomic across directories. A write or verification failure keeps every source and attempts to remove any destinations created by that invocation. If source cleanup fails partway, all verified destinations remain, already removed sources stay removed, and the remaining sources stay at their old or reported quarantine paths; the command exits non-zero and lists the state. If an unexpected object blocks exclusive restoration, or quarantine restoration cannot be proven, the affected quarantine path is reported and every verified destination is retained as a recovery copy. If a source vanishes before quarantine, its unresolved path is reported and the verified destination is kept. If an exclusive destination restore is blocked, its private recovery path is kept and reported. Review git diff, then either remove the remaining legacy sources to finish, or restore tracked sources from Git and remove the new destinations to roll back. A failed best-effort destination cleanup can leave both names and must be resolved before retrying because the destination is never overwritten. Git cannot restore an untracked source; after partial cleanup, its verified destination is the remaining encrypted copy.

For explicit environment control, use run:

dotenc run --env <environment> <command> [...args]
# or
dotenc run -e <environment> <command> [...args]

Example:

dotenc run -e production node app.js

You can also specify multiple environments:

dotenc run -e base,production node app.js

In the example above, production will override any variables also present in base.

If you want run to fail when any selected environment cannot be loaded, use strict mode:

dotenc run --strict -e base,production node app.js

Before spawning the command, dotenc rejects decrypted variables that can alter executable resolution, runtime loaders, shell startup, or GitHub Actions control files. It prints variable names only, never values. All decrypted DOTENC_* names and the GitHub control-file names are reserved and cannot be overridden. For an exceptional trusted workflow, allow one non-reserved process variable at a time with a repeatable flag:

dotenc run -e test --allow-process-env NODE_OPTIONS node app.js
dotenc dev --allow-process-env NODE_OPTIONS npm start

The exemption is exact and has no wildcard form. Bare commands are resolved against the original parent PATH before decrypted values are merged, so an allowed decrypted PATH cannot redirect the initial executable. The child never receives DOTENC_PRIVATE_KEY_BASE64, legacy DOTENC_PRIVATE_KEY, DOTENC_PRIVATE_KEY_PASSPHRASE, or DOTENC_ENV from either source.

In a monorepo, run merges environment files from the project root down to the current directory (local values win). To load only from the current directory and skip ancestor directories:

dotenc run --local-only -e staging node app.js
dotenc dev --local-only npm start

Checking your identity

dotenc whoami

Shows your name, active SSH key, fingerprint, and the environments you have access to in this project.

Tooling and Maintenance

CLI updates

dotenc update

Runs the appropriate update flow for your installation method (Homebrew, Scoop, npm, or manual binary instructions).

Integration helpers

dotenc tools install-vscode-extension
dotenc tools install-agent-skill
dotenc tools install-agent-skill --force
dotenc tools install-github-diffs \
  --environment .env.production.enc
# Add --allow-fork only when the target repository is itself a fork.
  • install-vscode-extension adds extension recommendations for supported editors and can open the extension page.
  • install-agent-skill installs the dotenc agent skill through the pinned [email protected] runner via Bun. The source is an immutable GitHub archive at dotenc/skills@dc3245191988923fced07c63b31df8184a1d1853.
  • Bun's bun executable must be available on PATH; the installer invokes its bun x package runner directly, and standalone dotenc binaries do not bundle Bun. --force maps to non-interactive mode (-y) for automation.
  • install-github-diffs creates a dedicated, least-privilege GitHub identity, grants it only to explicitly selected environments (or --all), uploads its private key without writing it to disk, and generates the immutable-SHA-pinned redacted pull-request diff workflow. It requires authenticated gh, clean tracked selected environments, and --allow-fork to acknowledge installation when the target repository is itself a fork. Fork pull requests can expose the documented equality-oracle signal. See the GitHub Actions runbook.

Team Collaboration

In a real-world scenario, you will likely have multiple environments (e.g., development, test, production) and a team of developers who need access to these environments. Let's walk through how to set this up.

Granting access to a new team member

Alice just joined your team and she needs access to the shared environments, except production. She already has an SSH key (because of course she does - she's a developer).

She sends you her public key (~/.ssh/id_ed25519.pub) β€” it's a public key, so Slack, email, or even a sticky note will do β€” and you grant her access:

git checkout -b grant-alice-key
dotenc key add alice --from-file alice.pub
dotenc auth grant development alice
dotenc auth grant test alice
git add .
git commit -m "Grant alice access to development and test environments"
git push

Now, Alice will be able to decrypt the development and test environments using her SSH key. No new tools for her to install, no custom keys to generate - just her existing SSH key.

Revoking access from a team member

To revoke a team member's key from every environment in the current repository state (e.g., John), use auth purge:

dotenc auth purge john --yes

This resolves John's alias to its key fingerprint, preflights every environment, revokes and re-encrypts every affected environment, verifies the fingerprint is gone everywhere, and only then removes every .pub alias for that same key. Then, commit your changes:

git checkout -b offboard-john
git add .
git commit -m "Offboard John from all environments"
git push origin offboard-john

Once merged, John's private key can no longer decrypt the current environment files. auth purge does not rewrite Git history or erase existing clones, so that key may still decrypt historical ciphertext addressed to it. Complete offboarding also requires removing repository access and rotating every external secret. If policy requires removing historical ciphertext, rewrite or purge the retained history and require fresh clones, while assuming old clones, mirrors, backups, and other copies remain readable.

If you only want to remove the key file without revoking environment access, use key remove:

dotenc key remove john

This removes the .pub file only. Access to encrypted environments is left intact until you run auth purge.

Listing access

dotenc auth list [environment]

Lists all public keys that have access to the specified environment.

Offboarding a Team Member

Use auth purge to revoke a team member's key from every environment in the current repository state and remove its public-key aliases in one operation:

dotenc auth purge <user> [--yes]

This command:

  1. Resolves the named public-key alias to its fingerprint
  2. Validates and pre-decrypts every affected environment before making changes
  3. Revokes that fingerprint and re-encrypts each affected environment
  4. Rescans the project and deletes all .pub aliases for that fingerprint only after complete verification

Unreadable environments, a zero-recipient result, a failed rewrite, or a failed rescan make the command exit non-zero and retain the public key for a safe retry. Some environments may already have been rewritten after a mid-operation failure; the command never reports that state as successful.

This command does not rewrite Git history or erase existing clones. A holder of the purged private key may still decrypt historical ciphertext that names that key as a recipient.

After running auth purge, also:

  • Remove the teammate's repository and organization access
  • Rotate external secrets (database passwords, API tokens, etc.); this remains necessary because retained copies cannot be recalled
  • Deploy updated configuration
  • If policy requires removing historical ciphertext, rewrite or purge retained Git history and require fresh clones, while treating old clones, mirrors, backups, and other copies as still readable

Individual environment management

dotenc env delete [environment] [--yes]

Deletes an environment file in the current directory. In a monorepo, cd to the directory containing the file first.

dotenc env rotate [environment]

Rotates the data key for a single environment in the current directory. In a monorepo, cd to the directory containing the file first.

dotenc env rotate --all [--yes]

Rotates the data key for all environments in one step β€” recursively discovers and rotates every .env.*.enc file under the project tree. Useful for periodic key rotation or after a security event.

CI/CD Integration

CI runners and build servers need their own identity to decrypt environments. The approach is the same as local development: generate a key, grant access, and provide the private key at runtime.

1. Generate a dedicated CI key

Create an Ed25519 key pair for your CI environment. This example creates a passwordless key; if you choose a passphrase-protected key, also set DOTENC_PRIVATE_KEY_PASSPHRASE in your CI provider:

ssh-keygen -t ed25519 -f ci_key -N "" -C "ci"

This produces two files: ci_key (private) and ci_key.pub (public).

2. Add the key and grant access

Register the public key in the project and grant it access to the environments CI needs:

dotenc key add ci --from-ssh ./ci_key
dotenc auth grant test ci
dotenc auth grant production ci
git add .
git commit -m "Add CI key and grant access to test and production"
git push

3. Set the private key in your CI provider

Base64-encode the private key file and store the result as a secret environment variable named DOTENC_PRIVATE_KEY_BASE64 in your CI provider:

base64 < ci_key | tr -d '\n'

DOTENC_PRIVATE_KEY with the raw private key text is still supported for backwards compatibility, but new provider setup should use DOTENC_PRIVATE_KEY_BASE64.

If this private key is passphrase-protected, also set:

DOTENC_PRIVATE_KEY_PASSPHRASE=<your-passphrase>

Once stored, delete the local private key file:

rm ci_key

The public key (ci_key.pub) can also be deleted β€” it's already tracked inside .dotenc/ci.pub.

4. Use dotenc in your CI pipeline

With DOTENC_PRIVATE_KEY_BASE64 set (and DOTENC_PRIVATE_KEY_PASSPHRASE when using an encrypted key), dotenc will automatically pick up the key. No ~/.ssh directory required:

dotenc run -e test npm test
dotenc run -e production node app.js

GitHub Actions example

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 24
      - run: npm ci
      - uses: dotenc/setup-action@v1
      - uses: dotenc/run-action@v1
        with:
          environment: test
          command: npm test
        env:
          DOTENC_PRIVATE_KEY_BASE64: ${{ secrets.DOTENC_PRIVATE_KEY_BASE64 }}
          DOTENC_PRIVATE_KEY_PASSPHRASE: ${{ secrets.DOTENC_PRIVATE_KEY_PASSPHRASE }}

Reusable GitHub Actions

dotenc also ships reusable GitHub Actions for common CI patterns:

  • dotenc/setup-action@v1 installs the CLI.
  • dotenc/run-action@v1 runs one command under dotenc run --strict.
  • dotenc/export-action@v1 exports an explicit allowlist to later steps through $GITHUB_ENV.
  • dotenc/write-file-action@v1 writes one decrypted value to a 0600 file for file-shaped credentials.
  • dotenc/diff-action@v1 produces redacted semantic pull-request diffs. Its privileged automatic workflow must use the hardened full-SHA-pinned setup generated by dotenc tools install-github-diffs, not the moving wrapper ref.

This lets GitHub keep only the dotenc bootstrap secret(s) in GitHub while provider tokens live in an encrypted dotenc environment for jobs that actually run on GitHub. Store the bootstrap key as DOTENC_PRIVATE_KEY_BASE64. If the bootstrap key is encrypted, also store DOTENC_PRIVATE_KEY_PASSPHRASE. See docs/GITHUB_ACTIONS.md for examples.

The redacted diff workflow has its own narrowly granted identity stored as DOTENC_DIFF_PRIVATE_KEY_BASE64; do not reuse or replace the ordinary CI bootstrap identity for it.

Redacted pull-request diffs

Encrypted files are opaque in GitHub's native diff, so dotenc can add a redacted semantic report to each pull request. Install the hardened workflow with a dedicated, narrowly granted identity:

dotenc tools install-github-diffs \
  --environment .env.production.enc

The report intentionally exposes variable names, recipient changes, and a changed/unchanged equality signal. Install it only when that disclosure is acceptable for every pull-request author. Repositories that GitHub reports as forks require an explicit --allow-fork acknowledgement.

The action posts or updates one comment that looks like this:

dotenc environment diff

production

Environment modified Β· .env.production.enc

Variables

~ DATABASE_URL
+ OPENAI_API_KEY
- LEGACY_TOKEN

Access

~ ci-old β†’ ci-new
+ ci-production
- contractor-john

development

Data key rotated Β· .env.development.enc

+ means added or granted, - means removed or revoked, and ~ means changed or renamed. A data-key-only rotation verified through the dedicated diff recipient gets the compact entry shown above. Formatting-only edits and same-key ciphertext churn publish no report; with comments enabled, the action removes its stale bot-owned report comments. Reports contain variable names and access metadata, never variable values.

The generated workflow is pinned to an immutable action commit and never checks out pull-request code into the privileged reporting job. See the GitHub Actions runbook for the security boundary, fork policy, and manual setup.

Provider runbooks

Some CI/CD providers need extra setup because build steps, environment resolution, or secret storage work differently from a generic shell pipeline.

  • GitHub Actions β€” use a dedicated GitHub Actions key, then run commands, export allowlisted variables, or write file-shaped credentials from encrypted dotenc environments.
  • Expo / EAS β€” choose one lean path: EAS cloud builds with an EAS dotenc identity, or GitHub local builds with a GitHub dotenc identity.
  • Vercel β€” use a dedicated Vercel dotenc identity for Vercel-owned cloud builds, then wrap the Vercel build command.
  • Netlify β€” use a dedicated Netlify dotenc identity for Netlify-owned cloud builds, then wrap the Netlify build command.
  • Cloudflare β€” choose the trust boundary for Pages Git builds, Pages Direct Upload, Workers Builds, or external Wrangler deploys.
  • Coolify β€” add dotenc to the application's existing Dockerfile, Railpack, or Nixpacks image, then keep decryption at runtime when possible.
  • Railpack β€” compose the CLI image as a generic build or deploy layer without replacing the auto-detected application runtime.
  • Nixpacks β€” support existing Nixpacks deployments with a pinned standalone binary; prefer Railpack for new generated-image deployments.
  • Provider helpers roadmap β€” track planned presets, provider plugins, provider CLIs, and the criteria for revisiting builder images.

Key Management

dotenc keeps key management minimal by design. Your SSH keys are your identity - dotenc just uses them.

Existing filesystem private keys stay in ~/.ssh/ and are read in place. Public keys are stored in your project's .dotenc/ folder, derived from the corresponding private keys.

1Password SSH keys (experimental)

When the 1Password CLI 2.x (op) is installed and authenticated, dotenc can use SSH Key items without additional configuration. dotenc init and interactive dotenc key add show local keys immediately. Choose Use a key from 1Password (experimental) under Actions to load public key metadata; a temporary loading group is then replaced by account groups backed by stable account IDs, so accounts and duplicate item titles remain unambiguous. For scripts, --private-key and --from-private-key accept a qualified 1password:<account-id>:<vault-id>:<item-id> selector and load its provider metadata directly.

After a 1Password key is selected or successfully used, dotenc stores a disposable machine-local locator under ~/.cache/dotenc (or the platform cache directory). It contains only the public-key fingerprint and opaque account, vault, and item IDsβ€”never key material, names, account URLs, or project paths. Later commands can go directly to one fingerprint-verified op read instead of rescanning every account and SSH item. Invalid or mismatched entries are evicted; transient CLI, timeout, and authorization failures preserve the locator and fail the current operation without starting a full provider scan.

After an interactive 1Password selection, dotenc also offers to save an unencrypted private-key copy in ~/.ssh. The default remains locator-only. If you explicitly opt in, dotenc fingerprint-verifies the retrieved key, chooses a non-conflicting id_<algorithm>_1password_<fingerprint> filename, and writes it with mode 0600. This avoids future 1Password authorization and provider latency, but expands private-key exposure from 1Password-managed memory access to a persistent local file.

Local keys keep priority during decryption. If none matches an environment, local commands such as run, dev, environment edit/decrypt, and access rotation can ask op for one fingerprint-matched private key. 1Password may show its native authorization dialog at that point. After op read returns, the key remains only in the dotenc process: it is not written to disk, persisted in project files, or forwarded to a wrapped command or its environment. dotenc whoami likewise consults 1Password only when no project identity matches a local key.

Git textconv may use an already cached locator and show the same authorization dialog, but it never performs full 1Password discovery. Without a valid cached locator it immediately preserves the encrypted diff content.

See 1Password SSH key connector for the full behavior and security boundaries.

Supported Key Types

dotenc supports the following SSH key types:

  • Ed25519
  • RSA (2048-bit or larger)

These types are widely supported and provide strong security guarantees.

Note: dotenc can use passphrase-protected SSH keys when DOTENC_PRIVATE_KEY_PASSPHRASE is set. In interactive flows (dotenc init and interactive dotenc key add), selecting a passphrase-protected key also offers an optional passwordless copy flow (for example id_ed25519_passwordless). If you prefer a dedicated passwordless key, you can generate one with:

ssh-keygen -t ed25519 -N ""

Adding a public key

dotenc key add [name] [--from-private-key <name-or-selector>] [--from-ssh <path>] [-f, --from-file <file>] [-s, --from-string <pem_string>]

Adds a public key into the project (.dotenc/<name>.pub).

  • --from-ssh <path> β€” Derive the public key from an SSH key file (private or public). Supports both Ed25519 and RSA keys.
  • --from-private-key <name-or-selector> β€” Choose a discovered private-key candidate by name or qualified selector.
  • -f, --from-file <file> β€” Read a public (or private) key from a PEM file.
  • -s, --from-string <pem_string> β€” Use a PEM string directly.
  • No arguments β€” Interactive mode: choose a discovered SSH key, create a local key, or paste a PEM public key.
  • Key names may contain letters, numbers, dots (.), hyphens (-), and underscores (_).

Listing public keys

dotenc key list

Lists all public keys in the project, showing each key's name and algorithm.

Removing a public key

dotenc key remove [name]

Removes the public key file from the project (.dotenc/<name>.pub). Does not revoke environment access. To fully offboard a key β€” revoking access from all environments and deleting the file β€” use auth purge instead.

Monorepo Usage

dotenc works in monorepos out of the box. Run dotenc init once at the repository root to create a shared .dotenc/ folder and a root-level environment. Subdirectory packages can then have their own .env.*.enc files that overlay the root.

How environment loading works:

  • dotenc run walks from the project root to the current directory, loading and merging each requested environment. dotenc dev does the same for required development and its selected accessible personal.<profile>. Local values override root values.
  • Use --local-only to load only the current directory's environments, skipping ancestor layers.

Creating, editing, rotating, and deleting environments:

All env write commands operate on the current directory. cd to the target directory first:

# Create an environment in a package subdirectory
cd packages/web
dotenc env create staging

# Edit, rotate, or delete that same file
dotenc env edit staging
dotenc env rotate staging
dotenc env delete staging

Listing environments:

dotenc env list        # list environments in the current directory
dotenc env list --all  # recursively list all environments in the project

Batch operations cover the whole tree:

dotenc env rotate --all --yes  # rotate all .env.*.enc files recursively
dotenc auth purge alice --yes  # revoke alice from all envs in the project

Tips

For convenience, you can setup your package.json file like this:

  // ...
  "scripts": {
    "dev": "dotenc dev tsx src/app.ts",
    "start": "dotenc run -e production node dist/app.js",
    "test": "dotenc run -e test vitest"
  }

Alternatively, the DOTENC_ENV variable can be used to set the environment, so the -e option can be omitted. For example:

  export DOTENC_ENV="production"
  dotenc run node app.js

DOTENC_ENV selects the environment for dotenc itself and is removed before the wrapped child command starts.

Update checks

The CLI checks for new versions when you run dotenc dev and prints a notification when an update is available. Native Linux packages use their package manager's update channel instead.

For npm installations, dotenc dev also shows a one-time notice if Node's OpenSSL version predates the reviewed certificate-processing fix. Commands continue normally. Check your Node.js or distribution security updates; vendor backports may already include the fix. Automatic checks and notices are skipped on Windows, where secure home-configuration persistence is unavailable.

Troubleshooting

Diagnose local setup

When a clone looks right but the local workflow does not, run:

dotenc doctor

The default scan follows the same effective scope as dotenc dev: project root through the current directory. Use --local-only for only the current directory, or --all for a bounded recursive audit from the project root. --profile alice diagnoses the personal.alice suffix explicitly; it is not a public-key alias lookup and cannot be combined with --all.

No personal profiles is healthy. Several accessible personal profiles is also healthy; doctor reports that dotenc dev will prompt in an interactive terminal. Warnings remain exit 0 by default so normal developer flow stays usable; add --strict when warnings should fail automation.

Doctor is read-only and offline. It does not repair files, fetch Git history, invoke 1Password, open an authorization prompt, or decrypt environment content. It checks bounded envelope metadata and matching wrapped data keys in memory, zeroes unwrapped keys, and inspects plaintext .env filenames and Git state without reading plaintext file contents.

Human output stays compact and copyable:

βœ“ development          2 layers, data key decryptable.
! personal.alice       A tracked personal profile was deleted from the working tree.
  Paths: .env.personal.alice.enc
  Run: git -C . --literal-pathspecs restore -- .env.personal.alice.enc

0 errors Β· 1 warning Β· 4 checks passed

On Windows, recovery lines are labeled Run (PowerShell) and use PowerShell literal quoting. JSON recovery commands remain shell-neutral argv arrays on every platform.

Exit codes are stable:

  • 0 β€” the scan completed without errors; warnings are allowed unless --strict is set.
  • 1 β€” errors were found, or --strict promoted one or more warnings.
  • 2 β€” the invocation was invalid or the scan/evidence could not complete.

Use dotenc doctor --json for one versioned JSON object. schemaVersion: 1 accompanies command, complete, scope, optional project metadata, typed findings, passed checks, summary, and exitCode. Finding paths are project-relative and recovery commands are argv arrays, not shell strings. The report never includes plaintext variable names or values, plaintext file contents, ciphertext, wrapped data keys, private-key material or paths, cached provider IDs, or raw provider exceptions.

For a recovery finding, a missing or empty commands array means doctor could not establish a safe command from current local evidence; it is not permission to guess. A fresh dotenc env create personal.<profile> suggestion appears only when complete, non-shallow local history finds no recoverable revision, and the message states that the new environment starts empty and cannot recover old values. Doctor never fetches history to change that conclusion.

Scan deployment artifacts

Before uploading a generated build or publish directory, scan it for likely secret leaks:

dotenc doctor artifacts dist

The scan fails on plaintext .env files, private-key headers, and any active dotenc bootstrap value found in a regular artifact file. Bootstrap variable names alone are warnings because a runtime reference can be intentional. Encrypted .env.*.enc files and .env.example, .env.sample, and .env.template files are not treated as plaintext environments.

To compare an application secret without putting its value on the command line, pass only its environment-variable name. The option is repeatable:

dotenc doctor artifacts .vercel/output \
  --secret-name DATABASE_URL \
  --secret-name OPENAI_API_KEY \
  --strict

Each selected name must identify a value already present in the doctor process environment whose UTF-8 representation is between 8 bytes and 1 MiB. The scan compares literal values and common JSON/JavaScript string escapes, URL encoding, base64/base64url, hexadecimal, and fully Unicode-escaped representations. Value matches are errors; name-only matches are warnings. The same size bounds apply to active dotenc bootstrap values so a value that cannot be compared safely makes the scan incomplete instead of being silently skipped. Reports contain only generic finding categories and artifact-relative pathsβ€”never the selected names or values. Paths containing a matched name or value representation are omitted. Use --json for the versioned machine-readable report.

Gzip (.gz or gzip magic bytes) and Brotli (.br) assets are also inspected in memory. Decompression is limited to 16 MiB of compressed input and 32 MiB of expanded output per file; both count toward the 512 MiB scan budget. Recognized unsupported archives (including ZIP and tar), nested compression, malformed compressed files, and exceeded limits return exit 2. Scan an unpacked build directory before packaging; the command never extracts files.

Run after the build has finished. This is a finite pattern check, not proof that a build contains no secrets: arbitrary string splitting, mixed/nested encodings, encryption, unknown container formats, and transformations outside the supported representations still need review. It does not decrypt dotenc environments; selected application values must already be available to the scanner. At most 128 names and 16 MiB of total search patterns are supported.

The scanner reads regular files through bounded, no-follow file handles. It does not follow symbolic links, and it exits 2 instead of claiming success when an entry is unsafe, changes during inspection, cannot be read, or exceeds a file, directory, entry, or byte limit. Exit 1 means a high-confidence leak was found, or --strict promoted a warning; exit 0 means the bounded scan completed without errors.

How dotenc compares

dotenc is a Git-native encryption layer designed for teams who want encrypted environment files committed alongside their code. It does not aim to replace centralized secret managers like Vault or Doppler β€” it serves a different operational model.

| Capability | dotenc | SOPS (+ age) | Vault | Doppler | |-------------|---------|--------------|--------|----------| | Git-native encrypted files | βœ… | βœ… | ❌ | ❌ | | Uses existing SSH identity | βœ… | ❌ (age / PGP) | ❌ | ❌ | | No external service required | βœ… | βœ… | ❌ | ❌ | | Environments versioned with code | βœ… | βœ… | ❌ | ❌ | | Centralized runtime secret API | ❌ | ❌ | βœ… | βœ… | | Dynamic / short-lived secrets | ❌ | ❌ | βœ… | ❌ | | Built-in org policy engine | ❌ | ❌ | βœ… | βœ… | | Requires running infrastructure | ❌ | ❌ | βœ… | ❌ |

When NOT to use dotenc

  • If you require centralized policy enforcement
  • If your compliance mandates HSM-backed key storage
  • If you need runtime secret injection via remote API

License

MIT