varlock 0.9.1 → 1.1.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 (116) hide show
  1. package/dist/auto-load.js +2 -2
  2. package/dist/{chunk-LOGMWG5X.js → chunk-6RF54KKR.js} +36 -7
  3. package/dist/chunk-6RF54KKR.js.map +1 -0
  4. package/dist/{chunk-FSLTOPCT.js → chunk-7GFD2ATN.js} +4 -4
  5. package/dist/{chunk-FSLTOPCT.js.map → chunk-7GFD2ATN.js.map} +1 -1
  6. package/dist/{chunk-NWKETKFP.js → chunk-A6THM3IR.js} +5 -5
  7. package/dist/{chunk-NWKETKFP.js.map → chunk-A6THM3IR.js.map} +1 -1
  8. package/dist/{chunk-253NGIPN.js → chunk-CDLU5P62.js} +3 -3
  9. package/dist/{chunk-253NGIPN.js.map → chunk-CDLU5P62.js.map} +1 -1
  10. package/dist/chunk-E3F6QKDZ.js +426 -0
  11. package/dist/chunk-E3F6QKDZ.js.map +1 -0
  12. package/dist/{chunk-YHOWSHVH.js → chunk-F5H5MJ6U.js} +32 -5
  13. package/dist/chunk-F5H5MJ6U.js.map +1 -0
  14. package/dist/chunk-GURKQO4J.js +1202 -0
  15. package/dist/chunk-GURKQO4J.js.map +1 -0
  16. package/dist/{chunk-FWJAZMC7.js → chunk-H6NILU2I.js} +4 -4
  17. package/dist/{chunk-FWJAZMC7.js.map → chunk-H6NILU2I.js.map} +1 -1
  18. package/dist/chunk-HDKXXS2X.js +1959 -0
  19. package/dist/chunk-HDKXXS2X.js.map +1 -0
  20. package/dist/chunk-JIUWL2NT.js +149 -0
  21. package/dist/chunk-JIUWL2NT.js.map +1 -0
  22. package/dist/chunk-JUPAI2X4.js +30 -0
  23. package/dist/chunk-JUPAI2X4.js.map +1 -0
  24. package/dist/chunk-O3WTD6L4.js +37 -0
  25. package/dist/chunk-O3WTD6L4.js.map +1 -0
  26. package/dist/{chunk-QHIRGHGG.js → chunk-QP7TS4SU.js} +6 -5
  27. package/dist/chunk-QP7TS4SU.js.map +1 -0
  28. package/dist/chunk-QSYH5IDD.js +26 -0
  29. package/dist/chunk-QSYH5IDD.js.map +1 -0
  30. package/dist/chunk-RBFS2QGC.js +199 -0
  31. package/dist/chunk-RBFS2QGC.js.map +1 -0
  32. package/dist/{chunk-BFRONY2O.js → chunk-S5O4AAVX.js} +6 -6
  33. package/dist/{chunk-BFRONY2O.js.map → chunk-S5O4AAVX.js.map} +1 -1
  34. package/dist/{chunk-UG4RTI6V.js → chunk-SDN53OAC.js} +5 -4
  35. package/dist/chunk-SDN53OAC.js.map +1 -0
  36. package/dist/{chunk-4VC5S7NB.js → chunk-TQXYC3G3.js} +5 -5
  37. package/dist/{chunk-4VC5S7NB.js.map → chunk-TQXYC3G3.js.map} +1 -1
  38. package/dist/{chunk-7P3SVUZ5.js → chunk-U2O3AUM2.js} +6 -6
  39. package/dist/{chunk-7P3SVUZ5.js.map → chunk-U2O3AUM2.js.map} +1 -1
  40. package/dist/{chunk-GH73MG2H.js → chunk-VN4LKYXR.js} +4 -4
  41. package/dist/{chunk-GH73MG2H.js.map → chunk-VN4LKYXR.js.map} +1 -1
  42. package/dist/{chunk-W3GUFLIV.js → chunk-XWYFSG46.js} +371 -1131
  43. package/dist/chunk-XWYFSG46.js.map +1 -0
  44. package/dist/{chunk-US7YQTJZ.js → chunk-YO6WHPM4.js} +5 -5
  45. package/dist/{chunk-US7YQTJZ.js.map → chunk-YO6WHPM4.js.map} +1 -1
  46. package/dist/{chunk-ZRHT6QHO.js → chunk-ZJNDICC4.js} +5 -5
  47. package/dist/{chunk-ZRHT6QHO.js.map → chunk-ZJNDICC4.js.map} +1 -1
  48. package/dist/cli/cli-executable.js +47 -31
  49. package/dist/cli/cli-executable.js.map +1 -1
  50. package/dist/config-item-6LTV4PNH.js +7 -0
  51. package/dist/{config-item-IK3DUE5F.js.map → config-item-6LTV4PNH.js.map} +1 -1
  52. package/dist/dist-WGIHRGBZ.js +4 -0
  53. package/dist/dist-WGIHRGBZ.js.map +1 -0
  54. package/dist/dotenv-compat.js +2 -2
  55. package/dist/encrypt.command-F2OTB6HD.js +14 -0
  56. package/dist/encrypt.command-F2OTB6HD.js.map +1 -0
  57. package/dist/{env-graph-DaF8Aebq.d.ts → env-graph-iNQyTcya.d.ts} +7 -0
  58. package/dist/explain.command-TEIPRC7Q.js +15 -0
  59. package/dist/{explain.command-5DG3ACIH.js.map → explain.command-TEIPRC7Q.js.map} +1 -1
  60. package/dist/index.d.ts +2 -2
  61. package/dist/index.js +8 -5
  62. package/dist/index.js.map +1 -1
  63. package/dist/init.command-5LP3UFKD.js +13 -0
  64. package/dist/{init.command-EWCFF2D5.js.map → init.command-5LP3UFKD.js.map} +1 -1
  65. package/dist/install-plugin.command-X7RSLPUJ.js +13 -0
  66. package/dist/{install-plugin.command-6ESRFNKI.js.map → install-plugin.command-X7RSLPUJ.js.map} +1 -1
  67. package/dist/lib/exec-sync-varlock.d.ts +34 -9
  68. package/dist/lib/exec-sync-varlock.js +1 -1
  69. package/dist/load.command-7SQRDQ3E.js +15 -0
  70. package/dist/{load.command-QNDTE2VK.js.map → load.command-7SQRDQ3E.js.map} +1 -1
  71. package/dist/lock.command-4LTGMJA3.js +7 -0
  72. package/dist/lock.command-4LTGMJA3.js.map +1 -0
  73. package/dist/plugin-lib.d.ts +2 -2
  74. package/dist/printenv.command-ON7RMFEU.js +15 -0
  75. package/dist/{printenv.command-SBCXAVQT.js.map → printenv.command-ON7RMFEU.js.map} +1 -1
  76. package/dist/reveal.command-BW6XYVXH.js +15 -0
  77. package/dist/reveal.command-BW6XYVXH.js.map +1 -0
  78. package/dist/run.command-5QADABYL.js +16 -0
  79. package/dist/{run.command-64JM27VM.js.map → run.command-5QADABYL.js.map} +1 -1
  80. package/dist/runtime/env.d.ts +1 -1
  81. package/dist/scan.command-ZVW3XAUG.js +16 -0
  82. package/dist/{scan.command-YFM7VO2I.js.map → scan.command-ZVW3XAUG.js.map} +1 -1
  83. package/dist/telemetry.command-PY6E4QSH.js +13 -0
  84. package/dist/{telemetry.command-4AEVBTVE.js.map → telemetry.command-PY6E4QSH.js.map} +1 -1
  85. package/dist/typegen.command-KZ4O5IKQ.js +15 -0
  86. package/dist/{typegen.command-27OCEPZM.js.map → typegen.command-KZ4O5IKQ.js.map} +1 -1
  87. package/native-bins/darwin/VarlockEnclave.app/Contents/CodeResources +0 -0
  88. package/native-bins/darwin/VarlockEnclave.app/Contents/Info.plist +28 -0
  89. package/native-bins/darwin/VarlockEnclave.app/Contents/MacOS/varlock-local-encrypt +0 -0
  90. package/native-bins/darwin/VarlockEnclave.app/Contents/Resources/AppIcon.icns +0 -0
  91. package/native-bins/darwin/VarlockEnclave.app/Contents/Resources/varlock-menu-locked.pdf +0 -0
  92. package/native-bins/darwin/VarlockEnclave.app/Contents/Resources/varlock-menu-unlocked.pdf +0 -0
  93. package/native-bins/darwin/VarlockEnclave.app/Contents/_CodeSignature/CodeResources +150 -0
  94. package/native-bins/linux-arm64/varlock-local-encrypt +0 -0
  95. package/native-bins/linux-x64/varlock-local-encrypt +0 -0
  96. package/native-bins/win32-x64/varlock-local-encrypt.exe +0 -0
  97. package/package.json +9 -2
  98. package/dist/chunk-LOGMWG5X.js.map +0 -1
  99. package/dist/chunk-MIMMYDBC.js +0 -83
  100. package/dist/chunk-MIMMYDBC.js.map +0 -1
  101. package/dist/chunk-QHIRGHGG.js.map +0 -1
  102. package/dist/chunk-UG4RTI6V.js.map +0 -1
  103. package/dist/chunk-UUDTMOJS.js +0 -22
  104. package/dist/chunk-UUDTMOJS.js.map +0 -1
  105. package/dist/chunk-W3GUFLIV.js.map +0 -1
  106. package/dist/chunk-YHOWSHVH.js.map +0 -1
  107. package/dist/config-item-IK3DUE5F.js +0 -5
  108. package/dist/explain.command-5DG3ACIH.js +0 -12
  109. package/dist/init.command-EWCFF2D5.js +0 -11
  110. package/dist/install-plugin.command-6ESRFNKI.js +0 -11
  111. package/dist/load.command-QNDTE2VK.js +0 -12
  112. package/dist/printenv.command-SBCXAVQT.js +0 -12
  113. package/dist/run.command-64JM27VM.js +0 -13
  114. package/dist/scan.command-YFM7VO2I.js +0 -13
  115. package/dist/telemetry.command-4AEVBTVE.js +0 -11
  116. package/dist/typegen.command-27OCEPZM.js +0 -12
