LemonCore. Secrets
(lemon_core v0.1.0)
View Source
Encrypted secrets storage backed by LemonCore.Store.
Secret values are encrypted at rest and never returned by list/status APIs.
Master key
Values are encrypted with AES-256-GCM under a per-secret key derived from the
master key (LemonCore.Secrets.Crypto). Where that master key comes from —
system keychain, environment variable, key file, or a provider you supply —
is configurable; see LemonCore.Secrets.KeyProvider.
config :lemon_core, LemonCore.Secrets,
key_providers: [:keychain, :env, :file],
key_file: "~/.lemon/secrets_master_key",
env_var: "LEMON_SECRETS_MASTER_KEY"Key rotation
Rotation is not supported yet: there is no re-encryption path, so
replacing the master key makes every stored secret undecryptable. Tracked as
item 1.5 in docs/platform-split.md.
The workaround is export/import — with the old key still in place, read the plaintext values out, swap the key, then write them back:
{:ok, entries} = LemonCore.Secrets.list()
exported = Map.new(entries, &{&1.name, elem(LemonCore.Secrets.get(&1.name), 1)})
# rotate the key (e.g. mix lemon.secrets.init --force), then:
Enum.each(exported, fn {name, value} -> LemonCore.Secrets.set(name, value) end)Keep the exported values in memory (or a 0600 file you delete afterwards),
never in shell history or logs.
Summary
Types
Functions
@spec default_owner() :: owner()
Convenience wrapper around resolve/2 that returns just the value or nil.
Drop-in replacement for System.get_env/1 — tries the encrypted store first,
then falls back to the environment variable of the same name.
iex> LemonCore.Secrets.fetch_value("ANTHROPIC_API_KEY")
"sk-ant-..."
iex> LemonCore.Secrets.fetch_value("MISSING_KEY")
nil
@spec import_from_env( name(), keyword() ) :: {:ok, secret_metadata()} | {:error, atom()}
Import a secret from an environment variable into the encrypted store.
Options
:env_name- The environment variable name (defaults to secret name):delete_after- If true, deletes the env var after import (default: false)- Other options are passed to
set/3
Examples
iex> System.put_env("OLD_API_KEY", "sk-...")
iex> LemonCore.Secrets.import_from_env("NEW_API_KEY", env_name: "OLD_API_KEY")
{:ok, %{name: "NEW_API_KEY", ...}}
@spec list(keyword()) :: {:ok, [secret_metadata()]}
@spec persist(name(), String.t(), keyword()) :: {:ok, secret_metadata()} | {:error, atom()}
Persist a secret to the encrypted store.
This is an alias for set/3 that provides a consistent API for
modules that need a persist/2 function.
Examples
iex> LemonCore.Secrets.persist("MY_API_KEY", "sk-...")
{:ok, %{name: "MY_API_KEY", ...}}
@spec set(name(), String.t(), keyword()) :: {:ok, secret_metadata()} | {:error, atom()}
@spec table() :: atom()