@centient/secrets 0.9.0 → 0.11.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.
Files changed (152) hide show
  1. package/README.md +424 -8
  2. package/dist/audit/hmac-chain.d.ts +117 -0
  3. package/dist/audit/hmac-chain.d.ts.map +1 -0
  4. package/dist/audit/hmac-chain.js +811 -0
  5. package/dist/audit/hmac-chain.js.map +1 -0
  6. package/dist/audit/ocsf.d.ts +108 -0
  7. package/dist/audit/ocsf.d.ts.map +1 -0
  8. package/dist/audit/ocsf.js +204 -0
  9. package/dist/audit/ocsf.js.map +1 -0
  10. package/dist/cli/hidden-prompt.d.ts.map +1 -1
  11. package/dist/cli/hidden-prompt.js +55 -10
  12. package/dist/cli/hidden-prompt.js.map +1 -1
  13. package/dist/cli/messages.d.ts +40 -8
  14. package/dist/cli/messages.d.ts.map +1 -1
  15. package/dist/cli/messages.js +34 -8
  16. package/dist/cli/messages.js.map +1 -1
  17. package/dist/cli/secrets-cli.d.ts.map +1 -1
  18. package/dist/cli/secrets-cli.js +191 -32
  19. package/dist/cli/secrets-cli.js.map +1 -1
  20. package/dist/client.d.ts +26 -0
  21. package/dist/client.d.ts.map +1 -0
  22. package/dist/client.js +129 -0
  23. package/dist/client.js.map +1 -0
  24. package/dist/compatibility.d.ts +199 -0
  25. package/dist/compatibility.d.ts.map +1 -0
  26. package/dist/compatibility.js +419 -0
  27. package/dist/compatibility.js.map +1 -0
  28. package/dist/crypto/darwin-keychain.d.ts +394 -0
  29. package/dist/crypto/darwin-keychain.d.ts.map +1 -0
  30. package/dist/crypto/darwin-keychain.js +793 -0
  31. package/dist/crypto/darwin-keychain.js.map +1 -0
  32. package/dist/crypto/vault-common.d.ts +55 -12
  33. package/dist/crypto/vault-common.d.ts.map +1 -1
  34. package/dist/crypto/vault-common.js +74 -71
  35. package/dist/crypto/vault-common.js.map +1 -1
  36. package/dist/environment/EnvironmentManager.d.ts.map +1 -1
  37. package/dist/environment/EnvironmentManager.js +5 -10
  38. package/dist/environment/EnvironmentManager.js.map +1 -1
  39. package/dist/index.d.ts +37 -9
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +38 -6
  42. package/dist/index.js.map +1 -1
  43. package/dist/key-providers/keychain-provider.d.ts +30 -2
  44. package/dist/key-providers/keychain-provider.d.ts.map +1 -1
  45. package/dist/key-providers/keychain-provider.js +29 -5
  46. package/dist/key-providers/keychain-provider.js.map +1 -1
  47. package/dist/key-providers/onepassword-provider.d.ts +65 -5
  48. package/dist/key-providers/onepassword-provider.d.ts.map +1 -1
  49. package/dist/key-providers/onepassword-provider.js +159 -67
  50. package/dist/key-providers/onepassword-provider.js.map +1 -1
  51. package/dist/key-providers/op-cli.d.ts +78 -0
  52. package/dist/key-providers/op-cli.d.ts.map +1 -0
  53. package/dist/key-providers/op-cli.js +123 -0
  54. package/dist/key-providers/op-cli.js.map +1 -0
  55. package/dist/key-providers/passphrase-provider.d.ts +20 -0
  56. package/dist/key-providers/passphrase-provider.d.ts.map +1 -1
  57. package/dist/key-providers/passphrase-provider.js +49 -0
  58. package/dist/key-providers/passphrase-provider.js.map +1 -1
  59. package/dist/key-providers/probe.d.ts +46 -0
  60. package/dist/key-providers/probe.d.ts.map +1 -0
  61. package/dist/key-providers/probe.js +70 -0
  62. package/dist/key-providers/probe.js.map +1 -0
  63. package/dist/key-providers/types.d.ts +51 -1
  64. package/dist/key-providers/types.d.ts.map +1 -1
  65. package/dist/platform/agent-detect.d.ts +39 -3
  66. package/dist/platform/agent-detect.d.ts.map +1 -1
  67. package/dist/platform/agent-detect.js +55 -9
  68. package/dist/platform/agent-detect.js.map +1 -1
  69. package/dist/platform/session-context.d.ts +77 -0
  70. package/dist/platform/session-context.d.ts.map +1 -0
  71. package/dist/platform/session-context.js +83 -0
  72. package/dist/platform/session-context.js.map +1 -0
  73. package/dist/policies/audit-trail.d.ts +25 -0
  74. package/dist/policies/audit-trail.d.ts.map +1 -0
  75. package/dist/policies/audit-trail.js +31 -0
  76. package/dist/policies/audit-trail.js.map +1 -0
  77. package/dist/secret-state.d.ts +54 -0
  78. package/dist/secret-state.d.ts.map +1 -0
  79. package/dist/secret-state.js +2 -0
  80. package/dist/secret-state.js.map +1 -0
  81. package/dist/sinks/index.d.ts +9 -0
  82. package/dist/sinks/index.d.ts.map +1 -0
  83. package/dist/sinks/index.js +5 -0
  84. package/dist/sinks/index.js.map +1 -0
  85. package/dist/sinks/jsonl.d.ts +16 -0
  86. package/dist/sinks/jsonl.d.ts.map +1 -0
  87. package/dist/sinks/jsonl.js +51 -0
  88. package/dist/sinks/jsonl.js.map +1 -0
  89. package/dist/sinks/otel.d.ts +22 -0
  90. package/dist/sinks/otel.d.ts.map +1 -0
  91. package/dist/sinks/otel.js +155 -0
  92. package/dist/sinks/otel.js.map +1 -0
  93. package/dist/sinks/queue.d.ts +12 -0
  94. package/dist/sinks/queue.d.ts.map +1 -0
  95. package/dist/sinks/queue.js +89 -0
  96. package/dist/sinks/queue.js.map +1 -0
  97. package/dist/sinks/syslog.d.ts +17 -0
  98. package/dist/sinks/syslog.d.ts.map +1 -0
  99. package/dist/sinks/syslog.js +103 -0
  100. package/dist/sinks/syslog.js.map +1 -0
  101. package/dist/sinks/types.d.ts +27 -0
  102. package/dist/sinks/types.d.ts.map +1 -0
  103. package/dist/sinks/types.js +18 -0
  104. package/dist/sinks/types.js.map +1 -0
  105. package/dist/vault/policy.d.ts +64 -34
  106. package/dist/vault/policy.d.ts.map +1 -1
  107. package/dist/vault/policy.js +109 -67
  108. package/dist/vault/policy.js.map +1 -1
  109. package/dist/vault/session-vault-errors.d.ts +72 -4
  110. package/dist/vault/session-vault-errors.d.ts.map +1 -1
  111. package/dist/vault/session-vault-errors.js +95 -4
  112. package/dist/vault/session-vault-errors.js.map +1 -1
  113. package/dist/vault/session-vault.d.ts +145 -2
  114. package/dist/vault/session-vault.d.ts.map +1 -1
  115. package/dist/vault/session-vault.js +548 -72
  116. package/dist/vault/session-vault.js.map +1 -1
  117. package/dist/vault/sidecar.d.ts +9 -0
  118. package/dist/vault/sidecar.d.ts.map +1 -1
  119. package/dist/vault/sidecar.js +72 -3
  120. package/dist/vault/sidecar.js.map +1 -1
  121. package/dist/vault/types.d.ts +58 -10
  122. package/dist/vault/types.d.ts.map +1 -1
  123. package/dist/vault/types.js.map +1 -1
  124. package/dist/vault/vault-env.d.ts +43 -50
  125. package/dist/vault/vault-env.d.ts.map +1 -1
  126. package/dist/vault/vault-env.js +141 -62
  127. package/dist/vault/vault-env.js.map +1 -1
  128. package/dist/vault/vault-gpg.d.ts +6 -1
  129. package/dist/vault/vault-gpg.d.ts.map +1 -1
  130. package/dist/vault/vault-gpg.js +10 -8
  131. package/dist/vault/vault-gpg.js.map +1 -1
  132. package/dist/vault/vault-libsecret.d.ts +6 -1
  133. package/dist/vault/vault-libsecret.d.ts.map +1 -1
  134. package/dist/vault/vault-libsecret.js +16 -14
  135. package/dist/vault/vault-libsecret.js.map +1 -1
  136. package/dist/vault/vault-onepassword.d.ts +119 -0
  137. package/dist/vault/vault-onepassword.d.ts.map +1 -0
  138. package/dist/vault/vault-onepassword.js +271 -0
  139. package/dist/vault/vault-onepassword.js.map +1 -0
  140. package/dist/vault/vault-utils.d.ts +37 -3
  141. package/dist/vault/vault-utils.d.ts.map +1 -1
  142. package/dist/vault/vault-utils.js +65 -3
  143. package/dist/vault/vault-utils.js.map +1 -1
  144. package/dist/vault/vault-windows.d.ts +7 -2
  145. package/dist/vault/vault-windows.d.ts.map +1 -1
  146. package/dist/vault/vault-windows.js +21 -16
  147. package/dist/vault/vault-windows.js.map +1 -1
  148. package/dist/vault/vault.d.ts +120 -9
  149. package/dist/vault/vault.d.ts.map +1 -1
  150. package/dist/vault/vault.js +222 -139
  151. package/dist/vault/vault.js.map +1 -1
  152. package/package.json +8 -2
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Vault backend — 1Password credential storage (ADR-004).
3
+ *
4
+ * Stores credential **values** in a dedicated 1Password vault via the `op` CLI.
5
+ * This is a different layer from ADR-001's `OnePasswordProvider`, which stores
6
+ * the vault *encryption key*: an operator may keep the key in the Keychain and
7
+ * the credentials in 1Password, or the reverse. The two carry separate config
8
+ * blocks for exactly that reason (ADR-004 §1).
9
+ *
10
+ * ## Two properties this backend exists to hold
11
+ *
12
+ * 1. **Never auto-selected.** `op` being installed must not silently route
13
+ * credentials into someone's personal vault — surprising secret placement is
14
+ * a security defect (P15/P2). Selection is explicit opt-in, and
15
+ * {@link OnePasswordVault.detect} refuses without it.
16
+ * 2. **Secret values never touch argv.** Writes build the item JSON in-process
17
+ * and pipe it to `op item create -`, so nothing readable in `ps` ever carries
18
+ * a secret. This is the pattern issue #102 retrofitted to the key provider,
19
+ * which now writes through the same `op item create -` stdin seam. Its
20
+ * replace ordering differs — create first, delete the superseded item last —
21
+ * because losing the vault *master key* between two calls would be
22
+ * unrecoverable, where a credential value can be re-fetched from its source.
23
+ */
24
+ import { assertValidKey } from "./vault-utils.js";
25
+ import { detectOpCli, runOp, OpCliError, OP_READ_TIMEOUT_MS, OP_WRITE_TIMEOUT_MS, } from "../key-providers/op-cli.js";
26
+ // =============================================================================
27
+ // Constants
28
+ // =============================================================================
29
+ /** Default tag applied to every item this backend writes. */
30
+ export const DEFAULT_OP_TAG = "centient";
31
+ /** The 1Password field a credential value lives in. */
32
+ const FIELD_NAME = "password";
33
+ /**
34
+ * List-cache TTL, mirroring `KEYCHAIN_LIST_CACHE_TTL_MS` (ADR-004 §8).
35
+ *
36
+ * Only key **names** are cached, never values: a cached value would both extend
37
+ * how long plaintext sits in the heap and serve a rotated or revoked credential
38
+ * until expiry. 1Password stays the source of truth for values.
39
+ */
40
+ export const OP_LIST_CACHE_TTL_MS = 5_000;
41
+ // =============================================================================
42
+ // Implementation
43
+ // =============================================================================
44
+ export class OnePasswordVault {
45
+ name = "1password";
46
+ vault;
47
+ tag;
48
+ now;
49
+ listCache = null;
50
+ warned = false;
51
+ /**
52
+ * @param config - must carry a `vault`; there is deliberately no default
53
+ * (ADR-004 §1 — guessing a vault name risks writing credentials somewhere
54
+ * the operator did not intend).
55
+ * @throws {Error} when `vault` is missing or blank.
56
+ */
57
+ constructor(config) {
58
+ const vault = config.vault?.trim();
59
+ if (!vault) {
60
+ throw new Error('secrets.onePasswordBackend.vault is required when backend is "1password" ' +
61
+ "(or set CENTIENT_OP_VAULT). There is no default vault — see ADR-004 §1.");
62
+ }
63
+ this.vault = vault;
64
+ this.tag = config.tag?.trim() || DEFAULT_OP_TAG;
65
+ this.now = config.now ?? Date.now;
66
+ }
67
+ /**
68
+ * Whether this backend may be used.
69
+ *
70
+ * Requires **both** an explicit opt-in and a usable `op`. The opt-in half is
71
+ * what keeps 1Password out of the auto-cascade: without it this returns false
72
+ * even on a machine where `op` is installed and signed in.
73
+ *
74
+ * @param optedIn - true when config or env explicitly selected this backend.
75
+ */
76
+ static detect(optedIn) {
77
+ if (!optedIn)
78
+ return false;
79
+ return detectOpCli();
80
+ }
81
+ /**
82
+ * Write a credential. The value travels on stdin and never appears in argv.
83
+ *
84
+ * Update is modeled as replace (delete-then-create) so create and update share
85
+ * one argv-safe path rather than needing a separate `op item edit` form.
86
+ */
87
+ store(key, value) {
88
+ assertValidKey(key, "write");
89
+ const item = {
90
+ title: key,
91
+ category: "PASSWORD",
92
+ vault: { name: this.vault },
93
+ tags: [this.tag],
94
+ fields: [{ id: FIELD_NAME, type: "CONCEALED", value }],
95
+ };
96
+ try {
97
+ // Replace semantics: drop any existing item first so `create` is always
98
+ // the write path. delete() is idempotent, so a missing item is fine.
99
+ this.deleteItem(key);
100
+ runOp(["item", "create", "--format=json", "-"], {
101
+ input: JSON.stringify(item),
102
+ timeoutMs: OP_WRITE_TIMEOUT_MS,
103
+ });
104
+ this.invalidateListCache();
105
+ return true;
106
+ }
107
+ catch (err) {
108
+ this.warnOnce("store", key, err);
109
+ return false;
110
+ }
111
+ }
112
+ /**
113
+ * Read a credential value. Returns null when absent or on failure.
114
+ *
115
+ * @throws {InvalidCredentialKeyError} for a key this backend could never
116
+ * have written — building an `op://` reference from it is exactly the
117
+ * misparse the key grammar exists to prevent, and `null` would report the
118
+ * impossible key as a merely absent one.
119
+ */
120
+ retrieve(key) {
121
+ assertValidKey(key, "read");
122
+ try {
123
+ const value = runOp(["read", `op://${this.vault}/${key}/${FIELD_NAME}`], { timeoutMs: OP_WRITE_TIMEOUT_MS });
124
+ return value === "" ? null : value;
125
+ }
126
+ catch (err) {
127
+ this.warnOnce("retrieve", key, err);
128
+ return null;
129
+ }
130
+ }
131
+ /**
132
+ * Delete a credential. Idempotent — a missing item is success.
133
+ *
134
+ * @throws {InvalidCredentialKeyError} — deliberately NOT folded into the
135
+ * idempotent-success path. "Missing" and "impossible" are different
136
+ * answers: such a key was never storable here, so reporting success would
137
+ * assert something about a key this backend does not accept.
138
+ */
139
+ delete(key) {
140
+ assertValidKey(key, "delete");
141
+ try {
142
+ this.deleteItem(key);
143
+ this.invalidateListCache();
144
+ return true;
145
+ }
146
+ catch (err) {
147
+ this.warnOnce("delete", key, err);
148
+ return false;
149
+ }
150
+ }
151
+ /**
152
+ * Enumerate credential keys, optionally prefix-filtered.
153
+ *
154
+ * Per the {@link VaultBackend} contract this returns `[]` for a genuinely
155
+ * empty vault but **throws** on a transient failure, so a caller can tell
156
+ * "nothing stored" from "1Password did not answer" and retry the latter.
157
+ * The `--tags` filter keeps unrelated items in a shared vault out of the
158
+ * result; no secret values appear in `op item list` output.
159
+ */
160
+ async listKeys(prefix) {
161
+ const cached = this.readListCache();
162
+ const keys = cached ?? this.fetchKeys();
163
+ if (cached === null) {
164
+ this.listCache = {
165
+ keys,
166
+ expiresAt: this.now() + OP_LIST_CACHE_TTL_MS,
167
+ };
168
+ }
169
+ return prefix === undefined ? [...keys] : keys.filter((k) => k.startsWith(prefix));
170
+ }
171
+ // ---------------------------------------------------------------------------
172
+ // Private helpers
173
+ // ---------------------------------------------------------------------------
174
+ fetchKeys() {
175
+ let raw;
176
+ try {
177
+ raw = runOp(["item", "list", "--vault", this.vault, "--tags", this.tag, "--format=json"], { timeoutMs: OP_READ_TIMEOUT_MS });
178
+ }
179
+ catch (err) {
180
+ // A missing/empty vault is an empty enumeration; anything else is
181
+ // transient and must surface so the caller can retry (contract, §7).
182
+ if (err instanceof OpCliError && err.isNotFound)
183
+ return [];
184
+ this.warnOnce("listKeys", null, err);
185
+ throw err;
186
+ }
187
+ if (raw === "")
188
+ return [];
189
+ let parsed;
190
+ try {
191
+ parsed = JSON.parse(raw);
192
+ }
193
+ catch (err) {
194
+ this.warnOnce("listKeys", null, err);
195
+ throw new Error(`op item list returned unparseable JSON: ${String(err)}`);
196
+ }
197
+ if (!Array.isArray(parsed))
198
+ return [];
199
+ return parsed
200
+ .map((item) => typeof item === "object" && item !== null
201
+ ? item.title
202
+ : undefined)
203
+ .filter((title) => typeof title === "string" && title.length > 0);
204
+ }
205
+ // Key validation lives in `assertValidKey` (vault-utils), not in a private
206
+ // `acceptKey` here.
207
+ //
208
+ // #167 had to enforce locally and quietly. This was the first backend where
209
+ // the `op://<vault>/<key>/password` reference made the invariant
210
+ // load-bearing — a key containing `/` stores fine (a 1Password title is just
211
+ // a string) and then re-parses on read into a different item and field — but
212
+ // the shared path enforced nothing, so a loud refusal here would have been
213
+ // this backend alone diverging from the others. The compromise was a
214
+ // warn-once that returned false/null.
215
+ //
216
+ // #168 removed the reason for it. `vault.ts` now rejects a non-conforming
217
+ // key before any backend is reached, and every backend asserts the same
218
+ // grammar with the same typed error. Keeping the warn-once as well would
219
+ // leave two layers enforcing one rule with two different outcomes — a throw
220
+ // from the cascade, a quiet `false`/`null` here — which is the divergence
221
+ // #168 exists to remove. The stderr warning is redundant too:
222
+ // `InvalidCredentialKeyError` names the key, the operation and the reason,
223
+ // and unlike a warning it cannot be missed.
224
+ deleteItem(key) {
225
+ try {
226
+ runOp(["item", "delete", key, "--vault", this.vault], {
227
+ timeoutMs: OP_WRITE_TIMEOUT_MS,
228
+ });
229
+ }
230
+ catch (err) {
231
+ // Idempotent: the item not being there is the desired end state.
232
+ if (err instanceof OpCliError && err.isNotFound)
233
+ return;
234
+ throw err;
235
+ }
236
+ }
237
+ readListCache() {
238
+ if (this.listCache === null)
239
+ return null;
240
+ if (this.now() >= this.listCache.expiresAt) {
241
+ this.listCache = null;
242
+ return null;
243
+ }
244
+ return this.listCache.keys;
245
+ }
246
+ invalidateListCache() {
247
+ this.listCache = null;
248
+ }
249
+ /**
250
+ * Surface an unexpected `op` failure once per instance (ADR-004 §7, the
251
+ * libsecret lesson from #121).
252
+ *
253
+ * A not-found is normal and stays quiet. Anything else means we are
254
+ * authenticated but the call still failed — a misconfiguration the operator
255
+ * needs to see, since the non-throwing backend contract would otherwise
256
+ * swallow it entirely. Once per instance keeps a hot loop from flooding
257
+ * stderr while still making the first occurrence visible.
258
+ */
259
+ warnOnce(op, key, err) {
260
+ if (err instanceof OpCliError && err.isNotFound)
261
+ return;
262
+ if (this.warned)
263
+ return;
264
+ this.warned = true;
265
+ const target = key === null ? `vault ${this.vault}` : `${this.vault}/${key}`;
266
+ const detail = err instanceof Error ? err.message : String(err);
267
+ process.stderr.write(`[secrets] WARNING: 1Password backend ${op} failed on ${target}: ${detail}\n` +
268
+ `[secrets] Further 1Password warnings from this instance are suppressed.\n`);
269
+ }
270
+ }
271
+ //# sourceMappingURL=vault-onepassword.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault-onepassword.js","sourceRoot":"","sources":["../../src/vault/vault-onepassword.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,14 @@
2
2
  * Auth Vault — Shared Utilities
