keebox 0.9.0 → 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 CHANGED
@@ -10,9 +10,17 @@
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- <a href="https://keybox.eissa.top">Website</a> ·
13
+ <a href="https://www.npmjs.com/package/keebox"><img src="https://img.shields.io/npm/v/keebox?color=f5a524&label=npm" alt="npm version"></a>
14
+ <a href="https://www.npmjs.com/package/keebox"><img src="https://img.shields.io/npm/dm/keebox?color=f5a524" alt="npm downloads"></a>
15
+ <a href="https://opensource.org/license/mit"><img src="https://img.shields.io/badge/license-MIT-f5a524" alt="MIT license"></a>
16
+ <img src="https://img.shields.io/badge/node-%3E%3D18-f5a524?logo=nodedotjs&logoColor=white" alt="node >= 18">
17
+ <img src="https://img.shields.io/badge/dependencies-0-7ccf8a" alt="zero dependencies">
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="https://keebox.eissa.top">Website</a> ·
14
22
  <a href="#get-started">Get started</a> ·
15
- <a href="#commands-using-the-pass-alias">Commands</a> ·
23
+ <a href="#commands">Commands</a> ·
16
24
  <a href="#encryption">Security</a>
17
25
  </p>
18
26
 
@@ -27,7 +35,7 @@ Most password managers ask you to trust them with everything: your passwords sit
27
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.
28
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.
29
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.
30
- - **Zero dependencies.** No `npm install` and 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.
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.
31
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.
32
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.
33
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.
@@ -39,9 +47,22 @@ Most password managers ask you to trust them with everything: your passwords sit
39
47
  Requires Node.js 18+.
40
48
 
41
49
  ```
42
- git clone https://github.com/eissapk/keybox.git keebox
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
43
64
  cd keebox # no npm install needed, there are no dependencies
44
- node index.js -h
65
+ node bin/index.js -h
45
66
  ```
46
67
 
47
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.
@@ -51,9 +72,10 @@ Clipboard: macOS and Windows/WSL work as is, Linux needs `xclip` (X11) or `wl-cl
51
72
  ```
52
73
  ├── assets
53
74
  │ └── logo.svg
75
+ ├── bin
76
+ │ └── index.js # CLI entry (the `keebox` command): commands + master password prompt
54
77
  ├── credentials.json # created on first run (master password lock), not in git
55
78
  ├── db.json # created on first run (accounts + recovery lock), not in git
56
- ├── index.js # CLI entry: commands + master password prompt
57
79
  ├── node # optional, for a portable copy (see below), not in git
58
80
  │ ├── linux/node
59
81
  │ ├── mac/node
@@ -86,9 +108,11 @@ Clipboard: macOS and Windows/WSL work as is, Linux needs `xclip` (X11) or `wl-cl
86
108
  └── wordlist.js # BIP39 English wordlist
87
109
  ```
88
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
+
89
113
  ##### Make it portable (USB flash)
90
114
 
91
- 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:
92
116
 
93
117
  ```
94
118
  cp -r /path/to/project /path/to/usb-flash
@@ -98,46 +122,47 @@ cp "$(command -v node)" /path/to/usb-flash/node/linux/node # repeat for mac/node
98
122
  Run it from the flash drive:
99
123
 
100
124
  ```
101
- /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
102
126
  ```
103
127
 
104
128
  Or add an alias in your `.bashrc` / `.zshrc`, and use it anywhere:
105
129
 
106
130
  ```
107
- alias pass='node /path/to/usb-flash/index.js'
108
- pass --help
109
- pass --version
131
+ alias keebox='node /path/to/usb-flash/bin/index.js'
132
+ keebox --help
133
+ keebox --version
110
134
  ```
111
135
 
112
- ##### Commands (using the `pass` alias)
136
+ ##### Commands
113
137
 
114
138
  ```
115
- pass get # list all accounts
116
- pass get --id 2 # show account with id 2
117
- pass get --name google # show accounts whose name contains "google"
118
- pass get --id 2 --cp # copy its password to the clipboard (auto-cleared after 20s)
119
- pass get --id 2 --show # show its login/notes
120
- pass get --id 2 --edit # update it (Enter = keep a value, '-' = clear it)
121
- pass get --id 2 --rm # delete it
122
- pass add --name google --username foo@gmail.com --url www.google.com # add an account (asks for its password)
123
- pass add --name github --username foo --url github.com --login foo@mail.com --notes "2FA via phone" # with login/notes (encrypted)
124
- pass make --size 20 # generate a random strong password of 20 chars
125
- pass export # export all accounts, decrypted, to keebox-export-<date>.csv
126
- pass export --name google --out google.csv # export matching accounts to google.csv
127
- pass import --file accounts.csv # add accounts from a csv (same columns as export; id is ignored, duplicates skipped)
128
- pass check # damage check: lists accounts that are broken or can't be decrypted (read-only, exit code 1 if any)
129
- pass recovery # create (or replace) the 12-word recovery phrase
130
- pass recover # lost credentials.json / forgot the master password: unlock with the recovery phrase, set a new master password
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
131
156
  ```
132
157
 
133
158
  Exported CSV files are **not encrypted**, delete them when you're done.
134
159
 
135
160
  ##### Create / change master password
136
161
 
137
- On a new (empty) vault, the first `pass add` / `pass 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):
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):
138
163
 
139
164
  ```
140
- node utils/config.js
165
+ keebox master
141
166
  ```
142
167
 
143
168
  ##### Encryption
@@ -147,14 +172,14 @@ Each password, login and notes is encrypted (AES-256-GCM) with one random 256-bi
147
172
  | Where | Locked with | Opened by |
