Skip to main content

Secure API key and configuration management for Python developers

Project description

SafeConfig

Secure API key and configuration management for Python developers.

CI PyPI version Python 3.10+ License: MIT Ruff Checked with mypy codecov


SafeConfig solves the configuration management problem once and for all:

  • Multi-source loading — .env, JSON, YAML, and OS environment with priority merging
  • AES-256-GCM encryption — protect sensitive values at rest with one command
  • Multi-environment — development / staging / production out of the box
  • Required-key validation — fail fast at startup, not at runtime
  • CLI toolsafeconfig init, set, get, encrypt, doctor
  • Source-code scanner — detect accidentally committed secrets
  • Fully typed — complete type hints, ships a py.typed marker
  • Zero magic — explicit, auditable, and predictable

Table of Contents


Installation

pip install safeconfig

For development (includes linting, type checking, and test tools):

git clone https://github.com/yourusername/safeconfig
cd safeconfig
pip install -e ".[dev]"
pre-commit install

Dependencies: cryptography, python-dotenv, pyyaml, click, rich, platformdirs


Quick Start

1. Initialise your project

safeconfig init

This creates a .env file and generates a master encryption key. Copy the key to a safe location (password manager, secret vault).

🔑  Master Key
────────────────────────────────────────
Your new master key

abc123XYZ...  ← store this securely!
────────────────────────────────────────
✔  Created .env

2. Add your secrets

safeconfig set DATABASE_URL postgres://localhost/mydb
safeconfig set API_KEY sk-prod-very-secret --encrypt   # stored as AES-256 ciphertext

3. Use in your application

from safeconfig import SafeConfig

config = SafeConfig(
    environment="production",
    required_keys=["DATABASE_URL", "API_KEY"],
)
config.load()

db_url  = config.get("DATABASE_URL")
api_key = config.get("API_KEY", decrypt=True)   # transparent decryption
workers = config.get_int("MAX_WORKERS", default=4)
debug   = config.get_bool("DEBUG", default=False)
hosts   = config.get_list("ALLOWED_HOSTS")

4. Diagnose problems

safeconfig doctor
┌────────────────────────────────────────┬──────────┬───────────────────────────────┐
│ Check                                  │ Status   │ Detail                        │
├────────────────────────────────────────┼──────────┼───────────────────────────────┤
│ Master key (env var)                   │ ✔ OK     │ Valid key found in environment │
│ .env file (.env)                       │ ✔ OK     │ File found                    │
│ .gitignore                             │ ✔ OK     │ .env is ignored               │
│ Source code scan                       │ ✔ OK     │ No exposed secrets in 12 file  │
└────────────────────────────────────────┴──────────┴───────────────────────────────┘
All checks passed! Your configuration looks healthy.

Configuration Sources

SafeConfig merges configuration from multiple sources. Priority order (highest → lowest):

  1. OS environment variables
  2. Custom loaders (added via add_loader(), in reverse order of registration)
  3. Environment-specific .env file (e.g. .env.production)
  4. Base .env file
from safeconfig import SafeConfig
from safeconfig.core.loaders import DotEnvLoader, JsonLoader, YamlLoader

config = SafeConfig()
config.add_json("config/database.json")
config.add_yaml("config/features.yaml")
config.add_loader(DotEnvLoader(".env.local"))
config.load()

.env file

# .env
DATABASE_URL=postgres://localhost/mydb
API_KEY=enc:AQID...   # encrypted blob — auto-decrypted on get()
DEBUG=true
MAX_WORKERS=4
ALLOWED_HOSTS=localhost,127.0.0.1

JSON (nested keys are flattened with .)

{
  "redis": { "host": "localhost", "port": 6379 },
  "s3":    { "bucket": "my-bucket", "region": "us-east-1" }
}
config.get("redis.host")    # "localhost"
config.get_int("redis.port") # 6379

YAML (same flattening rules)

redis:
  host: localhost
  port: 6379
feature_flags:
  beta_signup: true

Encryption

SafeConfig uses AES-256-GCM (authenticated encryption) with PBKDF2-HMAC-SHA256 key derivation. Each encrypted value carries its own random salt and nonce, so encrypting the same plaintext twice always produces different ciphertexts.

Generate a master key

safeconfig init          # generates a key and writes it to .env
# or:
python -c "from safeconfig import SafeConfig; print(SafeConfig.generate_master_key())"

Store the key in SAFECONFIG_MASTER_KEY (env var or CI/CD secret). Never commit it.

Encrypt from the CLI

# Encrypt an existing plaintext value in .env (in place):
safeconfig encrypt SECRET_API_KEY

# Set a new value as encrypted:
safeconfig set STRIPE_SECRET sk_live_... --encrypt

Encrypt from Python

config = SafeConfig(master_key="your-key")
config.load()

blob = config.encrypt("PLAINTEXT_KEY")      # updates the in-memory store
value = config.get("PLAINTEXT_KEY", decrypt=True)

Encrypted blob format

<version:1B> <salt:16B> <nonce:12B> <ciphertext+tag:NB>

encoded as URL-safe base64. Values stored in .env have an enc: prefix so SafeConfig auto-decrypts them on get().


Multi-Environment

# Reads APP_ENV from the environment, defaulting to "development"
config = SafeConfig(environment="production", config_dir="config/")
config.load()

