CLI tool
Shopify/ejson avatar
Shopify/ejson

Shopify/ejson for Encrypted Secrets in Git Repositories

EJSON is a small library to manage encrypted secrets using asymmetric encryption.

1,481 stars72 forksGoMIT

At a glance

What is it?
Shopify/ejson is a small Go utility that encrypts string values in JSON files so they can be committed with application source. It is intended for teams that need encrypted secrets in Git while keeping decryption keys on selected machines or servers.
Who is it for?
Adopt ejson when application configuration must stay close to source control and a small JSON document is sufficient for the secret set. Do not adopt it for secrets that require a centralized secret store, structured non-string encryption or automatic runtime delivery without checking the missing integration requirements.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 22 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Shopify/ejson Solves

Ejson addresses the gap between storing configuration beside application code and keeping sensitive values out of plaintext Git history. It stores secrets in a JSON document, encrypts the string values and leaves the public key visible. A machine that needs to read the secrets must have the matching private key in a local key directory. This arrangement gives application operators access to decrypted values without placing private keys in the repository.

The intended users are development teams and deployment systems that already use Git for source and configuration. The README says that anyone with Git commit access can write new secrets, while decryption can be limited to production servers. It also presents line-by-line `git blame` as a way to audit changes. Those properties make ejson different from a general-purpose secrets manager: the format is intended to be reviewed and versioned with source code.

The scope is deliberately narrow. Ejson manages a collection of string secrets in JSON. It does not provide a remote secret service, role-based access control, an audit service or an application API for requesting secrets. The local filesystem remains part of the security model.

NaCl Box Encryption and JSON File Layout

Ejson uses public-key cryptography based on NaCl Box, with Curve25519, Salsa20 and Poly1305-AES named in the README. The JSON document contains a top-level `_public_key` field. Each encrypted value is represented as a string payload produced by the encryption format, while property names remain readable so changes can be reviewed in Git.

Encryption is selective. Every string value is encrypted by default, but existing encrypted values are left unchanged, and properties whose names begin with `_` are skipped. Numbers, booleans and null values are not encrypted. The skipped-name rule applies only to the property at that level: the README gives `{"_a": {"b": "c"}}` as an example where `c` is still encrypted because the marker does not propagate downward.

That rule is easy to implement but requires attention when designing the document. A metadata field can remain plaintext by prefixing its name with `_`, while a nested object may still contain encrypted values. The public key is therefore metadata in the document, not a secret. The matching private key is kept outside the JSON file in the key directory.

Key Generation, Key Directories and Access Control

Ejson separates the public key used to encrypt from the private key used to decrypt. `ejson keygen` prints both keys to stdout. The `-w` option writes the keypair into the configured key directory and prints the public key. By default, that directory is `/opt/ejson/keys`. Applications can change it with `EJSON_KEYDIR` or the `-keydir` option.

During decryption, ejson looks for a file whose name exactly matches the 64-byte hex-encoded public key embedded in the `.ejson` document. Its contents must be the similarly encoded private key. `ejson keygen -w` creates this arrangement automatically. Without `-w`, an administrator can distribute the printed keys through a separate configuration-management process.

This design makes the key directory a deployment security boundary. A repository user may be able to commit encrypted configuration without being able to decrypt it. Access to the private key is granted to whoever can read the local key file. The README does not describe hardware-backed keys, key rotation procedures, remote key services or a separate authorization layer, so deployments that require those controls need additional infrastructure.

Installing Ejson and Creating a First Encrypted File

The README offers a Debian package through GitHub Releases and a Homebrew installation for development machines. It also documents the command-line workflow for creating the key directory, generating keys, creating a JSON file and encrypting it. The examples below use the documented names and options.

On a development machine using Homebrew, install the utility with:

bash
brew tap shopify/shopify
brew install ejson

Create the default key directory. The command creates the directory if it is not present:

bash
mkdir -p /opt/ejson/keys

Generate a keypair and write it into that directory with `-w`. The command prints the public key, which is the value needed in `_public_key`:

bash
ejson keygen -w

Create `test.ejson` in a text editor, replacing `<key>` with the public key printed by `keygen`. The file contains plaintext values at this stage:

json
{
  "_public_key": "<key>",
  "_database_username": "1234username",
  "database_password": "1234password"
}

Encrypt the document. Existing encrypted values and skipped properties are left untouched, while new plaintext string values are encrypted:

bash
ejson encrypt test.ejson

After this command, `database_password` is stored as an encrypted EJSON value while `_public_key` remains readable. The property `_database_username` also remains unencrypted because the README states that names beginning with `_` are skipped.

