Machine-locked encrypted secret management library with Fernet encryption
Project description
WAuth — Machine-Locked Encrypted Secret Management
Store, rotate, back up, and retrieve secrets encrypted with Fernet (AES-256), backed by SQLite — tied to the machine they were created on.
Key Features
- Fernet Encryption — AES-128-CBC via the
cryptographylibrary, with a 32-byte machine-derived key (SHA-256 of salted machine ID). - SQLite-Backed Persistence — Secrets stored in a local SQLite database via
wsqlite, with automatic directory creation. - Docker Secret Support — Read Docker Swarm/Compose secrets from
/run/secretswhen running inside containers. - Dual Driver Architecture — Factory pattern automatically selects between local encrypted vault and Docker secrets based on runtime environment.
- File & Text Support — Store both plaintext secrets and encrypted files (
.pem,.key, certificates). - Key Rotation — Rotate encryption keys with automatic re-encryption of all secrets via
rotate_key(). - TTL Support — Set time-to-live on secrets for automatic expiration.
- Backup & Restore — Export and import encrypted vault snapshots.
- Async Support —
async_set(),async_get(),async_delete(),async_backup(),async_restore()for async workflows. - Custom Exceptions — Structured exception hierarchy (
DecryptionError,KeyNotFoundError,VaultError, etc.). - Structured Logging — Full
loguruintegration for diagnostics. - Configuration File — TOML-based configuration support.
- Pydantic Validation — All secret models validated with Pydantic v2 for type safety.
- Code Quality — Pylint score ≥ 9.5 across all modules, 98% test coverage, Google Style Docstrings, full type hints.
- Security Audited — Bandit scan with 0 medium/high findings. See SECURITY.md
Technical Stack
| Layer | Technology |
|---|---|
| Language | Python 3.9+ |
| Encryption | Fernet (AES-128-CBC) via cryptography |
| Storage | SQLite via wsqlite (ORM-backed by Pydantic) |
| Testing | pytest, pytest-cov |
| Linting | Pylint, Ruff, Black, MyPy |
| Logging | loguru |
| Documentation | Sphinx (furo theme), reStructuredText |
| CI/CD | GitHub Actions (lint, test, security, publish) |
Installation & Setup
From PyPI
pip install wauth
From Source
git clone https://github.com/wisrovi/wauth.git
cd wauth
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,docs]"
Dependencies
Runtime:
cryptography — Fernet encryption
wsqlite — SQLite ORM with Pydantic integration
pydantic — Data validation and serialization
loguru — Structured logging
Development:
pytest — Testing framework
pytest-cov — Coverage reporting
black — Code formatting
ruff — Fast linter
mypy — Static type checking
Documentation:
sphinx — Documentation generator
furo — Modern Sphinx theme
sphinx-copybutton — Copy code blocks
myst-parser — Markdown support
Architecture & Workflow
Directory Structure
wauth/
├── __init__.py # WAuth class, functional API, async methods
├── core.py # CryptoEngine — Fernet encryption/decryption
├── vault.py # SecretModel (Pydantic) + Vault (SQLite + TTL)
├── utils.py # Machine ID detection & key derivation
├── exceptions.py # Custom exception hierarchy
├── deprecation.py # @deprecated decorator & warn_deprecated()
├── drivers/
│ ├── __init__.py # DriverFactory — auto-selects local or Docker
│ ├── local.py # LocalDriver — encrypted local + rotation
│ └── docker.py # DockerDriver — reads /run/secrets
├── test/
│ ├── conftest.py # Shared fixtures
│ ├── test_core.py # CryptoEngine (13 tests)
│ ├── test_vault.py # Vault & SecretModel (22 tests)
│ ├── test_utils.py # Utility functions (10 tests)
│ ├── test_drivers.py # Driver tests (17 tests)
│ ├── test_wauth.py # WAuth & API (30 tests)
│ ├── test_exceptions.py # Exception hierarchy (10 tests)
│ └── test_deprecation.py # Deprecation utilities (7 tests)
├── benchmarks/
│ └── performance.py # Performance benchmark suite
├── examples/
│ └── 00 base/
│ └── example.py # Basic usage example
├── docs/ # Sphinx documentation
├── README.md # This file
├── SECURITY.md # Security policy & threat model
├── VERSIONING.md # SemVer & LTS policy
├── MIGRATION.md # Migration guides between versions
├── PYLINT_REPORT.md # Static analysis quality report
└── pyproject.toml # Build configuration, dependencies
System Workflow
Application --> WAuth.set(key, value, ttl) --> CryptoEngine (Fernet AES) --> Vault (SQLite)
Application --> WAuth.get(key) --> Vault --> CryptoEngine --> Decrypt
Docker Container: DriverFactory checks /run/secrets, falls back to LocalDriver
Encryption Flow
App:set("API_KEY", "secret")
--> WAuth
--> CryptoEngine.encrypt()
--> Derive key (machine_id or custom_key)
--> Fernet.encrypt()
--> Vault.save()
--> SQLite
File Structure
wauth/
├── __init__.py # WAuth class
├── core.py # CryptoEngine (Fernet)
├── vault.py # SecretModel + Vault
├── utils.py # Machine ID detection
├── exceptions.py # Exception hierarchy
├── drivers/
│ ├── __init__.py # DriverFactory
│ ├── local.py # LocalDriver
│ └── docker.py # DockerDriver
└── test/ # pytest suite
Encryption Flow
# set() operation:
App:set("API_KEY", "secret")
--> WAuth
--> CryptoEngine.encrypt()
--> Derive key (machine_id + salt --> SHA-256)
--> Fernet.encrypt() --> token
--> Vault.save(token, "text", ttl)
--> SQLite INSERT
# get() operation:
App:get("API_KEY")
--> Vault.get(key)
--> SQLite SELECT
--> Check TTL (delete if expired)
--> CryptoEngine.decrypt(token)
--> Return plaintext
Configuration
Environment Variables
| Variable | Description | Default |
|---|---|---|
WAUTH_DB_PATH |
Custom SQLite database path | ~/.wisrovi/wauth.db |
TOML Configuration File
Create ~/.wauth.toml:
[wauth]
db_path = "~/.wisrovi/wauth.db"
custom_key = "my-encryption-key" # Optional
Usage:
from wauth import WAuth
auth = WAuth(config_path="~/.wauth.toml")
Database Path
By default, WAuth stores secrets in ~/.wisrovi/wauth.db. To use a custom path:
from wauth import WAuth
# Custom database path
auth = WAuth(db_path="/path/to/my_secrets.db")
auth.set("MY_KEY", "my-secret")
Important Security Note
Encryption keys are derived from the machine's unique identifier. This means:
- Secrets encrypted on Machine A cannot be decrypted on Machine B.
- This is intentional — it prevents accidental secret leakage across environments.
- For cross-machine secret sharing, use Docker secrets, environment variables, or the
custom_keyparameter.
Usage
Quick Start
from wauth import WAuth
# Initialize
auth = WAuth()
# Store a secret (automatically encrypted)
auth.set("TELEGRAM_TOKEN", "7483920:ABC-DEF-GHI")
# Retrieve the secret (auto-decrypted)
token = auth.get("TELEGRAM_TOKEN")
print(token) # 7483920:ABC-DEF-GHI
Storing Files
from wauth import WAuth
auth = WAuth()
# Encrypt and store a file (e.g., TLS certificate)
auth.set_file("TLS_CERT", "/etc/ssl/certs/my-cert.pem")
# Retrieve the file contents as bytes
cert_bytes = auth.get("TLS_CERT")
with open("/tmp/restored-cert.pem", "wb") as f:
f.write(cert_bytes)
Functional API
from wauth import set, get, set_file, delete, list_keys
# No need to instantiate WAuth
set("API_KEY", "sk-12345")
value = get("API_KEY")
# Store a file
set_file("SSH_KEY", "~/.ssh/id_rsa")
key_data = get("SSH_KEY")
# Delete a secret
delete("OLD_KEY")
# List all keys
keys = list_keys()
Delete Secrets
from wauth import WAuth
auth = WAuth()
auth.set("TEMP_TOKEN", "expires-soon")
auth.delete("TEMP_TOKEN") # Permanently removes the secret
Time-To-Live (TTL)
from wauth import WAuth
auth = WAuth()
# Secret expires after 1 hour (3600 seconds)
auth.set("SESSION_TOKEN", "abc123", ttl=3600)
# After 1 hour, get() returns None
Key Rotation
from wauth import WAuth
auth = WAuth()
auth.set("KEY1", "value1")
auth.set("KEY2", "value2")
# Rotate the encryption key — all secrets are re-encrypted
results = auth.rotate_key("new-encryption-key")
# {"KEY1": True, "KEY2": True}
Backup & Restore
from wauth import WAuth
auth = WAuth()
auth.set("IMPORTANT", "critical-data")
# Export all secrets (still encrypted in the backup)
backup_path = auth.backup("my_vault_backup.wauth")
# Later, or on another machine with the same key
count = auth.restore("my_vault_backup.wauth")
print(f"Restored {count} secrets")
Docker Integration
from wauth import WAuth
from wauth.drivers import DriverFactory
# In a Docker container, DriverFactory tries Docker secrets first
factory = DriverFactory()
value = factory.get_value("DB_PASSWORD")
# Returns Docker secret if available, falls back to local vault
Async Support
import asyncio
from wauth import WAuth
async def main():
auth = WAuth()
await auth.async_set("ASYNC_KEY", "async-value")
value = await auth.async_get("ASYNC_KEY")
await auth.async_delete("ASYNC_KEY")
print(value) # async-value
asyncio.run(main())
Exception Handling
from wauth import WAuth
from wauth.exceptions import DecryptionError, KeyNotFoundError, WAuthError
auth = WAuth(custom_key="my-key")
try:
value = auth.get("MISSING")
except KeyNotFoundError:
print("Key does not exist")
try:
# Decrypting with wrong key raises DecryptionError
auth._driver.engine.decrypt("invalid-token")
except DecryptionError:
print("Wrong key or corrupted data")
# Catch any WAuth error
try:
auth.delete("MISSING")
except WAuthError:
print("Any WAuth-related error")
Running the Example
python "examples/00 base/example.py"
# Output: Retrieved token: 7483920:ABC-DEF-GHI valid
# Retrieved token with custom key: 7483920:ABC-DEF-GHI valid
Testing & Quality
# Run tests with coverage
make test # pytest with coverage report
# Lint all code
make lint-all # Pylint on source and tests
# Run benchmarks
python benchmarks/performance.py
# Full quality check
make quality # lint + test + format check
Quality Metrics
| Metric | Score | Target |
|---|---|---|
| Pylint (wauth/) | 9.95/10 | ≥ 9.5 |
| Pylint (test/) | 9.89/10 | ≥ 9.5 |
| Test Coverage | 98% | ≥ 95% |
| Tests | 117 passing | 100% |
| Bandit Security | 0 medium/high | 0 |
LTS Status
WAuth v1.6.0 is an LTS (Long Term Support) release:
- 24 months of security backports
- Stable public API with deprecation guarantees
- SemVer-compliant versioning
- See VERSIONING.md
- See MIGRATION.md
- See SECURITY.md
Documentation
make html # Build Sphinx docs
make serve # Preview docs locally
Full API reference is available at docs/_build/html/index.html.
Author
William Rodríguez — wisrovi
Technology Evangelist & Open Source Advocate
WAuth is designed for developers who need simple, secure, machine-locked secret storage. Perfect for local development environments, CI/CD pipelines, and single-node deployments. LTS since v1.6.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wauth-0.1.1.tar.gz.
File metadata
- Download URL: wauth-0.1.1.tar.gz
- Upload date:
- Size: 40.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
18ddfb187503b194686b91b76263fac21c93263aa881f0f1790ec527c65982c7
|
|
| MD5 |
eeed631e97cc8d3798b383102a0b6e5a
|
|
| BLAKE2b-256 |
6c7aac94008e62207525e05caf67c29f25f1423bfcd35a17a7ea775b170cff7e
|
File details
Details for the file wauth-0.1.1-py3-none-any.whl.
File metadata
- Download URL: wauth-0.1.1-py3-none-any.whl
- Upload date:
- Size: 23.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ecf6243a3876a0d5a5f32bfb5e91f970105b1c29c37dc1d84b19869e15921ea
|
|
| MD5 |
24cb4066a924d90a99d09f02e4c32ee4
|
|
| BLAKE2b-256 |
0ea800ef904ecd4fb7962623aee69bf2f217a8dcc844909821ed4b6212aa0830
|