@elizaos/vault 2.0.0-alpha.537

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.
Files changed (62) hide show
  1. package/README.md +159 -0
  2. package/dist/audit.d.ts +14 -0
  3. package/dist/audit.d.ts.map +1 -0
  4. package/dist/audit.js +27 -0
  5. package/dist/audit.js.map +1 -0
  6. package/dist/credentials.d.ts +58 -0
  7. package/dist/credentials.d.ts.map +1 -0
  8. package/dist/credentials.js +157 -0
  9. package/dist/credentials.js.map +1 -0
  10. package/dist/crypto.d.ts +18 -0
  11. package/dist/crypto.d.ts.map +1 -0
  12. package/dist/crypto.js +67 -0
  13. package/dist/crypto.js.map +1 -0
  14. package/dist/external-credentials.d.ts +62 -0
  15. package/dist/external-credentials.d.ts.map +1 -0
  16. package/dist/external-credentials.js +335 -0
  17. package/dist/external-credentials.js.map +1 -0
  18. package/dist/index.d.ts +35 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +26 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/install.d.ts +70 -0
  23. package/dist/install.d.ts.map +1 -0
  24. package/dist/install.js +163 -0
  25. package/dist/install.js.map +1 -0
  26. package/dist/inventory.d.ts +140 -0
  27. package/dist/inventory.d.ts.map +1 -0
  28. package/dist/inventory.js +319 -0
  29. package/dist/inventory.js.map +1 -0
  30. package/dist/manager.d.ts +161 -0
  31. package/dist/manager.d.ts.map +1 -0
  32. package/dist/manager.js +466 -0
  33. package/dist/manager.js.map +1 -0
  34. package/dist/master-key.d.ts +86 -0
  35. package/dist/master-key.d.ts.map +1 -0
  36. package/dist/master-key.js +247 -0
  37. package/dist/master-key.js.map +1 -0
  38. package/dist/password-managers.d.ts +17 -0
  39. package/dist/password-managers.d.ts.map +1 -0
  40. package/dist/password-managers.js +59 -0
  41. package/dist/password-managers.js.map +1 -0
  42. package/dist/profiles.d.ts +68 -0
  43. package/dist/profiles.d.ts.map +1 -0
  44. package/dist/profiles.js +189 -0
  45. package/dist/profiles.js.map +1 -0
  46. package/dist/store.d.ts +22 -0
  47. package/dist/store.d.ts.map +1 -0
  48. package/dist/store.js +137 -0
  49. package/dist/store.js.map +1 -0
  50. package/dist/testing.d.ts +32 -0
  51. package/dist/testing.d.ts.map +1 -0
  52. package/dist/testing.js +70 -0
  53. package/dist/testing.js.map +1 -0
  54. package/dist/types.d.ts +56 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +12 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/vault.d.ts +77 -0
  59. package/dist/vault.d.ts.map +1 -0
  60. package/dist/vault.js +269 -0
  61. package/dist/vault.js.map +1 -0
  62. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # @elizaos/vault