3
3
  *
4
4
  * Shared validation and helper functions used across all vault backends.
5
+ *
6
+ * The key grammar is a **package-wide invariant**, not a per-backend
7
+ * preference: `vault.ts` enforces it once at the cascade before any backend
8
+ * is dispatched to (#168), and each backend re-asserts it so that a directly
9
+ * constructed backend cannot bypass the rule. Several backends place keys in
10
+ * subprocess argv or an `op://` reference path; the grammar keeps that shared
11
+ * namespace portable and is defense in depth, never the subprocess injection
12
+ * boundary.
5
13
  */
6
14
  /**
7
15
  * Returns true if the given credential key name is valid.
@@ -14,9 +22,35 @@
14
22
  * Hyphens and dots are both permitted as namespace separators so callers
15
23
  * can choose whichever convention reads best (`soma-anthropic-token1`,
16
24
  * `soma.anthropic.token1`). Underscores, uppercase, whitespace, and
17
- * shell metacharacters are deliberately rejected so keys can safely be
18
- * interpolated into subprocess argv positions without additional
19
- * escaping.
25
+ * shell metacharacters are deliberately rejected to keep the cross-backend
26
+ * namespace portable. Subprocess callers must still use structural argv/stdin
27
+ * separation rather than treating this grammar as an injection boundary.
20
28
  */
21
29
  export declare function isValidKey(key: string): boolean;
30
+ /**
31
+ * Returns true if `prefix` could be the leading substring of a valid key.
32
+ *
33
+ * The empty string is accepted: `listCredentials("")` selects everything,
34
+ * exactly as omitting the argument does.
35
+ *
36
+ * The length bound is one shorter for a prefix ending in a separator. A key
37
+ * must end alphanumeric and is capped at 64, so a 64-character prefix ending
38
+ * in `.` or `-` could only ever be extended into a 65-character key — no valid
39
+ * key starts with it. Accepting it would dispatch an unsatisfiable filter to
40
+ * the backend and return an empty list, which reads as "no such credentials"
41
+ * rather than the `InvalidCredentialKeyError` every other malformed prefix
42
+ * raises — the same not-found/malformed collapse this module exists to end.
43
+ */
44
+ export declare function isValidKeyPrefix(prefix: string): boolean;
45
+ /** Operations a key can be rejected for — mirrors `SecretsOperation["type"]`. */
46
+ export type KeyOperation = "read" | "write" | "delete" | "enumerate";
47
+ /**
48
+ * Throw `InvalidCredentialKeyError` unless `key` matches the key grammar.
49
+ *
50
+ * Backends call this instead of returning `false`/`null`, so a malformed key
51
+ * is never mistaken for a failed write or an absent credential (#168, P2).
52
+ */
53
+ export declare function assertValidKey(key: string, operation: KeyOperation): void;
54
+ /** Throw `InvalidCredentialKeyError` unless `prefix` is a valid key prefix. */
55
+ export declare function assertValidKeyPrefix(prefix: string, operation: KeyOperation): void;
22
56
  //# sourceMappingURL=vault-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vault-utils.d.ts","sourceRoot":"","sources":["../../src/vault/vault-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C"}
1
+ {"version":3,"file":"vault-utils.d.ts","sourceRoot":"","sources":["../../src/vault/vault-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAgBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAMxD;AAED,iFAAiF;AACjF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAErE;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,GAAG,IAAI,CAGzE;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,GAAG,IAAI,CAGlF"}
@@ -2,7 +2,16 @@
2
2
  * Auth Vault — Shared Utilities
3
3
  *
4
4
  * Shared validation and helper functions used across all vault backends.
5
+ *
6
+ * The key grammar is a **package-wide invariant**, not a per-backend
7
+ * preference: `vault.ts` enforces it once at the cascade before any backend
8
+ * is dispatched to (#168), and each backend re-asserts it so that a directly
9
+ * constructed backend cannot bypass the rule. Several backends place keys in
10
+ * subprocess argv or an `op://` reference path; the grammar keeps that shared
11
+ * namespace portable and is defense in depth, never the subprocess injection
12
+ * boundary.
5
13
  */
14
+ import { InvalidCredentialKeyError } from "./session-vault-errors.js";
6
15
  /**
7
16
  * Allowed key name pattern — lowercase alphanumeric plus hyphen and dot,
8
17
  * 2-64 characters. The inner class permits hyphens and dots so that
@@ -23,11 +32,64 @@ const VALID_KEY_RE = /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/;
23
32
  * Hyphens and dots are both permitted as namespace separators so callers
24
33
  * can choose whichever convention reads best (`soma-anthropic-token1`,
25
34
  * `soma.anthropic.token1`). Underscores, uppercase, whitespace, and
26
- * shell metacharacters are deliberately rejected so keys can safely be
27
- * interpolated into subprocess argv positions without additional
28
- * escaping.
35
+ * shell metacharacters are deliberately rejected to keep the cross-backend
36
+ * namespace portable. Subprocess callers must still use structural argv/stdin
37
+ * separation rather than treating this grammar as an injection boundary.
29
38
  */
30
39
  export function isValidKey(key) {
31
40
  return VALID_KEY_RE.test(key) && key.length <= 64;
32
41
  }
42
+ /**
43
+ * Allowed key-*prefix* pattern — every proper prefix of a valid key, plus the
44
+ * valid keys themselves.
45
+ *
46
+ * `listCredentials(prefix)` filters by string prefix, so the natural way to
47
+ * scope an enumeration to a namespace is to pass the separator too:
48
+ * `listCredentials("soma.anthropic.")`. That is not a valid *key* (it ends in
49
+ * a separator), so validating a prefix with `isValidKey` would reject the
50
+ * documented usage. A prefix therefore relaxes exactly one rule — the trailing
51
+ * character may be a separator — and keeps the rest, since a prefix reaches
52
+ * the same subprocess argv / `op://` positions a key does.
53
+ */
54
+ const VALID_KEY_PREFIX_RE = /^[a-z0-9][a-z0-9.-]*$/;
55
+ /**
56
+ * Returns true if `prefix` could be the leading substring of a valid key.
57
+ *
58
+ * The empty string is accepted: `listCredentials("")` selects everything,
59
+ * exactly as omitting the argument does.
60
+ *
61
+ * The length bound is one shorter for a prefix ending in a separator. A key
62
+ * must end alphanumeric and is capped at 64, so a 64-character prefix ending
63
+ * in `.` or `-` could only ever be extended into a 65-character key — no valid
64
+ * key starts with it. Accepting it would dispatch an unsatisfiable filter to
65
+ * the backend and return an empty list, which reads as "no such credentials"
66
+ * rather than the `InvalidCredentialKeyError` every other malformed prefix
67
+ * raises — the same not-found/malformed collapse this module exists to end.
68
+ */
69
+ export function isValidKeyPrefix(prefix) {
70
+ if (prefix === "")
71
+ return true;
72
+ if (!VALID_KEY_PREFIX_RE.test(prefix))
73
+ return false;
74
+ // A trailing separator needs at least one more alphanumeric to become a key.
75
+ const max = /[.-]$/.test(prefix) ? 63 : 64;
76
+ return prefix.length <= max;
77
+ }
78
+ /**
79
+ * Throw `InvalidCredentialKeyError` unless `key` matches the key grammar.
80
+ *
81
+ * Backends call this instead of returning `false`/`null`, so a malformed key
82
+ * is never mistaken for a failed write or an absent credential (#168, P2).
83
+ */
84
+ export function assertValidKey(key, operation) {
85
+ if (isValidKey(key))
86
+ return;
87
+ throw new InvalidCredentialKeyError(key, operation, "key");
88
+ }
89
+ /** Throw `InvalidCredentialKeyError` unless `prefix` is a valid key prefix. */
90
+ export function assertValidKeyPrefix(prefix, operation) {
91
+ if (isValidKeyPrefix(prefix))
92
+ return;
93
+ throw new InvalidCredentialKeyError(prefix, operation, "prefix");
94
+ }
33
95
  //# sourceMappingURL=vault-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"vault-utils.js","sourceRoot":"","sources":["../../src/vault/vault-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;GAOG;AACH,MAAM,YAAY,GAAG,+BAA+B,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;AACpD,CAAC"}
1
+ {"version":3,"file":"vault-utils.js","sourceRoot":"","sources":["../../src/vault/vault-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAEtE;;;;;;;GAOG;AACH,MAAM,YAAY,GAAG,+BAA+B,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,mBAAmB,GAAG,uBAAuB,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC/B,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACpD,6EAA6E;IAC7E,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3C,OAAO,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC;AAC9B,CAAC;AAKD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,SAAuB;IACjE,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO;IAC5B,MAAM,IAAI,yBAAyB,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,oBAAoB,CAAC,MAAc,EAAE,SAAuB;IAC1E,IAAI,gBAAgB,CAAC,MAAM,CAAC;QAAE,OAAO;IACrC,MAAM,IAAI,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AACnE,CAAC"}
@@ -12,7 +12,11 @@
12
12
  * Storage: credentials are stored in the Windows Credential Manager under the
13
13
  * resource name "centient", with the logical key as the username.
14
14
  *
15
- * Error handling: all methods return false/null on failure never throw.
15
+ * Error handling: a storage failure returns false/null this backend never
16
+ * throws for that. A credential key that violates the shared key grammar is
17
+ * NOT a storage failure and throws `InvalidCredentialKeyError` (#168): such a
18
+ * key is unaddressable here, and reporting it as a failed write or an absent
19
+ * credential would be indistinguishable from the ordinary miss.
16
20
  */
17
21
  import type { VaultBackend } from "./types.js";
18
22
  /**
@@ -29,6 +33,7 @@ import type { VaultBackend } from "./types.js";
29
33
  * }
30
34
  */
31
35
  export declare class WindowsVault implements VaultBackend {
36
+ readonly name = "windows";
32
37
  /**
33
38
  * Returns true if running inside a WSL environment where powershell.exe is
34
39
  * accessible.
@@ -49,7 +54,7 @@ export declare class WindowsVault implements VaultBackend {
49
54
  * as the username and `value` as the password.
50
55
  *
51
56
  * @param key - Logical key name (e.g. 'auth-token', 'refresh-token')
52
- * @param value - The credential value to store
57
+ * @param value - The credential value to store (UTF-8/base64 over stdin)
53
58
  * @returns true on success, false if storage fails
54
59
  */
55
60
  store(key: string, value: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"vault-windows.d.ts","sourceRoot":"","sources":["../../src/vault/vault-windows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA+C/C;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAa,YAAW,YAAY;IAC/C;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,IAAI,OAAO;IAoBxB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAWpC;;;;;;;;;OASG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAmB1C;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAoBpC;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAsB5B;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CA2BnD"}
1
+ {"version":3,"file":"vault-windows.d.ts","sourceRoot":"","sources":["../../src/vault/vault-windows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAgD/C;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAa,YAAW,YAAY;IAC/C,QAAQ,CAAC,IAAI,aAAa;IAE1B;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,IAAI,OAAO;IAoBxB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAWpC;;;;;;;;;OASG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAqB1C;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAoBpC;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAsB5B;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CA2BnD"}
@@ -12,11 +12,15 @@
12
12
  * Storage: credentials are stored in the Windows Credential Manager under the
13
13
  * resource name "centient", with the logical key as the username.
14
14
  *
15
- * Error handling: all methods return false/null on failure never throw.
15
+ * Error handling: a storage failure returns false/null this backend never
16
+ * throws for that. A credential key that violates the shared key grammar is
17
+ * NOT a storage failure and throws `InvalidCredentialKeyError` (#168): such a
18
+ * key is unaddressable here, and reporting it as a failed write or an absent
19
+ * credential would be indistinguishable from the ordinary miss.
16
20
  */
17
21
  import { spawnSync } from "child_process";
18
22
  import { readFileSync } from "fs";
19
- import { isValidKey } from "./vault-utils.js";
23
+ import { assertValidKey } from "./vault-utils.js";
20
24
  // =============================================================================
21
25
  // Constants
22
26
  // =============================================================================
@@ -36,12 +40,13 @@ function escapePsValue(value) {
36
40
  * Executes a PowerShell command via powershell.exe with no profile and
37
41
  * non-interactive mode. Returns stdout trimmed, or null on error.
38
42
  *
39
- * Uses spawnSync to bypass the shell entirely, preventing shell injection
40
- * via credential values that contain double-quotes or shell metacharacters.
43
+ * Uses spawnSync to bypass the OS shell. Credential values are UTF-8/base64
44
+ * encoded on stdin and decoded by a fixed PowerShell fragment, so arbitrary
45
+ * value bytes never enter the interpreter command string.
41
46
  */
42
- function runPowershell(command) {
47
+ function runPowershell(command, input) {
43
48
  try {
44
- const result = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], { encoding: "utf8" });
49
+ const result = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], { encoding: "utf8", input });
45
50
  if (result.error || result.status !== 0)
46
51
  return null;
47
52
  return result.stdout.trim();
@@ -67,6 +72,7 @@ function runPowershell(command) {
67
72
  * }
68
73
  */
69
74
  export class WindowsVault {
75
+ name = "windows";
70
76
  /**
71
77
  * Returns true if running inside a WSL environment where powershell.exe is
72
78
  * accessible.
@@ -114,23 +120,24 @@ export class WindowsVault {
114
120
  * as the username and `value` as the password.
115
121
  *
116
122
  * @param key - Logical key name (e.g. 'auth-token', 'refresh-token')
117
- * @param value - The credential value to store
123
+ * @param value - The credential value to store (UTF-8/base64 over stdin)
118
124
  * @returns true on success, false if storage fails
119
125
  */
120
126
  store(key, value) {
121
- if (!isValidKey(key))
122
- return false;
127
+ assertValidKey(key, "write");
123
128
  const escapedKey = escapePsValue(key);
124
- const escapedValue = escapePsValue(value);
129
+ const encodedValue = Buffer.from(value, "utf8").toString("base64");
125
130
  const command = [
126
131
  "& {",
127
132
  "[void][Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime];",
133
+ "$passwordBytes = [System.Convert]::FromBase64String([Console]::In.ReadToEnd());",
134
+ "$password = [System.Text.Encoding]::UTF8.GetString($passwordBytes);",
128
135
  "$vault = New-Object Windows.Security.Credentials.PasswordVault;",
129
- `$cred = New-Object Windows.Security.Credentials.PasswordCredential('${escapePsValue(RESOURCE_NAME)}', '${escapedKey}', '${escapedValue}');`,
136
+ `$cred = New-Object Windows.Security.Credentials.PasswordCredential('${escapePsValue(RESOURCE_NAME)}', '${escapedKey}', $password);`,
130
137
  "$vault.Add($cred)",
131
138
  "}",
132
139
  ].join(" ");
133
- const result = runPowershell(command);
140
+ const result = runPowershell(command, encodedValue);
134
141
  return result !== null;
135
142
  }
136
143
  /**
@@ -140,8 +147,7 @@ export class WindowsVault {
140
147
  * @returns The stored password, or null if not found / retrieval fails
141
148
  */
142
149
  retrieve(key) {
143
- if (!isValidKey(key))
144
- return null;
150
+ assertValidKey(key, "read");
145
151
  const escapedKey = escapePsValue(key);
146
152
  const command = [
147
153
  "& {",
@@ -164,8 +170,7 @@ export class WindowsVault {
164
170
  * @returns true on success (including "not found"), false on unexpected error
165
171
  */
166
172
  delete(key) {
167
- if (!isValidKey(key))
168
- return false;
173
+ assertValidKey(key, "delete");
169
174
  const escapedKey = escapePsValue(key);
170
175
  const command = [
171
176
  "& {",
@@ -1 +1 @@
1
- {"version":3,"file":"vault-windows.js","sourceRoot":"","sources":["../../src/vault/vault-windows.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vault-windows.js","sourceRoot":"","sources":["../../src/vault/vault-windows.ts"],"names":[],"mappings":"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"}