One command. 33 platforms. Your secrets, everywhere they need to be.
Named after the banto (番頭) — the head clerk of Edo-period Japanese merchant houses who held the keys to the storehouse and managed the account books.
banto syncs your API keys from macOS Keychain to 33 cloud platforms in one command. No more copy-pasting secrets into dashboards. Tell your AI agent "deploy my secrets to Vercel" and it's done — the agent orchestrates, banto syncs, and secret values never enter the chat. Optional budget gating adds intelligent cost control on top.
%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#262626',
'primaryBorderColor': '#0D3B66',
'lineColor': '#262626',
'secondaryColor': '#0D3B66',
'tertiaryColor': '#F2EFE9',
'edgeLabelBackground':'#ffffff'
}
}}%%
graph LR
subgraph Origin ["1. Secure Origin"]
KC[(macOS Keychain)]
end
subgraph Hub ["banto Engine"]
B{{"banto"}}
V[[Guard Logic]]
end
subgraph Cloud ["33+ Sync Targets"]
T1[GitHub]
T2[Vercel]
T3[AWS / GCP / Azure]
T4[Cloudflare / etc.]
end
KC --- B
B --- V
V -.->|Verify| B
B ==>|Secure Sync| T1
B ==>|Secure Sync| T2
B ==>|Secure Sync| T3
B ==>|Secure Sync| T4
style B fill:#0D3B66,color:#fff,stroke:#0D3B66
style V fill:#f8f9fa,stroke:#B13546,stroke-width:1px
Table of Contents
- 🔑 Key features
- 🏮 Why banto?
- 📋 Requirements
- ⚡ Quick start
- 💻 CLI reference
- 🤖 MCP server
- 🔒 Security
- 🐍 Python API
- 🔌 Custom backends
- ⚙️ Configuration
- ❓ FAQ
- ⚖️ Disclaimer
- 📄 License
🔑 Key features
- 33+ platform sync — one command deploys secrets to Vercel, Cloudflare, AWS, GCP, Azure, Kubernetes, GitHub Actions, and 26 more. No dashboard clicking
- One-command setup —
banto sync setup vercel:my-projectqueries the platform for env var names, matches Keychain entries, and configures sync. Falls back to known env var catalog only with--guess - AI agent integration — tell Claude or ChatGPT "sync my secrets to Vercel" and it's done. Agents orchestrate; humans provide values via browser popup. Values never enter the chat
- Pre-push validation — health-check keys against 6 provider endpoints (OpenAI, Anthropic, Gemini, GitHub, Cloudflare, xAI) before pushing to catch invalid keys early
- Drift detection — SHA-256 fingerprints track Keychain changes vs. last push;
banto sync auditcatches when your deployed secrets are out of date - Budget guard — optional hold/settle pattern for LLM cost control. No budget = no key. Global, per-provider, and per-model limits
- Dynamic leases — acquire short-lived credentials with TTL, auto-revoke on expiry
- MCP server — native tool integration for Claude Code (stdio) and ChatGPT Connector (HTTP/SSE via tunnel). 14 tools (10 sync/validate/lease tools that return metadata only, plus 4 sealed-signing tools that return public keys/signatures — never secret values)
- Keychain-native storage — macOS Security framework via ctypes; secret values never appear in process arguments
- Web dashboard — localhost-only CRUD interface with CSRF protection (
banto sync ui) - Notifications — Slack, Microsoft Teams, Datadog Events, PagerDuty
- Zero dependencies — stdlib only; MCP server requires optional
mcppackage
33 platform sync drivers — Cloudflare, Vercel, AWS, GCP, Azure, and 28 more
Cloudflare, Vercel, AWS, GCP, Azure, Kubernetes, Docker, Heroku, Fly.io, Netlify, Render, Railway, Supabase, GitLab, GitHub Actions, CircleCI, Bitbucket, Terraform Cloud, Azure DevOps, Deno Deploy, Hasura, Laravel Forge, DigitalOcean, Alibaba, Tencent, Huawei, Naver, NHN, JD Cloud, Sakura, Volcengine, and more
🏮 Why banto?
Most secret managers require you to manually configure every secret in every platform. banto lets your AI agent do it:
You: "Deploy my secrets to my-project on Vercel."
Agent: → banto_sync_setup(platform="vercel", project="my-project")
✓ 3 keys matched from Keychain
✗ 1 key missing — opening browser for you to enter
→ banto_register_key(provider="database")
[Browser popup opens — you enter the value]
→ banto_sync_push()
4 secrets pushed to Vercel.
You: Done.
The agent orchestrates. The human provides values. Secret values never enter the chat.
This is possible because banto's architecture separates what to do (agent-safe metadata operations) from the actual secrets (Keychain + browser popup only).
Terminal output: sync setup
$ banto sync setup vercel:my-project --dry-run
BANTO SYNC SETUP — vercel:my-project
(dry run — no changes will be made)
MATCH OPENAI_API_KEY -> openai
MATCH ANTHROPIC_API_KEY -> anthropic
MATCH GITHUB_TOKEN -> claude-mcp-github
MISS DATABASE_URL (no Keychain match)
Would register 3 secret(s). Remove --dry-run to apply.
Note:
sync setupqueries the platform CLI (e.g.vercel env ls --project <name>) for env var names. If discovery returns nothing (auth issue, wrong project name, or empty project), the command fails closed and suggests re-running with--guessto fall back to a known env var catalog.
Terminal output: sync validate
$ banto sync validate --keychain
BANTO SYNC VALIDATE — Testing 4 key(s)
PASS openai: Key valid
PASS anthropic: Key valid
UNKNOWN xai: Cannot verify (403)
PASS github: Token valid
📋 Requirements
- macOS (uses Keychain for secret storage)
- Python 3.10+
- No external dependencies (MCP server:
pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git")
⚡ Quick start
1. Install
# banto is not published on PyPI yet. Install directly from the GitHub repository.
# uv (recommended)
uv tool install "git+https://github.com/allnew-llc/banto.git"
# pipx
pipx install "git+https://github.com/allnew-llc/banto.git"
# pip
pip install "git+https://github.com/allnew-llc/banto.git"
# From source
git clone https://github.com/allnew-llc/banto.git
cd banto
pip install -e .
# With MCP server support
pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"
2. Sync to a platform — one command
banto sync setup vercel:my-project # query platform + match Keychain
banto sync push # deploy to all targets
That's it. banto queries the platform for env var names, matches them to Keychain entries, and pushes.
Or just tell your AI agent:
"Sync my secrets to my-project on Vercel."
3. Register keys (if not already in Keychain)
banto register openai # opens browser popup — enter your key there
banto store openai # or paste at masked terminal prompt
4. Validate before pushing
banto sync validate --keychain # test all keys against provider APIs
banto sync push --validate # validate + push in one step
5. (Optional) Add budget control
banto init # create budget config
banto budget 100 # $100/month global limit
banto budget --provider openai 30 # $30/month for OpenAI
💻 CLI reference
Core commands (9)
| Command | Description |
|---|---|
banto status |
Budget status with per-provider/model breakdown |
banto budget [amount] |
View or set budget limits (global, provider, model) |
banto profile [name] |
Show or set the active model profile (quality/balanced/budget) |
banto store <provider> |
Store an API key in Keychain (terminal prompt) |
banto register [provider] |
Open browser popup to store an API key |
banto delete <provider> |
Delete an API key from Keychain |
banto list |
List stored keys and budget summary |
banto check <model> ... |
Dry-run budget check (--tokens, --n, --seconds, --quality, --size) |
banto init |
Copy default config to ~/.config/banto/ |
Sync commands (26)
| Command | Description |
|---|---|
banto sync setup <plat:proj> |
Query platform for env vars + match Keychain entries (--dry-run, --guess) |
banto sync init |
Create default sync.json config |
banto sync status |
Sync status matrix (secrets x targets) |
banto sync classify |
Group secrets by rotation strategy |
banto sync incident-report |
Prioritize secrets for incident response |
banto sync vercel-inventory |
Read-only Vercel env inventory without values |
banto sync push [name] |
Push secrets from Keychain to targets (--validate for pre-push check) |
banto sync add <name> |
Add a new secret (--env, --target platform:project) |
banto sync import-keychain <name> |
Import an existing Keychain item into the configured sync account without printing the value |
banto sync google-api-key <name> |
Issue a Google API key and propagate it safely |
banto sync openai-service-account <name> |
Issue an OpenAI service account key and propagate it safely |
banto sync openai-service-accounts |
List OpenAI service accounts without exposing key values |
banto sync openai-revoke-service-account |
Revoke an OpenAI service account |
banto sync xai-api-key <name> |
Issue an xAI runtime API key using a management key |
banto sync browser-batch <plan.json> |
Run multiple closed-loop browser rotations from one metadata-only plan |
banto sync browser-record <name> |
Record a provider console flow into a reusable browser recipe without writing secret values |
banto sync browser-issue <name> |
Issue a key through a local browser recipe, then store and sync without printing the value |
banto sync browser-revoke <name> |
Revoke a browser-managed key by provider key id/label after replacement is live |
banto sync quicktrust-credential <name> |
Issue a QuickTrust API key or webhook secret through a dashboard browser recipe |
banto sync cloudflare-account-token <name> |
Issue a Cloudflare Account API token and propagate it without printing the value |
banto sync stripe-webhook-endpoint <name> |
Create a Stripe webhook endpoint and propagate its signing secret |
banto sync propagate <name> |
Store + sync a replacement value from a trusted local command |
banto sync rotate <name> |
Rotate a secret interactively or via --from-cli '<command>' |
banto sync audit |
Drift detection: existence, fingerprint, file mismatch, staleness (--max-age-days N) |
banto sync validate |
Test keys against provider endpoints (--keychain, --dry-run) |
banto sync history <name> |
Show version history with fingerprints |
banto sync run [--env E] -- <cmd> |
Run a command with secrets injected as env vars |
banto sync export |
Export secrets in env/json/docker format (--format, --env) |
banto sync import <file> |
Import secrets from .env or .json file |
banto sync ui [--port N] |
Launch localhost web dashboard (default port 8384) |
Lease commands (5)
| Command | Description |
|---|---|
banto lease acquire <name> |
Acquire a short-lived credential (--cmd, --revoke-cmd, --ttl) |
banto lease get <lease_id> |
Retrieve credential value (stdout, for piping) |
banto lease revoke <lease_id> |
Explicitly revoke a lease |
banto lease list |
Show active leases with remaining TTL |
banto lease cleanup |
Revoke all expired leases |
ChatGPT command (1)
| Command | Description |
|---|---|
banto chatgpt connect |
Start MCP HTTP server + tunnel, print ChatGPT Connector URL (--ngrok or --cloudflared) |
All commands support --json for machine-readable output.
🤖 MCP server
banto exposes an MCP server so AI agents can orchestrate secret management. Agents never receive secret values — sync/validate/lease tools return metadata only, and sealed-signing tools return only public keys or signatures.
Claude Code
Add to your .mcp.json:
{
"mcpServers": {
"banto": {
"command": "banto-mcp",
"args": []
}
}
}
Requires the optional MCP dependency:
pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"
ChatGPT
banto chatgpt connect
This starts the MCP server in HTTP mode, opens a tunnel (ngrok or cloudflared), and prints a secure Connector URL with a capability token. Paste the URL into ChatGPT's Connector settings.
Developer Mode only. banto is designed for use as a local ChatGPT Connector, not for public App Store submission (OpenAI's submission guidelines prohibit collecting API keys). The tunnel URL contains a random token — treat it like a password. MCP request/response traffic is proxied through the tunnel provider (ngrok or Cloudflare); secret values are never included in responses, but metadata (secret names, sync status) may transit.
Transport modes
banto-mcp # stdio (Claude Code)
banto-mcp --transport sse --port 8385 # SSE (dev)
banto-mcp --transport http --port 8385 # HTTP (production / ChatGPT)
Available tools
| Tool | Purpose | Notes |
|---|---|---|
banto_sync_status |
Show sync matrix (secrets x targets) | Read-only |
banto_sync_push |
Push secrets to cloud targets | Modifies cloud state |
banto_sync_audit |
Check drift and staleness | Read-only |
banto_validate |
Validate keys in sync.json | Sends keys to provider APIs |
banto_validate_keychain |
Scan + validate all Keychain keys | Sends keys to provider APIs |
banto_budget_status |
Budget breakdown | Read-only |
banto_register_key |
Open browser popup for key entry | Human enters value |
banto_lease_list |
List active leases | Read-only |
banto_lease_cleanup |
Revoke expired leases | Modifies Keychain |
banto_sync_setup |
Discover platform env vars and match Keychain entries | Queries cloud platforms |
banto_sealed_create_key |
Create a Secure Enclave–sealed signing key | Private key never leaves the enclave |
banto_sealed_export_pubkey |
Export a sealed key's public key | Read-only |
banto_sealed_sign |
Sign a payload with a sealed key | Touch ID / passcode prompt (user-presence keys) |
banto_sealed_list |
List sealed keys | Read-only |
All tools include OpenAI-compatible annotations (readOnlyHint, destructiveHint, openWorldHint).
🔒 Security
- ctypes Keychain access —
store()andget()call macOS Security framework directly (SecKeychainAddGenericPassword / SecKeychainFindGenericPassword). No subprocess, no argv exposure - stdin-based sync drivers — all 33 drivers pass secrets via stdin pipe, tempfile (0600), or
curl -K -/-d @file. Secret values never appear in process arguments - CSRF protection on web UI — per-session token required on all POST endpoints, Origin header validation, Content-Type enforcement
- Capability URLs for ChatGPT —
banto chatgpt connectgenerates a random path token; the URL is the bearer credential - Fail-closed history —
record()returnsNoneif Keychain write fails; no metadata saved for broken versions - Browser registration popup — localhost-only (127.0.0.1), random port, single-use; value never echoed back
- Validate is opt-in —
banto sync validate --keychainrequires explicit flag;--dry-runavailable - Lease credential isolation — lease values stay in Keychain;
lease-state.jsoncontains only metadata
Threat model
banto protects secrets from exposure through process arguments and standard logging. It does not protect against:
- Agents with unrestricted shell access — they can query macOS Keychain directly via
securityCLI - Kernel-level inspection — OS audit subsystems or filesystem-level access can observe tempfiles before deletion
- Tunnel provider visibility —
banto chatgpt connectroutes metadata (secret names, sync status) through ngrok or Cloudflare
For defense-in-depth: restrict shell access in your agent runtime, and consider OS-level access controls for sensitive environments.
sync export and sync run intentionally materialize secrets into environment variables or stdout for interoperability. Do not use these in agent contexts.
🐍 Python API
With budget gating
from banto import SecureVault, BudgetExceededError, KeyNotFoundError
vault = SecureVault(caller="my_app", budget=True)
try:
key = vault.get_key(
model="gpt-4o",
input_tokens=1000,
output_tokens=500,
)
response = openai.chat.completions.create(
model="gpt-4o",
messages=[...],
api_key=key,
)
vault.record_usage(
model="gpt-4o",
input_tokens=response.usage.prompt_tokens,
output_tokens=response.usage.completion_tokens,
provider="openai",
operation="chat",
)
except BudgetExceededError as e:
print(f"Over budget: ${e.remaining:.2f} remaining of ${e.limit:.2f}")
except KeyNotFoundError as e:
print(f"No key for {e.provider}. Run: banto store {e.provider}")
Without budget (key storage + sync only)
from banto import SecureVault
vault = SecureVault(caller="my_app", budget=False)
key = vault.get_key(provider="openai")
# Use key directly — no cost tracking
Auto-detect mode (default)
vault = SecureVault(caller="my_app")
# budget=None: auto-detects from config
# Enabled when monthly_limit_usd > 0, disabled otherwise
CostGuard (budget tracking without secret storage)
from banto import CostGuard
guard = CostGuard(caller="my_mcp")
hold_id = guard.hold_budget(model="dall-e-3", provider="openai",
n=1, quality="standard", size="1024x1024")
# ... call API ...
guard.settle_hold(hold_id, model="dall-e-3", n=1, provider="openai", operation="image")
🔌 Custom backends
The secret storage is pluggable via the SecretBackend protocol. Any object with get, store, delete, exists, and list_providers methods works. No inheritance required.
Environment variables
import os
from banto import SecureVault
class EnvVarBackend:
"""Read API keys from BANTO_KEY_<PROVIDER> environment variables."""
def get(self, provider: str) -> str | None:
return os.environ.get(f"BANTO_KEY_{provider.upper()}")
def store(self, provider: str, api_key: str) -> bool:
os.environ[f"BANTO_KEY_{provider.upper()}"] = api_key
return True
def delete(self, provider: str) -> bool:
return os.environ.pop(f"BANTO_KEY_{provider.upper()}", None) is not None
def exists(self, provider: str) -> bool:
return f"BANTO_KEY_{provider.upper()}" in os.environ
def list_providers(self, known_providers: list[str]) -> list[str]:
return [p for p in known_providers if self.exists(p)]
vault = SecureVault(caller="my_app", backend=EnvVarBackend())
1Password CLI
import json
import subprocess
from banto import SecureVault
class OnePasswordBackend:
"""Retrieve API keys from 1Password using the `op` CLI."""
def __init__(self, vault_name: str = "Private"):
self.vault_name = vault_name
def get(self, provider: str) -> str | None:
try:
result = subprocess.run(
["op", "item", "get", f"banto-{provider}",
"--vault", self.vault_name,
"--fields", "label=credential", "--format", "json"],
capture_output=True, text=True,
)
if result.returncode == 0:
return json.loads(result.stdout).get("value")
except (subprocess.SubprocessError, OSError):
pass
return None
def store(self, provider: str, api_key: str) -> bool: ...
def delete(self, provider: str) -> bool: ...
def exists(self, provider: str) -> bool:
return self.get(provider) is not None
def list_providers(self, known_providers: list[str]) -> list[str]:
return [p for p in known_providers if self.exists(p)]
vault = SecureVault(caller="my_app", backend=OnePasswordBackend())
In-memory (for testing)
from banto import SecureVault
class InMemoryBackend:
def __init__(self, keys: dict[str, str] | None = None):
self._store = dict(keys) if keys else {}
def get(self, provider: str) -> str | None:
return self._store.get(provider)
def store(self, provider: str, api_key: str) -> bool:
self._store[provider] = api_key
return True
def delete(self, provider: str) -> bool:
return self._store.pop(provider, None) is not None
def exists(self, provider: str) -> bool:
return provider in self._store
def list_providers(self, known_providers: list[str]) -> list[str]:
return [p for p in known_providers if p in self._store]
vault = SecureVault(
caller="test",
backend=InMemoryBackend({"openai": "test-key-12345"}),
)
See examples/06_custom_backend.py for complete implementations.
⚙️ Configuration
All config files live in ~/.config/banto/. Run banto init to create defaults.
config.json — Budget and provider settings
{
"monthly_limit_usd": 0,
"providers": {
"openai": {
"models": ["gpt-4o", "dall-e-3", "sora-2"]
},
"google": {
"models": ["gemini-3-pro-image-preview", "imagen-4.0-generate-001"]
}
}
}
Budget is in USD, enforced per calendar month. Default is 0 (budget disabled; keys returned without cost checks). Set a value to enable budget gating.
pricing.json — Model pricing table
{
"gpt-4o": {
"type": "per_token",
"input_per_1k": 0.0025,
"output_per_1k": 0.01
},
"dall-e-3": {
"type": "per_image",
"variants": {
"standard_1024x1024": 0.040,
"hd_1024x1024": 0.080
},
"fallback": 0.120
},
"sora-2": {
"type": "per_second",
"rate": 0.10
}
}
Prices are static. banto does not fetch pricing from provider APIs at runtime. Verify rates at each provider's official pricing page and update pricing.json accordingly.
sync.json — Multi-platform sync config
{
"version": 1,
"keychain_service": "my-vault",
"secrets": {},
"environments": {
"dev": {},
"prd": { "inherits": "dev" }
}
}
Created by banto sync init. Contains metadata only — no secret values.
❓ FAQ
What happens when the budget is exceeded?
get_key() raises BudgetExceededError and the API key is not returned. The agent has no code path to obtain the key through banto's API. The exception includes remaining and limit amounts for error messages.
How often should I update pricing.json?
Check provider pricing pages monthly. banto does not fetch pricing at runtime — no major provider offers a public pricing API. When a provider changes rates, edit ~/.config/banto/pricing.json. Incorrect rates mean inaccurate budget estimates, not security issues.
What if my agent crashes before calling record_usage()?
The hold stays reserved. This is by design (safe-side bias). The held amount continues to count against your budget until the month resets or the hold times out (default: 24 hours via hold_timeout_hours).
Can an agent bypass banto and read Keychain directly?
Yes, if the agent has direct shell access. banto protects against agents that use only get_key(). For defense-in-depth, restrict shell access in your agent runtime. See the Security section.
Does banto work on Linux or Windows?
Not yet. banto uses macOS Keychain (Security.framework via ctypes). The SecretBackend protocol allows pluggable backends — a libsecret (Linux) or DPAPI (Windows) backend is possible but not implemented.
Can I use banto without budget gating?
Yes. SecureVault(budget=False) returns keys directly without cost checks. Budget is opt-in since v4.0.0.
What's the performance overhead?
Negligible. banto runs entirely locally — Keychain access via ctypes is sub-millisecond, budget checks read a local JSON file with fcntl locking, and sync drivers call platform CLIs directly. There is no remote proxy, no daemon, and no network latency added to key retrieval.
⚖️ Disclaimer
banto is a budget management aid, not a guarantee against excessive API charges. The authors shall not be liable for any financial losses arising from inaccurate pricing tables, software defects, configuration errors, or agents that bypass banto's API. Users are solely responsible for monitoring actual API spend through each provider's billing dashboard and for keeping the pricing table up to date.
📄 License
Dual license:
- Personal use (free): Individuals may use, modify, and redistribute banto at no cost for personal, educational, and research purposes.
- Commercial use (paid): Organizations and companies require a commercial license from AllNew LLC.
See LICENSE for full terms.
Metadata
Release files for banto 5.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| banto-5.2.2.tar.gz | 244.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| banto-5.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 513.5 kB
Release files / banto-5.2.2.tar.gz
| Download URL | banto-5.2.2.tar.gz |
|---|---|
| Size | 244.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f7ef5a574a7a643f404941e80018a1c9a3a6f4eaf48de16fb0472e2e1ec6b95c
|
|
BLAKE2b-256 checksum How to use checksums |
5701b18c1311fca4616810ed56ab6d9c7c8f256a9e7c785a2953a7d12ae24f7b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / banto-5.2.2-py3-none-any.whl
| Download URL | banto-5.2.2-py3-none-any.whl |
|---|---|
| Size | 268.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
66a4c1b22e42a61d9add99019e6073b42f623abeb8bcc7260a4eb8cbb8b1632b
|
|
BLAKE2b-256 checksum How to use checksums |
fd11459ae28dd4d55ecd3020f473fadf07494e727fb73c1556fe9a4be535ec3a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|