keymap-cli 0.0.0-stage → 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nekruz Kasimov
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.
package/README.md CHANGED
@@ -1,3 +1,114 @@
1
- # Temporary Holding Version
1
+ # keymap
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A values-free map of every secret you run, where it lives, and one command to rotate it everywhere.
4
+
5
+ **It is a map, not a vault.** keymap never stores a secret value. The manifest and its state hold names, locations, timestamps and short salted hashes. Values move only between your existing CLIs (`gh`, `railway`, …) and the host, over stdin or a mode-600 temp file, never through a shell string or on a command line.
6
+
7
+ Built for solo and vibe-coded deployments after the OpenCode (Oct 16, 2026) and Railway (Oct 21, 2026) key thefts, when "rotate every variable" met "I don't remember where half of these are used".
8
+
9
+ ## Status
10
+
11
+ Day 4. All planned commands exist: `init`, `audit`, `mark`, `rotate`, `incident`, `remind`, `doctor`, for `local` (.env files), `github` (Actions secrets), `railway`, `vercel`, `fly`, `cloudflare` (Workers) and `supabase` (Edge Function secrets). OpenRouter keys can be minted by API. Not yet published to npm.
12
+
13
+ ## Quick start
14
+
15
+ ```sh
16
+ npm i -g keymap-cli # or: npx keymap-cli (not published yet; see Status)
17
+ cd your-project
18
+ keymap init -s local:.env github:owner/repo railway:api/production
19
+ # edit keymap.yaml: issuers, rotate_every, ignore list
20
+ keymap audit # exit 1 on missing / drift / overdue / unmapped
21
+ keymap mark OPENAI_API_KEY # you rotated it by hand today
22
+ pbpaste | keymap rotate OPENAI_API_KEY # push a new key everywhere, hosts first, laptop last
23
+ keymap rotate OPENAI_API_KEY # same, but prompts for the value without echo
24
+ keymap rotate SESSION_SECRET --generate # mint a random value for a secret you own
25
+ keymap rotate OPENROUTER_API_KEY --mint --limit 20 --limit-reset monthly # create at OpenRouter with a cap, push
26
+ keymap revoke OPENROUTER_API_KEY # after every host redeployed: delete the key it replaced
27
+ keymap incident railway # a host was breached: what lives there, most dangerous first
28
+ keymap incident railway --walk # ...and rotate each one in turn
29
+ keymap doctor # which host CLIs are installed and logged in
30
+ KEYMAP_WEBHOOK=https://hooks.slack.com/... keymap remind # post the audit summary (names only)
31
+ ```
32
+
33
+ ## Run it every day
34
+
35
+ Copy `templates/keymap-audit.yml` to `.github/workflows/` and add the host tokens it lists as repository secrets. It runs `keymap audit` each morning and, if `KEYMAP_WEBHOOK` is set, posts a one-message summary. Findings never contain values, so the summary is safe for a shared channel. Every package and action in the template is pinned to an exact version or commit: that job holds your host tokens, so do not loosen the pins.
36
+
37
+ `rotate` stops at the first failing sink, tells you which sinks still hold the old value, and never records a rotation it did not finish. The value comes from stdin, a file, or a no-echo prompt, so it stays out of shell history and `ps`. The old key is never revoked automatically: hosts keep running the old value until they redeploy, so revocation is a separate step (`keymap revoke`, or the printed checklist for issuers without an API).
38
+
39
+ ## When a host is breached
40
+
41
+ ```
42
+ $ keymap incident railway
43
+ INCIDENT railway: 4 secrets to rotate, most dangerous first
44
+
45
+ [0] AI provider keys (a stolen key is a credit card for compute)
46
+ OPENAI_API_KEY openai exposed at railway:api; also lives at local:.env
47
+ [1] Cloud, payments and infrastructure tokens
48
+ STRIPE_SECRET_KEY stripe exposed at railway:api; also lives at vercel:web/production+preview
49
+ [2] Database and storage credentials
50
+ DATABASE_URL custom exposed at railway:api; also lives at local:.env
51
+ [4] Package registry credentials
52
+ NPM_TOKEN custom exposed at railway:worker; also lives at nowhere else
53
+ ```
54
+
55
+ The order follows Railway's own disclosure guidance: AI keys, cloud and payments, databases, source control, package registries. `--walk` asks for each new value, pushes it everywhere, and ends with the list of anything still exposed.
56
+
57
+ ## keymap.yaml
58
+
59
+ ```yaml
60
+ version: 1
61
+ project: flashlight-finder
62
+ ignore: [FEATURE_FLAG_*]
63
+ secrets:
64
+ OPENAI_API_KEY:
65
+ issuer: openai
66
+ rotate_every: 30d
67
+ sinks:
68
+ - railway: {service: api, environment: production}
69
+ - github: {repo: nek/flashlight-finder}
70
+ - local: {file: .env}
71
+ ```
72
+
73
+ Commit this file, and review changes to it like code: a sink added here receives the next rotated value. Local sinks may only point at `.env`-style files inside the project, and `rotate` refuses to write one that git would commit.
74
+
75
+ State (`~/.keymap/<project>-<hash>.state.json`, mode 600) stays on your machine and is keyed by the checkout's path: move the folder and keymap starts a fresh state (run `keymap revoke --hash` by hand for any key it was tracking). It records timestamps, salted hash prefixes, and for minted keys the issuer's key id, never a value.
76
+
77
+ ## What audit checks
78
+
79
+ | Finding | Meaning |
80
+ |---|---|
81
+ | MISSING | mapped secret absent at a sink |
82
+ | DRIFT | value differs between sinks that can be read back (shown as `≠`) |
83
+ | OVERDUE | older than `rotate_every` |
84
+ | UNKNOWN AGE | never rotated or marked through keymap |
85
+ | UNMAPPED | a sink has a name that is not in the manifest or `ignore` |
86
+ | ERROR | a host CLI failed (not logged in, etc.) |
87
+
88
+ GitHub Actions and Cloudflare secrets are write-only, so they show as `(wo)`: presence is checked, drift is not. Fly and Supabase expose only their own digests, which cannot be compared with other sinks, so drift is not checked there either.
89
+
90
+ ## Sinks
91
+
92
+ | Sink | Shorthand for `init -s` | List | Set |
93
+ |---|---|---|---|
94
+ | local | `local:.env` | parse file | rewrite one line |
95
+ | github | `github:owner/repo[/env]` | `gh secret list` | `gh secret set` (stdin) |
96
+ | railway | `railway:service[/environment]` | `railway variable list --json` | `railway variable set --stdin --skip-deploys` |
97
+ | vercel | `vercel:project[/production+preview]` | `vercel env list --json` | `vercel env add --force` (stdin) |
98
+ | fly | `fly:app` | `flyctl secrets list --json` | `flyctl secrets import --stage` (stdin) |
99
+ | cloudflare | `cloudflare:worker[/env]` | `wrangler secret list` | `wrangler secret put` (stdin) |
100
+ | supabase | `supabase:projectref` | `supabase secrets list -o json` | `supabase secrets set --env-file` (mode-600 temp file) |
101
+
102
+ Every adapter shells out to the official CLI you are already logged into, with a 2-minute timeout, seeing only its own host's token and none of keymap's own environment variables. Nothing is proxied, no value touches a shell string or argv, and CLI error text is redacted and never includes a listing's stdout.
103
+
104
+ ## Security notes
105
+
106
+ - Reviewed 2026-10-22 against a written threat model (value leakage, injection, file safety, supply chain, rotation logic). Findings and fixes are in the commit history.
107
+ - `--mint` records the new key's id before pushing and leaves the previous key live until you run `revoke`. If a rotation fails part-way the minted key stays live too (some sinks already hold it); a later successful `--mint` moves it to the same revoke-after-redeploy set, and `keymap revoke` deletes all of them. A second `--mint` is refused until the previous key has been revoked.
108
+ - Each host CLI runs with only its own credential variable; every other host token and all `KEYMAP_*` variables are stripped from its environment.
109
+ - Hash prefixes are HMAC-SHA256 with a per-machine salt (`~/.keymap/.salt`), so they cannot be checked offline against a wordlist.
110
+ - Report issues privately to the maintainer before opening a public issue.
111
+
112
+ ## License
113
+
114
+ MIT