@centient/secrets 0.5.0 → 0.7.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 (55) hide show
  1. package/README.md +39 -1
  2. package/dist/cli/hidden-input.d.ts +52 -0
  3. package/dist/cli/hidden-input.d.ts.map +1 -0
  4. package/dist/cli/hidden-input.js +109 -0
  5. package/dist/cli/hidden-input.js.map +1 -0
  6. package/dist/cli/hidden-prompt.d.ts +48 -0
  7. package/dist/cli/hidden-prompt.d.ts.map +1 -0
  8. package/dist/cli/hidden-prompt.js +127 -0
  9. package/dist/cli/hidden-prompt.js.map +1 -0
  10. package/dist/cli/secrets-cli.d.ts +9 -1
  11. package/dist/cli/secrets-cli.d.ts.map +1 -1
  12. package/dist/cli/secrets-cli.js +272 -175
  13. package/dist/cli/secrets-cli.js.map +1 -1
  14. package/dist/crypto/vault-common.d.ts +17 -4
  15. package/dist/crypto/vault-common.d.ts.map +1 -1
  16. package/dist/crypto/vault-common.js +23 -6
  17. package/dist/crypto/vault-common.js.map +1 -1
  18. package/dist/index.d.ts +7 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +7 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/key-providers/index.d.ts +4 -1
  23. package/dist/key-providers/index.d.ts.map +1 -1
  24. package/dist/key-providers/index.js +1 -0
  25. package/dist/key-providers/index.js.map +1 -1
  26. package/dist/key-providers/passphrase-provider.d.ts +79 -0
  27. package/dist/key-providers/passphrase-provider.d.ts.map +1 -0
  28. package/dist/key-providers/passphrase-provider.js +298 -0
  29. package/dist/key-providers/passphrase-provider.js.map +1 -0
  30. package/dist/key-providers/resolve.d.ts +8 -3
  31. package/dist/key-providers/resolve.d.ts.map +1 -1
  32. package/dist/key-providers/resolve.js +34 -10
  33. package/dist/key-providers/resolve.js.map +1 -1
  34. package/dist/key-providers/types.d.ts +20 -2
  35. package/dist/key-providers/types.d.ts.map +1 -1
  36. package/dist/key-providers/types.js +1 -1
  37. package/dist/vault/file-lock.d.ts +33 -0
  38. package/dist/vault/file-lock.d.ts.map +1 -0
  39. package/dist/vault/file-lock.js +143 -0
  40. package/dist/vault/file-lock.js.map +1 -0
  41. package/dist/vault/session-vault-errors.d.ts +38 -0
  42. package/dist/vault/session-vault-errors.d.ts.map +1 -0
  43. package/dist/vault/session-vault-errors.js +67 -0
  44. package/dist/vault/session-vault-errors.js.map +1 -0
  45. package/dist/vault/session-vault.d.ts +147 -0
  46. package/dist/vault/session-vault.d.ts.map +1 -0
  47. package/dist/vault/session-vault.js +669 -0
  48. package/dist/vault/session-vault.js.map +1 -0
  49. package/dist/vault/sidecar.d.ts +37 -0
  50. package/dist/vault/sidecar.d.ts.map +1 -0
  51. package/dist/vault/sidecar.js +84 -0
  52. package/dist/vault/sidecar.js.map +1 -0
  53. package/dist/vault/types.d.ts +1 -1
  54. package/dist/vault/types.d.ts.map +1 -1
  55. package/package.json +1 -1