2
+
3
+ Simple secrets/config vault for Eliza. **One** API for sensitive
4
+ credentials and non-sensitive configuration.
5
+
6
+ ## Why this exists
7
+
8
+ Eliza's Settings flow had four real bugs that all came from the same
9
+ root cause — credentials and config were scattered across multiple
10
+ writers, multiple file layouts, and a guess-which-field-is-the-key
11
+ heuristic. The vault is the single seam those bugs disappear behind:
12
+
13
+ 1. **Model slug overwrote API key** — the save path used
14
+ `Object.values(config).find(non-empty)` to identify the credential,
15
+ so typing the model field before the API-key field corrupted the
16
+ key. The vault's typed API makes this structurally impossible.
17
+ 2. **Dual writer** — values landed in `env.X` AND `env.vars.X`. One
18
+ writer, one storage location.
19
+ 3. **Orphan `tts/media/embeddings/rpc` routes after Eliza Cloud
20
+ disconnect** — fixed at the disconnect-handler level by clearing
21
+ the routes when the account unlinks.
22
+ 4. **No reveal** — saved values were write-only. `vault.reveal(key)`
23
+ round-trips through the audit log.
24
+
25
+ ## API
26
+
27
+ ```ts
28
+ import { createVault } from "@elizaos/vault";
29
+
30
+ const vault = createVault();
31
+
32
+ // Same call signature for sensitive and non-sensitive:
33
+ await vault.set("openrouter.apiKey", "sk-or-v1-...", { sensitive: true });
34
+ await vault.set("ui.theme", "dark");
35
+
36
+ // Reads:
37
+ await vault.get("openrouter.apiKey"); // → "sk-or-v1-..."
38
+ await vault.has("openrouter.apiKey"); // → true
39
+ await vault.describe("openrouter.apiKey"); // → { source, sensitive, lastModified }
40
+ await vault.reveal("openrouter.apiKey", "settings-ui"); // logged in audit
41
+ await vault.list(); // → all keys, no values
42
+ await vault.list("openrouter"); // → prefix-filtered
43
+ await vault.remove("openrouter.apiKey");
44
+ await vault.stats(); // → { total, sensitive, nonSensitive, references }
45
+
46
+ // Password-manager references — value lives there, vault stores reference:
47
+ await vault.setReference("openrouter.apiKey", {
48
+ source: "1password",
49
+ path: "Personal/OpenRouter/api-key",
50
+ });
51
+ ```
52
+
53
+ ## SecretsManager — pick which password managers to use
54
+
55
+ The `Vault` is the storage primitive. The `SecretsManager` sits on top
56
+ and routes direct writes based on user preferences. External password
57
+ managers are not written through this API yet; callers store references
58
+ with `vault.setReference()` after the value already exists in the vendor
59
+ tool.
60
+
61
+ ```ts
62
+ import { createManager } from "@elizaos/vault";
63
+
64
+ const manager = createManager();
65
+
66
+ // Probe what's available on this machine:
67
+ const statuses = await manager.detectBackends();
68
+ // [
69
+ // { id: "in-house", available: true, signedIn: true, label: "Eliza (local, encrypted)" },
70
+ // { id: "1password", available: true, signedIn: true, label: "1Password" },
71
+ // { id: "bitwarden", available: true, signedIn: false, label: "Bitwarden", detail: "...not signed in. Run `bw login`." },
72
+ // { id: "protonpass", available: false, label: "Proton Pass", detail: "...not installed (CLI in beta)." },
73
+ // ]
74
+
75
+ // User picks their backends in Settings:
76
+ await manager.setPreferences({
77
+ enabled: ["1password", "in-house"],
78
+ routing: { "anthropic.apiKey": "in-house" }, // optional per-key override
79
+ });
80
+
81
+ // External direct writes fail loudly until vendor write semantics exist:
82
+ await manager.set("openrouter.apiKey", "sk-or-...", { sensitive: true });
83
+ // → throws: backend "1password" cannot accept direct writes yet
84
+
85
+ // Store explicit references through the vault primitive:
86
+ await manager.vault.setReference("openrouter.apiKey", {
87
+ source: "1password",
88
+ path: "Personal/OpenRouter/api-key",
89
+ });
90
+
91
+ await manager.set("anthropic.apiKey", "sk-ant-...", { sensitive: true });
92
+ // → in-house (per-key override above)
93
+
94
+ await manager.set("ui.theme", "dark");
95
+ // → always in-house (non-sensitive values don't go to password managers)
96
+ ```
97
+
98
+ **Three modes the user can run in:**
99
+
100
+ - **None** — nothing enabled but `in-house`. Default. Local-only.
101
+ - **One** — pick 1Password OR Proton Pass OR Bitwarden. Direct sensitive
102
+ writes fail until vendor write support exists; explicit references can
103
+ still be stored with `vault.setReference()`.
104
+ - **All** — all backends enabled. Per-key routing in Settings, or just
105
+ use the priority order.
106
+
107
+ `in-house` is always available. External backend failures are surfaced
108
+ instead of silently falling back to local storage.
109
+
110
+ ## Storage
111
+
112
+ - **Sensitive values** — AES-256-GCM encrypted at rest with the vault
113
+ key as additional authenticated data. Master key in OS keychain
114
+ (cross-platform via `@napi-rs/keyring`: macOS Keychain, Windows
115
+ Credential Manager, Linux libsecret).
116
+ - **Non-sensitive values** — plaintext in `~/.eliza/vault.json`
117
+ (mode 0600). Atomic-rename writes.
118
+ - **References** — stored as `{ source, path }`. The actual value lives
119
+ in 1Password / Proton Pass; resolved at use time via the vendor's
120
+ CLI.
121
+
122
+ ## Sync
123
+
124
+ Sync = your existing tools. If you want secrets across devices, store
125
+ them as 1Password references — 1Password syncs your vault, the
126
+ references stay portable, your secrets follow. We don't build a
127
+ separate cloud sync.
128
+
129
+ ## Audit log
130
+
131
+ Every operation appends one JSONL line to
132
+ `~/.eliza/audit/vault.jsonl`:
133
+
134
+ ```jsonl
135
+ {"ts":1714330000000,"action":"set","key":"openrouter.apiKey"}
136
+ {"ts":1714330000010,"action":"get","key":"openrouter.apiKey"}
137
+ {"ts":1714330000020,"action":"reveal","key":"openrouter.apiKey","caller":"settings-ui"}
138
+ ```
139
+
140
+ Records keys, never values. Pass an optional `caller` to `reveal()` so
141
+ the log shows who asked.
142
+
143
+ ## Testing
144
+
145
+ ```ts
146
+ import { createTestVault } from "@elizaos/vault/testing";
147
+
148
+ const test = await createTestVault({
149
+ values: { "ui.theme": "dark" },
150
+ secrets: { "openrouter.apiKey": "test-key" },
151
+ });
152
+
153
+ await test.vault.set("openai.apiKey", "test-2", { sensitive: true });
154
+ const records = await test.getAuditRecords();
155
+ await test.dispose();
156
+ ```
157
+
158
+ Real vault, real encryption, real audit log — temp dir cleaned up on
159
+ `dispose()`. No OS keychain access (uses an in-memory master key).
@@ -0,0 +1,14 @@
1
+ import type { AuditRecord, VaultLogger } from "./types.js";
2
+ /**
3
+ * Append-only JSONL audit log. One line per vault operation. Records
4
+ * keys, never values.
5
+ */
6
+ export declare class AuditLog {
7
+ private readonly path;
8
+ private readonly logger?;
9
+ constructor(path: string, logger?: VaultLogger | undefined);
10
+ record(entry: Omit<AuditRecord, "ts"> & {
11
+ ts?: number;
12
+ }): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE3D;;;GAGG;AACH,qBAAa,QAAQ;IAEjB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBADP,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,WAAW,YAAA;IAGjC,MAAM,CACV,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAC/C,OAAO,CAAC,IAAI,CAAC;CAcjB"}
package/dist/audit.js ADDED
@@ -0,0 +1,27 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ /**
4
+ * Append-only JSONL audit log. One line per vault operation. Records
5
+ * keys, never values.
6
+ */
7
+ export class AuditLog {
8
+ path;
9
+ logger;
10
+ constructor(path, logger) {
11
+ this.path = path;
12
+ this.logger = logger;
13
+ }
14
+ async record(entry) {
15
+ const record = { ts: entry.ts ?? Date.now(), ...entry };
16
+ const line = `${JSON.stringify(record)}\n`;
17
+ try {
18
+ await fs.mkdir(dirname(this.path), { recursive: true });
19
+ await fs.appendFile(this.path, line, { mode: 0o600 });
20
+ }
21
+ catch (err) {
22
+ // Audit failure must not block the caller, but it must be visible.
23
+ this.logger?.warn(`[vault] failed to append audit record to ${this.path}`, err);
24
+ }
25
+ }
26
+ }
27
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;;GAGG;AACH,MAAM,OAAO,QAAQ;IAEA;IACA;IAFnB,YACmB,IAAY,EACZ,MAAoB;QADpB,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAc;IACpC,CAAC;IAEJ,KAAK,CAAC,MAAM,CACV,KAAgD;QAEhD,MAAM,MAAM,GAAgB,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC;QACrE,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,MAAM,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,mEAAmE;YACnE,IAAI,CAAC,MAAM,EAAE,IAAI,CACf,4CAA4C,IAAI,CAAC,IAAI,EAAE,EACvD,GAAG,CACJ,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Saved-login helpers for in-app browser autofill.
3
+ *
4
+ * The browser tab preload detects login forms and asks the host for
5
+ * matching credentials. The host reads them from the vault using the
6
+ * helpers here. Storage layout:
7
+ *
8
+ * creds.<domain>.<account> → JSON-encoded SavedLoginRecord, sensitive
9
+ * creds.<domain>.__autoallow → "1" / "0", non-sensitive (whitelist toggle)
10
+ *
11
+ * `<domain>` is the registrable hostname (e.g. `github.com`, no port).
12
+ * `<account>` is the URL-encoded username (so `@`, `/`, `.` are safe in
13
+ * the segment). Vault prefix matching uses dot segments, so listing
14
+ * `creds.github.com` returns every account under that domain plus the
15
+ * autoallow flag.
16
+ *
17
+ * Sensitive values are AES-GCM encrypted by the vault. Listing returns
18
+ * metadata only — passwords are never copied into the listing payload.
19
+ */
20
+ import type { Vault } from "./vault.js";
21
+ export interface SavedLogin {
22
+ /** Registrable hostname, e.g. `github.com`. Lower-cased on write. */
23
+ readonly domain: string;
24
+ /** User identifier as typed: email, handle, etc. */
25
+ readonly username: string;
26
+ /** Plaintext password. Encrypted at rest by the vault. */
27
+ readonly password: string;
28
+ /** TOTP seed for sites with 2FA. */
29
+ readonly otpSeed?: string;
30
+ /** Free-form note. */
31
+ readonly notes?: string;
32
+ /** Unix ms of last write. Set by `setSavedLogin`. */
33
+ readonly lastModified: number;
34
+ }
35
+ export interface SavedLoginSummary {
36
+ readonly domain: string;
37
+ readonly username: string;
38
+ readonly lastModified: number;
39
+ }
40
+ /** Persist (or replace) a login. Stamps `lastModified` automatically. */
41
+ export declare function setSavedLogin(vault: Vault, login: Omit<SavedLogin, "lastModified">): Promise<void>;
42
+ /** Read a login. Returns null when missing. */
43
+ export declare function getSavedLogin(vault: Vault, domain: string, username: string): Promise<SavedLogin | null>;
44
+ /**
45
+ * List logins. With no `domain`, returns every saved login summary
46
+ * across the vault. With a domain, scopes to that hostname.
47
+ *
48
+ * Returns metadata only. The password values stay encrypted at rest;
49
+ * callers must `getSavedLogin` to decrypt one entry at a time.
50
+ */
51
+ export declare function listSavedLogins(vault: Vault, domain?: string): Promise<readonly SavedLoginSummary[]>;
52
+ /** Remove a single login. Idempotent. */
53
+ export declare function deleteSavedLogin(vault: Vault, domain: string, username: string): Promise<void>;
54
+ /** Read the autoallow flag for a domain. False when unset. */
55
+ export declare function getAutofillAllowed(vault: Vault, domain: string): Promise<boolean>;
56
+ /** Toggle the autoallow flag. `true` skips consent on next autofill for that domain. */
57
+ export declare function setAutofillAllowed(vault: Vault, domain: string, allowed: boolean): Promise<void>;
58
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAQxC,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oCAAoC;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAwBD,yEAAyE;AACzE,wBAAsB,aAAa,CACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED,+CAA+C;AAC/C,wBAAsB,aAAa,CACjC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAM5B;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,KAAK,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAyBvC;AAED,yCAAyC;AACzC,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,8DAA8D;AAC9D,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAKlB;AAED,wFAAwF;AACxF,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAEf"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Saved-login helpers for in-app browser autofill.
3
+ *
4
+ * The browser tab preload detects login forms and asks the host for
5
+ * matching credentials. The host reads them from the vault using the
6
+ * helpers here. Storage layout:
7
+ *
8
+ * creds.<domain>.<account> → JSON-encoded SavedLoginRecord, sensitive
9
+ * creds.<domain>.__autoallow → "1" / "0", non-sensitive (whitelist toggle)
10
+ *
11
+ * `<domain>` is the registrable hostname (e.g. `github.com`, no port).
12
+ * `<account>` is the URL-encoded username (so `@`, `/`, `.` are safe in
13
+ * the segment). Vault prefix matching uses dot segments, so listing
14
+ * `creds.github.com` returns every account under that domain plus the
15
+ * autoallow flag.
16
+ *
17
+ * Sensitive values are AES-GCM encrypted by the vault. Listing returns
18
+ * metadata only — passwords are never copied into the listing payload.
19
+ */
20
+ const PREFIX = "creds";
21
+ // Sentinel for the per-domain autoallow flag. The colon prefix is URL-
22
+ // encoded by `encodeAccount`, so a literal username `:autoallow` lives at
23
+ // `%3Aautoallow` and cannot collide with this sentinel.
24
+ const AUTOALLOW_SEGMENT = ":autoallow";
25
+ /** Encode an account segment so vault key parsing stays unambiguous. */
26
+ function encodeAccount(username) {
27
+ return encodeURIComponent(username);
28
+ }
29
+ function decodeAccount(segment) {
30
+ return decodeURIComponent(segment);
31
+ }
32
+ /** Lower-case domains so `Github.com` and `github.com` collide. */
33
+ function normalizeDomain(domain) {
34
+ return domain.trim().toLowerCase();
35
+ }
36
+ function loginKey(domain, username) {
37
+ return `${PREFIX}.${normalizeDomain(domain)}.${encodeAccount(username)}`;
38
+ }
39
+ function autoallowKey(domain) {
40
+ return `${PREFIX}.${normalizeDomain(domain)}.${AUTOALLOW_SEGMENT}`;
41
+ }
42
+ /** Persist (or replace) a login. Stamps `lastModified` automatically. */
43
+ export async function setSavedLogin(vault, login) {
44
+ if (login.domain.trim().length === 0) {
45
+ throw new TypeError("setSavedLogin: domain required");
46
+ }
47
+ if (login.username.length === 0) {
48
+ throw new TypeError("setSavedLogin: username required");
49
+ }
50
+ if (typeof login.password !== "string" || login.password.length === 0) {
51
+ throw new TypeError("setSavedLogin: password required");
52
+ }
53
+ const record = {
54
+ domain: normalizeDomain(login.domain),
55
+ username: login.username,
56
+ password: login.password,
57
+ ...(login.otpSeed ? { otpSeed: login.otpSeed } : {}),
58
+ ...(login.notes ? { notes: login.notes } : {}),
59
+ lastModified: Date.now(),
60
+ };
61
+ await vault.set(loginKey(login.domain, login.username), JSON.stringify(record), { sensitive: true });
62
+ }
63
+ /** Read a login. Returns null when missing. */
64
+ export async function getSavedLogin(vault, domain, username) {
65
+ const key = loginKey(domain, username);
66
+ const has = await vault.has(key);
67
+ if (!has)
68
+ return null;
69
+ const raw = await vault.get(key);
70
+ return parseLogin(raw);
71
+ }
72
+ /**
73
+ * List logins. With no `domain`, returns every saved login summary
74
+ * across the vault. With a domain, scopes to that hostname.
75
+ *
76
+ * Returns metadata only. The password values stay encrypted at rest;
77
+ * callers must `getSavedLogin` to decrypt one entry at a time.
78
+ */
79
+ export async function listSavedLogins(vault, domain) {
80
+ const prefix = domain ? `${PREFIX}.${normalizeDomain(domain)}` : PREFIX;
81
+ const keys = await vault.list(prefix);
82
+ const summaries = [];
83
+ const failures = [];
84
+ for (const key of keys) {
85
+ const parsed = parseLoginKey(key);
86
+ if (!parsed)
87
+ continue;
88
+ if (parsed.account === AUTOALLOW_SEGMENT)
89
+ continue;
90
+ const descriptor = await vault.describe(key);
91
+ if (!descriptor)
92
+ continue;
93
+ // describe() returns lastModified directly; we don't need to
94
+ // decrypt the value to render the listing UI.
95
+ summaries.push({
96
+ domain: parsed.domain,
97
+ username: decodeAccount(parsed.account),
98
+ lastModified: descriptor.lastModified,
99
+ });
100
+ }
101
+ if (failures.length > 0) {
102
+ throw new Error(`listSavedLogins: failed to describe ${failures.length} key(s): ${failures.join(", ")}`);
103
+ }
104
+ return summaries;
105
+ }
106
+ /** Remove a single login. Idempotent. */
107
+ export async function deleteSavedLogin(vault, domain, username) {
108
+ await vault.remove(loginKey(domain, username));
109
+ }
110
+ /** Read the autoallow flag for a domain. False when unset. */
111
+ export async function getAutofillAllowed(vault, domain) {
112
+ const key = autoallowKey(domain);
113
+ if (!(await vault.has(key)))
114
+ return false;
115
+ const raw = await vault.get(key);
116
+ return raw === "1";
117
+ }
118
+ /** Toggle the autoallow flag. `true` skips consent on next autofill for that domain. */
119
+ export async function setAutofillAllowed(vault, domain, allowed) {
120
+ await vault.set(autoallowKey(domain), allowed ? "1" : "0");
121
+ }
122
+ function parseLoginKey(key) {
123
+ // creds.<domain>.<account-or-flag>
124
+ // We split on the first two dots: the first separates the prefix,
125
+ // the second separates the domain from the account segment.
126
+ if (!key.startsWith(`${PREFIX}.`))
127
+ return null;
128
+ const rest = key.slice(PREFIX.length + 1);
129
+ // The domain part itself contains dots (e.g. github.com), so the
130
+ // account segment is everything after the LAST dot.
131
+ const lastDot = rest.lastIndexOf(".");
132
+ if (lastDot <= 0)
133
+ return null;
134
+ const domain = rest.slice(0, lastDot);
135
+ const account = rest.slice(lastDot + 1);
136
+ if (!domain || !account)
137
+ return null;
138
+ return { domain, account };
139
+ }
140
+ function parseLogin(raw) {
141
+ const parsed = JSON.parse(raw);
142
+ if (typeof parsed.domain !== "string" ||
143
+ typeof parsed.username !== "string" ||
144
+ typeof parsed.password !== "string" ||
145
+ typeof parsed.lastModified !== "number") {
146
+ throw new Error(`vault credentials: stored entry is malformed (got keys: ${Object.keys(parsed).join(", ")})`);
147
+ }
148
+ return {
149
+ domain: parsed.domain,
150
+ username: parsed.username,
151
+ password: parsed.password,
152
+ ...(parsed.otpSeed ? { otpSeed: parsed.otpSeed } : {}),
153
+ ...(parsed.notes ? { notes: parsed.notes } : {}),
154
+ lastModified: parsed.lastModified,
155
+ };
156
+ }
157
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * AES-256-GCM envelope for a single value.
3
+ *
4
+ * Wire format: `v1:<nonce_b64>:<tag_b64>:<ct_b64>` (all base64).
5
+ * - nonce: 12 bytes (96-bit GCM standard)
6
+ * - tag: 16 bytes (128-bit auth tag)
7
+ *
8
+ * The vault key is bound as additional authenticated data (AAD) so a
9
+ * swapped ciphertext between slots fails decryption.
10
+ */
11
+ export declare const KEY_BYTES = 32;
12
+ export declare class CryptoError extends Error {
13
+ constructor(message: string);
14
+ }
15
+ export declare function generateMasterKey(): Buffer;
16
+ export declare function encrypt(masterKey: Buffer, plaintext: string, aad: string): string;
17
+ export declare function decrypt(masterKey: Buffer, ciphertext: string, aad: string): string;
18
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,eAAO,MAAM,SAAS,KAAK,CAAC;AAI5B,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,OAAO,CACrB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,MAAM,CAUR;AAED,wBAAgB,OAAO,CACrB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,MAAM,CAkCR"}
package/dist/crypto.js ADDED
@@ -0,0 +1,67 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
+ /**
3
+ * AES-256-GCM envelope for a single value.
4
+ *
5
+ * Wire format: `v1:<nonce_b64>:<tag_b64>:<ct_b64>` (all base64).
6
+ * - nonce: 12 bytes (96-bit GCM standard)
7
+ * - tag: 16 bytes (128-bit auth tag)
8
+ *
9
+ * The vault key is bound as additional authenticated data (AAD) so a
10
+ * swapped ciphertext between slots fails decryption.
11
+ */
12
+ export const KEY_BYTES = 32;
13
+ const NONCE_BYTES = 12;
14
+ const TAG_BYTES = 16;
15
+ export class CryptoError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = "CryptoError";
19
+ }
20
+ }
21
+ export function generateMasterKey() {
22
+ return randomBytes(KEY_BYTES);
23
+ }
24
+ export function encrypt(masterKey, plaintext, aad) {
25
+ if (masterKey.length !== KEY_BYTES) {
26
+ throw new CryptoError(`master key must be ${KEY_BYTES} bytes`);
27
+ }
28
+ const nonce = randomBytes(NONCE_BYTES);
29
+ const cipher = createCipheriv("aes-256-gcm", masterKey, nonce);
30
+ cipher.setAAD(Buffer.from(aad, "utf8"));
31
+ const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
32
+ const tag = cipher.getAuthTag();
33
+ return `v1:${nonce.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
34
+ }
35
+ export function decrypt(masterKey, ciphertext, aad) {
36
+ if (masterKey.length !== KEY_BYTES) {
37
+ throw new CryptoError(`master key must be ${KEY_BYTES} bytes`);
38
+ }
39
+ const parts = ciphertext.split(":");
40
+ if (parts.length !== 4 || parts[0] !== "v1") {
41
+ throw new CryptoError("malformed ciphertext or unsupported version");
42
+ }
43
+ const nonceB64 = parts[1];
44
+ const tagB64 = parts[2];
45
+ const ctB64 = parts[3];
46
+ if (nonceB64 === undefined || tagB64 === undefined || ctB64 === undefined) {
47
+ throw new CryptoError("malformed ciphertext");
48
+ }
49
+ const nonce = Buffer.from(nonceB64, "base64");
50
+ const tag = Buffer.from(tagB64, "base64");
51
+ const ct = Buffer.from(ctB64, "base64");
52
+ if (nonce.length !== NONCE_BYTES || tag.length !== TAG_BYTES) {
53
+ throw new CryptoError("malformed ciphertext");
54
+ }
55
+ const decipher = createDecipheriv("aes-256-gcm", masterKey, nonce);
56
+ decipher.setAAD(Buffer.from(aad, "utf8"));
57
+ decipher.setAuthTag(tag);
58
+ try {
59
+ return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
60
+ }
61
+ catch (err) {
62
+ throw new CryptoError(err instanceof Error
63
+ ? `decryption failed: ${err.message}`
64
+ : "decryption failed");
65
+ }
66
+ }
67
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * External credential adapters for password-manager backends.
3
+ *
4
+ * Reads Login items out of `op` (1Password) and `bw` (Bitwarden) using the
5
+ * session token persisted by the secrets-manager installer at
6
+ * `pm.<backend>.session`. Returns a uniform shape so the manager layer can
7
+ * merge them with the in-house saved-logins list.
8
+ *
9
+ * list* → metadata only, never returns passwords
10
+ * reveal* → explicit second step, returns username + password (+ totp)
11
+ *
12
+ * The CLI is shelled out via an injected `ExecFn` so tests can stub the
13
+ * subprocess without spawning real processes.
14
+ */
15
+ import type { Vault } from "./vault.js";
16
+ export type ExternalLoginSource = "1password" | "bitwarden";
17
+ export interface ExternalLoginListEntry {
18
+ readonly source: ExternalLoginSource;
19
+ /** op item id / bw item id — opaque to callers. */
20
+ readonly externalId: string;
21
+ readonly title: string;
22
+ readonly username: string;
23
+ /** Best-effort registrable hostname extracted from urls[0]; null when none. */
24
+ readonly domain: string | null;
25
+ readonly url: string | null;
26
+ /** Epoch ms; 0 when the backend didn't supply a timestamp. */
27
+ readonly updatedAt: number;
28
+ }
29
+ export interface ExternalLoginReveal extends ExternalLoginListEntry {
30
+ readonly password: string;
31
+ readonly totp?: string;
32
+ }
33
+ export declare class BackendNotSignedInError extends Error {
34
+ readonly source: ExternalLoginSource;
35
+ constructor(source: ExternalLoginSource);
36
+ }
37
+ /**
38
+ * Subprocess executor injected by the manager (tests pass a stub).
39
+ *
40
+ * Mirrors `node:child_process.execFile` with promises: returns combined
41
+ * stdout/stderr, throws on non-zero exit. The `env` option matters for
42
+ * Bitwarden (BW_SESSION) — 1Password uses an explicit `--session` flag.
43
+ */
44
+ export type ExecFn = (cmd: string, args: readonly string[], opts: {
45
+ readonly env?: NodeJS.ProcessEnv;
46
+ readonly timeoutMs?: number;
47
+ readonly stdin?: string;
48
+ }) => Promise<{
49
+ readonly stdout: string;
50
+ readonly stderr: string;
51
+ }>;
52
+ export declare function listOnePasswordLogins(vault: Vault, exec: ExecFn): Promise<readonly ExternalLoginListEntry[]>;
53
+ export declare function revealOnePasswordLogin(vault: Vault, exec: ExecFn, externalId: string): Promise<ExternalLoginReveal>;
54
+ export declare function listBitwardenLogins(vault: Vault, exec: ExecFn): Promise<readonly ExternalLoginListEntry[]>;
55
+ export declare function revealBitwardenLogin(vault: Vault, exec: ExecFn, externalId: string): Promise<ExternalLoginReveal>;
56
+ /**
57
+ * Production `ExecFn` wrapping `node:child_process.execFile`. Tests inject
58
+ * stubs instead of using this. Lives here so callers can `import` a single
59
+ * default rather than wiring `child_process` themselves.
60
+ */
61
+ export declare function defaultExecFn(): ExecFn;
62
+ //# sourceMappingURL=external-credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-credentials.d.ts","sourceRoot":"","sources":["../src/external-credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,WAAW,CAAC;AAE5D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,mDAAmD;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAoB,SAAQ,sBAAsB;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,EAAE,mBAAmB;gBAA3B,MAAM,EAAE,mBAAmB;CAIjD;AAED;;;;;;GAMG;AACH,MAAM,MAAM,MAAM,GAAG,CACnB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE;IACJ,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB,KACE,OAAO,CAAC;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAgCnE,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,SAAS,sBAAsB,EAAE,CAAC,CAyC5C;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAwC9B;AAsDD,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,SAAS,sBAAsB,EAAE,CAAC,CA+B5C;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAiC9B;AAuJD;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CA8BtC"}