Configuration Loading
money configuration is explicit. It does not silently read donor config paths, cwd .env, or provider environment variables that are not referenced by config.
Config Shape
Secret references use YAML objects with exactly one env key:
database:
path: ~/.money/data/money.db
encryption_key:
env: MONEY_DB_ENCRYPTION_KEY
providers:
plaid:
client_id:
env: PLAID_CLIENT_ID
secret:
env: PLAID_SECRET
environment: sandbox
products: [transactions]
country_codes: [US]
additional_consented_products: [investments]
required_if_supported_products: [liabilities]
optional_products: [auth]
redirect_uri:
env: PLAID_REDIRECT_URI
bridge:
client_id:
env: BRIDGE_CLIENT_ID
client_secret:
env: BRIDGE_CLIENT_SECRETDirect scalar values are allowed only for non-secrets such as database.path, providers.plaid.environment, products, country_codes, and Plaid Link consent product lists. Direct scalar secrets are accepted for manually edited files, but config loading emits a structured warning recommending .env references.
Path Resolution
- If
--config <path>is present, use it. - Else if
MONEY_CONFIGis set, use it. - Else use the
--profileflag to determine the default config path:default(or not specified):~/.money/config.yaml- Any other profile name:
~/.money/profiles/<name>/config.yaml
- If config has
env_file, resolve it relative to the config file's directory when it is not absolute. - Else the env companion is
.envin the config file's directory. - Never load cwd
.envimplicitly.
Profile names must be alphanumeric, hyphen, or underscore only; path traversal characters are rejected.
~ expansion is supported only at the start of configured paths.
Load Order
- Resolve the config path.
- Read config YAML if it exists. Missing config is a config error for real commands unless the command is
setup,doctor --fix, or demo mode. - Determine the env companion path from config or default rules.
- Load key/value pairs from that env file if it exists.
- Overlay process environment values over env-file values for the same variable names.
- Resolve each
env:reference by looking up the named variable in the merged env map. - Validate required fields for the command being executed.
- Return config values plus structured warnings, such as direct secrets in YAML or broad env-file permissions.
Environment variables complete explicit references; they do not form a magic override chain. For example, PLAID_SECRET is used only when config says secret: { env: PLAID_SECRET } or a setup/configure command writes that reference.
Plaid Dashboard OAuth bootstrap state is stored outside YAML at plaid-dashboard-auth.json beside the resolved config file. It is local bootstrap state for money plaid login, written 0600, and may include Dashboard access/refresh tokens plus selected team_id and client_id. Provider API credentials still use the normal .env plus YAML env: references model.
Pseudocode
load(flags, process_env):
config_path = flags.config || process_env["MONEY_CONFIG"] || "~/.money/config.yaml"
config_path = expand_home(config_path)
raw = read_yaml(config_path)
env_path = raw.env_file
? resolve_relative_to_config_dir(raw.env_file)
: join(dirname(config_path), ".env")
file_env = parse_dotenv(env_path)
merged_env = file_env overlaid by process_env
resolved.database.path = expand_home(required_scalar(raw.database.path))
resolved.database.encryption_key = resolve_secret(raw.database.encryption_key, merged_env)
resolved.read_only = raw.read_only || process_env["MONEY_READ_ONLY"] == "1"
for provider in raw.providers:
resolve explicit secret refs from merged_env
copy non-secret scalar/list values from YAML
collect warnings for direct secret scalars
validate command-specific requirements
return resolved config, config paths, warningsDatabase Encryption Key
Generated keys are exactly 32 cryptographically random bytes, encoded with base64url without padding into MONEY_DB_ENCRYPTION_KEY. That is 256 bits of key material.
Config loading decodes the base64url string into 32 bytes. Any malformed base64url value or decoded length other than 32 bytes is a config error. The store adapter converts those bytes into the selected SQLite encryption driver's key input format without printing or logging the key.
Demo Mode
money demo <command...> does not require MONEY_DB_ENCRYPTION_KEY, does not open the real database, and does not run real migrations against disk. Demo mode opens an in-memory SQLite database, runs the same migration SQL without encryption, seeds deterministic fixtures, marks JSON envelopes with meta.demo: true, and blocks real Provider network flows.
This is not a plaintext fallback for real data because demo never reads or writes real financial records.