148
173
  |---|---|---|
149
174
  | `credentials.json` → `{ salt, n, key }` | scrypt(master password, salt) + AES-256-GCM | the master password |
150
- | `db.json` → `recovery: { salt, n, key }` | scrypt(12-word phrase, salt) + AES-256-GCM | the recovery phrase (`pass recover`) |
175
+ | `db.json` → `recovery: { salt, n, key }` | scrypt(12-word phrase, salt) + AES-256-GCM | the recovery phrase (`keebox recover`) |
151
176
 
152
177
  What gets you into the accounts:
153
178
 
154
179
  | You have | Access? |
155
180
  |---|---|
156
181
  | `db.json` + `credentials.json` + master password | yes (normal use) |
157
- | `db.json` + recovery phrase | yes: `pass recover` sets a new master password and writes a new `credentials.json` |
182
+ | `db.json` + recovery phrase | yes: `keebox recover` sets a new master password and writes a new `credentials.json` |
158
183
  | `db.json` + master password, but no `credentials.json` | **no**: the master password only opens `credentials.json` (restore `credentials.json.bak` or a backup) |
159
184
  | `db.json` alone | **no**, for anyone: it would mean guessing a 128-bit phrase or a 256-bit key |
160
185
 
@@ -162,15 +187,15 @@ So keep `db.json` and `credentials.json` together and backed up, choose a strong
162
187
 
163
188
  ##### Recovery phrase (emergency key)
164
189
 
165
- `pass 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.
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.
166
191
 
167
- If `credentials.json` is deleted or you forget the master password, `pass 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 `pass recovery` again replaces the phrase (the old one stops working, except against an older `db.json.bak`).
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`).
168
193
 
169
194
  Anyone with the phrase + `db.json` can read every account, so guard it like the master password.
170
195
 
171
196
  ##### Files
172
197
 
173
- `db.json` (`recovery` is `null` until you run `pass recovery`):
198
+ `db.json` (`recovery` is `null` until you run `keebox recovery`):
174
199
 
175
200
  ```json
176
201
  {
@@ -197,12 +222,12 @@ Anyone with the phrase + `db.json` can read every account, so guard it like the
197
222
  { "salt": "...", "n": 32768, "key": "iv.tag.data" }
198
223
  ```
199
224
 
200
- 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 `pass recover` or restore `credentials.json.bak` instead.
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.
201
226
 
202
227
  ##### Decryption flow
203
228
 
204
229
  ```
205
- pass get --id 11 --cp
230
+ keebox get --id 11 --cp
206
231
  │
207
232
  ▼
208
233
  ┌───────────────────────────────┐
@@ -269,7 +294,7 @@ Both are created automatically if missing, and every save keeps the previous ver
269
294
  ##### Recovery flow
270
295
 
271
296
  ```
272
- pass recover
297
+ keebox recover
273
298
  │
274
299
  ▼
275
300
  ┌───────────────────────────────┐
@@ -296,10 +321,10 @@ Both are created automatically if missing, and every save keeps the previous ver
296
321
 
297
322
  | Step | File | Uses |
298
323
  |---|---|---|
299
- | 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 |
300
325
  | Master password → vault key | `utils/vault.js` → `unlock` | `credentials.json` `salt`, `n`, `key` |
301
- | Create the recovery phrase (`pass recovery`) | `options/recovery.js` → `createRecovery`, `utils/vault.js` → `makeRecovery`, `utils/mnemonic.js` | `db.json` `recovery` |
302
- | Recovery phrase → vault key (`pass recover`) | `options/recovery.js` → `recoverVault`, `utils/vault.js` → `recover` | `db.json` `recovery` |
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` |
303
328
  | Vault key → account password | `utils/index.js` → `decrypt` (called from `options/get.js` → `copyEntry`) | `iv`, `tag`, `data` |
304
329
 
305
330
  ##### Tests
@@ -1,7 +1,8 @@
1
- const { mkPass, askPassword, loadData, copyOrPrint, run, FIELDS, SECRET_FIELDS, log, hasMaster, unlock, createMaster, parseArgs } = require("./utils");
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("./package.json");
4
- const { getEntry, addEntry, exportEntries, importEntries, checkDb, createRecovery, recoverVault } = require("./options");
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,20 +1,25 @@
1
1
  {
2
2
  "name": "keebox",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "KeeBox is an offline password manager that lives in your terminal",
5
- "main": "index.js",
5
+ "bin": {
6
+ "keebox": "./bin/index.js"
7
+ },
8
+ "engines": {
9
+ "node": ">=18"
10
+ },
6
11
  "scripts": {
7
12
  "test": "node --test"
8
13
  },
9
14
  "repository": {
10
15
  "type": "git",
11
- "url": "git+https://github.com/eissapk/keybox.git"
16
+ "url": "git+https://github.com/eissapk/keebox.git"
12
17
  },
13
18
  "keywords": [],
14
19
  "author": "Eissa Saber",
15
20
  "license": "MIT",
16
21
  "bugs": {
17
- "url": "https://github.com/eissapk/keybox/issues"
22
+ "url": "https://github.com/eissapk/keebox/issues"
18
23
  },
19
- "homepage": "https://github.com/eissapk/keybox#readme"
24
+ "homepage": "https://github.com/eissapk/keebox#readme"
20
25
  }
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 (!data) return;
11
- const { db, cred } = data;
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
- const dbPath = path.resolve(__dirname, "../db.json");
16
- const credPath = path.resolve(__dirname, "../credentials.json");
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
- const fd = fs.openSync(tmp, "w");
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' | node index.js get --id 1 --rm
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 (node index.js get > list.txt)
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
  }