Decrypting Ejson Files Safely

Decryption requires the private key file to be present in the key directory under the exact public-key filename. The command reads the encrypted document and prints plaintext to stdout:

bash
ejson decrypt foo.ejson

The output includes the readable metadata and the decrypted secret values. This stdout behavior is useful for checking the document, but it can expose secrets if the output is captured in logs or other tooling.

To write plaintext directly to a file, use the documented `-o` option:

bash
ejson decrypt -o output.json foo.ejson

On Unix, new output files use mode `0600`, further restricted by the process umask. Existing files keep their permissions and ownership, including files reached through symlinks or hard links. Ejson follows symlinks and does not revoke access from processes that already have the file open. The README specifically warns that upgrades do not tighten permissions on existing output files. It also distinguishes the CLI from shell redirection: `ejson decrypt foo.ejson > output.json` uses the shell's file-creation permissions, not the CLI's. For a deployment, the destination should already have the intended ownership and permissions before decryption.

Operational Limits and Alternative Secret Stores

Ejson's main limitation is its dependence on a local private key and a JSON-shaped configuration file. It is a poor fit for secrets that must be retrieved from a managed service, changed without file changes, supplied to workloads through a platform API or protected by fine-grained identity policies. The README does not document automatic key rotation, revocation after a private-key leak, multi-recipient key wrapping or a mechanism for encrypting numbers, booleans and null values.

The format also has an operational consequence: values can be changed by anyone with Git commit access, even if that person cannot decrypt existing values. This is useful for ordinary team workflows, but it means repository write access remains part of the trust model. A private key copied to several servers increases the number of systems that need protection and cleanup after rotation.

A real alternative is a dedicated secrets manager backed by a service or platform secret store. Such a system usually separates secret storage from the application repository and can provide access policies, audit events, change workflows or runtime injection. Ejson instead keeps the encrypted data in the repository and uses Git history as the configuration change record. The choice is therefore between a simple, file-based and source-controlled workflow versus a centralized control plane. The README points to `ejson2env` for exporting a portion of secrets as environment variables, but it does not describe that tool's retention or access model.

Maintenance, Upgrades and License Boundaries

The repository is not archived. Its last push was on 2026-10-01, and recent releases include `v1.6.0` on 2026-09-29, `v1.5.5` on 2026-08-07 and `v1.5.4` on 2025-02-03. The repository contains Go sources, tests, packaging files and a `Makefile` with targets for binaries, a Debian package and a Ruby gem. The module declares Go 1.24.0 and toolchain go1.24.1, which matters for anyone building from source rather than installing a release package.

The README does not provide a formal upgrade procedure or rollback procedure. It does explain that existing decrypted output files retain their permissions after an upgrade, so an upgrade must be treated as separate from a permission review. Before replacing a binary, operators should check whether the new version accepts the existing EJSON document format, whether their key directory is unchanged and whether destination file permissions still meet the deployment policy. The repository's release and packaging layout gives some evidence of release automation, but it does not by itself document how quickly security fixes will reach every supported platform.

The license is MIT. That permits use, modification, distribution and sublicensing under the license's conditions, subject to its notice requirements. The license file should be reviewed for the exact obligations before redistribution. It does not make private keys safe to commit, and it does not replace access controls on the key directory or decrypted output.

Editorial conclusion

Adopt ejson when application configuration must stay close to source control and a small JSON document is sufficient for the secret set. Do not adopt it for secrets that require a centralized secret store, structured non-string encryption or automatic runtime delivery without checking the missing integration requirements. Verify first that your key directory, destination permissions and deployment model match the documented workflow. Ejson is licensed under MIT, and the repository's last push was on 2026-10-01.

Frequently asked questions

How do I install Shopify/ejson?

The README provides two documented installation routes: download the `.deb` package from GitHub Releases, or use Homebrew on a 64-bit Linux or OS X development machine with `brew tap shopify/shopify` followed by `brew install ejson`.

Where does Shopify/ejson store private keys?

By default, EJSON looks for keys in `/opt/ejson/keys`. The location can be changed with `EJSON_KEYDIR` or the `-keydir` option, and `ejson keygen -w` writes the generated keypair into that directory.

Which values does Shopify/ejson encrypt?

String values are encrypted by default. Numbers, booleans and null values are not encrypted, and properties whose names begin with `_` are left unencrypted, including the `_public_key` field. Existing encrypted values are also left unchanged by `ejson encrypt`.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. Shopify/ejson on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/shopify-ejson.svg)](https://hysenlabs.com/projects/shopify-ejson)

Was this useful?