SafeConfig automatically loads .env and .env.production. The environment-specific file takes precedence.

Supported environments: development, staging, production, test, local.


CLI Reference

Usage: safeconfig [OPTIONS] COMMAND [ARGS]...

Options:
  --env-file TEXT  Path to the .env file  [default: .env]
  -v, --verbose    Enable verbose logging
  --help           Show this message and exit.

Commands:
  init     Scaffold a .env file and generate a master encryption key.
  set      Set KEY to VALUE in the .env file.
  get      Print the value of KEY from the .env file.
  encrypt  Encrypt an existing plaintext value in the .env file.
  doctor   Diagnose common configuration issues.

safeconfig init

safeconfig init
safeconfig init -k DATABASE_URL -k API_KEY   # pre-populate template keys
safeconfig init --force                       # overwrite existing .env

safeconfig set

safeconfig set DATABASE_URL postgres://localhost/mydb
safeconfig set SECRET sk_live_abc123 --encrypt
safeconfig --env-file .env.production set LOG_LEVEL WARNING

safeconfig get

safeconfig get DATABASE_URL
safeconfig get SECRET           # auto-decrypts enc: values
safeconfig get SECRET --raw     # print encrypted blob without decrypting

safeconfig encrypt

safeconfig encrypt SECRET_API_KEY          # encrypt in-place in .env
safeconfig encrypt SECRET --no-in-place   # print blob, don't modify file

safeconfig doctor

safeconfig doctor
safeconfig doctor --scan-dir src/    # scan specific directory for exposed secrets

API Reference

SafeConfig

SafeConfig(
    environment="development",     # or read from APP_ENV
    required_keys=["DB_URL"],      # raise MissingRequiredKeyError if absent
    config_dir=".",                # where to look for .env files
    master_key=None,               # or read from SAFECONFIG_MASTER_KEY
    auto_load_env=True,            # auto-register .env and .env.<environment>
    strict=False,                  # raise KeyNotFoundError instead of returning None
)
Method Description
.load(force=False) Load all sources and validate required keys
.reload() Force a full reload
.get(key, default, decrypt=False) Get a value
.require(key, decrypt=False) Get a value, raise if absent
.get_int(key, default) Get as integer
.get_bool(key, default) Get as boolean
.get_list(key, default, separator=",") Get as list
.set(key, value, encrypt=False) Set in-memory value
.encrypt(key) Encrypt in-place in the in-memory store
.add_loader(loader) Register a custom loader
.add_json(path) Add a JSON source
.add_yaml(path) Add a YAML source
.all() Return all loaded key-value pairs
.keys() Return sorted list of keys
SafeConfig.generate_master_key() Generate a new master key

Exceptions

Exception Raised when
ConfigurationError Base class for all library errors
KeyNotFoundError Key not found (in strict mode or via .require())
MissingRequiredKeyError Required key absent after load
EncryptionError Encryption failed
DecryptionError Decryption failed (wrong key or corrupted value)
InvalidEnvironmentError Unknown environment name
MasterKeyError Master key missing or malformed
ExposedKeyWarning Hard-coded secret detected in source (warning, not error)

Publishing to PyPI

Prerequisites

  1. Register an account at pypi.org
  2. Install build tools: pip install build twine

Manual publish

# 1. Bump version in pyproject.toml
# 2. Build distributions
python -m build

# 3. Upload to TestPyPI first (recommended)
twine upload --repository testpypi dist/*

# 4. Test the installation
pip install --index-url https://test.pypi.org/simple/ safeconfig

# 5. Upload to PyPI
twine upload dist/*

Automated publish via GitHub Actions

The included CI workflow publishes automatically when you push a version tag:

git tag v1.0.0
git push origin v1.0.0

Configure PyPI Trusted Publishing in your PyPI project settings to avoid storing API tokens in GitHub Secrets.


Contributing

# Clone and set up
git clone https://github.com/yourusername/safeconfig
cd safeconfig
pip install -e ".[dev]"
pre-commit install

# Run tests
pytest

# Run type checker
mypy src/safeconfig/

# Run linter
ruff check src/ tests/

PRs are welcome! Please open an issue first to discuss substantial changes.


License

MIT © 2024 Your Name

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

safeconfigalpha-1.0.0.tar.gz (28.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

safeconfigalpha-1.0.0-py3-none-any.whl (29.8 kB view details)

Uploaded Python 3

File details

Details for the file safeconfigalpha-1.0.0.tar.gz.

File metadata

  • Download URL: safeconfigalpha-1.0.0.tar.gz
  • Upload date:
  • Size: 28.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for safeconfigalpha-1.0.0.tar.gz
Algorithm Hash digest
SHA256 8f4f384e0616869c571a81e59d83ce99f74dc88a6271899862276929364702b6
MD5 f5041a053c6bb40d622a95d20ba7578e
BLAKE2b-256 031b3d8ab68860d6b84317f7bbec4f48f1e0f63c92e6b0512e78491adbe3fbd4

See more details on using hashes here.

File details

Details for the file safeconfigalpha-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for safeconfigalpha-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9a1ba2b60a30b1b586db89f8bcd8918dd8b6ca2d9e0ec849a917ba52202fbb62
MD5 0b0c530ec273e5bdc793c3d98b9b6ebf
BLAKE2b-256 2a98512962c980e3dd24ae247d8d3afc95c5b227f2643bc42111926615948363

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page