<div align="center">
# 🔒 dotprot
**Pick up where you left off without worrying about your `.env` files.**
Lock your `.env` (and anything else you list) into a dedicated 1Password vault,
then restore it on demand. dotprot uploads the exact bytes, **verifies the copy
round-trips correctly, and only then removes the file from disk.**
[](LICENSE)
[](https://www.rust-lang.org)
[](https://buy.stripe.com/5kQ4gBe7Lc7s68YaqZ4F200)
☕ **[Support this project](https://buy.stripe.com/5kQ4gBe7Lc7s68YaqZ4F200)**
(Appreciated but not necessary)
</div>
---
## Why?
As a software engineer, I like using `.env` files but my paranoia doesn't allow for me to leave a bunch of .env files on my disk without worrying about it. I'm a big fan of the [1Password](https://1password.com/) password manager and I use its [CLI tool](https://1password.com/downloads/command-line) extensively. So I thought, why not use a 1Password vault to store my `.env` files? Then it just felt like a no-brainer to create a little easy-to-use wrapper for 1Password CLI to lock `.env` files away inside of a 1Password vault and then retrieve them when I'm ready to work again.
## Contents
- [🔒 dotprot](#-dotprot)
- [Why?](#why)
- [Contents](#contents)
- [Quickstart](#quickstart)
- [How it works](#how-it-works)
- [Requirements](#requirements)
- [Install](#install)
- [Homebrew](#homebrew)
- [Cargo](#cargo)
- [Prebuilt binaries](#prebuilt-binaries)
- [From source](#from-source)
- [Usage](#usage)
- [The toggle](#the-toggle)
- [Trying it safely with `--keep`](#trying-it-safely-with---keep)
- [The `.prot` file](#the-prot-file)
- [Safety guarantees](#safety-guarantees)
- [Development](#development)
- [License](#license)
## Quickstart
- [Install dotprot](#install)
- Change directory to any directory with a `.env` file.
- If you don't use `.env` then type `dotprot setup` and add your file(s) to `.prot`
- Sign in to 1Password via `op signin`
- Type `dotprot`
```shell
Created .prot (protecting: .env*).
Created 1Password vault ".prot" (6djqbcxlh235372hbuvtuqnr4i).
(one-time setup — future runs reuse it)
locked .env -> 1Password
Locked 1 file(s) into vault ".prot".
```
- Type `ls -a`
- Notice `.env` is no longer there.
- Check your brand new `.prot` vault in 1Password
- 🎉 There it is!
- Type `dotprot` again to bring your .env file back. (It stays in 1Password)
## How it works
dotprot reads a `.prot` file in your working directory that lists which files to
protect — globs, one per line, defaulting to `.env*`. Each protected file is
stored as a 1Password **document** in a vault named `.prot`. The document IDs are
written back into your `.prot` file so the same directory can lock and unlock
repeatedly.
```
┌─────────────┐ dotprot lock ┌──────────────────────┐
│ ./.env │ ────────────────► │ 1Password ".prot" │
│ (on disk) │ ◄──────────────── │ vault (documents) │
└─────────────┘ dotprot unlock └──────────────────────┘
```
## Requirements
- The [1Password CLI (`op`)](https://developer.1password.com/docs/cli/get-started/),
signed in (`op signin`).
dotprot never touches your 1Password credentials — it shells out to `op`, which
handles all authentication. If you're not signed in, dotprot tells you to run
`op signin` and does nothing else.
## Install
### Homebrew
```sh
brew install jstilwell/tap/dotprot
```
### Cargo
```sh
cargo install dotprot
```
### Prebuilt binaries
Download the binary for your platform from the
[releases page](https://github.com/jstilwell/dotprot/releases) and put it on
your `PATH`.
### From source
```sh
git clone https://github.com/jstilwell/dotprot
cd dotprot
cargo build --release # binary at target/release/dotprot
```
## Usage
```sh
dotprot # smart toggle: lock if files present, unlock if absent
dotprot lock # force lock: upload → verify → delete from disk
dotprot unlock # force unlock: restore files from 1Password
dotprot --keep # lock, but keep the originals on disk (don't delete)
dotprot setup # optional: pre-create the .prot vault in 1Password
```
You don't have to run `dotprot setup` first. The very first `dotprot` (or
`dotprot lock`) in a directory creates the `.prot` vault automatically if it
doesn't exist, tells you it did, and carries on:
```text
$ dotprot
Created 1Password vault ".prot" (abc123...).
(one-time setup — future runs reuse it)
Created .prot (protecting: .env*).
locked .env -> 1Password
Locked 1 file(s) into vault ".prot".
```
### The toggle
Running bare `dotprot` figures out which way to go from what's on disk:
| State | What `dotprot` does |
| -------------------------------------- | --------------------------------------------------------------------- |
| Protected files are **present** | Locks them into 1Password |
| Protected files are **missing** | Restores them from 1Password |
| **Mixed** (some present, some missing) | Stops and asks you to be explicit (`dotprot lock` / `dotprot unlock`) |
### Trying it safely with `--keep`
Before trusting dotprot to delete anything, upload-and-verify without removing
the originals:
```sh
dotprot --keep # .env is copied to 1Password and verified, but stays on disk
# open the .prot vault in the 1Password app and confirm .env is there
dotprot lock # now remove the local copy
```
## The `.prot` file
Auto-created on first lock. dotprot owns everything above the sentinel and
rewrites it freely; the file list below the sentinel is yours to edit:
```text
# dotprot — managed below, do not edit
vault: abcd1234efgh5678
doc .env: 6yx...
doc config/secrets.json: q9z...
# ---- your files (edit below) ----
.env*
config/secrets.json
```
> **Tip:** commit `.prot` to version control. It contains only 1Password
> vault/item IDs — **no secrets** — and lets teammates `dotprot unlock` the same
> files, given access to the vault.
## Safety guarantees
dotprot's whole reason to exist is to make secrets _easier_ to manage, never to
put them at risk. The design centers on one rule: **a local file is deleted only
after a verified, recoverable copy exists in 1Password.**
- **Verify-then-delete.** A file is removed from disk only after its 1Password
copy is uploaded, **read back, and confirmed byte-identical**. Any failure in
that chain leaves the original untouched. Use `--keep` to skip deletion
entirely.
- **Scoped to the `.prot` vault only.** Every 1Password operation is scoped with
`--vault .prot`. dotprot **never deletes 1Password items in normal
operation** — lock creates/updates documents, unlock only reads them.
- **Incremental persistence.** `.prot` is updated after _each_ file locks, so an
interruption mid-batch leaves you in a consistent, recoverable state.
- **Backups are kept.** Documents stay in 1Password after unlock, so a directory
stays re-lockable and you always have a copy. Re-locking overwrites the
existing document in place.
- **Stable titles.** Document titles are the file's **absolute path**, so
they're easy to find in the 1Password UI and never collide across directories
or machines.
- **Minimal on-disk exposure.** Secrets are handed to `op` via a short-lived
`0600` temp file that's deleted immediately afterward (the 1Password CLI does
not reliably accept piped stdin). Restored files and the `.prot` state file are
also written `0600` (newly created ones; an existing `.prot`'s mode is left as
you set it).
> **Platform note (Windows):** the owner-only (`0600`) file-permission hardening
> is currently enforced on **macOS and Linux only**. On Windows the temp and
> restored files are created with default ACLs. The verify-then-delete guarantee
> and vault scoping hold on all platforms; only the file-permission tightening is
> Unix-only for now. See [CHANGELOG.md](CHANGELOG.md).
## Development
```sh
cargo build # debug build
cargo test # run unit tests
cargo clippy # lint
cargo build --release # optimized binary
```
See [CHANGELOG.md](CHANGELOG.md) for release history.
## License
[MIT](LICENSE)