senzu
Stop manually copy-pasting secrets from GCP Secret Manager into .env files like an animal. Senzu syncs secrets between GCP Secret Manager and local .env files, tracks where every key came from, and won't let you blow up production by pushing stale local changes over remote ones.
It's a CLI + Python library for teams using GCP Secret Manager who need their secrets to actually stay in sync — across multiple environments, multiple secrets, multiple people.
Why it's actually sick
Most teams end up with a shared .env in a private Slack channel, a Notion doc, or some other nightmare. If you're using Secret Manager, you're at least in the right place — but the workflow is still garbage. You open the GCP console, copy values one by one, paste them into a file you hope you don't accidentally commit.
Senzu fixes this:
-
senzu pull— fetches all your configured secrets into a local.envfile in one command. Handles JSON and dotenv formats automatically. Works across multiple secrets per environment. If your local file already has keys that aren't in remote yet, they're preserved — remote wins on conflicts, but local-only keys survive. Use--overwriteif you want the remote to fully replace your local file. -
senzu push— pushes local changes back to Secret Manager. But here's the thing: it actually checks if someone else changed the remote since you last pulled. If they did, it blocks you. -
senzu diff— see exactly what's different between your local file and what's in Secret Manager, without touching anything. Pipe it into CI, use it in code review, whatever. -
Lock file — after a pull, Senzu writes
senzu.lockwhich tracks which key came from which secret and which project. This is what makes push safe. It knows exactly where to send each key back, even if you're pulling from 5 different secrets into one.env. -
senzu import— already have a.envfile and want to get into Secret Manager without touching the GCP console?senzu import dev --from .envcreates the secret if it doesn't exist, pushes the keys, and writessenzu.lockso you're immediately ready to pull/push. If the secret already has data, it merges — your local keys win. Before confirming, it shows you exactly which keys are new, which are changed, and which are unchanged, so you know what you're actually pushing. If nothing has changed, it exits early. -
Multiple environments —
dev,staging,prod, whatever you want. Each one can have its own GCP project, its own secrets, its own local file.senzu pull devorsenzu pull prod, no config flags needed. -
SenzuSettings— drop-in Pydantic BaseSettings subclass. Automatically reads the right.envfile based on yourENVvar, parses nested JSON objects into proper Python dicts/lists, and falls back to reading directly from Secret Manager in Cloud Run by just settingSENZU_USE_SECRET_MANAGER=true. -
senzu generate— auto-generates a typed Pydantic settings class from your actual secrets. You never have to manually writeapi_key: strfor every field again.
Install
pip install senzu
# or
uv add senzu
Requires Python 3.10+. You'll need GCP credentials set up — either gcloud auth application-default login locally or a service account in prod.
Setup
Run the init wizard in your project root:
senzu init
# or skip the prompts entirely with flags
senzu init --project my-gcp-project --env dev --file .env.dev --secret app-env
This creates senzu.toml and updates .gitignore to exclude your .env.* files. You don't want those committed. The --env flag controls the environment name (defaults to dev) — useful if you want to scaffold a non-dev env first.
Or write senzu.toml yourself:
[envs.dev]
project = "my-gcp-project-dev"
file = ".env.dev"
secrets = [
{ secret = "app-env" },
{ secret = "db-creds", format = "json" },
]
[envs.prod]
project = "my-gcp-project-prod"
file = ".env.prod"
secrets = [
{ secret = "app-env-prod" },
]
Each secret in the secrets array is fetched and merged into the local file. If you want the entire secret stored as a single env var rather than expanded into individual keys, use type = "raw". env_var is required — it sets the env var name the value is written under:
secrets = [
# Scalar value (e.g. a webhook secret string)
{ secret = "stripe-webhook-secret", type = "raw", env_var = "STRIPE_WEBHOOK_SECRET" },
# JSON object stored whole — useful when you want one dict, not individual keys
{ secret = "firebase-sdk", type = "raw", env_var = "FIREBASE_CREDS" },
]
For the JSON case, Senzu stores it single-quoted in the .env file (FIREBASE_CREDS='{"type":"service_account",...}'). Declare the field as dict in SenzuSettings and it's automatically deserialized.
Cross-project secrets
A secret can pull from a different GCP project than the environment default. This is useful for shared infrastructure secrets owned by a central project:
[envs.prod]
project = "my-app-prod"
file = ".env.prod"
secrets = [
{ secret = "app-env-prod" },
{ secret = "datadog-api-key", project = "shared-infra" },
]
When multiple secrets share a key name, the last secret listed wins. Senzu will emit a warning so you know it happened.
Usage
# Bootstrap — import an existing .env into Secret Manager for the first time
senzu import dev --from .env
senzu import dev --from .env --secret app-env # skip interactive routing, send all keys to this secret
senzu import dev --from .env --keys DB_URL,DB_PASSWORD # specific keys only
senzu import dev --from .env --format json # write as JSON instead of dotenv
# Pull secrets to local .env files
senzu pull # all environments defined in senzu.toml
senzu pull dev # specific environment only
senzu pull dev --overwrite # fully replace local file with remote (discards local-only keys)
# See what's different between local and remote
senzu diff # all environments
senzu diff dev # specific environment only
# Push local changes back to Secret Manager
senzu push # all environments (prompts for confirmation per env)
senzu push dev # specific environment
senzu push dev --force # skip confirmation even if remote has unretrieved changes
# Show all configured environments, their secrets, GCP projects, and local file status
senzu status
# Generate a typed Pydantic settings class from your secrets
senzu generate dev --out settings.py
Using in Python
If you have a Python app and want type-safe settings without the manual config:
from senzu import SenzuSettings
class Settings(SenzuSettings):
database_url: str
api_key: str
firebase_creds: dict # see note on nested JSON below
settings = Settings()
GCP secrets often store nested JSON objects (service account keys, connection configs, etc.) that can't be written as raw dotenv values. Senzu encodes these as single-quoted strings in the .env file:
// Secret Manager — app-env secret value
{
"database_url": "postgres://...",
"api_key": "sk-...",
"firebase_creds": { "type": "service_account", "project_id": "my-app" }
}
# .env.dev — what Senzu writes after senzu pull
DATABASE_URL=postgres://...
API_KEY=sk-...
FIREBASE_CREDS='{"type":"service_account","project_id":"my-app"}'
When you declare the field as dict (or list) in SenzuSettings, Senzu automatically deserializes it — so settings.firebase_creds is already a Python dict, not a string.
Senzu reads ENV or SENZU_ENV to figure out which environment you're in, finds the right .env file from senzu.toml, and loads it. In Cloud Run or any environment where you don't have a file, set SENZU_USE_SECRET_MANAGER=true and it reads directly from Secret Manager.
The lock file
After senzu pull, you'll have a senzu.lock file. This is how Senzu knows which of your 40 env vars came from which of your 5 secrets. Don't delete it — push won't work without it. Commit it — it contains no secret values, just routing metadata (which key lives in which secret), and your teammates need it to push without doing a redundant pull first.
Auth
Senzu uses the standard GCP auth chain via google-cloud-secret-manager. Locally, run:
gcloud auth application-default login
In CI/CD or Cloud Run, use a service account with Secret Manager Secret Accessor role on the relevant secrets.
Development
With Nix (recommended)
The repo uses uv2nix to provide a fully reproducible dev environment. Dependencies are pinned in uv.lock.
nix develop
This drops you into a shell with the right Python version, all deps installed, and senzu itself available as an editable install — changes to the source are reflected immediately without reinstalling. gcloud is also available.
To run the CLI directly without entering a shell:
nix run
Without Nix
uv sync
uv run senzu --help
Or with a standard virtualenv:
python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
Running tests
pytest
# or inside nix develop:
pytest tests/
Metadata
Release files for senzu 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| senzu-0.3.1.tar.gz | 86.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| senzu-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 114.9 kB
Release files / senzu-0.3.1.tar.gz
| Download URL | senzu-0.3.1.tar.gz |
|---|---|
| Size | 86.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
350a20cd9e71832845282df08dcae099b9a89d0d922060414710be8481eeb26d
|
|
BLAKE2b-256 checksum How to use checksums |
0871475d3a343cceb3b255a8c5b5759fda0c2296e0b9359b80d4c61d88bb2388
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 31, 2026.
Transparency logRelease files / senzu-0.3.1-py3-none-any.whl
| Download URL | senzu-0.3.1-py3-none-any.whl |
|---|---|
| Size | 28.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a4d630c88c2c179b03fbaa06526bfda74009c814549d4eee1017af2392e5799e
|
|
BLAKE2b-256 checksum How to use checksums |
2bd1ffbfaac168e3f7d3d8cf4ee66af6dc1b1131e55b9f6a177b481a71065b36
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 31, 2026.
Transparency logSecurity for senzu 0.3.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for senzu-0.3.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.