Skip to content

anzellai/sky-env

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sky-env

Encrypted environment variable manager for developers who are tired of .env files lying around in plaintext.

sky-env stores your environment variables in an AES-256-CBC encrypted SQLite database, organised by project and environment. The database is portable across machines, safe to back up, and (with the right secret hygiene) safe to commit to a private git repository.

Built with Sky — a pure functional language that compiles to a single Go binary, no runtime dependencies.


Why sky-env?

The standard developer workflow for environment variables is broken:

  • .env files sit on disk in plaintext, often in the project root
  • .env.example drifts out of sync with the real .env
  • Switching between dev, staging, prod means juggling files
  • Sharing secrets with teammates means Slack DMs, 1Password lookups, or "ask Bob"
  • Backing up secrets means hoping you remembered to copy .env before reformatting your laptop
  • Multiple projects mean multiple .env files to track and rotate

sky-env fixes all of these:

Pain Fix
Plaintext on disk AES-256-CBC encrypted at rest
.env.example drift sky-env diff shows missing keys across environments
Juggling files One database, multiple projects, multiple environments
Onboarding teammates Share the encrypted DB + secret separately (Slack the secret, commit the DB)
Laptop reformat cp ~/.local/sky-env/skyenv.db /backup/ and you're done
Multi-project Auto-detected from cwd's basename

Install

macOS / Linux (one-liner)

curl -fsSL https://github.com/anzellai/sky-env/releases/latest/download/sky-env-$(uname -s | tr A-Z a-z)-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/') -o /usr/local/bin/sky-env
chmod +x /usr/local/bin/sky-env
sky-env init

Manual download

Grab the binary for your platform from Releases:

Platform Binary
macOS arm64 (Apple Silicon) sky-env-darwin-arm64
macOS x64 (Intel) sky-env-darwin-x64
Linux x64 sky-env-linux-x64
Linux arm64 sky-env-linux-arm64
Windows x64 sky-env-windows-x64.exe

chmod +x the binary, drop it on your PATH, and you're ready.

Requirements

  • openssl must be on your PATH (installed by default on macOS and most Linux distributions)
  • That's it — no Node, no Python, no Go, no Sky runtime

Build from source

git clone https://github.com/anzellai/sky-env
cd sky-env
sky install
sky build src/Main.sky
./sky-out/sky-env --help

Quick start

1. Generate your secret (one time, per machine)

$ sky-env init

Generated sky-env secret (256 bits, hex-encoded):

  2f7eb3c30e4a50f2111e8b402643ca35d0248131ef773550515bd33433326d4d

Add this to your shell config (~/.bashrc or ~/.zshrc):

  export SKY_ENV_SECRET="2f7eb3c30e4a50f2111e8b402643ca35d0248131ef773550515bd33433326d4d"

Then restart your shell or run: source ~/.zshrc

Keep this secret safe — losing it means losing access to
all encrypted environment variables stored under this key.

The secret is read from /dev/urandom (256 bits of true entropy). It is the only thing you need to keep safe — every encrypted value derives its key from this secret via PBKDF2 (100,000 iterations).

sky-env init will refuse to overwrite an existing SKY_ENV_SECRET to prevent accidentally destroying access to existing data.

2. Import your .env file into a named environment

$ cd ~/code/my-app

$ ls .env*
.env.dev    .env.prod    .env.example

$ sky-env import dev
Imported 12 variables into my-app/dev

$ sky-env import prod
Imported 14 variables into my-app/prod

sky-env import <env> looks for .env.<env> first, then falls back to .env, then prompts you with .env.example keys interactively. The project name comes from the current directory's basename.

You can now safely delete or git-ignore .env.dev and .env.prod — they're in the encrypted database.

3. Print decrypted variables when you need them

$ sky-env print dev
API_KEY=abc123
DB_URL=postgres://localhost/myapp_dev
STRIPE_KEY=sk_test_xxx
...

# Pipe straight back into your shell
$ eval "$(sky-env print dev | sed 's/^/export /')"

# Or write back to a temporary file for tools that need .env
$ sky-env print dev > .env
node server.js
rm .env

4. Set or update individual variables securely

$ sky-env set dev API_KEY
  Enter value for API_KEY: ********
Set API_KEY in my-app/dev

The value is read from stdin so it never appears in your shell history (unlike export API_KEY=...).

5. Diff environments to find missing keys

$ sky-env diff
Comparing environments for my-app:

KEY                           dev         staging     prod
------------------------------------------------------------

API_KEY                       OK          OK          OK
DB_URL                        OK          OK          OK
STRIPE_KEY                    OK          MISSING     OK
SENTRY_DSN                    MISSING     OK          OK

