bella_baxter Ruby SDK

Ruby client for Bella Baxter — load secrets into your Ruby or Rails application with zero runtime dependencies.

Features

  • Zero runtime dependencies — uses only Ruby's stdlib (openssl, net/http, json)
  • Rails Railtie — auto-loads secrets before database.yml is evaluated
  • End-to-end encryption — optional E2EE using ECDH-P256-HKDF-SHA256-AES256GCM (stdlib OpenSSL)
  • Thread-safe — singleton client, mutex-protected HTTP
  • ENV injectionload_into_env! respects existing values (local dev overrides work)

Installation

# Gemfile
gem "bella_baxter"
bundle install

Quick start

require "bella_baxter"

client = BellaBaxter::Client.new(
  baxter_url:  "https://baxter.example.com",
  api_key:     "bax-...",
  project:     "my-app",
  environment: "production"
)

secrets = client.all_secrets.secrets
puts secrets["DATABASE_URL"]

Rails integration

Add the gem to your Gemfile. That's all.

The gem ships a Railtie that injects all secrets into ENV during Rails' before_configuration hook — before database.yml is parsed, before any initializers run.

Environment variables to set on your host (the only secrets you need there):

BELLA_BAXTER_URL=https://baxter.example.com
BELLA_API_KEY=bax-...
BELLA_PROJECT=my-app
# BELLA_ENV defaults to Rails.env
# BELLA_E2EE=true  to enable end-to-end encryption

database.yml — nothing secret lives here:

production:
  adapter: postgresql
  url: <%= ENV.fetch("DATABASE_URL") %>

DATABASE_URL is injected by Bella Baxter before Rails reads this file.

Boot order

1. Gemfile loaded        → Railtie registered
2. config/application.rb starts
3. [before_configuration] ← Bella Baxter injects ENV here
4. database.yml evaluated ← ENV["DATABASE_URL"] available ✓
5. config/initializers/
6. App boots

ENV injection

# Inject into ENV (existing values are NOT overwritten — local dev wins)
count = BellaBaxter.load_into_env!
puts "Loaded #{count} secrets"

# Force overwrite (e.g. secret rotation without restart)
BellaBaxter.load_into_env!(overwrite: true)

End-to-end encryption

client = BellaBaxter::Client.new(
  baxter_url:  "https://baxter.example.com",
  api_key:     "bax-...",
  project:     "my-app",
  environment: "production",
  enable_e2ee: true   # Client generates P-256 keypair; server encrypts the response
)

# Decryption is transparent — same API
secrets = client.all_secrets.secrets

Algorithm: ECDH-P256 → HKDF-SHA256 → AES-256-GCM. All operations use Ruby's built-in openssl — no extra gems required.

Write operations

# Create
client.create_secret(
  provider:    "my-vault",       # provider slug
  key:         "DATABASE_URL",
  value:       "postgres://...",
  description: "Primary database"
)

# Update
client.update_secret(
  provider: "my-vault",
  key:      "DATABASE_URL",
  value:    "postgres://new-host/..."
)

# Delete
client.delete_secret(provider: "my-vault", key: "DATABASE_URL")

Lightweight version check

Avoid transferring all secret values just to check if anything changed:

v = client.secrets_version
puts "Version #{v.version}, last changed #{v.last_modified}"

Global configuration (optional)

# config/initializers/bella_baxter.rb
BellaBaxter.configure do |c|
  c.baxter_url  = ENV["BELLA_BAXTER_URL"]
  c.api_key     = ENV["BELLA_API_KEY"]
  c.project     = "my-app"
  c.environment = Rails.env
  c.enable_e2ee = Rails.env.production?
end

# Access the singleton client anywhere
BellaBaxter.client.all_secrets

Samples

Sample Description
samples/01-standalone/ Pure Ruby script — all usage patterns
samples/02-rails/ Rails integration with database.yml example

Typed Secret Code Generation

bella secrets generate ruby fetches the secrets manifest (key names + type hints, no values) from the Bella API and generates a typed AppSecrets module. Each method calls ENV.fetch at runtime — no secret values are ever embedded in the generated file.

bella secrets generate ruby \
  --project my-app \
  --environment production \
  --output app_secrets.rb

Generated app_secrets.rb:

# Auto-generated by bella secrets generate ruby — do not edit manually.

module AppSecrets
  def self.database_url
    ENV.fetch('DATABASE_URL') { raise "Secret 'DATABASE_URL' is not set." }
  end

  def self.port
    ENV.fetch('PORT') { raise "Secret 'PORT' is not set." }.to_i
  end

  def self.enable_feature_x
    v = ENV.fetch('ENABLE_FEATURE_X') { raise "Secret 'ENABLE_FEATURE_X' is not set." }
    %w[1 true yes].include?(v.downcase)
  end
end

Usage alongside the Ruby SDK

# The Railtie or BellaBaxter.load_into_env! has already populated ENV.
require_relative "app_secrets"

db_url  = AppSecrets.database_url    # String — raises if missing
port    = AppSecrets.port            # Integer — parsed automatically

Because each method calls ENV.fetch on every invocation (not memoized), secrets injected or refreshed at any point are always current.

Options

Option Default Description
-p, --project <slug> .bella context Project slug
-e, --environment <slug> .bella context Environment slug
--provider <slug> default Provider slug
-o, --output <path> app_secrets.rb Output file path
--class-name <name> AppSecrets Module name
--dry-run Print to stdout without writing

Requirements

  • Ruby >= 2.7
  • No runtime gem dependencies

License

Elastic License 2.0 (ELv2)