seekrit — Elixir SDK

Read-path SDK for seekrit. Authenticate with a service token, resolve your environment, and get decrypted secrets — the API only ever returns ciphertext; decryption happens in your process.

This repo is a read-only mirror published from seekrit's monorepo so the code that holds your token and decrypts plaintext is auditable. Don't commit here — it's overwritten on each sync. Issues and PRs welcome.

Install

# mix.exs
def deps do
[{:seekrit, "~> 0.1"}]
end

Requires Elixir 1.18+ / OTP 25+. No dependencies:crypto and :public_key for the decrypt path, :httpc for the request, and the JSON module Elixir 1.18 ships.

Usage

secrets = Seekrit.resolve!() # token from $SEEKRIT_TOKEN
secrets["DATABASE_URL"] # "postgres://…"

Or hold a client, which parses the token once:

{:ok, client} = Seekrit.Client.new()
{:ok, secrets} = Seekrit.Client.resolve(client)
Seekrit.Client.get!(client, "API_KEY", "")

Every function has a {:ok, _} | {:error, exception} form and a ! form that raises.

Releases and config/runtime.exs

config/runtime.exs runs before your applications start, which is the right place to pull secrets in. Seekrit.load_env!/1 starts :inets and :ssl itself, so it works there:

# config/runtime.exs
if config_env() == :prod do
Seekrit.load_env!()
config :my_app, MyApp.Repo, url: System.fetch_env!("DATABASE_URL")
config :my_app, MyAppWeb.Endpoint, secret_key_base: System.fetch_env!("SECRET_KEY_BASE")
end

load_env!/1 keeps variables that are already set; pass override: true to let seekrit win.

Configuration

OptionEnv varDefault
:tokenSEEKRIT_TOKEN— (required)
:api_urlSEEKRIT_API_URLhttps://api.seekrit.dev
:with%{}
:connect_timeout / :timeout10_000 / 30_000 (milliseconds)
:interpolatetrue
:ssl_optionsverify against the system trust store

A service token binds to a single app environment (plus its composed group slices). Pass :with to pull a different environment slice of a composed group:

Seekrit.resolve!(with: %{"shared" => "dev"})

Errors

Elixir exceptions have no hierarchy, so there are four types rather than one tree:

resolve is fail-closed: any resolve or decrypt failure is an error rather than a partial result. A Seekrit.Client and a Seekrit.Crypto.TokenKey both redact themselves in inspect/1, so a crash report cannot leak the credential.

Secret references

A secret's value may reference another with ${OTHER_SECRET}. References are stored literally and expanded here, after the layers are merged — so a reference picks up whichever layer won that name, and rotating the referenced secret updates every value that uses it. $${OTHER_SECRET} is a literal; an unknown name is left as written; a reference cycle is an error. Full rules: seekrit.dev/docs/guides/references.

Seekrit.resolve!(interpolate: false) # get the stored text instead

Zero-knowledge

GET /v1/resolve returns ciphertext plus a data-encryption key wrapped to your token's public key. This SDK recovers the token's private key, unwraps the DEK (ECDH P-256 → HKDF-SHA256 → AES-256-GCM), and decrypts each secret (AES-256-GCM, AAD-bound to environmentId/NAME) — the exact scheme used by the CLI, seekrit run, and every other seekrit client. See seekrit.dev/docs.

The request pins verify: :verify_peer against the system trust store; :httpc does not verify certificates unless told to, and an unverified connection would be a silent downgrade of the whole token-bearing request.

Tests

mix test

test/vectors_test.exs decrypts a golden fixture generated from the canonical @seekrit/crypto implementation and asserts byte-for-byte equality, so this SDK cannot drift from the others.

License

MIT