{"id":50306217,"url":"https://github.com/moinsen-dev/vibe-secrets","last_synced_at":"2026-05-28T16:30:52.939Z","repository":{"id":352801378,"uuid":"1215957574","full_name":"moinsen-dev/vibe-secrets","owner":"moinsen-dev","description":"Local encrypted secret vault for AI-assisted development — one place for every API key; agents write .env without seeing raw values","archived":false,"fork":false,"pushed_at":"2026-04-21T06:38:51.000Z","size":113,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-21T08:31:06.279Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://github.com/moinsen-dev/vibe-secrets","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moinsen-dev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T12:26:07.000Z","updated_at":"2026-04-21T08:17:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/moinsen-dev/vibe-secrets","commit_stats":null,"previous_names":["moinsen-dev/vibe-secrets"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/moinsen-dev/vibe-secrets","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fvibe-secrets","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fvibe-secrets/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fvibe-secrets/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fvibe-secrets/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moinsen-dev","download_url":"https://codeload.github.com/moinsen-dev/vibe-secrets/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fvibe-secrets/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33617718,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-28T16:30:51.984Z","updated_at":"2026-05-28T16:30:52.927Z","avatar_url":"https://github.com/moinsen-dev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# vibe-secrets\n\nLocal encrypted secret vault for a solo developer working across many\nAI-integrated projects. One place for every API key. Coding agents populate\n`.env` files **without ever seeing the raw values**.\n\n## Why\n\nYou have 30 side projects. Each needs `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`,\nmaybe `GOOGLE_MAPS_KEY`, sometimes a project-specific Supabase URL. Copying\nkeys between folders leaks them into shell history, chat context, and stale\n`.env` files. Rotation across projects is a nightmare.\n\n`vibe-secrets` solves that, locally.\n\n## Design\n\n- **Local-first.** No network. No cloud sync. No telemetry.\n- **Encrypted at rest.** Fernet (AES-128-CBC + HMAC-SHA256) with a 256-bit\n  master key.\n- **Master key in the OS keychain.** Never written to disk by this tool.\n- **Scoped overlay.** Resolution order: `project:\u003cname\u003e:\u003cenv\u003e` → `global`.\n- **Agent-safe.** The `agent` subcommands never return raw values — they\n  write `.env` on disk at a path you choose and report only\n  `ok / missing / revoked / skipped`.\n- **Append-only audit log** of every read, inject, rotation.\n- **Cross-agent.** Emits rules for AGENTS.md, CLAUDE.md, Cursor, Copilot,\n  Windsurf.\n- **Small.** One CLI binary, one TUI, one installable Claude Code skill.\n\n## Install\n\n### Quickest — global user install via pipx\n\n```bash\npipx install .                     # from a clone\n# or:\npipx install vibe-secrets          # once published to PyPI\n```\n\n### With the Justfile\n\n```bash\njust install          # pipx install --force .\njust install-dev      # editable, for hacking on vibe-secrets itself\njust onboard          # install + bootstrap (vault + Claude skill)\n```\n\n### For development\n\n```bash\njust dev              # create .venv and install with dev deps\njust test             # run the suite (101 tests)\n```\n\n## New machine, zero to useful in one command\n\n```bash\nvibe-secrets bootstrap\n```\n\nCreates the vault (master key in the OS keychain), installs the Claude Code\nskill into `~/.claude/skills/vibe-secrets/`, and prints next steps. Add a\nproject path to also onboard it in the same call:\n\n```bash\nvibe-secrets bootstrap ~/projects/myapp\n```\n\nThen add a key the user-only way (the value is prompted, never echoed):\n\n```bash\nvibe-secrets add ANTHROPIC_API_KEY --scope global\n```\n\n## Typical flows\n\n### Greenfield — new project\n\n```bash\ncd ~/projects/newapp\nvibe-secrets setup .                        # writes .vault.yaml + rules + .gitignore\n# … write code that references env vars …\nvibe-secrets sync .                         # scan code, resolve from vault, write .env\n```\n\n### Brownfield — existing project with `.env` files\n\n```bash\ncd ~/projects/oldapp\nvibe-secrets setup .\nvibe-secrets import .                       # per-key prompts: which scope?\n# (or non-interactive:)\nvibe-secrets import . --yes \\\n  --default-scope \"project:oldapp:dev\" \\\n  --on-conflict rotate\nvibe-secrets sync .                         # .env now written from the vault\n```\n\n### Rotation across every project that uses a key\n\n```bash\nvibe-secrets rotate ANTHROPIC_API_KEY --scope global\nvibe-secrets fanout ANTHROPIC_API_KEY       # re-inject into every registered project\n```\n\n### Drift check without exposing values\n\n```bash\nvibe-secrets diff .                         # match / differ / missing / revoked / only_in_env\n```\n\n## Cross-agent rules\n\n`setup` writes onboarding rules for any assistant whose directory it finds.\nAuto-detected:\n\n| Target | Path | Trigger |\n|---|---|---|\n| AGENTS.md | project root | always |\n| CLAUDE.md | project root | always |\n| Cursor | `.cursor/rules/vibe-secrets.mdc` | `.cursor/` exists |\n| Copilot | `.github/copilot-instructions.md` | `.github/` exists |\n| Windsurf | `.windsurfrules` | `.windsurfrules` exists |\n\nForce a specific set:\n\n```bash\nvibe-secrets setup . --emit agents,cursor,copilot\nvibe-secrets setup . --emit all\n```\n\nEach target uses marker-delimited blocks (`\u003c!-- vibe-secrets:begin --\u003e` /\n`end`) and is fully idempotent — re-running `setup` updates the block in\nplace rather than duplicating.\n\n## Claude Code skill\n\n```bash\nvibe-secrets skill install          # → ~/.claude/skills/vibe-secrets/SKILL.md\nvibe-secrets skill status\nvibe-secrets skill uninstall\n```\n\nThe skill teaches Claude to use the vault correctly: never ask the user to\npaste API keys, never echo values, always use `vibe-secrets agent inject`\nto populate `.env`. Source of truth lives in\n`src/vibe_secrets/templates.py`.\n\n## Agent mode\n\nAI agents call the `agent` subcommands. Every one of them emits JSON with no\nsecret values. Set `VIBE_SECRETS_ACTOR=claude-code` (or `codex`, `cursor`,\netc.) so the audit log attributes correctly.\n\n```bash\nvibe-secrets agent status                     # { \"exists\": true, \"has_master\": true, … }\nvibe-secrets agent list-names [--scope S]     # [{\"name\":…, \"scope\":…, \"status\":…}, …]\nvibe-secrets agent scan \u003cpath\u003e                # [\"ANTHROPIC_API_KEY\", …]\nvibe-secrets agent inject \u003cpath\u003e [--env E]    # writes .env, returns summary (no values)\nvibe-secrets agent diff \u003cpath\u003e [--env E]      # structural only; no values\nvibe-secrets agent sync \u003cpath\u003e [--env E]      # scan + inject + update registry\nvibe-secrets agent setup \u003cpath\u003e               # onboard a project\nvibe-secrets agent fanout NAME                # re-inject after rotation\nvibe-secrets agent projects                   # list registered projects\n```\n\n## Full CLI reference\n\n### Lifecycle\n\n| Command | Purpose |\n|---|---|\n| `init` | Create the vault file + master key (stored in OS keychain) |\n| `status` | Vault path, existence, record counts |\n| `bootstrap [PROJECT]` | New-machine one-shot: init + install skill + optional setup |\n| `tui` | Launch the interactive vault manager |\n| `help` | Show the quickstart card |\n\n### Key operations\n\n| Command | Purpose |\n|---|---|\n| `add NAME [--scope S]` | Add a secret (value via stdin or prompt) |\n| `list [--scope S]` | List secrets (metadata only) |\n| `search PATTERN` | Glob-match names across all scopes |\n| `show NAME [--scope S]` | Metadata for one key (never the value) |\n| `reveal NAME [--scope S]` | Print the raw value — requires confirmation |\n| `copy NAME [--scope S]` | Copy the value to OS clipboard |\n| `rotate NAME [--scope S]` | Replace the value |\n| `revoke NAME [--scope S]` | Mark revoked (kept for metadata history) |\n| `delete NAME [--scope S]` | Hard-delete; irreversible |\n\n### Project operations\n\n| Command | Purpose |\n|---|---|\n| `setup PATH [--emit …]` | Onboard: `.vault.yaml` + rules + `.gitignore` + register |\n| `import PATH` | Read existing `.env*` files into the vault (interactive) |\n| `scan PATH` | Discover env-var names referenced in a project |\n| `inject PATH [--env E]` | Resolve + write `.env` (lower-level than `sync`) |\n| `sync PATH [--env E]` | scan + resolve + inject + update registry (overwrite by default) |\n| `diff PATH [--env E]` | Structural comparison `.env` vs vault — never prints values |\n| `fanout NAME` | Re-inject NAME into every registered project that uses it |\n| `projects` | List registered projects |\n\n### Integration (user-level)\n\n| Command | Purpose |\n|---|---|\n| `skill install [--force]` | Drop Claude Code skill into `~/.claude/skills/` |\n| `skill uninstall` | Remove it |\n| `skill status` | Report installation state |\n\n### Audit\n\n| Command | Purpose |\n|---|---|\n| `audit [--limit N] [--json]` | Tail the local audit log |\n\n### Scopes\n\n- `global` — available to every project unless overridden\n- `project:\u003cname\u003e:\u003cenv\u003e` — `\u003cenv\u003e` is a free string, typically `dev` / `prod` / `test`\n\n## Storage layout\n\n```\n~/.vibe-secrets/\n├── vault.enc           # encrypted JSON of records (0600)\n├── projects.json       # plain registry: paths ↔ names ↔ keys-per-env\n└── audit.log           # append-only JSONL\n```\n\nOverride the directory with `VIBE_SECRETS_HOME`. Override the Claude skills\ndirectory with `CLAUDE_SKILLS_HOME`. Declare the actor in audit entries with\n`VIBE_SECRETS_ACTOR`.\n\n## Security model\n\n- Master key: `cryptography.fernet` 256-bit key, stored in the OS keychain\n  (macOS Keychain, Linux Secret Service, Windows Credential Manager). For\n  automated tests only, set `VIBE_SECRETS_MASTER` to a base64 Fernet key to\n  bypass the keyring.\n- Vault at rest: encrypted with Fernet (AES-128-CBC for confidentiality,\n  HMAC-SHA256 for integrity). Any bit flip is detected — we have a test for\n  it.\n- File permissions: all vault-owned files are `0o600`, directories `0o700`.\n- Value exfiltration: `reveal` and `copy` require interactive confirmation\n  (or `--yes`). `agent` commands never emit values through any code path.\n- Tamper detection: modifying `vault.enc` out of band makes the next read\n  fail with `VaultError`.\n- Audit: every read, inject, rotation is appended to `audit.log` with\n  timestamp, actor, op, key, scope, project.\n\n## Non-goals\n\n- Not a team secret manager. No sharing, no sync.\n- Not a password manager for humans.\n- Not a cloud KMS replacement.\n- Not a code-scanner — `scan` only finds env-var names, not the values.\n\n## Project structure\n\n```\nsrc/vibe_secrets/\n├── config.py       runtime paths\n├── models.py       KeyRecord + scope/name validation\n├── keystore.py     OS-keychain master management\n├── vault.py        encrypted store, atomic writes\n├── audit.py        append-only JSONL log\n├── resolver.py     overlay scope resolution\n├── scanner.py      env-var name discovery\n├── envwriter.py    .env merge (preserve / overwrite)\n├── registry.py     plain-JSON project registry\n├── projectops.py   setup / import / sync / diff / fanout\n├── templates.py    agent rules (single source of truth)\n├── installer.py    Claude Code skill install\n├── clipboard.py    pbcopy / xclip / wl-copy / clip\n├── cli.py          click CLI\n└── tui.py          Textual TUI\n\nclaude/skills/vibe-secrets/SKILL.md  # regenerated from templates.py\ntests/               101 tests total\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoinsen-dev%2Fvibe-secrets","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoinsen-dev%2Fvibe-secrets","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoinsen-dev%2Fvibe-secrets/lists"}