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 +21 -0
- package/README.md +113 -2
- package/dist/cli.js +12062 -0
- package/package.json +55 -4
- package/templates/keymap-audit.yml +68 -0
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
|
-
#
|
|
1
|
+
# keymap
|
|
2
2
|
|
|
3
|
-
|
|
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
|