@@ -0,0 +1,1202 @@
1
+ import { isWSL, getUserVarlockDir } from './chunk-O3WTD6L4.js';
2
+ import { __name } from './chunk-6PEHRAEP.js';
3
+ import { spawn, execFileSync, spawnSync } from 'child_process';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import net from 'net';
8
+ import crypto, { webcrypto } from 'crypto';
9
+
10
+ var __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
11
+ function debug(msg) {
12
+ if (process.env.VARLOCK_DEBUG) {
13
+ process.stderr.write(`[varlock:binary-resolver] ${msg}
14
+ `);
15
+ }
16
+ }
17
+ __name(debug, "debug");
18
+ var BINARY_NAME = "varlock-local-encrypt";
19
+ var MACOS_APP_BUNDLE = "VarlockEnclave.app";
20
+ function resolvePackageRoot() {
21
+ let dir = __dirname$1;
22
+ for (let i = 0; i < 10; i++) {
23
+ const pkgJsonPath = path.join(dir, "package.json");
24
+ if (fs.existsSync(pkgJsonPath)) {
25
+ try {
26
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
27
+ if (pkgJson.name === "varlock") return dir;
28
+ } catch {
29
+ }
30
+ }
31
+ const parent = path.dirname(dir);
32
+ if (parent === dir) break;
33
+ dir = parent;
34
+ }
35
+ return path.resolve(__dirname$1, "..", "..", "..");
36
+ }
37
+ __name(resolvePackageRoot, "resolvePackageRoot");
38
+ function getPlatformBinaryName() {
39
+ if (process.platform === "win32" || isWSL()) return `${BINARY_NAME}.exe`;
40
+ return BINARY_NAME;
41
+ }
42
+ __name(getPlatformBinaryName, "getPlatformBinaryName");
43
+ function getNativeBinSubdir() {
44
+ if (process.platform === "darwin") return "darwin";
45
+ if (process.platform === "win32") return `win32-${process.arch}`;
46
+ if (isWSL()) return "win32-x64";
47
+ return `${process.platform}-${process.arch}`;
48
+ }
49
+ __name(getNativeBinSubdir, "getNativeBinSubdir");
50
+ function resolveMacOSBinary(dir) {
51
+ const appBundlePath = path.join(dir, MACOS_APP_BUNDLE, "Contents", "MacOS", BINARY_NAME);
52
+ if (fs.existsSync(appBundlePath)) return appBundlePath;
53
+ const barePath = path.join(dir, BINARY_NAME);
54
+ if (fs.existsSync(barePath)) return barePath;
55
+ return void 0;
56
+ }
57
+ __name(resolveMacOSBinary, "resolveMacOSBinary");
58
+ function resolveStandardBinary(dir) {
59
+ const binaryPath = path.join(dir, getPlatformBinaryName());
60
+ if (fs.existsSync(binaryPath)) return binaryPath;
61
+ return void 0;
62
+ }
63
+ __name(resolveStandardBinary, "resolveStandardBinary");
64
+ function resolveBinaryFromDir(dir) {
65
+ if (process.platform === "darwin") return resolveMacOSBinary(dir);
66
+ return resolveStandardBinary(dir);
67
+ }
68
+ __name(resolveBinaryFromDir, "resolveBinaryFromDir");
69
+ function resolveSeaSibling() {
70
+ const execDir = path.dirname(fs.realpathSync(process.execPath));
71
+ const sibling = resolveBinaryFromDir(execDir);
72
+ if (sibling) return sibling;
73
+ const libexecDir = path.join(execDir, "..", "libexec");
74
+ return resolveBinaryFromDir(libexecDir);
75
+ }
76
+ __name(resolveSeaSibling, "resolveSeaSibling");
77
+ function resolveNpmBundled() {
78
+ const packageRoot = resolvePackageRoot();
79
+ const nativeBinsDir = path.join(packageRoot, "native-bins", getNativeBinSubdir());
80
+ if (fs.existsSync(nativeBinsDir)) return resolveBinaryFromDir(nativeBinsDir);
81
+ const adjacentNativeBinsDir = path.join(path.dirname(packageRoot), "native-bins", getNativeBinSubdir());
82
+ if (fs.existsSync(adjacentNativeBinsDir)) return resolveBinaryFromDir(adjacentNativeBinsDir);
83
+ return void 0;
84
+ }
85
+ __name(resolveNpmBundled, "resolveNpmBundled");
86
+ function resolveDevFallback() {
87
+ let dir = __dirname$1;
88
+ for (let i = 0; i < 10; i++) {
89
+ const parent = path.dirname(dir);
90
+ if (parent === dir) break;
91
+ dir = parent;
92
+ if (process.platform === "darwin") {
93
+ const swiftBuild = path.join(dir, "packages", "encryption-binary-swift", "swift", ".build", "release", "VarlockEnclave");
94
+ if (fs.existsSync(swiftBuild)) return swiftBuild;
95
+ }
96
+ const rustBuild = path.join(dir, "packages", "encryption-binary-rust", "target", "release", getPlatformBinaryName());
97
+ if (fs.existsSync(rustBuild)) return rustBuild;
98
+ }
99
+ return void 0;
100
+ }
101
+ __name(resolveDevFallback, "resolveDevFallback");
102
+ function ensureExecutable(binaryPath) {
103
+ try {
104
+ fs.accessSync(binaryPath, fs.constants.X_OK);
105
+ } catch {
106
+ if (process.platform !== "win32") {
107
+ fs.chmodSync(binaryPath, 493);
108
+ }
109
+ }
110
+ return binaryPath;
111
+ }
112
+ __name(ensureExecutable, "ensureExecutable");
113
+ var _cachedBinaryPath = null;
114
+ function resolveNativeBinary() {
115
+ if (_cachedBinaryPath !== null) return _cachedBinaryPath;
116
+ if (process.env._VARLOCK_FORCE_FILE_ENCRYPTION_FALLBACK) {
117
+ debug("_VARLOCK_FORCE_FILE_ENCRYPTION_FALLBACK is set \u2014 skipping native binary resolution");
118
+ _cachedBinaryPath = void 0;
119
+ return void 0;
120
+ }
121
+ debug(`resolving: platform=${process.platform}, isWSL=${isWSL()}, binaryName=${getPlatformBinaryName()}, subdir=${getNativeBinSubdir()}`);
122
+ const seaSibling = resolveSeaSibling();
123
+ if (seaSibling) {
124
+ debug(`resolved via SEA sibling: ${seaSibling}`);
125
+ _cachedBinaryPath = ensureExecutable(seaSibling);
126
+ return _cachedBinaryPath;
127
+ }
128
+ const npmBundled = resolveNpmBundled();
129
+ if (npmBundled) {
130
+ debug(`resolved via npm bundled: ${npmBundled}`);
131
+ _cachedBinaryPath = ensureExecutable(npmBundled);
132
+ return _cachedBinaryPath;
133
+ }
134
+ const devFallback = resolveDevFallback();
135
+ if (devFallback) {
136
+ debug(`resolved via dev fallback: ${devFallback}`);
137
+ _cachedBinaryPath = ensureExecutable(devFallback);
138
+ return _cachedBinaryPath;
139
+ }
140
+ debug("NOT FOUND: no binary resolved from any strategy");
141
+ debug(` SEA sibling dir: ${path.dirname(process.execPath)}`);
142
+ const packageRoot = resolvePackageRoot();
143
+ debug(` npm bundled dir: ${path.join(packageRoot, "native-bins", getNativeBinSubdir())}`);
144
+ _cachedBinaryPath = void 0;
145
+ return void 0;
146
+ }
147
+ __name(resolveNativeBinary, "resolveNativeBinary");
148
+ var SEND_TIMEOUT_MS = 3e4;
149
+ var BIOMETRIC_TIMEOUT_MS = 9e4;
150
+ var INTERACTIVE_TIMEOUT_MS = 5 * 6e4;
151
+ var KILL_GRACE_MS = 2e3;
152
+ function debug2(msg) {
153
+ if (process.env.VARLOCK_DEBUG) {
154
+ process.stderr.write(`[varlock:daemon-client] ${msg}
155
+ `);
156
+ }
157
+ }
158
+ __name(debug2, "debug");
159
+ function killDaemonProcess(pid) {
160
+ try {
161
+ process.kill(pid, "SIGTERM");
162
+ } catch {
163
+ return true;
164
+ }
165
+ const start = Date.now();
166
+ while (Date.now() - start < KILL_GRACE_MS) {
167
+ try {
168
+ process.kill(pid, 0);
169
+ } catch {
170
+ return true;
171
+ }
172
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
173
+ }
174
+ debug2(`daemon pid ${pid} didn't respond to SIGTERM, sending SIGKILL`);
175
+ try {
176
+ process.kill(pid, "SIGKILL");
177
+ } catch {
178
+ return true;
179
+ }
180
+ const killStart = Date.now();
181
+ while (Date.now() - killStart < 500) {
182
+ try {
183
+ process.kill(pid, 0);
184
+ } catch {
185
+ return true;
186
+ }
187
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
188
+ }
189
+ debug2(`daemon pid ${pid} is unkillable (likely in uninterruptible kernel wait) \u2014 proceeding anyway`);
190
+ return false;
191
+ }
192
+ __name(killDaemonProcess, "killDaemonProcess");
193
+ function getSocketDir() {
194
+ return path.join(getUserVarlockDir(), "local-encrypt");
195
+ }
196
+ __name(getSocketDir, "getSocketDir");
197
+ function getSocketPath() {
198
+ if (process.platform === "win32") {
199
+ return "\\\\.\\pipe\\varlock-local-encrypt";
200
+ }
201
+ return path.join(getSocketDir(), "daemon.sock");
202
+ }
203
+ __name(getSocketPath, "getSocketPath");
204
+ function getLockPath() {
205
+ return `${getSocketPath()}.lock`;
206
+ }
207
+ __name(getLockPath, "getLockPath");
208
+ function getPidPath() {
209
+ return path.join(getSocketDir(), "daemon.pid");
210
+ }
211
+ __name(getPidPath, "getPidPath");
212
+ function getDaemonInfoPath() {
213
+ return path.join(getSocketDir(), "daemon.info");
214
+ }
215
+ __name(getDaemonInfoPath, "getDaemonInfoPath");
216
+ function getDaemonStateFiles() {
217
+ const files = [getPidPath(), getDaemonInfoPath()];
218
+ if (process.platform !== "win32") {
219
+ files.push(getSocketPath(), getLockPath());
220
+ }
221
+ return files;
222
+ }
223
+ __name(getDaemonStateFiles, "getDaemonStateFiles");
224
+ function cleanupDaemonFiles() {
225
+ for (const file of getDaemonStateFiles()) {
226
+ try {
227
+ fs.unlinkSync(file);
228
+ } catch {
229
+ }
230
+ }
231
+ }
232
+ __name(cleanupDaemonFiles, "cleanupDaemonFiles");
233
+ function checkDaemonBinaryStale() {
234
+ const infoPath = getDaemonInfoPath();
235
+ const pidPath = getPidPath();
236
+ let info;
237
+ try {
238
+ info = JSON.parse(fs.readFileSync(infoPath, "utf-8"));
239
+ } catch {
240
+ }
241
+ const currentBinaryPath = resolveNativeBinary();
242
+ if (!currentBinaryPath) return void 0;
243
+ if (info) {
244
+ if (currentBinaryPath !== info.binaryPath) {
245
+ debug2(`daemon binary path changed: ${info.binaryPath} \u2192 ${currentBinaryPath}`);
246
+ } else {
247
+ try {
248
+ const stat = fs.statSync(currentBinaryPath);
249
+ if (stat.mtimeMs === info.binaryMtimeMs) {
250
+ debug2("daemon binary is current \u2014 no restart needed");
251
+ return void 0;
252
+ }
253
+ debug2(`daemon binary mtime changed: ${info.binaryMtimeMs} \u2192 ${stat.mtimeMs}`);
254
+ } catch {
255
+ return void 0;
256
+ }
257
+ }
258
+ } else {
259
+ debug2("no daemon.info file \u2014 treating running daemon as stale");
260
+ }
261
+ try {
262
+ const pid = parseInt(fs.readFileSync(pidPath, "utf-8").trim(), 10);
263
+ process.kill(pid, 0);
264
+ return pid;
265
+ } catch {
266
+ debug2("stale PID file points to dead process \u2014 cleaning up");
267
+ cleanupDaemonFiles();
268
+ return void 0;
269
+ }
270
+ }
271
+ __name(checkDaemonBinaryStale, "checkDaemonBinaryStale");
272
+ function writeDaemonInfo(binaryPath) {
273
+ try {
274
+ const stat = fs.statSync(binaryPath);
275
+ fs.writeFileSync(getDaemonInfoPath(), JSON.stringify({
276
+ binaryPath,
277
+ binaryMtimeMs: stat.mtimeMs
278
+ }));
279
+ } catch {
280
+ }
281
+ }
282
+ __name(writeDaemonInfo, "writeDaemonInfo");
283
+ var DaemonClient = class {
284
+ static {
285
+ __name(this, "DaemonClient");
286
+ }
287
+ socket = null;
288
+ messageQueue = /* @__PURE__ */ new Map();
289
+ isConnected = false;
290
+ buffer = Buffer.alloc(0);
291
+ connectingPromise = null;
292
+ /** Set after we spawn a daemon in this process — skip stale check to avoid restart loops */
293
+ spawnedInThisProcess = false;
294
+ async ensureConnected() {
295
+ if (this.isConnected && this.socket) return;
296
+ if (this.connectingPromise) return this.connectingPromise;
297
+ this.connectingPromise = this.doConnect();
298
+ try {
299
+ await this.connectingPromise;
300
+ } finally {
301
+ this.connectingPromise = null;
302
+ }
303
+ }
304
+ /**
305
+ * Try to connect to an existing daemon without spawning a new one.
306
+ * Returns true if connected, false if no daemon is running.
307
+ */
308
+ async tryConnect() {
309
+ if (this.isConnected && this.socket) return true;
310
+ const socketPath = getSocketPath();
311
+ try {
312
+ await this.connectToSocket(socketPath);
313
+ return true;
314
+ } catch {
315
+ return false;
316
+ }
317
+ }
318
+ async doConnect() {
319
+ const socketPath = getSocketPath();
320
+ const stalePid = this.spawnedInThisProcess ? void 0 : checkDaemonBinaryStale();
321
+ if (stalePid) {
322
+ debug2(`killing stale daemon (pid ${stalePid}) \u2014 binary has been updated`);
323
+ killDaemonProcess(stalePid);
324
+ cleanupDaemonFiles();
325
+ } else {
326
+ try {
327
+ await this.connectToSocket(socketPath);
328
+ return;
329
+ } catch {
330
+ }
331
+ }
332
+ try {
333
+ await this.spawnDaemon();
334
+ } catch (err) {
335
+ debug2(`spawnDaemon failed: ${err instanceof Error ? err.message : err}`);
336
+ await new Promise((r) => {
337
+ setTimeout(r, 1e3);
338
+ });
339
+ }
340
+ await this.connectToSocket(socketPath);
341
+ }
342
+ async decrypt(ciphertext, keyId = "varlock-default") {
343
+ return this.withRetry(async () => {
344
+ await this.ensureConnected();
345
+ const result = await this.sendMessage({
346
+ action: "decrypt",
347
+ payload: { ciphertext, keyId }
348
+ }, BIOMETRIC_TIMEOUT_MS);
349
+ if (typeof result === "string") return result;
350
+ if (result && typeof result === "object" && "error" in result) {
351
+ throw new Error(String(result.error));
352
+ }
353
+ return String(result);
354
+ });
355
+ }
356
+ async promptSecret(opts) {
357
+ return this.withRetry(async () => {
358
+ await this.ensureConnected();
359
+ try {
360
+ const result = await this.sendMessage({
361
+ action: "prompt-secret",
362
+ payload: {
363
+ itemKey: opts?.itemKey,
364
+ message: opts?.message,
365
+ keyId: opts?.keyId
366
+ }
367
+ }, INTERACTIVE_TIMEOUT_MS);
368
+ if (result && typeof result === "object" && "ciphertext" in result) {
369
+ return result.ciphertext;
370
+ }
371
+ return void 0;
372
+ } catch (err) {
373
+ if (err instanceof Error && err.message === "cancelled") return void 0;
374
+ throw err;
375
+ }
376
+ });
377
+ }
378
+ async invalidateSession() {
379
+ return this.withRetry(async () => {
380
+ await this.ensureConnected();
381
+ await this.sendMessage({ action: "invalidate-session" });
382
+ });
383
+ }
384
+ async keychainGet(opts) {
385
+ return this.withRetry(async () => {
386
+ await this.ensureConnected();
387
+ const result = await this.sendMessage({
388
+ action: "keychain-get",
389
+ payload: opts
390
+ }, BIOMETRIC_TIMEOUT_MS);
391
+ if (typeof result === "string") return result;
392
+ if (result && typeof result === "object" && "error" in result) {
393
+ throw new Error(String(result.error));
394
+ }
395
+ return String(result);
396
+ });
397
+ }
398
+ async keychainSearch(opts) {
399
+ return this.withRetry(async () => {
400
+ await this.ensureConnected();
401
+ const result = await this.sendMessage({
402
+ action: "keychain-search",
403
+ payload: opts ?? {}
404
+ });
405
+ return result ?? [];
406
+ });
407
+ }
408
+ async keychainPick(opts) {
409
+ return this.withRetry(async () => {
410
+ await this.ensureConnected();
411
+ try {
412
+ const result = await this.sendMessage({
413
+ action: "keychain-pick",
414
+ payload: { itemKey: opts?.itemKey }
415
+ }, INTERACTIVE_TIMEOUT_MS);
416
+ if (result && typeof result === "object" && "service" in result) {
417
+ return result;
418
+ }
419
+ return void 0;
420
+ } catch (err) {
421
+ if (err instanceof Error && err.message === "cancelled") return void 0;
422
+ throw err;
423
+ }
424
+ });
425
+ }
426
+ cleanup() {
427
+ for (const { reject } of this.messageQueue.values()) {
428
+ reject(new Error("Connection closed"));
429
+ }
430
+ this.messageQueue.clear();
431
+ this.socket?.end();
432
+ this.socket = null;
433
+ this.isConnected = false;
434
+ this.buffer = Buffer.alloc(0);
435
+ }
436
+ // -- Private --
437
+ /**
438
+ * Run an async operation, and on recoverable failure (timeout, connection
439
+ * closed) clean up, reconnect to the daemon, and retry once.
440
+ */
441
+ async withRetry(fn) {
442
+ try {
443
+ return await fn();
444
+ } catch (err) {
445
+ const msg = err instanceof Error ? err.message : "";
446
+ const recoverable = msg.includes("timed out") || msg.includes("connection closed") || msg.includes("Not connected");
447
+ if (!recoverable) throw err;
448
+ debug2(`recoverable error, reconnecting: ${msg}`);
449
+ this.forceCleanup();
450
+ await this.ensureConnected();
451
+ return await fn();
452
+ }
453
+ }
454
+ /**
455
+ * Aggressive cleanup: kill the daemon process if we know its PID,
456
+ * then reset client state so the next ensureConnected spawns fresh.
457
+ */
458
+ forceCleanup() {
459
+ this.cleanup();
460
+ this.spawnedInThisProcess = false;
461
+ try {
462
+ const pid = parseInt(fs.readFileSync(getPidPath(), "utf-8").trim(), 10);
463
+ killDaemonProcess(pid);
464
+ } catch {
465
+ }
466
+ cleanupDaemonFiles();
467
+ }
468
+ connectToSocket(socketPath) {
469
+ return new Promise((resolve, reject) => {
470
+ const socket = new net.Socket();
471
+ const timeout = setTimeout(() => {
472
+ socket.destroy();
473
+ reject(new Error("Connection timeout"));
474
+ }, 5e3);
475
+ socket.on("connect", () => {
476
+ clearTimeout(timeout);
477
+ this.socket = socket;
478
+ this.isConnected = true;
479
+ this.buffer = Buffer.alloc(0);
480
+ resolve();
481
+ });
482
+ socket.on("data", (data) => {
483
+ this.handleData(data);
484
+ });
485
+ socket.on("error", (err) => {
486
+ clearTimeout(timeout);
487
+ this.isConnected = false;
488
+ reject(err);
489
+ });
490
+ socket.on("close", () => {
491
+ this.isConnected = false;
492
+ this.socket = null;
493
+ for (const { reject: rej } of this.messageQueue.values()) {
494
+ rej(new Error("Daemon connection closed"));
495
+ }
496
+ this.messageQueue.clear();
497
+ this.buffer = Buffer.alloc(0);
498
+ });
499
+ socket.connect(socketPath);
500
+ });
501
+ }
502
+ handleData(data) {
503
+ this.buffer = Buffer.concat([this.buffer, data]);
504
+ while (this.buffer.length >= 4) {
505
+ const messageLength = this.buffer.readUInt32LE(0);
506
+ if (this.buffer.length < 4 + messageLength) break;
507
+ const messageData = this.buffer.subarray(4, 4 + messageLength);
508
+ this.buffer = this.buffer.subarray(4 + messageLength);
509
+ try {
510
+ const message = JSON.parse(messageData.toString());
511
+ if (message.id && this.messageQueue.has(message.id)) {
512
+ const { resolve: res, reject: rej } = this.messageQueue.get(message.id);
513
+ this.messageQueue.delete(message.id);
514
+ if (message.error) {
515
+ rej(new Error(message.error));
516
+ } else {
517
+ res(message.result);
518
+ }
519
+ }
520
+ } catch {
521
+ }
522
+ }
523
+ }
524
+ sendMessage(message, timeoutMs = SEND_TIMEOUT_MS) {
525
+ return new Promise((resolve, reject) => {
526
+ if (!this.isConnected || !this.socket) {
527
+ reject(new Error("Not connected to daemon"));
528
+ return;
529
+ }
530
+ const messageId = `${Date.now().toString(36)}-${crypto.randomBytes(4).toString("hex")}`;
531
+ const messageWithId = { ...message, id: messageId };
532
+ const jsonData = JSON.stringify(messageWithId);
533
+ const messageBytes = Buffer.from(jsonData, "utf-8");
534
+ const lengthBuf = Buffer.alloc(4);
535
+ lengthBuf.writeUInt32LE(messageBytes.length, 0);
536
+ const timeout = setTimeout(() => {
537
+ this.messageQueue.delete(messageId);
538
+ reject(new Error(`Daemon message timed out after ${timeoutMs}ms (action: ${message.action})`));
539
+ }, timeoutMs);
540
+ this.messageQueue.set(messageId, {
541
+ resolve: /* @__PURE__ */ __name((value) => {
542
+ clearTimeout(timeout);
543
+ resolve(value);
544
+ }, "resolve"),
545
+ reject: /* @__PURE__ */ __name((err) => {
546
+ clearTimeout(timeout);
547
+ reject(err);
548
+ }, "reject")
549
+ });
550
+ this.socket.write(Buffer.concat([lengthBuf, messageBytes]));
551
+ });
552
+ }
553
+ async spawnDaemon() {
554
+ const binaryPath = resolveNativeBinary();
555
+ if (!binaryPath) {
556
+ throw new Error("Native encryption binary not found \u2014 cannot start daemon");
557
+ }
558
+ const socketPath = getSocketPath();
559
+ const pidPath = getPidPath();
560
+ const isWindows = process.platform === "win32";
561
+ if (!isWindows) {
562
+ fs.mkdirSync(path.dirname(socketPath), { recursive: true });
563
+ }
564
+ fs.mkdirSync(path.dirname(pidPath), { recursive: true });
565
+ if (fs.existsSync(pidPath)) {
566
+ try {
567
+ const pid = parseInt(fs.readFileSync(pidPath, "utf-8").trim(), 10);
568
+ process.kill(pid, 0);
569
+ try {
570
+ await this.connectToSocket(socketPath);
571
+ return;
572
+ } catch {
573
+ debug2(`daemon pid ${pid} alive but socket unresponsive \u2014 killing`);
574
+ killDaemonProcess(pid);
575
+ }
576
+ } catch {
577
+ }
578
+ }
579
+ cleanupDaemonFiles();
580
+ if (!isWindows && fs.existsSync(socketPath)) {
581
+ throw new Error(`Failed to clean up stale socket file: ${socketPath}`);
582
+ }
583
+ return new Promise((resolve, reject) => {
584
+ const child = spawn(binaryPath, [
585
+ "daemon",
586
+ "--socket-path",
587
+ socketPath,
588
+ "--pid-path",
589
+ pidPath
590
+ ], {
591
+ detached: true,
592
+ stdio: ["ignore", "pipe", "pipe"]
593
+ });
594
+ const timeout = setTimeout(() => {
595
+ reject(new Error("Daemon failed to start within timeout"));
596
+ }, 1e4);
597
+ let stdoutData = "";
598
+ let stderrData = "";
599
+ child.stdout.on("data", (data) => {
600
+ stdoutData += data.toString();
601
+ try {
602
+ const parsed = JSON.parse(stdoutData);
603
+ if (parsed.ready) {
604
+ clearTimeout(timeout);
605
+ writeDaemonInfo(binaryPath);
606
+ this.spawnedInThisProcess = true;
607
+ child.unref();
608
+ child.stdout.destroy();
609
+ child.stderr.destroy();
610
+ resolve();
611
+ }
612
+ } catch {
613
+ }
614
+ });
615
+ child.stderr.on("data", (data) => {
616
+ stderrData += data.toString();
617
+ });
618
+ child.on("error", (err) => {
619
+ clearTimeout(timeout);
620
+ reject(new Error(`Failed to spawn daemon: ${err.message}`));
621
+ });
622
+ child.on("exit", (code) => {
623
+ clearTimeout(timeout);
624
+ if (code !== 0) {
625
+ const details = [
626
+ stderrData.trim() && `stderr: ${stderrData.trim()}`,
627
+ stdoutData.trim() && `stdout: ${stdoutData.trim()}`,
628
+ `binary: ${binaryPath}`,
629
+ `socket: ${socketPath}`
630
+ ].filter(Boolean).join("\n");
631
+ reject(new Error(`Daemon exited with code ${code}
632
+ ${details}`));
633
+ }
634
+ });
635
+ });
636
+ }
637
+ };
638
+ var subtle = webcrypto.subtle;
639
+ var PAYLOAD_VERSION = 1;
640
+ var HKDF_SALT = new TextEncoder().encode("varlock-ecies-v1");
641
+ var EC_ALGORITHM = { name: "ECDH", namedCurve: "P-256" };
642
+ var PUBLIC_KEY_LENGTH = 65;
643
+ var NONCE_LENGTH = 12;
644
+ var TAG_LENGTH = 16;
645
+ var HEADER_LENGTH = 1 + PUBLIC_KEY_LENGTH + NONCE_LENGTH;
646
+ var bs = /* @__PURE__ */ __name((data) => data, "bs");
647
+ function concatBuffers(...buffers) {
648
+ const totalLength = buffers.reduce((sum, b) => sum + b.length, 0);
649
+ const result = new Uint8Array(totalLength);
650
+ let offset = 0;
651
+ for (const buf of buffers) {
652
+ result.set(buf, offset);
653
+ offset += buf.length;
654
+ }
655
+ return result;
656
+ }
657
+ __name(concatBuffers, "concatBuffers");
658
+ function bufferToBase64(buffer) {
659
+ if (buffer instanceof Uint8Array) {
660
+ return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength).toString("base64");
661
+ }
662
+ return Buffer.from(buffer).toString("base64");
663
+ }
664
+ __name(bufferToBase64, "bufferToBase64");
665
+ function base64ToUint8(base64) {
666
+ const buf = Buffer.from(base64, "base64");
667
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
668
+ }
669
+ __name(base64ToUint8, "base64ToUint8");
670
+ async function hkdfSha256(ikm, salt, info, outputByteCount) {
671
+ const saltKey = await subtle.importKey("raw", bs(salt), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
672
+ const prk = new Uint8Array(await subtle.sign("HMAC", saltKey, bs(ikm)));
673
+ const prkKey = await subtle.importKey("raw", bs(prk), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
674
+ const okm = new Uint8Array(outputByteCount);
675
+ let t = new Uint8Array(0);
676
+ let offset = 0;
677
+ let counter = 1;
678
+ while (offset < outputByteCount) {
679
+ const input = concatBuffers(t, info, new Uint8Array([counter]));
680
+ t = new Uint8Array(await subtle.sign("HMAC", prkKey, bs(input)));
681
+ okm.set(t.slice(0, Math.min(t.length, outputByteCount - offset)), offset);
682
+ offset += t.length;
683
+ counter++;
684
+ }
685
+ return okm;
686
+ }
687
+ __name(hkdfSha256, "hkdfSha256");
688
+ async function importPublicKey(base64) {
689
+ return subtle.importKey("raw", bs(base64ToUint8(base64)), EC_ALGORITHM, true, []);
690
+ }
691
+ __name(importPublicKey, "importPublicKey");
692
+ async function importPrivateKey(base64) {
693
+ return subtle.importKey("pkcs8", bs(base64ToUint8(base64)), EC_ALGORITHM, true, ["deriveBits"]);
694
+ }
695
+ __name(importPrivateKey, "importPrivateKey");
696
+ async function createKeyPair() {
697
+ const keyPair = await subtle.generateKey(EC_ALGORITHM, true, ["deriveBits"]);
698
+ const publicKeyRaw = await subtle.exportKey("raw", keyPair.publicKey);
699
+ const privateKeyPkcs8 = await subtle.exportKey("pkcs8", keyPair.privateKey);
700
+ return {
701
+ publicKey: bufferToBase64(publicKeyRaw),
702
+ privateKey: bufferToBase64(privateKeyPkcs8)
703
+ };
704
+ }
705
+ __name(createKeyPair, "createKeyPair");
706
+ async function encrypt(publicKeyBase64, plaintext) {
707
+ const recipientPublicKey = await importPublicKey(publicKeyBase64);
708
+ const recipientPubKeyRaw = base64ToUint8(publicKeyBase64);
709
+ const ephemeralKeyPair = await subtle.generateKey(EC_ALGORITHM, true, ["deriveBits"]);
710
+ const ephemeralPubKeyRaw = new Uint8Array(await subtle.exportKey("raw", ephemeralKeyPair.publicKey));
711
+ const sharedSecretBits = await subtle.deriveBits(
712
+ { name: "ECDH", public: recipientPublicKey },
713
+ ephemeralKeyPair.privateKey,
714
+ 256
715
+ );
716
+ const sharedSecret = new Uint8Array(sharedSecretBits);
717
+ const info = concatBuffers(ephemeralPubKeyRaw, recipientPubKeyRaw);
718
+ const aesKey = await hkdfSha256(sharedSecret, HKDF_SALT, info, 32);
719
+ const nonce = webcrypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
720
+ const plaintextBytes = new TextEncoder().encode(plaintext);
721
+ const cryptoKey = await subtle.importKey("raw", bs(aesKey), "AES-GCM", false, ["encrypt"]);
722
+ const encrypted = new Uint8Array(
723
+ await subtle.encrypt({ name: "AES-GCM", iv: bs(nonce), tagLength: TAG_LENGTH * 8 }, cryptoKey, bs(plaintextBytes))
724
+ );
725
+ const ciphertext = encrypted.slice(0, encrypted.length - TAG_LENGTH);
726
+ const tag = encrypted.slice(encrypted.length - TAG_LENGTH);
727
+ const payload = concatBuffers(
728
+ new Uint8Array([PAYLOAD_VERSION]),
729
+ ephemeralPubKeyRaw,
730
+ nonce,
731
+ ciphertext,
732
+ tag
733
+ );
734
+ return bufferToBase64(payload);
735
+ }
736
+ __name(encrypt, "encrypt");
737
+ async function decrypt(privateKeyBase64, publicKeyBase64, ciphertextBase64) {
738
+ const payloadBytes = base64ToUint8(ciphertextBase64);
739
+ if (payloadBytes.byteLength < HEADER_LENGTH + TAG_LENGTH) {
740
+ throw new Error("Payload too short");
741
+ }
742
+ const version = payloadBytes[0];
743
+ if (version !== PAYLOAD_VERSION) {
744
+ throw new Error(`Unsupported payload version: ${version}`);
745
+ }
746
+ const ephemeralPubKeyRaw = payloadBytes.slice(1, 1 + PUBLIC_KEY_LENGTH);
747
+ const nonce = payloadBytes.slice(1 + PUBLIC_KEY_LENGTH, HEADER_LENGTH);
748
+ const ciphertextAndTag = payloadBytes.slice(HEADER_LENGTH);
749
+ if (ciphertextAndTag.length < TAG_LENGTH) {
750
+ throw new Error("Payload too short for tag");
751
+ }
752
+ const privateKey = await importPrivateKey(privateKeyBase64);
753
+ const ephemeralPublicKey = await subtle.importKey("raw", bs(ephemeralPubKeyRaw), EC_ALGORITHM, true, []);
754
+ const recipientPubKeyRaw = base64ToUint8(publicKeyBase64);
755
+ const sharedSecretBits = await subtle.deriveBits(
756
+ { name: "ECDH", public: ephemeralPublicKey },
757
+ privateKey,
758
+ 256
759
+ );
760
+ const sharedSecret = new Uint8Array(sharedSecretBits);
761
+ const info = concatBuffers(ephemeralPubKeyRaw, recipientPubKeyRaw);
762
+ const aesKey = await hkdfSha256(sharedSecret, HKDF_SALT, info, 32);
763
+ const cryptoKey = await subtle.importKey("raw", bs(aesKey), "AES-GCM", false, ["decrypt"]);
764
+ try {
765
+ const decrypted = await subtle.decrypt(
766
+ { name: "AES-GCM", iv: bs(nonce), tagLength: TAG_LENGTH * 8 },
767
+ cryptoKey,
768
+ bs(ciphertextAndTag)
769
+ // already ciphertext || tag
770
+ );
771
+ return new TextDecoder().decode(decrypted);
772
+ } catch (err) {
773
+ throw new Error(
774
+ "Unable to decrypt value",
775
+ { cause: err }
776
+ );
777
+ }
778
+ }
779
+ __name(decrypt, "decrypt");
780
+
781
+ // src/lib/local-encrypt/file-backend.ts
782
+ var KEY_STORE_SUBDIR = "local-encrypt/keys";
783
+ var DEFAULT_KEY_ID = "varlock-default";
784
+ function getKeyStorePath() {
785
+ return path.join(getUserVarlockDir(), KEY_STORE_SUBDIR);
786
+ }
787
+ __name(getKeyStorePath, "getKeyStorePath");
788
+ function getKeyFilePath(keyId) {
789
+ return path.join(getKeyStorePath(), `${keyId}.json`);
790
+ }
791
+ __name(getKeyFilePath, "getKeyFilePath");
792
+ function keyExists(keyId = DEFAULT_KEY_ID) {
793
+ return fs.existsSync(getKeyFilePath(keyId));
794
+ }
795
+ __name(keyExists, "keyExists");
796
+ async function generateKey(keyId = DEFAULT_KEY_ID) {
797
+ const keyPair = await createKeyPair();
798
+ const stored = {
799
+ keyId,
800
+ publicKey: keyPair.publicKey,
801
+ privateKey: keyPair.privateKey,
802
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
803
+ };
804
+ const keyStorePath = getKeyStorePath();
805
+ fs.mkdirSync(keyStorePath, { recursive: true });
806
+ const filePath = getKeyFilePath(keyId);
807
+ fs.writeFileSync(filePath, JSON.stringify(stored, null, 2), { mode: 384 });
808
+ return { keyId, publicKey: keyPair.publicKey };
809
+ }
810
+ __name(generateKey, "generateKey");
811
+ function loadKeyPair(keyId) {
812
+ const filePath = getKeyFilePath(keyId);
813
+ if (!fs.existsSync(filePath)) {
814
+ throw new Error(`Key not found: ${keyId}`);
815
+ }
816
+ const data = fs.readFileSync(filePath, "utf-8");
817
+ const parsed = JSON.parse(data);
818
+ const privateKey = parsed.privateKey ?? (parsed.protection === "none" ? parsed.protectedPrivateKey : void 0) ?? parsed.protectedPrivateKey;
819
+ if (!parsed.publicKey || !privateKey) {
820
+ throw new Error(`Invalid key file format for key: ${keyId}`);
821
+ }
822
+ return {
823
+ keyId: parsed.keyId || keyId,
824
+ publicKey: parsed.publicKey,
825
+ privateKey,
826
+ createdAt: parsed.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
827
+ protection: parsed.protection,
828
+ protectedPrivateKey: parsed.protectedPrivateKey
829
+ };
830
+ }
831
+ __name(loadKeyPair, "loadKeyPair");
832
+ function getPublicKey(keyId) {
833
+ return loadKeyPair(keyId).publicKey;
834
+ }
835
+ __name(getPublicKey, "getPublicKey");
836
+ async function encryptValue(plaintext, keyId = DEFAULT_KEY_ID) {
837
+ const publicKey = getPublicKey(keyId);
838
+ return encrypt(publicKey, plaintext);
839
+ }
840
+ __name(encryptValue, "encryptValue");
841
+ async function decryptValue(ciphertext, keyId = DEFAULT_KEY_ID) {
842
+ const stored = loadKeyPair(keyId);
843
+ return decrypt(stored.privateKey, stored.publicKey, ciphertext);
844
+ }
845
+ __name(decryptValue, "decryptValue");
846
+
847
+ // src/lib/local-encrypt/index.ts
848
+ var DEFAULT_KEY_ID2 = "varlock-default";
849
+ function debug3(msg) {
850
+ if (process.env.VARLOCK_DEBUG) {
851
+ process.stderr.write(`[varlock:local-encrypt] ${msg}
852
+ `);
853
+ }
854
+ }
855
+ __name(debug3, "debug");
856
+ var _cachedSessionId;
857
+ function getSelfSessionId() {
858
+ if (_cachedSessionId) return _cachedSessionId;
859
+ try {
860
+ const ttyPath = fs.readlinkSync("/proc/self/fd/0");
861
+ if (ttyPath && ttyPath.startsWith("/dev/")) {
862
+ _cachedSessionId = ttyPath;
863
+ return ttyPath;
864
+ }
865
+ } catch {
866
+ }
867
+ try {
868
+ const chain = [process.pid];
869
+ let current = process.pid;
870
+ for (let i = 0; i < 64; i++) {
871
+ const stat = fs.readFileSync(`/proc/${current}/stat`, "utf-8");
872
+ const fields = stat.split(") ");
873
+ if (fields.length < 2) break;
874
+ const ppid = parseInt(fields[1].split(" ")[1], 10);
875
+ if (!ppid || ppid <= 1) break;
876
+ chain.push(ppid);
877
+ current = ppid;
878
+ }
879
+ if (chain.length >= 4) {
880
+ const scopePid = chain[chain.length - 3];
881
+ let startTime = 0;
882
+ try {
883
+ const scopeStat = fs.readFileSync(`/proc/${scopePid}/stat`, "utf-8");
884
+ const scopeFields = scopeStat.split(") ");
885
+ if (scopeFields.length >= 2) {
886
+ startTime = parseInt(scopeFields[1].split(" ")[19], 10) || 0;
887
+ }
888
+ } catch {
889
+ }
890
+ _cachedSessionId = `ptree:${scopePid}:${startTime}`;
891
+ return _cachedSessionId;
892
+ }
893
+ } catch {
894
+ }
895
+ _cachedSessionId = `pid:${process.pid}`;
896
+ return _cachedSessionId;
897
+ }
898
+ __name(getSelfSessionId, "getSelfSessionId");
899
+ var _wslDaemonPrestartAttempted = false;
900
+ function toWindowsPathFromWsl(pathInWsl) {
901
+ if (!isWSL()) return void 0;
902
+ try {
903
+ return execFileSync("wslpath", ["-w", pathInWsl], {
904
+ encoding: "utf-8",
905
+ timeout: 1e4
906
+ }).trim();
907
+ } catch (err) {
908
+ debug3(`toWindowsPathFromWsl failed: ${err instanceof Error ? err.message : err}`);
909
+ return void 0;
910
+ }
911
+ }
912
+ __name(toWindowsPathFromWsl, "toWindowsPathFromWsl");
913
+ function tryPrestartWindowsDaemonFromWsl(binaryPath) {
914
+ if (_wslDaemonPrestartAttempted) {
915
+ return true;
916
+ }
917
+ const windowsPath = toWindowsPathFromWsl(binaryPath);
918
+ if (!windowsPath) {
919
+ return false;
920
+ }
921
+ const escapedPath = windowsPath.replaceAll("'", "''");
922
+ const psScript = `Start-Process -WindowStyle Hidden -FilePath '${escapedPath}' -ArgumentList 'start-daemon'`;
923
+ const proc = spawnSync("powershell.exe", [
924
+ "-NoProfile",
925
+ "-NonInteractive",
926
+ "-ExecutionPolicy",
927
+ "Bypass",
928
+ "-Command",
929
+ psScript
930
+ ], {
931
+ encoding: "utf-8",
932
+ timeout: 2e4
933
+ });
934
+ if (proc.error) {
935
+ debug3(`tryPrestartWindowsDaemonFromWsl: powershell error: ${proc.error.message}`);
936
+ return false;
937
+ }
938
+ if (proc.status !== 0) {
939
+ debug3(`tryPrestartWindowsDaemonFromWsl: powershell exit ${proc.status}: ${(proc.stderr || proc.stdout || "").trim()}`);
940
+ return false;
941
+ }
942
+ debug3("tryPrestartWindowsDaemonFromWsl: start-daemon invoked via PowerShell");
943
+ _wslDaemonPrestartAttempted = true;
944
+ return true;
945
+ }
946
+ __name(tryPrestartWindowsDaemonFromWsl, "tryPrestartWindowsDaemonFromWsl");
947
+ function pingWindowsDaemonFromWsl(binaryPath, timeoutMs = 2e3) {
948
+ const proc = spawnSync(binaryPath, ["ping-daemon"], {
949
+ encoding: "utf-8",
950
+ timeout: timeoutMs
951
+ });
952
+ if (proc.error || proc.status !== 0) {
953
+ return false;
954
+ }
955
+ try {
956
+ const parsed = JSON.parse((proc.stdout || "").trim());
957
+ return parsed.ready === true;
958
+ } catch {
959
+ return false;
960
+ }
961
+ }
962
+ __name(pingWindowsDaemonFromWsl, "pingWindowsDaemonFromWsl");
963
+ function waitForWindowsDaemonFromWsl(binaryPath, timeoutMs = 12e3) {
964
+ const deadline = Date.now() + timeoutMs;
965
+ while (Date.now() < deadline) {
966
+ if (pingWindowsDaemonFromWsl(binaryPath)) {
967
+ debug3("waitForWindowsDaemonFromWsl: daemon is ready");
968
+ return true;
969
+ }
970
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 150);
971
+ }
972
+ debug3("waitForWindowsDaemonFromWsl: timed out waiting for daemon readiness");
973
+ return false;
974
+ }
975
+ __name(waitForWindowsDaemonFromWsl, "waitForWindowsDaemonFromWsl");
976
+ function runNativeBinary(args, opts) {
977
+ const binaryPath = resolveNativeBinary();
978
+ if (!binaryPath) {
979
+ debug3("runNativeBinary: no binary found");
980
+ throw new Error("Native binary not found");
981
+ }
982
+ debug3(`runNativeBinary: ${binaryPath} ${args.join(" ")}`);
983
+ const output = execFileSync(binaryPath, args, {
984
+ encoding: "utf-8",
985
+ timeout: opts?.timeout ?? 3e4
986
+ }).trim();
987
+ debug3(`runNativeBinary result: ${output.slice(0, 200)}`);
988
+ return output;
989
+ }
990
+ __name(runNativeBinary, "runNativeBinary");
991
+ function runNativeBinaryJson(args, opts) {
992
+ const output = runNativeBinary(args, opts);
993
+ const parsed = JSON.parse(output);
994
+ if (parsed.error) {
995
+ throw new Error(parsed.error);
996
+ }
997
+ return parsed;
998
+ }
999
+ __name(runNativeBinaryJson, "runNativeBinaryJson");
1000
+ var cachedBackendInfo;
1001
+ var cachedStatusKeys;
1002
+ function detectBackendType() {
1003
+ const binaryPath = resolveNativeBinary();
1004
+ debug3(`detectBackendType: binaryPath=${binaryPath ?? "NOT FOUND"}, isWSL=${isWSL()}, platform=${process.platform}`);
1005
+ if (!binaryPath) {
1006
+ const isFileFallback = ["darwin", "win32", "linux"].includes(process.platform);
1007
+ return { type: "file", isFileFallback };
1008
+ }
1009
+ if (isWSL()) return { type: "windows-tpm", isFileFallback: false };
1010
+ switch (process.platform) {
1011
+ case "darwin":
1012
+ return { type: "secure-enclave", isFileFallback: false };
1013
+ case "win32":
1014
+ return { type: "windows-tpm", isFileFallback: false };
1015
+ case "linux":
1016
+ return { type: "linux-tpm", isFileFallback: false };
1017
+ default:
1018
+ return { type: "file", isFileFallback: false };
1019
+ }
1020
+ }
1021
+ __name(detectBackendType, "detectBackendType");
1022
+ function getBackendInfo() {
1023
+ if (cachedBackendInfo) return cachedBackendInfo;
1024
+ const { type, isFileFallback } = detectBackendType();
1025
+ const binaryPath = type !== "file" ? resolveNativeBinary() : void 0;
1026
+ if (type !== "file" && binaryPath) {
1027
+ try {
1028
+ const status = runNativeBinaryJson(["status"]);
1029
+ debug3(`getBackendInfo: status result: hardwareBacked=${status.hardwareBacked}, biometricAvailable=${status.biometricAvailable}, backend=${status.backend}, keys=${status.keys?.join(",")}`);
1030
+ cachedStatusKeys = status.keys;
1031
+ cachedBackendInfo = {
1032
+ type,
1033
+ platform: process.platform,
1034
+ hardwareBacked: status.hardwareBacked,
1035
+ biometricAvailable: status.biometricAvailable,
1036
+ binaryPath
1037
+ };
1038
+ } catch (err) {
1039
+ debug3(`getBackendInfo: status command failed: ${err instanceof Error ? err.message : err}`);
1040
+ cachedBackendInfo = {
1041
+ type,
1042
+ platform: process.platform,
1043
+ hardwareBacked: type === "secure-enclave",
1044
+ biometricAvailable: type === "secure-enclave",
1045
+ binaryPath
1046
+ };
1047
+ }
1048
+ } else {
1049
+ debug3(`getBackendInfo: using file backend (type=${type}, binaryPath=${binaryPath ?? "none"}, isFileFallback=${isFileFallback})`);
1050
+ if (isFileFallback && !process.env._VARLOCK_FORCE_FILE_ENCRYPTION_FALLBACK) {
1051
+ process.stderr.write(
1052
+ "[varlock] Warning: native encryption binary not found, falling back to file-based encryption (not hardware-backed)\n"
1053
+ );
1054
+ }
1055
+ cachedBackendInfo = {
1056
+ type,
1057
+ platform: process.platform,
1058
+ hardwareBacked: false,
1059
+ biometricAvailable: false,
1060
+ binaryPath: void 0,
1061
+ isFileFallback
1062
+ };
1063
+ }
1064
+ debug3(`getBackendInfo: final result: type=${cachedBackendInfo.type}, biometric=${cachedBackendInfo.biometricAvailable}, hwBacked=${cachedBackendInfo.hardwareBacked}`);
1065
+ return cachedBackendInfo;
1066
+ }
1067
+ __name(getBackendInfo, "getBackendInfo");
1068
+ var daemonClient;
1069
+ function getDaemonClient() {
1070
+ daemonClient ||= new DaemonClient();
1071
+ return daemonClient;
1072
+ }
1073
+ __name(getDaemonClient, "getDaemonClient");
1074
+ function keyExists2(keyId = DEFAULT_KEY_ID2) {
1075
+ const backend = getBackendInfo();
1076
+ if (backend.type === "file") {
1077
+ return keyExists(keyId);
1078
+ }
1079
+ if (cachedStatusKeys) {
1080
+ debug3(`keyExists: using cached status keys for ${keyId}`);
1081
+ return cachedStatusKeys.includes(keyId);
1082
+ }
1083
+ const result = runNativeBinaryJson(["key-exists", "--key-id", keyId]);
1084
+ return result.exists;
1085
+ }
1086
+ __name(keyExists2, "keyExists");
1087
+ async function generateKey2(keyId = DEFAULT_KEY_ID2) {
1088
+ const backend = getBackendInfo();
1089
+ if (backend.type === "file") {
1090
+ return generateKey(keyId);
1091
+ }
1092
+ return runNativeBinaryJson(["generate-key", "--key-id", keyId]);
1093
+ }
1094
+ __name(generateKey2, "generateKey");
1095
+ async function ensureKey(keyId = DEFAULT_KEY_ID2) {
1096
+ if (!keyExists2(keyId)) {
1097
+ await generateKey2(keyId);
1098
+ }
1099
+ }
1100
+ __name(ensureKey, "ensureKey");
1101
+ async function encryptValue2(plaintext, keyId = DEFAULT_KEY_ID2) {
1102
+ const backend = getBackendInfo();
1103
+ if (backend.type === "file") {
1104
+ return encryptValue(plaintext, keyId);
1105
+ }
1106
+ const b64Input = Buffer.from(plaintext, "utf-8").toString("base64");
1107
+ if (isWSL()) {
1108
+ const binaryPath = resolveNativeBinary();
1109
+ if (!binaryPath) throw new Error("Native binary not found");
1110
+ const proc = spawnSync(binaryPath, ["encrypt", "--key-id", keyId, "--data-stdin"], {
1111
+ input: b64Input,
1112
+ encoding: "utf-8",
1113
+ timeout: 3e4
1114
+ });
1115
+ if (proc.error) throw proc.error;
1116
+ const result2 = JSON.parse(proc.stdout.trim());
1117
+ if (result2.error) throw new Error(result2.error);
1118
+ return result2.ciphertext;
1119
+ }
1120
+ const result = runNativeBinaryJson(["encrypt", "--key-id", keyId, "--data", b64Input]);
1121
+ return result.ciphertext;
1122
+ }
1123
+ __name(encryptValue2, "encryptValue");
1124
+ async function decryptValue2(ciphertext, keyId = DEFAULT_KEY_ID2) {
1125
+ const backend = getBackendInfo();
1126
+ if (backend.type === "file") {
1127
+ debug3("decryptValue: using file backend");
1128
+ return decryptValue(ciphertext, keyId);
1129
+ }
1130
+ if (backend.biometricAvailable) {
1131
+ if (isWSL()) {
1132
+ debug3("decryptValue: WSL2 biometric decrypt via --via-daemon");
1133
+ const binaryPath = resolveNativeBinary();
1134
+ if (!binaryPath) throw new Error("Native binary not found");
1135
+ const daemonAlreadyReady = pingWindowsDaemonFromWsl(binaryPath, 1500);
1136
+ const daemonPrestarted = daemonAlreadyReady || tryPrestartWindowsDaemonFromWsl(binaryPath);
1137
+ if (!daemonAlreadyReady && daemonPrestarted) {
1138
+ waitForWindowsDaemonFromWsl(binaryPath);
1139
+ }
1140
+ const stdinPayload = JSON.stringify({
1141
+ data: ciphertext,
1142
+ ttyId: getSelfSessionId()
1143
+ });
1144
+ const runViaDaemon = /* @__PURE__ */ __name((timeout) => spawnSync(binaryPath, ["decrypt", "--key-id", keyId, "--data-stdin", "--via-daemon"], {
1145
+ input: stdinPayload,
1146
+ encoding: "utf-8",
1147
+ timeout
1148
+ }), "runViaDaemon");
1149
+ let proc = runViaDaemon(daemonPrestarted ? 12e4 : 6e4);
1150
+ const output = (proc.stdout || proc.stderr || "").trim();
1151
+ const timedOut = proc.error && proc.error.code === "ETIMEDOUT";
1152
+ const needsRetry = Boolean(proc.error) || proc.status !== 0;
1153
+ const likelyDaemonStartupIssue = timedOut || /daemon is not running|daemon did not become ready within timeout|schtasks|windows hello daemon/i.test(output);
1154
+ if (needsRetry && likelyDaemonStartupIssue) {
1155
+ debug3(`decryptValue: via-daemon startup issue detected; attempting native start-daemon bridge. output=${output.slice(0, 180)}`);
1156
+ if (tryPrestartWindowsDaemonFromWsl(binaryPath)) {
1157
+ proc = runViaDaemon(12e4);
1158
+ }
1159
+ }
1160
+ if (proc.error) throw proc.error;
1161
+ if (proc.status !== 0) {
1162
+ const finalOutput = (proc.stdout || proc.stderr || "").trim();
1163
+ try {
1164
+ const parsed = JSON.parse(finalOutput);
1165
+ if (parsed.error) throw new Error(parsed.error);
1166
+ } catch {
1167
+ }
1168
+ const windowsPath = toWindowsPathFromWsl(binaryPath);
1169
+ const setupHint = windowsPath ? `
1170
+ Hint: In native Windows PowerShell run:
1171
+ Start-Process -WindowStyle Hidden "${windowsPath}" start-daemon` : "";
1172
+ throw new Error(`Decrypt failed (exit ${proc.status}): ${finalOutput}${setupHint}`);
1173
+ }
1174
+ const result2 = JSON.parse(proc.stdout.trim());
1175
+ if (result2.error) throw new Error(result2.error);
1176
+ debug3(`decryptValue: WSL2 result: ${proc.stdout.trim().slice(0, 100)}`);
1177
+ return result2.plaintext;
1178
+ }
1179
+ debug3("decryptValue: biometric decrypt via daemon client");
1180
+ const client = getDaemonClient();
1181
+ return client.decrypt(ciphertext, keyId);
1182
+ }
1183
+ debug3("decryptValue: non-biometric one-shot decrypt");
1184
+ const result = runNativeBinaryJson(["decrypt", "--key-id", keyId, "--data", ciphertext]);
1185
+ return result.plaintext;
1186
+ }
1187
+ __name(decryptValue2, "decryptValue");
1188
+ async function lockSession() {
1189
+ const backend = getBackendInfo();
1190
+ if (!backend.biometricAvailable) return;
1191
+ const client = getDaemonClient();
1192
+ const connected = await client.tryConnect();
1193
+ if (!connected) {
1194
+ throw new Error("No encryption daemon is running");
1195
+ }
1196
+ await client.invalidateSession();
1197
+ }
1198
+ __name(lockSession, "lockSession");
1199
+
1200
+ export { decryptValue2 as decryptValue, encryptValue2 as encryptValue, ensureKey, getBackendInfo, getDaemonClient, lockSession };
1201
+ //# sourceMappingURL=chunk-GURKQO4J.js.map
1202
+ //# sourceMappingURL=chunk-GURKQO4J.js.map