Ranbval SDK 是一个 Python 密钥管理客户端,通过加密令牌和仓库白名单机制防止 API 密钥泄露,并追踪每次访问。
Stars: 1 | Forks: 0
[](https://pypi.org/project/ranbval-sdk/)
[](https://pypi.org/project/ranbval-sdk/)
[](LICENSE)
# Ranbval SDK `v3.5.2`
**The Python client for Ranbval — a secret manager for API keys.** Encrypt secrets in the
Ranbval dashboard, store the encrypted tokens in `.ranbval` files, and decrypt them only at
runtime — AES-256-GCM with PBKDF2 key derivation, no plaintext ever touches source control.
Unlike a plain `.env`, a stolen config is useless off your allowlisted repos, and every use is
attributable in the Live Monitor.
pip install ranbval-sdk
## Why Ranbval Exists
Every team now juggles a pile of API keys — LLM providers, payment processors, databases,
third-party services. Those keys leak constantly, and almost always the same handful of ways:
- A key gets **committed to Git** — and bots scrape public repos within minutes.
- A `.env` file is **copied and shared** over Slack/email — then forwarded, forgotten, and
lives forever with no expiry.
- A key is **accidentally printed to logs** or captured by an error reporter — and now it sits
in Datadog/Sentry, readable by the whole org, retained for years.
- When a key *does* leak, **nobody knows who leaked it or which repo burned the tokens** — so
you can't rotate with confidence.
`.env` + `load_dotenv()` does nothing about any of this: the secret is plaintext on disk, works
anywhere it's copied, forever, with zero visibility. Ranbval is built to close exactly these
gaps.
### What it actually protects (and what nothing can)
Be clear-eyed about the threat model — it's what makes the guarantees trustworthy:
- **What no tool can stop:** an attacker who already runs code *inside your process*. If they can
execute in your app, they can read `os.environ`, hook functions, or dump memory — and **no**
secret manager (Vault, AWS/GCP Secrets Manager, Doppler, Ranbval) prevents that. It isn't the
real-world leak vector.
- **What Ranbval does stop** — the leaks that actually happen:
| Real-world leak | `.env` | Ranbval |
|---|---|---|
| Key committed to Git | 🔴 plaintext, public instantly | 🟢 encrypted token — a commit leaks nothing usable |
| Config file copied / shared | 🔴 works anywhere, forever | 🟢 **useless without the project secret *and* an allowlisted repo** |
| Key printed to logs / captured by Sentry | 🔴 sits in log storage for years | 🟢 `SecretString` masks every display path; can't be pickled into a cache/report |
| A key leaks — who? which repo? | 🔴 zero visibility | 🟢 **Live Monitor** flags the same credential on a new device/IP → rotate with proof |
The crown jewel is the **repo allowlist**: even if someone steals your entire `.ranbval` file
*and* your project secret, they still can't decrypt it from a repo that isn't on your
control-plane allowlist. A stolen config is a dead config.
### An analogy
You can't make a house key that opens *your* door but that a thief holding it can't use — if the
key opens the lock, whoever holds it gets in. That's physics, not a flaw. Real security comes
from three other things, and Ranbval gives you all three:
1. **The key isn't lying in the street** → plaintext never touches Git (encrypted tokens).
2. **The key only works at your house** → the repo allowlist makes a stolen file worthless elsewhere.
3. **An alarm rings if a stranger walks in** → leak detection alerts on a new device/IP.
### Why use it
- **Drop-in.** One `load_ranbval()` replaces scattered `load_dotenv()`; keys pass straight into
your existing SDKs — Ranbval ships no vendor dependencies.
- **Safe by default.** Secrets are sealed `SecretString`s that refuse to print, log, or serialize;
plain config is opt-in plaintext via `PUBLIC_` name prefixes and `public()`.
- **Accountable.** Every decrypt is attributable, and misuse is detectable — something a plain
`.env` can never offer.
## Quick Start
from ranbval_sdk import load_ranbval, decrypt_key
import os, openai
# 1. Load encrypted config from .ranbval files (no network, no decryption)
load_ranbval()
# 2. Decrypt a vault token — returns a SecretString, never printable.
# This also auto-reports the usage to your Live Monitor (no extra code).
api_key = decrypt_key("SECRET_OPENAI_KEY")
# 3. Pass directly to the SDK — value is never exposed in logs or prints
client = openai.OpenAI(api_key=api_key.use())
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello"}],
)
`.ranbval.local` (never commit this file):
RANBVAL_PROJECT_SECRET=your_dashboard_project_secret
SECRET_OPENAI_KEY=ranbval.4ii0a022aa.p1GOZ...ahsan
## CLI
`pip install ranbval-sdk` ships a `ranbval` command:
ranbval init # starter .ranbval + gitignore .ranbval.local
ranbval check # lint: unclassified keys, [section] headers, competing .env, mismatches
ranbval run -- python app.py # load .ranbval into the env, then run (secrets only in that process)
`ranbval check` exits non-zero on errors, so drop it into CI or a pre-commit hook.
## Remote config (no local file)
Pull the whole env-set from the Ranbval control plane instead of shipping a `.ranbval` — the
project secret is the credential, and everything downstream (decryption, prefixes, enforcement)
is identical to loading from a file:
from ranbval_sdk import load_ranbval, decrypt_key
load_ranbval(remote=True, project_secret="ranbval-proj-…") # fetches SECRET_/PROXY_/PUBLIC_
client = openai.OpenAI(api_key=decrypt_key("SECRET_OPENAI_KEY").use())
`SECRET_`/`PROXY_` values come down as encrypted `ranbval.*` tokens (decrypted client-side);
`PUBLIC_` values are plaintext. Add a key in the dashboard → it appears here on the next load.
**Owner vs developer.** The owner fetches with the project secret. A developer fetches with a
`ranbval-dev-…` token the owner issues from the dashboard, and can add `PUBLIC_` envs from code —
attributed to them:
load_ranbval(remote=True, api_key="ranbval-dev-…") # developer fetch
from ranbval_sdk import push_env
push_env("PUBLIC_FEATURE_FLAG", "on", api_key="ranbval-dev-…") # shows as "added by "
`SECRET_`/`PROXY_` keys stay owner-only (created encrypted in the dashboard).
## Environments (dev / staging / production)
A project holds up to **10 named environments**, and every key and `PUBLIC_` value lives in one of
them. The *same name* therefore holds a *different value* per stage:
project "My App"
├── development SECRET_OPENAI_KEY=ranbval.… PUBLIC_DATABASE_URL=postgres://dev…
├── staging SECRET_OPENAI_KEY=ranbval.… PUBLIC_DATABASE_URL=postgres://stg…
└── production SECRET_OPENAI_KEY=ranbval.… PUBLIC_DATABASE_URL=postgres://prod…
Pull exactly one:
load_ranbval(remote=True, environment="production")
client = openai.OpenAI(api_key=decrypt_key("SECRET_OPENAI_KEY").use()) # production's key
Only that stage's values are fetched — **production credentials never reach a development
machine**, even if the developer's token is valid for the project.
**How the stage is chosen** — explicit argument first, then the environment:
load_ranbval(remote=True, environment="staging") # 1. explicit
# else RANBVAL_ENV / ENVIRONMENT / ENV # 2. from the environment
# else the project's first environment # 3. server default
export RANBVAL_ENV=production # CI / server sets this once; code stays identical
`RANBVAL_ENV` is the same variable that picks a local `.ranbval.{mode}` file — one idea ("which
stage am I running in"), one variable, whether the config comes from disk or the control plane.
`push_env` takes the same argument, so a developer can add a `PUBLIC_` value to one stage:
push_env("PUBLIC_FEATURE_FLAG", "on", api_key="ranbval-dev-…", environment="staging")
Environments are created, renamed, and deleted from the dashboard. Deleting one deletes every key
and `PUBLIC_` value inside it; a project always keeps at least one.
## Module Reference
| Symbol | Description |
|--------|-------------|
| `load_ranbval()` | Merges layered `.ranbval*` files into `os.environ`; `remote=True, environment="…"` pulls one stage from the control plane |
| `public()` | Read a plaintext (unencrypted) config value — never decrypts |
| `public_config()` | Dict of every `PUBLIC_`-prefixed key as `{name: plaintext}` |
| `proxy_token()` | Raw encrypted token for a `PROXY_` key — pass to `proxy_request()` (never decrypted client-side) |
| `safe_decrypt()` | Decrypts a vault token string → `SecretString` |
| `decrypt_key()` | Reads an env var and decrypts it in one call |
| `SecretString` | Wrapper that blocks all display paths — value only via `.use()` |
| `require_reveal_scope()` / `reveal_scope()` | Restrict a secret so `.use()` works only inside an approved block |
| `install_access_monitor()` | Detect & report suspicious secret access / possible exfiltration |
| `set_enforcement()` / `is_enforced()` | Toggle strict mode — extraction attempts raise `RanbvalSecurityError` (on by default) |
| `proxy_request()` | Route an HTTP request through the Ranbval proxy (key injected server-side) |
| `emit_telemetry()` | Record a **custom** usage event (basic usage is auto-reported on every `decrypt_key()`) |
| `get_audit_log()` | Return the in-process audit log list |
| `clear_audit_log()` | Clear the in-process audit log |
| `get_project_key()` | Read `RANBVAL_PROJECT_SECRET` from env |
| `find_ranbval_file()` | Locate the nearest `.ranbval*` file on disk |
| `find_ranbval_directory()` | Locate the config root directory |
| `resolve_ranbval_mode()` | Determine the active mode from env/args |
## Package Layout
Everything is organized by concern. You only import from the top level
(`from ranbval_sdk import …`); the table shows where each piece lives.
ranbval_sdk/
├── __init__.py # the public API (re-exports everything below)
├── exceptions.py # RanbvalError hierarchy
├── py.typed # ships type information (PEP 561)
├── config/ # your .ranbval configuration surface
│ ├── loader.py # load_ranbval, find_*, resolve_ranbval_mode, get_project_key
│ ├── access.py # imperative access — Vault, env, inject, secrets, iter_secrets
│ └── declarative.py # class-based access — Secret, SecretConfig
├── crypto/ # cryptography & sealed secrets (only crypto lives here)
│ ├── cipher.py # AES-256-GCM decrypt + project-secret resolution
│ ├── secret_string.py # SecretString — the sealed, never-printable value
│ └── audit.py # in-memory log of every .use()
├── policy/ # provenance & access policy (the decrypt gate)
│ └── repo.py # git-remote allowlist enforcement (server-controlled)
├── serializers/ # wire (de)serializers — one module per payload shape
│ ├── telemetry.py # /api/telemetry body + security metadata
│ ├── proxy.py # /api/execute request body
│ ├── token.py # parse ranbval...