Missing keys:
  staging is missing: STRIPE_KEY
  dev is missing: SENTRY_DSN

This is the killer feature for keeping environments in sync. No more "it works on staging but breaks on prod because we forgot to add SENTRY_DSN."

6. List everything

$ sky-env list
my-app
  - dev
  - staging
  - prod
my-other-app
  - dev
  - prod
internal-tool
  - dev

7. Rotate the encryption secret

When you need to swap to a new secret (a teammate leaves, the old one was on a machine you no longer trust, scheduled rotation), one command does the whole thing:

$ sky-env rotate

WARNING: this will rotate the encryption secret.

It will:
  1. Back up the database file
  2. Generate a new 256-bit secret
  3. Decrypt every stored variable with the current secret
  4. Re-encrypt them with the new secret and write back

After rotation you MUST update SKY_ENV_SECRET in your shell
config and reload before running any other sky-env command.
The old secret will no longer decrypt the data.

Type 'rotate' to confirm: rotate

Database backed up to:
  ~/.local/sky-env/skyenv.db.bak.20260409-181237

Re-encrypted 26 variable(s).

NEW SECRET (save this NOW — it will not be shown again):

  068a0a3091e2f11cc8f532a7be085203323c4606903cb92c35b5744a8a7643c9

Update your shell config (~/.bashrc or ~/.zshrc):

  export SKY_ENV_SECRET="068a0a3091e2f11cc8f532a7be085203323c4606903cb92c35b5744a8a7643c9"

The rotation is transactionally safe:

  1. The database file is copied to a timestamped .bak.YYYYMMDD-HHMMSS backup before anything is touched.
  2. Every record is decrypted with the current secret and re-encrypted with the new secret in memory. If any single value fails to decrypt or re-encrypt, the rotation aborts and the database is left untouched.
  3. Only after every record is validated does sky-env write the new ciphertexts back.
  4. If a write fails mid-way, sky-env prints the exact cp command to restore from the backup.

You must type the literal word rotate to confirm — anything else cancels with no changes. After a successful rotation, update SKY_ENV_SECRET in your shell config and reload before running any other sky-env command.


Encryption model

sky-env uses AES-256-CBC with PBKDF2 key derivation, via the system openssl binary. Each encrypted value uses:

  • A fresh random 8-byte salt (per encryption call)
  • PBKDF2-HMAC-SHA256 with 100,000 iterations to derive the AES key from your secret
  • AES-256-CBC symmetric cipher
  • Output is base64-encoded with the Salted__ magic header so it round-trips through SQLite TEXT columns

This is the same encryption you get from openssl enc -aes-256-cbc -pbkdf2 -salt. It is standards-compliant, interoperable, and decryptable from any tool that can read the OpenSSL salted format — not a custom Sky-only scheme.

What this gets you

At-rest encryption. Without SKY_ENV_SECRET, the SQLite file is opaque ciphertext. No casual or determined reader can recover your values without the secret.

Per-value salts. Encrypting the same value twice produces different ciphertexts. An attacker comparing the database across snapshots cannot tell which values changed.

Slow key derivation. PBKDF2 with 100k iterations means brute-forcing the secret is computationally expensive — orders of magnitude slower than a naive sha256 hash.

Standards-based. If sky-env disappears tomorrow, you can decrypt your database with openssl enc -aes-256-cbc -pbkdf2 -d -base64 -A -pass env:SKY_ENV_SECRET <<< "<ciphertext>".

What it does NOT protect against

Losing the secret. PBKDF2 is one-way. If you lose SKY_ENV_SECRET, your data is gone. There is no recovery, no backdoor, no support email. Treat your secret like an SSH private key.

Active malware on your machine. If something is running as your user, it can read both the database AND your environment variable. sky-env protects data at rest, not against running adversaries.

Brief temp file exposure. During encrypt/decrypt, the plaintext is briefly written to a temp file (so openssl can read it without the value appearing in process arguments). This file is created in the OS temp directory with default user-only permissions and deleted immediately. Single-user developer machines: fine. Multi-user shared servers: avoid sky-env or use a tmpfs path.


Where is the data stored?

~/.local/sky-env/skyenv.db    -- SQLite database

The database has two tables:

CREATE TABLE projects (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL UNIQUE
);

CREATE TABLE env_vars (
    id INTEGER PRIMARY KEY,
    project_id INTEGER NOT NULL,
    environment TEXT NOT NULL,
    key TEXT NOT NULL,           -- plain text
    value TEXT NOT NULL,         -- AES-256-CBC ciphertext, base64
    UNIQUE(project_id, environment, key)
);

Variable keys are stored in plain text (so you can grep for WHERE key = 'API_KEY'). Variable values are encrypted. Project names are also plain text. If you consider key names sensitive (e.g. STRIPE_LIVE_SECRET_KEY), keep this in mind.


