kv-secrets 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+
9
+ # Test artifacts
10
+ *.db
11
+ *.db-shm
12
+ *.db-wal
13
+ *.log
14
+
15
+ # Secrets — NEVER commit these
16
+ .env
17
+ .secrets/
18
+
19
+ # IDE
20
+ .vscode/
21
+ .idea/
22
+ .cursor/
23
+
24
+ # Server — private (not open source)
25
+ kv_server/
26
+
27
+ # Reviewer workspace — internal only
28
+ reviewer/
29
+
30
+ # Deploy artifacts — private
31
+ deploy/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kv-secrets contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: kv-secrets
3
+ Version: 0.1.0
4
+ Summary: Encrypted secrets management for developers and AI agents
5
+ Project-URL: Homepage, https://github.com/sathi/kv-secrets
6
+ Project-URL: Documentation, https://github.com/sathi/kv-secrets#readme
7
+ Project-URL: Issues, https://github.com/sathi/kv-secrets/issues
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: cli,devtools,encryption,mcp,secrets
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Security
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: cryptography>=41.0.0
25
+ Provides-Extra: server
26
+ Requires-Dist: aiosqlite>=0.20.0; extra == 'server'
27
+ Requires-Dist: asyncpg>=0.29.0; extra == 'server'
28
+ Requires-Dist: bcrypt>=4.0.0; extra == 'server'
29
+ Requires-Dist: fastapi>=0.110.0; extra == 'server'
30
+ Requires-Dist: python-jose[cryptography]>=3.3.0; extra == 'server'
31
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.0; extra == 'server'
32
+ Requires-Dist: stripe>=8.0.0; extra == 'server'
33
+ Requires-Dist: uvicorn>=0.27.0; extra == 'server'
@@ -0,0 +1,162 @@
1
+ # kv
2
+
3
+ Encrypted secrets management for developers and AI coding agents.
4
+
5
+ **kv** encrypts your API keys, database URLs, and tokens with ChaCha20-Poly1305 — then lets your AI editor (Cursor, Claude Code, VS Code) use them safely through MCP.
6
+
7
+ ```
8
+ pip install kv-secrets
9
+ ```
10
+
11
+ ## Why kv?
12
+
13
+ Your AI coding agent needs your API keys to run and test code. But pasting secrets into chat is dangerous — they end up in logs, training data, and prompt history.
14
+
15
+ **kv keeps secrets encrypted on disk and injects them only at runtime.** Your AI agent never sees the plaintext values.
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # Initialize in your project
21
+ kv init
22
+
23
+ # Store secrets
24
+ kv set API_KEY sk-live-abc123
25
+ kv set DATABASE_URL postgres://user:pass@host/db
26
+
27
+ # Run commands with secrets injected
28
+ kv run -- python app.py
29
+ kv run -- npm start
30
+
31
+ # List keys (values stay hidden)
32
+ kv ls
33
+ ```
34
+
35
+ ## MCP Integration (AI Editors)
36
+
37
+ kv includes an MCP server so Cursor, Claude Code, and VS Code Copilot can manage secrets without ever seeing them.
38
+
39
+ ```bash
40
+ # Auto-configure your editor (one command)
41
+ kv setup cursor
42
+ kv setup claude-code
43
+ kv setup vscode
44
+ ```
45
+
46
+ That's it. Your AI agent now has access to these tools:
47
+
48
+ | Tool | Profile | What it does |
49
+ |------|---------|-------------|
50
+ | `kv_status` | safe | Check if kv is initialized |
51
+ | `kv_envs` | safe | List environments (dev, staging, prod) |
52
+ | `kv_list` | safe | List secret names (no values) |
53
+ | `kv_run` | safe | Run commands with secrets injected |
54
+ | `kv_set` | mutate | Store a secret (opt-in) |
55
+ | `kv_rm` | mutate | Remove a secret (opt-in) |
56
+ | `kv_get` | reveal | Read a secret value (opt-in) |
57
+
58
+ **Security profiles** control what your AI can do:
59
+
60
+ ```bash
61
+ # Default: safe only (list + run)
62
+ kv setup cursor
63
+
64
+ # Allow storing secrets
65
+ kv setup cursor --allow-mutate
66
+
67
+ # Allow reading values (use with caution)
68
+ kv setup cursor --allow-reveal
69
+ ```
70
+
71
+ ## How It Works
72
+
73
+ ```
74
+ You: "run the tests"
75
+
76
+ AI Agent kv
77
+ | |
78
+ |-- kv_run ["pytest"] --------->|
79
+ | |-- decrypt secrets
80
+ | |-- inject into env
81
+ | |-- subprocess.run(pytest)
82
+ | |-- return exit code only
83
+ |<-- "exit code: 0" -----------|
84
+
85
+ Secret values never appear in the chat.
86
+ ```
87
+
88
+ ## Encryption
89
+
90
+ - **Algorithm:** ChaCha20-Poly1305 (AEAD)
91
+ - **Key derivation:** BLAKE2b with environment name as context
92
+ - **Storage:** Binary `.enc` files — safe to commit to git
93
+ - **Master key:** Stored in `.secrets/key` — add to `.gitignore`
94
+
95
+ ## Multi-Environment
96
+
97
+ ```bash
98
+ # Switch environments
99
+ kv env staging
100
+ kv env prod
101
+
102
+ # Set per-environment secrets
103
+ kv set API_KEY sk-live-prod --env prod
104
+ kv set API_KEY sk-test-dev --env dev
105
+
106
+ # Run in specific environment
107
+ kv run --env prod -- python deploy.py
108
+ ```
109
+
110
+ ## All Commands
111
+
112
+ ```
113
+ kv init Initialize kv in current project
114
+ kv set KEY VAL Store an encrypted secret
115
+ kv get KEY Decrypt and print a secret
116
+ kv ls List secret names
117
+ kv rm KEY Remove a secret
118
+ kv run -- CMD Run command with secrets in env
119
+ kv envs List environments
120
+ kv env NAME Switch default environment
121
+ kv export Export as .env format
122
+ kv import FILE Import from .env file
123
+ kv status Show project info
124
+ kv setup EDITOR Configure MCP for your editor
125
+ kv version Print version
126
+ ```
127
+
128
+ ## Key Sharing
129
+
130
+ Share the master key with teammates out-of-band (Signal, 1Password, etc.):
131
+
132
+ ```bash
133
+ # Export key as portable string
134
+ kv export-key
135
+ # kvkey_dGhpcyBpcyBhIHRlc3Qga2V5...
136
+
137
+ # Teammate imports it
138
+ kv import-key kvkey_dGhpcyBpcyBhIHRlc3Qga2V5...
139
+ ```
140
+
141
+ The `.enc` files are safe to commit — without the key, they're just noise.
142
+
143
+ ## Security Model
144
+
145
+ | What | Where | Safe to share? |
146
+ |------|-------|---------------|
147
+ | `.enc` files | Project dir | Yes (commit to git) |
148
+ | Master key | `.secrets/key` | No (share via secure channel) |
149
+ | Plaintext | Never on disk | N/A |
150
+
151
+ - Zero-knowledge design — even the cloud sync server (coming soon) never sees plaintext
152
+ - Encrypted blobs are opaque to anyone without the key
153
+ - `kv run` uses `stdout=DEVNULL` — your AI agent only sees exit codes, never output
154
+
155
+ ## Requirements
156
+
157
+ - Python 3.10+
158
+ - `cryptography` (installed automatically)
159
+
160
+ ## License
161
+
162
+ MIT
@@ -0,0 +1,139 @@
1
+ # kv — Design Document
2
+
3
+ ## What
4
+ Encrypted secrets management CLI for developers. Local-first, CLI-first, designed to grow into a paid team product.
5
+
6
+ ## Why
7
+ - Dev teams share `.env` files through Slack/email (insecure)
8
+ - dotenvx = encryption only, no team features
9
+ - Doppler = $21/user, cloud-only
10
+ - Vault = overkill for small teams
11
+ - **Gap**: Simple CLI + real encryption + multi-environment + future team sync
12
+
13
+ ## Name Choice: `kv`
14
+ - Two letters, universally understood (key-value)
15
+ - Reads naturally: `kv set`, `kv get`, `kv run`
16
+ - Rejected: `stash` (git collision), `seal` (vague), `crypt` (scary), `vault` (taken)
17
+
18
+ ## Dependency Choice
19
+ - **`cryptography`** (single dependency) — Python stdlib has no symmetric cipher
20
+ - ChaCha20-Poly1305 AEAD — same as WireGuard, TLS 1.3
21
+ - Industry-standard, audited, constant-time
22
+
23
+ ## Encryption Architecture
24
+ - **Master key**: 32 random bytes, stored as base64url in `.secrets/key`
25
+ - **Per-env keys**: Derived via BLAKE2b keyed hash (stdlib) — no per-env key storage
26
+ - **Cipher**: ChaCha20-Poly1305 with 12-byte random nonce + environment name as AAD
27
+ - **Storage**: Entire secrets dict encrypted as single JSON blob (no key name leakage)
28
+
29
+ ## Storage Format
30
+ ```
31
+ .secrets/
32
+ key # Master key. NEVER committed.
33
+ config.json # Project metadata
34
+ dev.enc # KV\x00 + version(1) + nonce(12) + ciphertext+tag
35
+ staging.enc
36
+ .gitignore # Auto-generated
37
+ ```
38
+
39
+ ## CLI Interface
40
+ ```
41
+ kv init Initialize project
42
+ kv set KEY=VALUE [-e ENV] Set a secret
43
+ kv set KEY [-e ENV] Set interactively (hidden input)
44
+ kv get KEY [-e ENV] Decrypt and print
45
+ kv ls [-e ENV] [--reveal] List keys
46
+ kv run COMMAND [-e ENV] Run with secrets injected
47
+ kv export [-e ENV] [-o FILE] Export as .env
48
+ kv import FILE [-e ENV] Import .env file
49
+ kv rm KEY [-e ENV] Remove a secret
50
+ kv envs List environments
51
+ kv env create NAME Create environment
52
+ kv env copy SRC DST Copy between environments
53
+ kv status Project status
54
+ ```
55
+
56
+ ## Key Decisions
57
+ 1. **Single blob per env** — no key name leakage, atomic reads/writes
58
+ 2. **BLAKE2b for key derivation** — master key is high-entropy, no slow KDF needed
59
+ 3. **`find_project_root()` walks up dirs** — works from subdirectories (like git)
60
+ 4. **Atomic writes** — tmp + `os.replace()` prevents corruption
61
+ 5. **`.enc` safe to commit, `key` is not** — enables future git-based team sharing
62
+
63
+ ## Monetization
64
+ - **Free**: Local CLI (12 commands, full encryption, multi-environment)
65
+ - **$15/team/month**: Cloud sync, team management, CI/CD tokens, basic RBAC
66
+ - **$99/team/month**: Rotation automation, advanced RBAC, audit logs
67
+ - Undercuts Doppler ($21/user x 5 = $105/mo) with per-team pricing
68
+
69
+ ## Package Structure
70
+ ```
71
+ kv/ # CLI package
72
+ __init__.py # Version
73
+ __main__.py # Entry point, Windows fixes
74
+ cli.py # argparse (20 commands), dispatch, ANSI output
75
+ cli_remote.py # Remote command handlers (login, push/pull, team, token)
76
+ crypto.py # ChaCha20 encrypt/decrypt, BLAKE2b derivation, kvkey_ export/import
77
+ store.py # .enc file format, secret CRUD, raw blob read/write, atomic writes
78
+ env.py # Secret injection, .env import/export
79
+ config.py # Project init, config.json, find_project_root, sync state
80
+ auth.py # Session management (~/.kv/session.json), auth headers
81
+ remote.py # HTTP client (urllib.request), all API calls
82
+ sync.py # Push/pull orchestration, hash computation, conflict detection
83
+
84
+ kv_server/ # API server package
85
+ __init__.py # Version
86
+ __main__.py # uvicorn entry (python -m kv_server)
87
+ app.py # FastAPI app factory, CORS, lifespan
88
+ config.py # Settings from env vars
89
+ database.py # SQLAlchemy models (5 tables), engine, session
90
+ models.py # Pydantic request/response schemas
91
+ auth.py # JWT, bcrypt, API token validation
92
+ billing.py # Stripe integration (checkout, portal, webhooks)
93
+ middleware.py # Rate limiting placeholder
94
+ routes/
95
+ __init__.py # Route registration
96
+ auth_routes.py # /auth/register, /auth/login, /auth/refresh
97
+ sync_routes.py # /sync/push, /sync/pull, /sync/status
98
+ team_routes.py # /team/create, /team/invite, /team/members, /team/revoke
99
+ token_routes.py # /tokens/create, /tokens/list, /tokens/revoke
100
+ billing_routes.py # /billing/status, /billing/checkout, /billing/portal, /billing/webhook
101
+ ```
102
+
103
+ ## Build Status — Local CLI (v0.1)
104
+ - [x] Package entry (__init__, __main__)
105
+ - [x] Encryption engine (crypto.py) — ChaCha20-Poly1305, BLAKE2b derivation, key I/O
106
+ - [x] Project config (config.py) — init, find_project_root, env registry
107
+ - [x] Secret store (store.py) — .enc format, CRUD, atomic writes, tamper detection
108
+ - [x] CLI commands (cli.py) — 12 local commands, ANSI output
109
+ - [x] Env injection + import/export (env.py) — subprocess injection, .env parsing
110
+ - [x] Full test suite — 20/20 local tests passing
111
+ - [x] README.md
112
+
113
+ ## Build Status — Paid Tier (v0.2)
114
+ - [x] Server foundation (kv_server/) — FastAPI, SQLAlchemy, 5 DB tables, JWT auth
115
+ - [x] CLI auth (kv/auth.py) — login/signup/logout, session at ~/.kv/session.json
116
+ - [x] HTTP client (kv/remote.py) — stdlib urllib.request, all API calls
117
+ - [x] Push/pull sync (kv/sync.py) — encrypted blob transfer, hash-based conflict detection
118
+ - [x] Team management — create, invite, members, revoke, kvkey_ key sharing
119
+ - [x] CI/CD tokens — scoped API tokens (pull/push/admin), env restriction, expiry
120
+ - [x] Stripe billing — checkout, portal, webhook handler, trial period
121
+ - [x] CLI commands (cli.py) — expanded to 20 commands total
122
+ - [x] End-to-end test suite — 20/20 integration tests passing
123
+
124
+ ## Paid Tier Architecture
125
+ - **Zero-knowledge**: Server stores raw .enc blobs, never sees plaintext
126
+ - **Auth**: JWT (1h access + 30d refresh) for CLI, API tokens (kvt_...) for CI/CD
127
+ - **Key sharing**: `kvkey_` prefix + base64url master key, shared out-of-band
128
+ - **Sync**: Client reads .enc blob -> base64 -> POST /sync/push, reverse for pull
129
+ - **Conflict resolution**: Last-write-wins with version numbers + blob hashes
130
+ - **Billing**: $15/team/month, 14-day trial, Stripe Checkout + webhooks
131
+ - **Server deps**: fastapi, uvicorn, sqlalchemy[asyncio], aiosqlite, python-jose, bcrypt, stripe
132
+
133
+ ## Gotchas Found During Build
134
+ - `argparse.REMAINDER` field must not collide with subparser `dest` field name — renamed to `cmd`
135
+ - `subprocess.run` needs list args (not joined string) to preserve quoting for `python -c "..."`
136
+ - Optional flags (`-e`, `-q`) MUST come before REMAINDER positional in parser definition
137
+ - passlib + bcrypt compatibility broken on Python 3.12 — use `bcrypt` directly instead
138
+ - SQLite returns naive datetimes — normalize to UTC before comparing with aware datetimes
139
+ - Pydantic `EmailStr` requires `email-validator` package — use plain `str` to avoid extra dep
@@ -0,0 +1,175 @@
1
+ # kv
2
+
3
+ Encrypted secrets management for developers. Set, get, inject — no plaintext `.env` files.
4
+
5
+ ```
6
+ pip install cryptography
7
+ ```
8
+
9
+ ```
10
+ python -m kv init
11
+ python -m kv set DATABASE_URL=postgres://localhost/mydb
12
+ python -m kv set API_KEY # prompts for value (hidden)
13
+ python -m kv run python app.py # secrets injected as env vars
14
+ ```
15
+
16
+ ## Why
17
+
18
+ Teams share `.env` files through Slack and email. That's plaintext secrets in chat logs, email servers, and clipboard history. Existing solutions are either encryption-only (dotenvx), cloud-only and expensive (Doppler at $21/user), or overkill (HashiCorp Vault).
19
+
20
+ **kv** encrypts secrets locally with ChaCha20-Poly1305 (same cipher as WireGuard and TLS 1.3), supports multiple environments, and injects secrets into any command — all from a two-letter CLI.
21
+
22
+ ## Commands
23
+
24
+ ```
25
+ kv init Initialize project
26
+ kv set KEY=VALUE [-e ENV] Set a secret
27
+ kv set KEY [-e ENV] Set interactively (hidden input)
28
+ kv get KEY [-e ENV] Decrypt and print a secret
29
+ kv ls [-e ENV] [--reveal] List secrets (values hidden by default)
30
+ kv rm KEY [-e ENV] [-f] Remove a secret
31
+ kv run [-e ENV] [-q] COMMAND... Run command with secrets as env vars
32
+ kv export [-e ENV] [-o FILE] Export as .env format
33
+ kv import FILE [-e ENV] Import from .env file
34
+ kv envs List all environments
35
+ kv env create NAME Create a new environment
36
+ kv env copy SRC DST Copy secrets between environments
37
+ kv status Project overview
38
+ kv --version Print version
39
+ ```
40
+
41
+ ### Quick start
42
+
43
+ ```bash
44
+ # Initialize — creates .secrets/ with master key and config
45
+ python -m kv init
46
+
47
+ # Store secrets
48
+ python -m kv set DATABASE_URL=postgres://localhost:5432/mydb
49
+ python -m kv set STRIPE_KEY=sk_test_abc123
50
+ python -m kv set SESSION_SECRET # hidden prompt, nothing on screen
51
+
52
+ # Retrieve
53
+ python -m kv get DATABASE_URL # prints raw value (pipe-friendly)
54
+ python -m kv ls # list keys, values masked
55
+ python -m kv ls --reveal # list keys with decrypted values
56
+
57
+ # Inject into any command
58
+ python -m kv run python app.py
59
+ python -m kv run node server.js
60
+ python -m kv run -e staging python migrate.py
61
+
62
+ # Multiple environments
63
+ python -m kv set -e staging DATABASE_URL=postgres://staging-host/db
64
+ python -m kv set -e prod DATABASE_URL=postgres://prod-host/db
65
+ python -m kv envs # dev, staging, prod
66
+ python -m kv env copy dev staging # clone secrets across envs
67
+
68
+ # Import/export
69
+ python -m kv export -o .env # decrypt to .env file
70
+ python -m kv import legacy.env -e prod # encrypt from .env file
71
+
72
+ # Remove
73
+ python -m kv rm API_KEY # confirmation prompt
74
+ python -m kv rm API_KEY -f # skip confirmation
75
+ ```
76
+
77
+ ## How it works
78
+
79
+ ### Encryption
80
+
81
+ - **Cipher**: ChaCha20-Poly1305 AEAD (authenticated encryption with associated data)
82
+ - **Master key**: 32 random bytes generated at `kv init`, stored in `.secrets/key`
83
+ - **Per-environment keys**: Derived from master key via BLAKE2b keyed hash — deterministic, no per-env key storage needed
84
+ - **Nonce**: 12 random bytes per write, prepended to ciphertext
85
+ - **AAD**: Environment name is bound as additional authenticated data — tampering or swapping `.enc` files between environments is detected
86
+ - **Payload**: All secrets for an environment are encrypted as a single JSON blob — key names are never visible without the master key
87
+
88
+ ### Storage
89
+
90
+ ```
91
+ your-project/
92
+ .secrets/
93
+ key Master key (base64url). NEVER commit this.
94
+ config.json Project metadata (environments, cipher, version)
95
+ dev.enc Encrypted secrets for dev
96
+ staging.enc Encrypted secrets for staging
97
+ prod.enc Encrypted secrets for prod
98
+ .gitignore Auto-generated: ignores key, allows *.enc
99
+ ```
100
+
101
+ The `.gitignore` inside `.secrets/` is auto-configured:
102
+ - `key` is ignored (your master key never enters git)
103
+ - `*.enc` files are allowed (encrypted blobs are safe to commit)
104
+ - `config.json` is allowed (no sensitive data)
105
+
106
+ This means `.enc` files can live in your repo. Anyone without the `key` file sees binary gibberish. Share the key through a secure channel once — after that, secrets travel with the code.
107
+
108
+ ### Binary format
109
+
110
+ Each `.enc` file:
111
+ ```
112
+ Bytes 0-2: KV\x00 Magic bytes
113
+ Byte 3: 0x01 Version
114
+ Bytes 4-15: nonce 12-byte random nonce
115
+ Bytes 16+: ciphertext ChaCha20-Poly1305 encrypted payload + 16-byte auth tag
116
+ ```
117
+
118
+ Decrypted payload (JSON):
119
+ ```json
120
+ {
121
+ "_meta": {"updated": "2026-02-18T14:35:00+00:00", "count": 3},
122
+ "secrets": {
123
+ "DATABASE_URL": "postgres://localhost/mydb",
124
+ "API_KEY": "sk-test123",
125
+ "SESSION_SECRET": "a1b2c3d4"
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## Security model
131
+
132
+ **What's protected:**
133
+ - Secret values are encrypted at rest with a 256-bit key
134
+ - Secret key names are encrypted (single-blob-per-env design)
135
+ - Environment isolation — per-env derived keys, AAD binding
136
+ - Tamper detection — Poly1305 authentication tag catches any modification
137
+ - Atomic writes — tmp file + `os.replace()` prevents partial writes on crash
138
+
139
+ **What's NOT protected (yet):**
140
+ - The master key in `.secrets/key` is plaintext on disk (protected by file permissions, `.gitignore`)
141
+ - No access control — anyone with the key can read/write all environments
142
+ - No audit log — no record of who changed what
143
+ - No key rotation — changing the master key requires re-encrypting everything manually
144
+
145
+ These are the features that make up the paid team tier (cloud sync, RBAC, audit logs, rotation).
146
+
147
+ ## Architecture
148
+
149
+ ```
150
+ kv/
151
+ __init__.py Package version
152
+ __main__.py Entry point (python -m kv), Windows terminal fixes
153
+ crypto.py ChaCha20-Poly1305 encrypt/decrypt, BLAKE2b key derivation
154
+ store.py SecretStore class, .enc binary format, atomic CRUD
155
+ config.py Project init, find_project_root(), environment registry
156
+ cli.py argparse commands, ANSI-colored output
157
+ env.py Subprocess injection, .env import/export
158
+ ```
159
+
160
+ **Single dependency**: `cryptography` (for ChaCha20-Poly1305). Everything else is stdlib.
161
+
162
+ ## Platform
163
+
164
+ Works on Windows, macOS, and Linux. Tested primarily on Windows with PowerShell.
165
+
166
+ Run from any subdirectory — `kv` walks up the directory tree to find `.secrets/` (like `git` finds `.git/`).
167
+
168
+ ```powershell
169
+ # From project root
170
+ python -m kv set FOO=bar
171
+
172
+ # From a subdirectory — still works
173
+ cd src/
174
+ python -m kv get FOO # finds .secrets/ in parent
175
+ ```
@@ -0,0 +1,2 @@
1
+ """kv — encrypted secrets management for developers."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,16 @@
1
+ """Entry point for python -m kv."""
2
+
3
+ import os
4
+ import sys
5
+
6
+ # Windows terminal setup
7
+ if os.name == "nt":
8
+ os.system("") # enable ANSI escape codes
9
+ for stream in (sys.stdout, sys.stderr, sys.stdin):
10
+ if hasattr(stream, "reconfigure"):
11
+ stream.reconfigure(encoding="utf-8")
12
+
13
+ from .cli import main
14
+
15
+ if __name__ == "__main__":
16
+ main()
@@ -0,0 +1,93 @@
1
+ """Authentication for kv CLI.
2
+
3
+ Manages user sessions, login/signup flows, and token storage.
4
+ Session stored at ~/.kv/session.json (per-user, not per-project).
5
+ """
6
+
7
+ import json
8
+ import os
9
+
10
+
11
+ SESSION_DIR = ".kv"
12
+ SESSION_FILE = "session.json"
13
+
14
+ # Default server URL — overridden by KV_API_URL env var
15
+ DEFAULT_API_URL = "http://127.0.0.1:8000"
16
+
17
+
18
+ def get_user_config_dir():
19
+ """Get ~/.kv/ directory, create if needed."""
20
+ home = os.path.expanduser("~")
21
+ d = os.path.join(home, SESSION_DIR)
22
+ os.makedirs(d, exist_ok=True)
23
+ # Restrict permissions on Unix (owner-only access)
24
+ if os.name != "nt":
25
+ os.chmod(d, 0o700)
26
+ return d
27
+
28
+
29
+ def session_path():
30
+ """Full path to session.json."""
31
+ return os.path.join(get_user_config_dir(), SESSION_FILE)
32
+
33
+
34
+ def load_session():
35
+ """Load the current session, or None if not logged in."""
36
+ path = session_path()
37
+ if not os.path.isfile(path):
38
+ return None
39
+ with open(path, "r", encoding="utf-8") as f:
40
+ return json.load(f)
41
+
42
+
43
+ def save_session(session):
44
+ """Write session to disk."""
45
+ path = session_path()
46
+ tmp = path + ".tmp"
47
+ with open(tmp, "w", encoding="utf-8") as f:
48
+ json.dump(session, f, indent=2)
49
+ f.write("\n")
50
+ os.replace(tmp, path)
51
+ # Restrict permissions on Unix (owner-only read/write)
52
+ if os.name != "nt":
53
+ os.chmod(path, 0o600)
54
+
55
+
56
+ def delete_session():
57
+ """Remove session file (logout)."""
58
+ path = session_path()
59
+ if os.path.isfile(path):
60
+ os.remove(path)
61
+
62
+
63
+ def get_api_url():
64
+ """Get the API server URL."""
65
+ session = load_session()
66
+ if session and session.get("api_url"):
67
+ return session["api_url"]
68
+ return os.environ.get("KV_API_URL", DEFAULT_API_URL)
69
+
70
+
71
+ def get_auth_headers():
72
+ """Get Authorization headers from session or KV_TOKEN env var.
73
+
74
+ Returns dict with Authorization header, or empty dict.
75
+ """
76
+ # CI/CD token takes priority
77
+ env_token = os.environ.get("KV_TOKEN")
78
+ if env_token:
79
+ return {"Authorization": f"Token {env_token}"}
80
+
81
+ session = load_session()
82
+ if session and session.get("token"):
83
+ return {"Authorization": f"Bearer {session['token']}"}
84
+
85
+ return {}
86
+
87
+
88
+ def require_session():
89
+ """Get current session or raise with helpful message."""
90
+ session = load_session()
91
+ if not session:
92
+ raise RuntimeError("not logged in — run 'python -m kv login' first")
93
+ return session