@@ -0,0 +1,143 @@
1
+ /**
2
+ * File lock — native exclusive advisory lock for vault writes.
3
+ *
4
+ * Extracted from session-vault.ts (M3) so session-vault remains focused on
5
+ * orchestration. The lock itself is filesystem-level (O_EXCL create on a
6
+ * `.lock` file) and cooperative: it only protects against writers that call
7
+ * `acquireWriteLock` before mutating.
8
+ *
9
+ * The lock file contains the holding process's PID so stale-lock stealing can
10
+ * distinguish "our PID still owns it" from "the previous holder crashed." On
11
+ * steal we write-and-verify to avoid two racing processes both thinking they
12
+ * stole the same stale lock (M2).
13
+ */
14
+ import { openSync, closeSync, writeFileSync, readFileSync, statSync, unlinkSync, } from "node:fs";
15
+ import { VaultLockError } from "./session-vault-errors.js";
16
+ /** Max time a writer will wait to acquire the file lock before giving up. */
17
+ export const LOCK_TIMEOUT_MS = 5_000;
18
+ /** Poll interval when waiting on a held lock. */
19
+ export const LOCK_RETRY_INTERVAL_MS = 25;
20
+ /** Stale-lock threshold — if a lock file is older than this, assume crash. */
21
+ export const LOCK_STALE_MS = 30_000;
22
+ /**
23
+ * Acquire an exclusive write lock via O_EXCL on `{vaultPath}.lock`. Yields
24
+ * the event loop between retries (no busy-spin) up to `LOCK_TIMEOUT_MS`.
25
+ * Locks older than `LOCK_STALE_MS` are considered orphaned (the holding
26
+ * process crashed) and stolen with a pid-verification handshake so two
27
+ * concurrent stealers can't both claim ownership.
28
+ *
29
+ * Returns a release function. Releasing is idempotent and swallows ENOENT
30
+ * (the lock file may have been stolen by another process after we already
31
+ * finished our critical section — that's fine; the lockfile, not the fd,
32
+ * is what matters).
33
+ */
34
+ export async function acquireWriteLock(vaultPath) {
35
+ const lockPath = `${vaultPath}.lock`;
36
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
37
+ const ourPid = process.pid;
38
+ const ourToken = `${ourPid}`;
39
+ while (Date.now() < deadline) {
40
+ // Attempt the happy path: create-exclusive.
41
+ try {
42
+ const fd = openSync(lockPath, "wx");
43
+ try {
44
+ // Write our pid so stale-lock stealing can verify ownership.
45
+ writeFileSync(lockPath, ourToken);
46
+ }
47
+ catch {
48
+ // Best-effort; even if we can't write pid, we still hold the lock.
49
+ }
50
+ // closeSync can throw on exotic filesystems; the lockfile — not the fd —
51
+ // is what matters, so swallow errors here. The release closure unlinks
52
+ // the file regardless (M2).
53
+ try {
54
+ closeSync(fd);
55
+ }
56
+ catch {
57
+ // Non-fatal.
58
+ }
59
+ return () => {
60
+ try {
61
+ unlinkSync(lockPath);
62
+ }
63
+ catch {
64
+ // Lock file may have been removed by stale-lock stealing in another
65
+ // process; ignore — our critical section is over regardless.
66
+ }
67
+ };
68
+ }
69
+ catch (err) {
70
+ if (err.code !== "EEXIST")
71
+ throw err;
72
+ // EEXIST: someone holds it. Check for staleness.
73
+ try {
74
+ const lockStat = statSync(lockPath);
75
+ if (Date.now() - lockStat.mtimeMs > LOCK_STALE_MS) {
76
+ // Stale. Steal with a write-and-verify handshake so two racing
77
+ // stealers can't both claim ownership. Whoever's token survives
78
+ // the read-back wins.
79
+ try {
80
+ unlinkSync(lockPath);
81
+ }
82
+ catch {
83
+ // Another process may have already stolen it; loop and retry.
84
+ continue;
85
+ }
86
+ try {
87
+ const fd = openSync(lockPath, "wx");
88
+ try {
89
+ writeFileSync(lockPath, ourToken);
90
+ }
91
+ catch {
92
+ // Non-fatal; proceed to verification.
93
+ }
94
+ try {
95
+ closeSync(fd);
96
+ }
97
+ catch {
98
+ // Non-fatal.
99
+ }
100
+ // Verification: read back and confirm our token is there. If a
101
+ // racer wrote first we'll see their token and back off.
102
+ try {
103
+ const recorded = readFileSync(lockPath, "utf8").trim();
104
+ if (recorded !== ourToken) {
105
+ // We lost the steal race; loop and retry normally.
106
+ continue;
107
+ }
108
+ }
109
+ catch {
110
+ // Read failed — treat as loss and retry.
111
+ continue;
112
+ }
113
+ return () => {
114
+ try {
115
+ unlinkSync(lockPath);
116
+ }
117
+ catch {
118
+ // Same rationale as happy-path release.
119
+ }
120
+ };
121
+ }
122
+ catch (stealErr) {
123
+ if (stealErr.code === "EEXIST") {
124
+ // Another process stole it between our unlink and open; retry.
125
+ continue;
126
+ }
127
+ throw stealErr;
128
+ }
129
+ }
130
+ }
131
+ catch {
132
+ // statSync failed — lock was just released, or racing cleanup.
133
+ // Fall through to sleep-and-retry.
134
+ }
135
+ // Held by a non-stale writer. Yield the event loop — do NOT busy-spin
136
+ // (C1). This allows every other async task on this event loop to run
137
+ // while we wait.
138
+ await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
139
+ }
140
+ }
141
+ throw new VaultLockError(`Timed out after ${LOCK_TIMEOUT_MS}ms waiting for vault write lock at ${lockPath}`);
142
+ }
143
+ //# sourceMappingURL=file-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/vault/file-lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * SessionVault error classes — extracted so helper modules (file-lock,
3
+ * sidecar) can throw them without creating an import cycle back into
4
+ * session-vault.ts.
5
+ *
6
+ * Each subclass restores the prototype chain with `Object.setPrototypeOf`
7
+ * so `instanceof VaultError` etc. remains robust when the class is consumed
8
+ * across an ES transpile boundary (L2 — matches the EngramError pattern in
9
+ * `packages/sdk/src/errors.ts`).
10
+ */
11
+ /** Base class for SessionVault errors. */
12
+ export declare class VaultError extends Error {
13
+ readonly code: string;
14
+ constructor(code: string, message: string);
15
+ }
16
+ /** Thrown when the master key can't be retrieved from the configured provider. */
17
+ export declare class VaultUnlockError extends VaultError {
18
+ constructor(message: string);
19
+ }
20
+ /** Thrown when decryption fails — wrong key, corrupted file, or AAD mismatch. */
21
+ export declare class VaultDecryptError extends VaultError {
22
+ constructor(message: string);
23
+ }
24
+ /** Thrown when rollback is detected and not explicitly accepted. */
25
+ export declare class VaultRollbackError extends VaultError {
26
+ readonly expected: number;
27
+ readonly actual: number;
28
+ constructor(expected: number, actual: number);
29
+ }
30
+ /** Thrown when operations are attempted on a closed vault. */
31
+ export declare class VaultClosedError extends VaultError {
32
+ constructor();
33
+ }
34
+ /** Thrown when the write-path file lock can't be acquired within the timeout. */
35
+ export declare class VaultLockError extends VaultError {
36
+ constructor(message: string);
37
+ }
38
+ //# sourceMappingURL=session-vault-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-vault-errors.d.ts","sourceRoot":"","sources":["../../src/vault/session-vault-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,0CAA0C;AAC1C,qBAAa,UAAW,SAAQ,KAAK;aACP,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1D;AAED,kFAAkF;AAClF,qBAAa,gBAAiB,SAAQ,UAAU;gBAClC,OAAO,EAAE,MAAM;CAK5B;AAED,iFAAiF;AACjF,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM;CAK5B;AAED,oEAAoE;AACpE,qBAAa,kBAAmB,SAAQ,UAAU;aAE9B,QAAQ,EAAE,MAAM;aAChB,MAAM,EAAE,MAAM;gBADd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;CAWjC;AAED,8DAA8D;AAC9D,qBAAa,gBAAiB,SAAQ,UAAU;;CAM/C;AAED,iFAAiF;AACjF,qBAAa,cAAe,SAAQ,UAAU;gBAChC,OAAO,EAAE,MAAM;CAK5B"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * SessionVault error classes — extracted so helper modules (file-lock,
3
+ * sidecar) can throw them without creating an import cycle back into
4
+ * session-vault.ts.
5
+ *
6
+ * Each subclass restores the prototype chain with `Object.setPrototypeOf`
7
+ * so `instanceof VaultError` etc. remains robust when the class is consumed
8
+ * across an ES transpile boundary (L2 — matches the EngramError pattern in
9
+ * `packages/sdk/src/errors.ts`).
10
+ */
11
+ /** Base class for SessionVault errors. */
12
+ export class VaultError extends Error {
13
+ code;
14
+ constructor(code, message) {
15
+ super(message);
16
+ this.code = code;
17
+ this.name = "VaultError";
18
+ Object.setPrototypeOf(this, new.target.prototype);
19
+ }
20
+ }
21
+ /** Thrown when the master key can't be retrieved from the configured provider. */
22
+ export class VaultUnlockError extends VaultError {
23
+ constructor(message) {
24
+ super("VAULT_UNLOCK_FAILED", message);
25
+ this.name = "VaultUnlockError";
26
+ Object.setPrototypeOf(this, new.target.prototype);
27
+ }
28
+ }
29
+ /** Thrown when decryption fails — wrong key, corrupted file, or AAD mismatch. */
30
+ export class VaultDecryptError extends VaultError {
31
+ constructor(message) {
32
+ super("VAULT_DECRYPT_FAILED", message);
33
+ this.name = "VaultDecryptError";
34
+ Object.setPrototypeOf(this, new.target.prototype);
35
+ }
36
+ }
37
+ /** Thrown when rollback is detected and not explicitly accepted. */
38
+ export class VaultRollbackError extends VaultError {
39
+ expected;
40
+ actual;
41
+ constructor(expected, actual) {
42
+ super("VAULT_VERSION_ROLLBACK_DETECTED", `Vault version rollback detected: sidecar expects version >= ${expected}, ` +
43
+ `but vault file reports version ${actual}. If this is an intentional ` +
44
+ `restore, pass { acceptRollback: true } to openVault().`);
45
+ this.expected = expected;
46
+ this.actual = actual;
47
+ this.name = "VaultRollbackError";
48
+ Object.setPrototypeOf(this, new.target.prototype);
49
+ }
50
+ }
51
+ /** Thrown when operations are attempted on a closed vault. */
52
+ export class VaultClosedError extends VaultError {
53
+ constructor() {
54
+ super("VAULT_CLOSED", "Vault has been closed; reopen with openVault() to continue.");
55
+ this.name = "VaultClosedError";
56
+ Object.setPrototypeOf(this, new.target.prototype);
57
+ }
58
+ }
59
+ /** Thrown when the write-path file lock can't be acquired within the timeout. */
60
+ export class VaultLockError extends VaultError {
61
+ constructor(message) {
62
+ super("VAULT_LOCK_FAILED", message);
63
+ this.name = "VaultLockError";
64
+ Object.setPrototypeOf(this, new.target.prototype);
65
+ }
66
+ }
67
+ //# sourceMappingURL=session-vault-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-vault-errors.js","sourceRoot":"","sources":["../../src/vault/session-vault-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,0CAA0C;AAC1C,MAAM,OAAO,UAAW,SAAQ,KAAK;IACP;IAA5B,YAA4B,IAAY,EAAE,OAAe;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;QADW,SAAI,GAAJ,IAAI,CAAQ;QAEtC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED,kFAAkF;AAClF,MAAM,OAAO,gBAAiB,SAAQ,UAAU;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,qBAAqB,EAAE,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED,iFAAiF;AACjF,MAAM,OAAO,iBAAkB,SAAQ,UAAU;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED,oEAAoE;AACpE,MAAM,OAAO,kBAAmB,SAAQ,UAAU;IAE9B;IACA;IAFlB,YACkB,QAAgB,EAChB,MAAc;QAE9B,KAAK,CACH,iCAAiC,EACjC,+DAA+D,QAAQ,IAAI;YACzE,kCAAkC,MAAM,8BAA8B;YACtE,wDAAwD,CAC3D,CAAC;QARc,aAAQ,GAAR,QAAQ,CAAQ;QAChB,WAAM,GAAN,MAAM,CAAQ;QAQ9B,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED,8DAA8D;AAC9D,MAAM,OAAO,gBAAiB,SAAQ,UAAU;IAC9C;QACE,KAAK,CAAC,cAAc,EAAE,6DAA6D,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED,iFAAiF;AACjF,MAAM,OAAO,cAAe,SAAQ,UAAU;IAC5C,YAAY,OAAe;QACzB,KAAK,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * SessionVault — public session-backed envelope vault API.
3
+ *
4
+ * Opens the CLI's encrypted vault file once per session (one KeyProvider
5
+ * prompt), caches the decrypted contents in RAM, and serves reads without
6
+ * further prompts. External writes (e.g. the CLI in another shell) become
7
+ * visible via mtime-check coherence on every read.
8
+ *
9
+ * Addresses the per-item Keychain-prompt problem flagged in issue #40:
10
+ * long-running daemons (centient-labs/maintainer) holding N credentials
11
+ * across a long lifetime should not reach into the OS keychain on every
12
+ * access. Envelope encryption with a single master-key unlock matches
13
+ * industry standard (KMS, HashiCorp Vault, 1Password, Bitwarden).
14
+ *
15
+ * ## Threat model (what this protects and doesn't)
16
+ *
17
+ * - Protects against filesystem-read-only adversaries (ciphertext is AEAD
18
+ * encrypted; forging plaintext requires the master key).
19
+ * - Protects against live-session and cold-start vault-file rollback by a
20
+ * filesystem-write-only adversary via the combined in-payload
21
+ * `vaultVersion` + sidecar-file `highestSeenVersion` scheme.
22
+ * - Does NOT protect against an adversary with **both** master-key access
23
+ * and filesystem write — game over for any local envelope vault.
24
+ * - Does NOT protect against an adversary with write access to the vault
25
+ * directory who chooses to downgrade both vault and sidecar in lockstep
26
+ * — the sidecar lives next to the vault. If your threat model includes
27
+ * adversarial writes to `~/.centient/secrets/`, use a secrets service
28
+ * with remote attestation (HashiCorp Vault, AWS Secrets Manager,
29
+ * 1Password Connect) instead.
30
+ * - Session key is in process RAM for the full session lifetime. Any code
31
+ * with execution in the process has access to all secrets in the vault.
32
+ * Operators running daemons with this API SHOULD disable core dumps
33
+ * (`ulimit -c 0` / `prlimit --core=0`) and disable the Node.js inspector
34
+ * (`NODE_OPTIONS=--inspect` grants heap read to anyone on the inspector
35
+ * socket — a full master-key compromise vector).
36
+ * - On macOS, a newly-started process will still prompt the user for
37
+ * Keychain access even if another process holds the vault open.
38
+ * Keychain ACLs are per-process, not per-vault-file.
39
+ */
40
+ import type { KeyProviderType } from "../key-providers/types.js";
41
+ import { VaultError, VaultUnlockError, VaultDecryptError, VaultRollbackError, VaultClosedError, VaultLockError } from "./session-vault-errors.js";
42
+ export { VaultError, VaultUnlockError, VaultDecryptError, VaultRollbackError, VaultClosedError, VaultLockError, };
43
+ /** Current payload schema version — bump requires a compat migration. */
44
+ export declare const VAULT_SCHEMA_VERSION = 1;
45
+ /** Default vault file location — same path the CLI uses, so they share state. */
46
+ export declare const DEFAULT_VAULT_PATH: string;
47
+ /** Default sidecar location — stores highest-ever-seen vault version. */
48
+ export declare const DEFAULT_SIDECAR_PATH: string;
49
+ /**
50
+ * AAD prefix — static byte header mixed into the vault ciphertext's
51
+ * Additional Authenticated Data. Binding this prefix into AAD means a
52
+ * ciphertext from some other AES-GCM user with the same key cannot be
53
+ * substituted into the vault. Exported so test fixtures can produce AAD
54
+ * consistent with the real implementation without duplicating the constant.
55
+ */
56
+ export declare const VAULT_AAD_PREFIX = "centient-secrets-vault";
57
+ /**
58
+ * Coherence strategy governs how the open vault reconciles in-memory state
59
+ * with concurrent external writes to the vault file.
60
+ */
61
+ export type CoherenceStrategy = "mtime-check" | "strict" | "best-effort";
62
+ /** Options for {@link openVault}. All fields are optional. */
63
+ export interface OpenVaultOptions {
64
+ /** Alternate vault file path. Defaults to the same path the CLI uses. */
65
+ path?: string;
66
+ /** Alternate sidecar path. Defaults to vault directory + `vault.seen-version`. */
67
+ sidecarPath?: string;
68
+ /**
69
+ * Coherence strategy for concurrent external writes. Default `mtime-check`:
70
+ * stat on every read; re-decrypt if mtime advanced. `strict` throws on stale
71
+ * snapshot. `best-effort` keeps the in-memory snapshot until `reload()`.
72
+ */
73
+ coherence?: CoherenceStrategy;
74
+ /**
75
+ * Opt-in acceptance of a detected rollback (sidecar version > vault version).
76
+ * Emits a scary warning on stderr. Use only when the operator explicitly
77
+ * intends to restore an older vault (backup restore, etc.).
78
+ */
79
+ acceptRollback?: boolean;
80
+ /**
81
+ * Opt-in acceptance of a missing sidecar. Default behaviour (`false`) is to
82
+ * **refuse** to open the vault when the sidecar is absent — this enforces
83
+ * the security invariant that rollback protection is always in effect.
84
+ * Pass `true` for legitimate first-use contexts (fresh install, test
85
+ * fixtures, post-migration) to auto-initialize `seenVersion = vaultVersion`
86
+ * with a stderr warning. See docs/session-vault.md §Missing sidecar.
87
+ */
88
+ acceptMissingSidecar?: boolean;
89
+ /**
90
+ * Optional auto-close TTL in milliseconds. Not set by default — daemons run
91
+ * forever; forced re-auth undoes the point of a session vault. Useful for
92
+ * short-lived script consumers that want defense-in-depth.
93
+ */
94
+ ttlMs?: number;
95
+ }
96
+ /**
97
+ * A long-lived handle to an unlocked vault. Construct with {@link openVault};
98
+ * close with {@link SessionVault.close}. Operations are async so policy
99
+ * `before` hooks can await (e.g. remote attestation).
100
+ */
101
+ export interface SessionVault {
102
+ /** Read a secret by name. Returns null if the name isn't in the vault. */
103
+ get(name: string): Promise<string | null>;
104
+ /** List all secret names, optionally prefix-filtered. Sorted ascending. */
105
+ list(prefix?: string): Promise<string[]>;
106
+ /** Write a secret. Re-encrypts and saves the vault file atomically. */
107
+ set(name: string, value: string): Promise<void>;
108
+ /** Delete a secret. Returns true if the name existed and was removed. */
109
+ delete(name: string): Promise<boolean>;
110
+ /** Force an immediate reload from disk regardless of coherence strategy. */
111
+ reload(): Promise<void>;
112
+ /** Release the session key and in-memory state. No-op if already closed. */
113
+ close(): void;
114
+ /** Diagnostic — the KeyProvider that unlocked this session. */
115
+ readonly provider: KeyProviderType;
116
+ /** Diagnostic — absolute path of the vault file. */
117
+ readonly path: string;
118
+ /** Diagnostic — the current in-memory vault version. */
119
+ readonly vaultVersion: number;
120
+ }
121
+ /**
122
+ * Open an encrypted session vault.
123
+ *
124
+ * Resolves the configured {@link KeyProvider} to obtain the master key,
125
+ * decrypts the vault file bound to its resolved real path (symlink-aware),
126
+ * checks rollback detection via the sidecar, and returns a long-lived
127
+ * {@link SessionVault} handle that serves reads from memory.
128
+ *
129
+ * @param opts - {@link OpenVaultOptions}. All fields are optional; defaults
130
+ * use the same paths the `centient secrets` CLI uses.
131
+ * @returns An open {@link SessionVault}. Call `close()` when done.
132
+ * @throws {@link VaultError} `VAULT_NOT_FOUND` when the vault file is absent.
133
+ * @throws {@link VaultUnlockError} when the KeyProvider cannot return a key.
134
+ * @throws {@link VaultDecryptError} when decryption fails (wrong key, AAD
135
+ * mismatch, corrupted payload).
136
+ * @throws {@link VaultRollbackError} when the sidecar indicates a rollback
137
+ * and `acceptRollback` is not set.
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * const vault = await openVault({ ttlMs: 60_000 });
142
+ * const apiKey = await vault.get("openai-api-key");
143
+ * vault.close();
144
+ * ```
145
+ */
146
+ export declare function openVault(opts?: OpenVaultOptions): Promise<SessionVault>;
147
+ //# sourceMappingURL=session-vault.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-vault.d.ts","sourceRoot":"","sources":["../../src/vault/session-vault.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAiBH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAejE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACf,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,GACf,CAAC;AAMF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,QAAuD,CAAC;AAEvF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,QAKhC,CAAC;AAKF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,2BAA2B,CAAC;AA2BzD;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,QAAQ,GAAG,aAAa,CAAC;AAEzE,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,2EAA2E;IAC3E,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,uEAAuE;IACvE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,yEAAyE;IACzE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,4EAA4E;IAC5E,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,4EAA4E;IAC5E,KAAK,IAAI,IAAI,CAAC;IACd,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AA2ED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,YAAY,CAAC,CAsJlF"}