Portability and backup

The single SQLite file is the entire data store. To move sky-env to a new machine:

# On the old machine
cp ~/.local/sky-env/skyenv.db /tmp/skyenv.db
# transfer to new machine (scp, syncthing, USB, etc.)

# On the new machine
mkdir -p ~/.local/sky-env
cp /tmp/skyenv.db ~/.local/sky-env/skyenv.db

# AND set the same SKY_ENV_SECRET on the new machine
export SKY_ENV_SECRET="<the same hex string from sky-env init>"

The secret never lives in the database, so the database alone is useless without it. The secret never lives on disk (unless you put it in .bashrc/.zshrc), so it's safe from anyone reading your home directory.

Can I commit the database to GitHub?

Private repo: yes, this is a reasonable backup strategy. Anyone with repo access still cannot read values without the secret. The database is at-rest encrypted with PBKDF2(100k iterations) + AES-256-CBC. Brute-forcing is expensive enough that even a leaked private repo + sufficient compute would not yield secrets quickly. Make sure to:

  • Never commit your SKY_ENV_SECRET (never put it in repo files, only in your shell config or a secrets manager)
  • Rotate the secret periodically and re-import all values

Public repo: technically possible but not recommended. Even with PBKDF2, a determined attacker with infinite time and compute can attempt offline brute force. If your secret has low entropy (which sky-env init's secrets do not — they're 256 bits from /dev/urandom), this becomes feasible. Use private repos or out-of-band backups for sky-env databases.


Sharing secrets with a team

The intended workflow:

  1. One person on the team generates the secret with sky-env init and shares it once via a secure channel (1Password, encrypted Slack DM, signed email, in person on a sticky note).
  2. Each developer sets SKY_ENV_SECRET in their shell config from that shared value.
  3. The encrypted database is committed to your private repo (or a shared dropbox / NAS / etc.).
  4. sky-env import dev populates the database when someone changes a variable.
  5. Everyone else git pulls to get the updated encrypted database.
  6. Everyone runs sky-env print dev to see the latest values.

When someone leaves the team, rotate the secret:

  1. Run sky-env rotate — confirm with rotate, and sky-env will back up the database, re-encrypt every value under a fresh 256-bit secret, and print the new secret
  2. Update SKY_ENV_SECRET in your shell config to the new value and reload
  3. Share the new secret with remaining teammates via your secure channel
  4. Commit the rotated database
  5. Once everyone has updated, delete the ~/.local/sky-env/skyenv.db.bak.* backup

Command reference

sky-env init               Generate a new 256-bit secret (refuses if SKY_ENV_SECRET already set)
sky-env import <env>       Encrypt and import .env.<env> (or .env, or interactive from .env.example)
sky-env print <env>        Decrypt and print all variables for <env>
sky-env set <env> <key>    Set a single variable (prompts for value via stdin)
sky-env list               List all stored projects and their environments
sky-env diff               Compare environments for the current project, show missing keys
sky-env rotate             Rotate the encryption secret (re-encrypts every value, with backup)

The "current project" is always the basename of the working directory. You can manage multiple projects by cd-ing between them.


How is sky-env built?

Entirely in Sky, a pure functional language that compiles to a single Go binary. The compiler is written in itself (self-hosted) and ships with:

  • Hindley-Milner type inference — no type annotations needed, but the compiler still catches every type mismatch
  • Exhaustiveness checking — all case expressions must cover every constructor
  • No runtime panics from FFI — Go errors flow through Result String a at the type level
  • Single 8MB binary output — no Node, no Python, no Go runtime, no shared libraries

sky-env itself is ~600 lines of Sky source split across 11 modules. The encryption logic in src/Env/Encrypt.sky is ~70 lines. The whole thing builds in under 2 seconds.

If you're curious about Sky as a language, the main repo has 17 examples ranging from "hello world" to a full Sky.Live monitoring dashboard.


License

MIT — see LICENSE.


Contributing

Bug reports and PRs welcome. The code is small and idiomatic Sky, so it's a good entry point if you've never seen a functional language compile to Go before.

If you want to add a feature, please open an issue first to discuss. Things on the roadmap:

  • sky-env export <env> > file — explicit export to a file (currently you pipe print)
  • sky-env unset <env> <key> — remove a single variable
  • sky-env rename <old> <new> — rename an environment
  • sky-env rotate — re-encrypt all values under a new secret in one command
  • Per-project secrets (different secret per project)
  • Native AES via Sky stdlib (currently shells out to openssl)

About

Encrypted environment variable manager — AES-256-CBC SQLite, portable across machines, written in Sky

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages