keebox 0.9.1 → 0.9.2
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/README.md +57 -40
- package/{index.js → bin/index.js} +10 -5
- package/package.json +7 -2
- package/utils/config.js +7 -4
- package/utils/index.js +15 -3
- package/utils/prompt.js +2 -2
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<p align="center">
|
|
21
21
|
<a href="https://keebox.eissa.top">Website</a> ·
|
|
22
22
|
<a href="#get-started">Get started</a> ·
|
|
23
|
-
<a href="#commands
|
|
23
|
+
<a href="#commands">Commands</a> ·
|
|
24
24
|
<a href="#encryption">Security</a>
|
|
25
25
|
</p>
|
|
26
26
|
|
|
@@ -35,7 +35,7 @@ Most password managers ask you to trust them with everything: your passwords sit
|
|
|
35
35
|
- **Nothing in the cloud.** KeeBox never touches the network: no sync server to breach, no telemetry, no account to phish. Your encrypted vault only exists where you put it.
|
|
36
36
|
- **Strong, standard encryption.** Every password, login and note is encrypted with AES-256-GCM. The key is locked behind your master password with scrypt, which makes guessing slow and expensive. Tampered or damaged entries are detected and refused, never silently decrypted into garbage.
|
|
37
37
|
- **A recovery phrase, like a crypto wallet.** Lost your credentials file or forgot your master password? A 12-word recovery phrase gets you back in. Without the phrase or the password, nobody can open the vault, not even the author of KeeBox.
|
|
38
|
-
- **Zero dependencies.** No
|
|
38
|
+
- **Zero dependencies.** No third-party packages, so there's no supply chain to compromise. The whole tool is about 1,300 lines of plain Node.js that you can read and audit in an afternoon.
|
|
39
39
|
- **Portable.** Copy the folder plus a Node.js binary to a USB drive and it runs on Linux, macOS, Windows and Android (Termux), no installation needed.
|
|
40
40
|
- **Fast from the terminal.** Generate strong passwords, find accounts by any field, and copy a password to the clipboard. It's wiped automatically after 20 seconds.
|
|
41
41
|
- **Yours to keep.** Export everything to CSV whenever you want, import accounts from CSV, and check your vault for damage. Open source and MIT licensed.
|
|
@@ -47,9 +47,22 @@ Most password managers ask you to trust them with everything: your passwords sit
|
|
|
47
47
|
Requires Node.js 18+.
|
|
48
48
|
|
|
49
49
|
```
|
|
50
|
-
|
|
50
|
+
npm install -g keebox
|
|
51
|
+
keebox -h
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Your vault (`db.json` + `credentials.json`) is kept in `~/.keebox/`, outside the npm folder, so updating or uninstalling KeeBox never touches it. To keep it somewhere else (an encrypted drive, a backed-up folder...), set `KEEBOX_HOME`:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
export KEEBOX_HOME=/path/to/vault-folder # in your .bashrc / .zshrc
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or run it from source, without installing (the vault then lives in the project folder):
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
git clone https://github.com/eissapk/keebox.git
|
|
51
64
|
cd keebox # no npm install needed, there are no dependencies
|
|
52
|
-
node index.js -h
|
|
65
|
+
node bin/index.js -h
|
|
53
66
|
```
|
|
54
67
|
|
|
55
68
|
Clipboard: macOS and Windows/WSL work as is, Linux needs `xclip` (X11) or `wl-clipboard` (Wayland). On Android (Termux) passwords are printed instead of copied.
|
|
@@ -59,9 +72,10 @@ Clipboard: macOS and Windows/WSL work as is, Linux needs `xclip` (X11) or `wl-cl
|
|
|
59
72
|
```
|
|
60
73
|
├── assets
|
|
61
74
|
│ └── logo.svg
|
|
75
|
+
├── bin
|
|
76
|
+
│ └── index.js # CLI entry (the `keebox` command): commands + master password prompt
|
|
62
77
|
├── credentials.json # created on first run (master password lock), not in git
|
|
63
78
|
├── db.json # created on first run (accounts + recovery lock), not in git
|
|
64
|
-
├── index.js # CLI entry: commands + master password prompt
|
|
65
79
|
├── node # optional, for a portable copy (see below), not in git
|
|
66
80
|
│ ├── linux/node
|
|
67
81
|
│ ├── mac/node
|
|
@@ -94,9 +108,11 @@ Clipboard: macOS and Windows/WSL work as is, Linux needs `xclip` (X11) or `wl-cl
|
|
|
94
108
|
└── wordlist.js # BIP39 English wordlist
|
|
95
109
|
```
|
|
96
110
|
|
|
111
|
+
`db.json` / `credentials.json` sit in the project folder when run from source or a portable copy, in `~/.keebox/` when installed with npm, or in `$KEEBOX_HOME` when it's set.
|
|
112
|
+
|
|
97
113
|
##### Make it portable (USB flash)
|
|
98
114
|
|
|
99
|
-
Copy the project with a Node.js binary per OS, so it runs on machines without Node installed:
|
|
115
|
+
Copy the project with a Node.js binary per OS, so it runs on machines without Node installed. The vault is kept next to it, on the flash drive:
|
|
100
116
|
|
|
101
117
|
```
|
|
102
118
|
cp -r /path/to/project /path/to/usb-flash
|
|
@@ -106,46 +122,47 @@ cp "$(command -v node)" /path/to/usb-flash/node/linux/node # repeat for mac/node
|
|
|
106
122
|
Run it from the flash drive:
|
|
107
123
|
|
|
108
124
|
```
|
|
109
|
-
/path/to/usb-flash/node/linux/node /path/to/usb-flash/index.js -h
|
|
125
|
+
/path/to/usb-flash/node/linux/node /path/to/usb-flash/bin/index.js -h
|
|
110
126
|
```
|
|
111
127
|
|
|
112
128
|
Or add an alias in your `.bashrc` / `.zshrc`, and use it anywhere:
|
|
113
129
|
|
|
114
130
|
```
|
|
115
|
-
alias
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
alias keebox='node /path/to/usb-flash/bin/index.js'
|
|
132
|
+
keebox --help
|
|
133
|
+
keebox --version
|
|
118
134
|
```
|
|
119
135
|
|
|
120
|
-
##### Commands
|
|
136
|
+
##### Commands
|
|
121
137
|
|
|
122
138
|
```
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
keebox get # list all accounts
|
|
140
|
+
keebox get --id 2 # show account with id 2
|
|
141
|
+
keebox get --name google # show accounts whose name contains "google"
|
|
142
|
+
keebox get --id 2 --cp # copy its password to the clipboard (auto-cleared after 20s)
|
|
143
|
+
keebox get --id 2 --show # show its login/notes
|
|
144
|
+
keebox get --id 2 --edit # update it (Enter = keep a value, '-' = clear it)
|
|
145
|
+
keebox get --id 2 --rm # delete it
|
|
146
|
+
keebox add --name google --username foo@gmail.com --url www.google.com # add an account (asks for its password)
|
|
147
|
+
keebox add --name github --username foo --url github.com --login foo@mail.com --notes "2FA via phone" # with login/notes (encrypted)
|
|
148
|
+
keebox make --size 20 # generate a random strong password of 20 chars
|
|
149
|
+
keebox export # export all accounts, decrypted, to keebox-export-<date>.csv
|
|
150
|
+
keebox export --name google --out google.csv # export matching accounts to google.csv
|
|
151
|
+
keebox import --file accounts.csv # add accounts from a csv (same columns as export; id is ignored, duplicates skipped)
|
|
152
|
+
keebox check # damage check: lists accounts that are broken or can't be decrypted (read-only, exit code 1 if any)
|
|
153
|
+
keebox recovery # create (or replace) the 12-word recovery phrase
|
|
154
|
+
keebox recover # lost credentials.json / forgot the master password: unlock with the recovery phrase, set a new master password
|
|
155
|
+
keebox master # create or change the master password
|
|
139
156
|
```
|
|
140
157
|
|
|
141
158
|
Exported CSV files are **not encrypted**, delete them when you're done.
|
|
142
159
|
|
|
143
160
|
##### Create / change master password
|
|
144
161
|
|
|
145
|
-
On a new (empty) vault, the first `
|
|
162
|
+
On a new (empty) vault, the first `keebox add` / `keebox get` asks you to create one. To create it yourself, or change it later (asks the current one first; `db.json` stays as is, and the recovery phrase keeps working):
|
|
146
163
|
|
|
147
164
|
```
|
|
148
|
-
|
|
165
|
+
keebox master
|
|
149
166
|
```
|
|
150
167
|
|
|
151
168
|
##### Encryption
|
|
@@ -155,14 +172,14 @@ Each password, login and notes is encrypted (AES-256-GCM) with one random 256-bi
|
|
|
155
172
|
| Where | Locked with | Opened by |
|
|
156
173
|
|---|---|---|
|
|
157
174
|
| `credentials.json` → `{ salt, n, key }` | scrypt(master password, salt) + AES-256-GCM | the master password |
|
|
158
|
-
| `db.json` → `recovery: { salt, n, key }` | scrypt(12-word phrase, salt) + AES-256-GCM | the recovery phrase (`
|
|
175
|
+
| `db.json` → `recovery: { salt, n, key }` | scrypt(12-word phrase, salt) + AES-256-GCM | the recovery phrase (`keebox recover`) |
|
|
159
176
|
|
|
160
177
|
What gets you into the accounts:
|
|
161
178
|
|
|
162
179
|
| You have | Access? |
|
|
163
180
|
|---|---|
|
|
164
181
|
| `db.json` + `credentials.json` + master password | yes (normal use) |
|
|
165
|
-
| `db.json` + recovery phrase | yes: `
|
|
182
|
+
| `db.json` + recovery phrase | yes: `keebox recover` sets a new master password and writes a new `credentials.json` |
|
|
166
183
|
| `db.json` + master password, but no `credentials.json` | **no**: the master password only opens `credentials.json` (restore `credentials.json.bak` or a backup) |
|
|
167
184
|
| `db.json` alone | **no**, for anyone: it would mean guessing a 128-bit phrase or a 256-bit key |
|
|
168
185
|
|
|
@@ -170,15 +187,15 @@ So keep `db.json` and `credentials.json` together and backed up, choose a strong
|
|
|
170
187
|
|
|
171
188
|
##### Recovery phrase (emergency key)
|
|
172
189
|
|
|
173
|
-
`
|
|
190
|
+
`keebox recovery` makes a 12-word phrase (BIP39 English words, like a crypto wallet seed: 128 random bits + checksum) and locks the same vault key with it into `db.json`. The phrase itself is never saved, it's shown once: write it down, then type it back to confirm.
|
|
174
191
|
|
|
175
|
-
If `credentials.json` is deleted or you forget the master password, `
|
|
192
|
+
If `credentials.json` is deleted or you forget the master password, `keebox recover` asks for the 12 words (any case/spacing; typos and swapped words are caught by the checksum), unlocks the vault key and lets you set a new master password. Only `credentials.json` is written, `db.json` isn't changed, and the same phrase keeps working afterwards. Running `keebox recovery` again replaces the phrase (the old one stops working, except against an older `db.json.bak`).
|
|
176
193
|
|
|
177
194
|
Anyone with the phrase + `db.json` can read every account, so guard it like the master password.
|
|
178
195
|
|
|
179
196
|
##### Files
|
|
180
197
|
|
|
181
|
-
`db.json` (`recovery` is `null` until you run `
|
|
198
|
+
`db.json` (`recovery` is `null` until you run `keebox recovery`):
|
|
182
199
|
|
|
183
200
|
```json
|
|
184
201
|
{
|
|
@@ -205,12 +222,12 @@ Anyone with the phrase + `db.json` can read every account, so guard it like the
|
|
|
205
222
|
{ "salt": "...", "n": 32768, "key": "iv.tag.data" }
|
|
206
223
|
```
|
|
207
224
|
|
|
208
|
-
Both are created automatically if missing, and every save keeps the previous version as `db.json.bak` / `credentials.json.bak`. If `credentials.json` is missing but `db.json` has data, KeeBox won't create a new master password (that would make a new vault key and lock out the existing accounts); it asks you to run `
|
|
225
|
+
Both are created automatically if missing, and every save keeps the previous version as `db.json.bak` / `credentials.json.bak`. If `credentials.json` is missing but `db.json` has data, KeeBox won't create a new master password (that would make a new vault key and lock out the existing accounts); it asks you to run `keebox recover` or restore `credentials.json.bak` instead.
|
|
209
226
|
|
|
210
227
|
##### Decryption flow
|
|
211
228
|
|
|
212
229
|
```
|
|
213
|
-
|
|
230
|
+
keebox get --id 11 --cp
|
|
214
231
|
│
|
|
215
232
|
▼
|
|
216
233
|
┌───────────────────────────────┐
|
|
@@ -277,7 +294,7 @@ Both are created automatically if missing, and every save keeps the previous ver
|
|
|
277
294
|
##### Recovery flow
|
|
278
295
|
|
|
279
296
|
```
|
|
280
|
-
|
|
297
|
+
keebox recover
|
|
281
298
|
│
|
|
282
299
|
▼
|
|
283
300
|
┌───────────────────────────────┐
|
|
@@ -304,10 +321,10 @@ Both are created automatically if missing, and every save keeps the previous ver
|
|
|
304
321
|
|
|
305
322
|
| Step | File | Uses |
|
|
306
323
|
|---|---|---|
|
|
307
|
-
| Ask for or create the master password | `index.js` → `askMaster`, `utils/config.js` → `createMaster` | master password |
|
|
324
|
+
| Ask for or create the master password | `bin/index.js` → `askMaster`, `utils/config.js` → `createMaster` | master password |
|
|
308
325
|
| Master password → vault key | `utils/vault.js` → `unlock` | `credentials.json` `salt`, `n`, `key` |
|
|
309
|
-
| Create the recovery phrase (`
|
|
310
|
-
| Recovery phrase → vault key (`
|
|
326
|
+
| Create the recovery phrase (`keebox recovery`) | `options/recovery.js` → `createRecovery`, `utils/vault.js` → `makeRecovery`, `utils/mnemonic.js` | `db.json` `recovery` |
|
|
327
|
+
| Recovery phrase → vault key (`keebox recover`) | `options/recovery.js` → `recoverVault`, `utils/vault.js` → `recover` | `db.json` `recovery` |
|
|
311
328
|
| Vault key → account password | `utils/index.js` → `decrypt` (called from `options/get.js` → `copyEntry`) | `iv`, `tag`, `data` |
|
|
312
329
|
|
|
313
330
|
##### Tests
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { mkPass, askPassword, loadData, copyOrPrint, run, FIELDS, SECRET_FIELDS, log, hasMaster, unlock, createMaster, changeMaster, parseArgs } = require("../utils");
|
|
2
3
|
const crypto = require("crypto");
|
|
3
|
-
const pkgJSON = require("
|
|
4
|
-
const { getEntry, addEntry, exportEntries, importEntries, checkDb, createRecovery, recoverVault } = require("
|
|
4
|
+
const pkgJSON = require("../package.json");
|
|
5
|
+
const { getEntry, addEntry, exportEntries, importEntries, checkDb, createRecovery, recoverVault } = require("../options");
|
|
5
6
|
|
|
6
7
|
run(main);
|
|
7
8
|
|
|
@@ -29,9 +30,9 @@ async function main() {
|
|
|
29
30
|
["--out <file>", "CSV file to write (default: keebox-export-<date>.csv) - use it with 'export' command"],
|
|
30
31
|
["--file <file>", "CSV file to read (columns: name,password + optional url,username,login,notes,created,updated) - use it with 'import' command"],
|
|
31
32
|
],
|
|
32
|
-
commands: { add: "Add account", get: "display account(s)", make: "Generate a password", export: "Export account(s) as decrypted CSV (filter with --id/--name/...)", import: "Import account(s) from a CSV file (e.g. one made by 'export')", check: "Damage check: find accounts that are broken or can't be decrypted (changes nothing)", recovery: "Create (or replace) the 12-word recovery phrase", recover: "Lost credentials.json / forgot the master password: unlock with the recovery phrase and set a new master password" },
|
|
33
|
+
commands: { add: "Add account", get: "display account(s)", make: "Generate a password", export: "Export account(s) as decrypted CSV (filter with --id/--name/...)", import: "Import account(s) from a CSV file (e.g. one made by 'export')", check: "Damage check: find accounts that are broken or can't be decrypted (changes nothing)", recovery: "Create (or replace) the 12-word recovery phrase", recover: "Lost credentials.json / forgot the master password: unlock with the recovery phrase and set a new master password", master: "Create or change the master password (accounts and recovery phrase keep working)" },
|
|
33
34
|
});
|
|
34
|
-
if (parsed) await { add, get, make, export: exportCsv, import: importCsv, check, recovery, recover }[parsed.command](parsed.opts, db, cred);
|
|
35
|
+
if (parsed) await { add, get, make, export: exportCsv, import: importCsv, check, recovery, recover, master }[parsed.command](parsed.opts, db, cred);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
// ask master password -> vault key, or null
|
|
@@ -106,3 +107,7 @@ async function recovery(opts, db, cred) {
|
|
|
106
107
|
async function recover(opts, db, cred) {
|
|
107
108
|
await recoverVault(db, cred);
|
|
108
109
|
}
|
|
110
|
+
|
|
111
|
+
async function master(opts, db, cred) {
|
|
112
|
+
await changeMaster(db, cred);
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keebox",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "KeeBox is an offline password manager that lives in your terminal",
|
|
5
|
-
"
|
|
5
|
+
"bin": {
|
|
6
|
+
"keebox": "./bin/index.js"
|
|
7
|
+
},
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18"
|
|
10
|
+
},
|
|
6
11
|
"scripts": {
|
|
7
12
|
"test": "node --test"
|
|
8
13
|
},
|
package/utils/config.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
const { askPassword, loadData, saveOrReport, run, credPath, log, lockVault, hasMaster, unlock } = require("./index");
|
|
2
2
|
const crypto = require("crypto");
|
|
3
3
|
|
|
4
|
-
// usage: node utils/config.js -> create the master password, or change it
|
|
4
|
+
// usage: keebox master (or node utils/config.js) -> create the master password, or change it
|
|
5
5
|
if (require.main === module) run(main);
|
|
6
|
-
module.exports = { createMaster };
|
|
6
|
+
module.exports = { createMaster, changeMaster };
|
|
7
7
|
|
|
8
8
|
async function main() {
|
|
9
9
|
const data = loadData();
|
|
10
|
-
if (
|
|
11
|
-
|
|
10
|
+
if (data) await changeMaster(data.db, data.cred);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// create the master password, or change it (asks the current one first)
|
|
14
|
+
async function changeMaster(db, cred) {
|
|
12
15
|
if (!hasMaster(cred)) {
|
|
13
16
|
// a new vault key would make the existing accounts unreadable
|
|
14
17
|
if (db.accounts.length || db.recovery) return log.error("credentials.json is missing, but db.json has data. Run 'recover' with your recovery phrase, or restore credentials.json.bak");
|
package/utils/index.js
CHANGED
|
@@ -12,8 +12,19 @@ const prompt = require("./prompt");
|
|
|
12
12
|
const drawTable = require("./table");
|
|
13
13
|
const parseArgs = require("./cli");
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// where db.json + credentials.json live:
|
|
16
|
+
// - $KEEBOX_HOME when set
|
|
17
|
+
// - ~/.keebox when installed with npm (inside node_modules, which npm wipes on update/uninstall)
|
|
18
|
+
// - the project folder otherwise (git clone / portable USB copy)
|
|
19
|
+
function dataDir() {
|
|
20
|
+
if (process.env.KEEBOX_HOME) return path.resolve(process.env.KEEBOX_HOME);
|
|
21
|
+
const appDir = path.resolve(__dirname, "..");
|
|
22
|
+
if (appDir.split(path.sep).includes("node_modules")) return path.join(require("os").homedir(), ".keebox");
|
|
23
|
+
return appDir;
|
|
24
|
+
}
|
|
25
|
+
const dir = dataDir();
|
|
26
|
+
const dbPath = path.join(dir, "db.json");
|
|
27
|
+
const credPath = path.join(dir, "credentials.json");
|
|
17
28
|
|
|
18
29
|
// cryptographically secure, exact length, at least one char of each class (length >= 4)
|
|
19
30
|
function mkPass(length) {
|
|
@@ -67,7 +78,8 @@ function decryptField(entry, field, key) {
|
|
|
67
78
|
function saveJson(file, data) {
|
|
68
79
|
const fs = require("fs");
|
|
69
80
|
const tmp = file + ".tmp";
|
|
70
|
-
|
|
81
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
82
|
+
const fd = fs.openSync(tmp, "w", 0o600);
|
|
71
83
|
try {
|
|
72
84
|
fs.writeFileSync(fd, JSON.stringify(data, null, 2));
|
|
73
85
|
fs.fsyncSync(fd);
|
package/utils/prompt.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// minimal inquirer replacement, public Node APIs only
|
|
2
2
|
// await prompt([{ type: "input" | "password" | "confirm", name, message, default }]) -> answers
|
|
3
3
|
// - interactive (stdin is a terminal): readline for input/confirm, raw-mode reader for passwords (masked with "•")
|
|
4
|
-
// - piped stdin: one shared line queue, so answers can be scripted: printf 'pw\ny\n' |
|
|
4
|
+
// - piped stdin: one shared line queue, so answers can be scripted: printf 'pw\ny\n' | keebox get --id 1 --rm
|
|
5
5
|
// - Ctrl+C exits 130; Ctrl+D / end of input cancels with exit 1 (never auto-answers, e.g. "delete? (Y/n)")
|
|
6
6
|
const readline = require("readline");
|
|
7
7
|
const readlinePromises = require("readline/promises");
|
|
8
8
|
|
|
9
|
-
// prompts go to the terminal even when stdout is redirected (
|
|
9
|
+
// prompts go to the terminal even when stdout is redirected (keebox get > list.txt)
|
|
10
10
|
function promptOut() {
|
|
11
11
|
return process.stdout.isTTY || !process.stderr.isTTY ? process.stdout : process.stderr;
|
|
12
12
|
}
|