deepadb 1.1.1 → 1.1.3

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 (230) hide show
  1. package/LICENSE +202 -21
  2. package/NOTICE +16 -0
  3. package/README.md +55 -29
  4. package/build/bridge/adb-bridge.d.ts +14 -0
  5. package/build/bridge/adb-bridge.d.ts.map +1 -1
  6. package/build/bridge/adb-bridge.js +34 -5
  7. package/build/bridge/adb-bridge.js.map +1 -1
  8. package/build/bridge/device-manager.d.ts.map +1 -1
  9. package/build/bridge/device-manager.js +18 -9
  10. package/build/bridge/device-manager.js.map +1 -1
  11. package/build/bridge/local-bridge.d.ts +10 -2
  12. package/build/bridge/local-bridge.d.ts.map +1 -1
  13. package/build/bridge/local-bridge.js +51 -16
  14. package/build/bridge/local-bridge.js.map +1 -1
  15. package/build/config/config.d.ts +7 -0
  16. package/build/config/config.d.ts.map +1 -1
  17. package/build/config/config.js +26 -10
  18. package/build/config/config.js.map +1 -1
  19. package/build/graphql-api.d.ts.map +1 -1
  20. package/build/graphql-api.js +73 -3
  21. package/build/graphql-api.js.map +1 -1
  22. package/build/http-transport.d.ts.map +1 -1
  23. package/build/http-transport.js +44 -7
  24. package/build/http-transport.js.map +1 -1
  25. package/build/index.d.ts.map +1 -1
  26. package/build/index.js +41 -16
  27. package/build/index.js.map +1 -1
  28. package/build/middleware/auth.d.ts.map +1 -1
  29. package/build/middleware/auth.js +43 -6
  30. package/build/middleware/auth.js.map +1 -1
  31. package/build/middleware/chipset.d.ts.map +1 -1
  32. package/build/middleware/chipset.js +16 -1
  33. package/build/middleware/chipset.js.map +1 -1
  34. package/build/middleware/cleanup.d.ts +39 -7
  35. package/build/middleware/cleanup.d.ts.map +1 -1
  36. package/build/middleware/cleanup.js +148 -16
  37. package/build/middleware/cleanup.js.map +1 -1
  38. package/build/middleware/fetch-utils.d.ts +50 -7
  39. package/build/middleware/fetch-utils.d.ts.map +1 -1
  40. package/build/middleware/fetch-utils.js +127 -14
  41. package/build/middleware/fetch-utils.js.map +1 -1
  42. package/build/middleware/fs-utils.d.ts +126 -0
  43. package/build/middleware/fs-utils.d.ts.map +1 -0
  44. package/build/middleware/fs-utils.js +229 -0
  45. package/build/middleware/fs-utils.js.map +1 -0
  46. package/build/middleware/logger.d.ts +16 -0
  47. package/build/middleware/logger.d.ts.map +1 -1
  48. package/build/middleware/logger.js +18 -0
  49. package/build/middleware/logger.js.map +1 -1
  50. package/build/middleware/output-processor.d.ts.map +1 -1
  51. package/build/middleware/output-processor.js +43 -8
  52. package/build/middleware/output-processor.js.map +1 -1
  53. package/build/middleware/parse-utils.d.ts +19 -0
  54. package/build/middleware/parse-utils.d.ts.map +1 -0
  55. package/build/middleware/parse-utils.js +30 -0
  56. package/build/middleware/parse-utils.js.map +1 -0
  57. package/build/middleware/png-utils.d.ts.map +1 -1
  58. package/build/middleware/png-utils.js +33 -11
  59. package/build/middleware/png-utils.js.map +1 -1
  60. package/build/middleware/result-handle.d.ts +76 -0
  61. package/build/middleware/result-handle.d.ts.map +1 -0
  62. package/build/middleware/result-handle.js +378 -0
  63. package/build/middleware/result-handle.js.map +1 -0
  64. package/build/middleware/sanitize.d.ts +26 -0
  65. package/build/middleware/sanitize.d.ts.map +1 -1
  66. package/build/middleware/sanitize.js +43 -2
  67. package/build/middleware/sanitize.js.map +1 -1
  68. package/build/middleware/security.d.ts.map +1 -1
  69. package/build/middleware/security.js +30 -5
  70. package/build/middleware/security.js.map +1 -1
  71. package/build/middleware/ui-dump.d.ts.map +1 -1
  72. package/build/middleware/ui-dump.js +30 -10
  73. package/build/middleware/ui-dump.js.map +1 -1
  74. package/build/server.d.ts.map +1 -1
  75. package/build/server.js +34 -5
  76. package/build/server.js.map +1 -1
  77. package/build/tool-context.d.ts.map +1 -1
  78. package/build/tool-context.js +2 -0
  79. package/build/tool-context.js.map +1 -1
  80. package/build/tools/accessibility.d.ts.map +1 -1
  81. package/build/tools/accessibility.js +39 -40
  82. package/build/tools/accessibility.js.map +1 -1
  83. package/build/tools/at-commands.d.ts.map +1 -1
  84. package/build/tools/at-commands.js +31 -6
  85. package/build/tools/at-commands.js.map +1 -1
  86. package/build/tools/baseband.d.ts.map +1 -1
  87. package/build/tools/baseband.js +18 -7
  88. package/build/tools/baseband.js.map +1 -1
  89. package/build/tools/build.d.ts.map +1 -1
  90. package/build/tools/build.js +30 -8
  91. package/build/tools/build.js.map +1 -1
  92. package/build/tools/ci.d.ts.map +1 -1
  93. package/build/tools/ci.js +16 -5
  94. package/build/tools/ci.js.map +1 -1
  95. package/build/tools/control.d.ts.map +1 -1
  96. package/build/tools/control.js +19 -11
  97. package/build/tools/control.js.map +1 -1
  98. package/build/tools/device-farm.d.ts.map +1 -1
  99. package/build/tools/device-farm.js +27 -3
  100. package/build/tools/device-farm.js.map +1 -1
  101. package/build/tools/device-profiles.d.ts.map +1 -1
  102. package/build/tools/device-profiles.js +70 -8
  103. package/build/tools/device-profiles.js.map +1 -1
  104. package/build/tools/device.d.ts.map +1 -1
  105. package/build/tools/device.js +4 -2
  106. package/build/tools/device.js.map +1 -1
  107. package/build/tools/diagnostics.d.ts.map +1 -1
  108. package/build/tools/diagnostics.js +17 -9
  109. package/build/tools/diagnostics.js.map +1 -1
  110. package/build/tools/emulator.d.ts.map +1 -1
  111. package/build/tools/emulator.js +12 -13
  112. package/build/tools/emulator.js.map +1 -1
  113. package/build/tools/files.d.ts +1 -1
  114. package/build/tools/files.d.ts.map +1 -1
  115. package/build/tools/files.js +98 -56
  116. package/build/tools/files.js.map +1 -1
  117. package/build/tools/firmware-analysis.d.ts.map +1 -1
  118. package/build/tools/firmware-analysis.js +35 -12
  119. package/build/tools/firmware-analysis.js.map +1 -1
  120. package/build/tools/forwarding.d.ts.map +1 -1
  121. package/build/tools/forwarding.js +248 -6
  122. package/build/tools/forwarding.js.map +1 -1
  123. package/build/tools/health.d.ts.map +1 -1
  124. package/build/tools/health.js +14 -3
  125. package/build/tools/health.js.map +1 -1
  126. package/build/tools/input-gestures.d.ts.map +1 -1
  127. package/build/tools/input-gestures.js +65 -13
  128. package/build/tools/input-gestures.js.map +1 -1
  129. package/build/tools/logcat-watch.d.ts.map +1 -1
  130. package/build/tools/logcat-watch.js +21 -14
  131. package/build/tools/logcat-watch.js.map +1 -1
  132. package/build/tools/logs.d.ts.map +1 -1
  133. package/build/tools/logs.js +8 -4
  134. package/build/tools/logs.js.map +1 -1
  135. package/build/tools/mirroring.d.ts.map +1 -1
  136. package/build/tools/mirroring.js +21 -19
  137. package/build/tools/mirroring.js.map +1 -1
  138. package/build/tools/multi-device.d.ts.map +1 -1
  139. package/build/tools/multi-device.js +10 -0
  140. package/build/tools/multi-device.js.map +1 -1
  141. package/build/tools/network-capture.d.ts.map +1 -1
  142. package/build/tools/network-capture.js +32 -8
  143. package/build/tools/network-capture.js.map +1 -1
  144. package/build/tools/network-discovery.d.ts.map +1 -1
  145. package/build/tools/network-discovery.js +11 -2
  146. package/build/tools/network-discovery.js.map +1 -1
  147. package/build/tools/ota-monitor.d.ts.map +1 -1
  148. package/build/tools/ota-monitor.js +26 -12
  149. package/build/tools/ota-monitor.js.map +1 -1
  150. package/build/tools/packages.d.ts.map +1 -1
  151. package/build/tools/packages.js +18 -11
  152. package/build/tools/packages.js.map +1 -1
  153. package/build/tools/plugins.d.ts +18 -0
  154. package/build/tools/plugins.d.ts.map +1 -1
  155. package/build/tools/plugins.js +47 -2
  156. package/build/tools/plugins.js.map +1 -1
  157. package/build/tools/prompts.d.ts.map +1 -1
  158. package/build/tools/prompts.js +2 -0
  159. package/build/tools/prompts.js.map +1 -1
  160. package/build/tools/qemu.d.ts +6 -3
  161. package/build/tools/qemu.d.ts.map +1 -1
  162. package/build/tools/qemu.js +28 -16
  163. package/build/tools/qemu.js.map +1 -1
  164. package/build/tools/registry.d.ts.map +1 -1
  165. package/build/tools/registry.js +76 -26
  166. package/build/tools/registry.js.map +1 -1
  167. package/build/tools/regression.d.ts +37 -0
  168. package/build/tools/regression.d.ts.map +1 -1
  169. package/build/tools/regression.js +63 -22
  170. package/build/tools/regression.js.map +1 -1
  171. package/build/tools/resources.d.ts.map +1 -1
  172. package/build/tools/resources.js +62 -15
  173. package/build/tools/resources.js.map +1 -1
  174. package/build/tools/result-handles.d.ts +37 -0
  175. package/build/tools/result-handles.d.ts.map +1 -0
  176. package/build/tools/result-handles.js +286 -0
  177. package/build/tools/result-handles.js.map +1 -0
  178. package/build/tools/ril-intercept.d.ts.map +1 -1
  179. package/build/tools/ril-intercept.js +15 -18
  180. package/build/tools/ril-intercept.js.map +1 -1
  181. package/build/tools/screen-record.d.ts.map +1 -1
  182. package/build/tools/screen-record.js +34 -5
  183. package/build/tools/screen-record.js.map +1 -1
  184. package/build/tools/screenshot-diff.d.ts.map +1 -1
  185. package/build/tools/screenshot-diff.js +29 -21
  186. package/build/tools/screenshot-diff.js.map +1 -1
  187. package/build/tools/selinux-audit.d.ts.map +1 -1
  188. package/build/tools/selinux-audit.js +7 -5
  189. package/build/tools/selinux-audit.js.map +1 -1
  190. package/build/tools/sensors.d.ts.map +1 -1
  191. package/build/tools/sensors.js +26 -4
  192. package/build/tools/sensors.js.map +1 -1
  193. package/build/tools/shell.d.ts.map +1 -1
  194. package/build/tools/shell.js +9 -0
  195. package/build/tools/shell.js.map +1 -1
  196. package/build/tools/snapshot.d.ts.map +1 -1
  197. package/build/tools/snapshot.js +38 -19
  198. package/build/tools/snapshot.js.map +1 -1
  199. package/build/tools/split-apk.d.ts.map +1 -1
  200. package/build/tools/split-apk.js +48 -24
  201. package/build/tools/split-apk.js.map +1 -1
  202. package/build/tools/test-gen.d.ts.map +1 -1
  203. package/build/tools/test-gen.js +29 -10
  204. package/build/tools/test-gen.js.map +1 -1
  205. package/build/tools/testing.d.ts.map +1 -1
  206. package/build/tools/testing.js +27 -9
  207. package/build/tools/testing.js.map +1 -1
  208. package/build/tools/thermal-power.d.ts +8 -1
  209. package/build/tools/thermal-power.d.ts.map +1 -1
  210. package/build/tools/thermal-power.js +35 -10
  211. package/build/tools/thermal-power.js.map +1 -1
  212. package/build/tools/ui.d.ts.map +1 -1
  213. package/build/tools/ui.js +20 -13
  214. package/build/tools/ui.js.map +1 -1
  215. package/build/tools/wireless-firmware.d.ts.map +1 -1
  216. package/build/tools/wireless-firmware.js +21 -7
  217. package/build/tools/wireless-firmware.js.map +1 -1
  218. package/build/tools/wireless.d.ts.map +1 -1
  219. package/build/tools/wireless.js +39 -3
  220. package/build/tools/wireless.js.map +1 -1
  221. package/build/tools/workflow-market.d.ts.map +1 -1
  222. package/build/tools/workflow-market.js +67 -13
  223. package/build/tools/workflow-market.js.map +1 -1
  224. package/build/tools/workflow.d.ts.map +1 -1
  225. package/build/tools/workflow.js +77 -8
  226. package/build/tools/workflow.js.map +1 -1
  227. package/build/ws-transport.d.ts.map +1 -1
  228. package/build/ws-transport.js +21 -3
  229. package/build/ws-transport.js.map +1 -1
  230. package/package.json +20 -8
@@ -1,20 +1,53 @@
1
+ // Copyright 2026 Jason <fullread@github>
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  /**
2
4
  * Process Cleanup Registry — Centralized signal handler for child process cleanup.
3
5
  *
4
- * Multiple modules (logcat-watch, ril-intercept, mirroring) spawn long-running
5
- * child processes that must be killed on server exit. Instead of each module
6
- * independently registering SIGINT/SIGTERM/exit handlers (which creates
7
- * ordering dependencies and redundant process.exit() calls), all modules
8
- * register their cleanup functions here.
6
+ * Multiple modules (logcat-watch, ril-intercept, mirroring, emulator, qemu)
7
+ * spawn long-running child processes that must be killed on server exit.
8
+ * Instead of each module independently registering SIGINT/SIGTERM/exit
9
+ * handlers (which creates ordering dependencies and redundant
10
+ * process.exit() calls), all modules register their cleanup functions here.
9
11
  *
10
12
  * One set of signal handlers runs all registered cleanups in order.
13
+ *
14
+ * Cleanup functions may return a Promise; the SIGINT/SIGTERM handlers
15
+ * await them. The synchronous 'exit' handler is best-effort — it fires
16
+ * the cleanups but cannot wait for async work to complete.
11
17
  */
12
18
  const registry = new Map();
13
19
  let registered = false;
14
- function runAllCleanups() {
15
- for (const [, fn] of registry) {
20
+ async function runAllCleanups() {
21
+ for (const [key, fn] of registry) {
22
+ try {
23
+ await fn();
24
+ }
25
+ catch (err) {
26
+ // K7 fix: log the failing cleanup's key + error to stderr so
27
+ // operators can diagnose process-zombification (the previous
28
+ // empty catch made this invisible).
29
+ const detail = err instanceof Error ? err.message : String(err);
30
+ process.stderr.write(`[cleanup] "${key}" failed: ${detail}\n`);
31
+ }
32
+ }
33
+ }
34
+ let warnedAsyncOnExit = false;
35
+ function runAllCleanupsSync() {
36
+ // Used by the 'exit' event handler, which cannot await. Async cleanups
37
+ // are still invoked but their promises are discarded — best-effort.
38
+ // K4 fix: log a one-shot stderr warning the first time we discard an
39
+ // async cleanup so developers see "this cleanup may be cut short on
40
+ // abnormal exit; for guaranteed completion route shutdown through
41
+ // SIGINT or SIGTERM."
42
+ for (const [key, fn] of registry) {
16
43
  try {
17
- fn();
44
+ const ret = fn();
45
+ if (ret && typeof ret.then === "function") {
46
+ if (!warnedAsyncOnExit) {
47
+ warnedAsyncOnExit = true;
48
+ process.stderr.write(`[cleanup] async cleanup "${key}" invoked from 'exit' handler — promise discarded. Async work may be cut short on abnormal exit; SIGINT/SIGTERM awaits all cleanups.\n`);
49
+ }
50
+ }
18
51
  }
19
52
  catch {
20
53
  // Cleanup must not throw during shutdown
@@ -25,20 +58,50 @@ function ensureHandlers() {
25
58
  if (registered)
26
59
  return;
27
60
  registered = true;
28
- process.on("exit", runAllCleanups);
29
- process.on("SIGINT", () => {
30
- runAllCleanups();
31
- process.exit(0);
61
+ process.on("exit", runAllCleanupsSync);
62
+ // K3 fix: SIGINT/SIGTERM handlers exit with the conventional Unix
63
+ // signal-based code (128 + signum) instead of always 0, so wrapper
64
+ // scripts checking $? can distinguish "completed normally" from
65
+ // "interrupted by user".
66
+ // SIGINT = signum 2 → exit 130
67
+ // SIGTERM = signum 15 → exit 143
68
+ process.on("SIGINT", async () => {
69
+ await runAllCleanups();
70
+ process.exit(130);
71
+ });
72
+ process.on("SIGTERM", async () => {
73
+ await runAllCleanups();
74
+ process.exit(143);
75
+ });
76
+ // K1 fix: cover additional shutdown paths that previously leaked child
77
+ // processes — SIGHUP (terminal disconnect), SIGBREAK (Windows Ctrl+Break),
78
+ // and catastrophic JS errors (uncaughtException, unhandledRejection).
79
+ // Without these, closing a parent terminal without Ctrl+C left orphan
80
+ // emulator/scrcpy/logcat children. Process.on for unsupported signals on
81
+ // a given platform is a no-op, so this is safe cross-platform.
82
+ process.on("SIGHUP", async () => {
83
+ await runAllCleanups();
84
+ process.exit(129); // 128 + SIGHUP(1)
32
85
  });
33
- process.on("SIGTERM", () => {
34
- runAllCleanups();
35
- process.exit(0);
86
+ process.on("SIGBREAK", async () => {
87
+ await runAllCleanups();
88
+ process.exit(149); // 128 + SIGBREAK(21) on Windows
89
+ });
90
+ process.on("uncaughtException", async (err) => {
91
+ process.stderr.write(`[cleanup] uncaughtException: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
92
+ await runAllCleanups();
93
+ process.exit(1);
94
+ });
95
+ process.on("unhandledRejection", async (reason) => {
96
+ process.stderr.write(`[cleanup] unhandledRejection: ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}\n`);
97
+ await runAllCleanups();
98
+ process.exit(1);
36
99
  });
37
100
  }
38
101
  /**
39
102
  * Register a cleanup function that will be called on process exit/SIGINT/SIGTERM.
40
103
  * Each module should register under a unique key. Re-registering the same key
41
- * replaces the previous function.
104
+ * replaces the previous function. The function may return a Promise.
42
105
  */
43
106
  export function registerCleanup(key, fn) {
44
107
  ensureHandlers();
@@ -50,4 +113,73 @@ export function registerCleanup(key, fn) {
50
113
  export function unregisterCleanup(key) {
51
114
  registry.delete(key);
52
115
  }
116
+ /**
117
+ * Kill a child process with two-stage SIGTERM → SIGKILL fallback.
118
+ *
119
+ * Sends SIGTERM, waits up to `graceMs` for the child to exit voluntarily,
120
+ * then escalates to SIGKILL if it's still running. Resolves when the
121
+ * child has exited OR after the SIGKILL has been sent.
122
+ *
123
+ * This closes audit findings AC3 (emulator) and AK8 (mirroring) — and
124
+ * matches the same hazard in logcat-watch.ts and ril-intercept.ts where
125
+ * a bare `proc.kill()` could leave a hung child orphaned if it ignored
126
+ * SIGTERM (common with mid-snapshot Android emulators, hung video pipes
127
+ * in scrcpy, and stuck logcat readers on a wedged device).
128
+ *
129
+ * Default `graceMs` = 1500: enough for a healthy child to clean up,
130
+ * short enough that shutdown stays responsive.
131
+ *
132
+ * Safe to call on an already-exited child (no-op). Safe to call from
133
+ * SIGINT/SIGTERM contexts via async cleanups registered with
134
+ * `registerCleanup` — the registry awaits each cleanup before exiting.
135
+ */
136
+ export async function gracefulKill(child, graceMs = 1500) {
137
+ // No-op if already dead or never started.
138
+ if (!child.pid || child.exitCode !== null || child.signalCode !== null) {
139
+ return;
140
+ }
141
+ // Stage 1: Send SIGTERM.
142
+ try {
143
+ child.kill("SIGTERM");
144
+ }
145
+ catch {
146
+ // Child may have died between the check and the kill — treat as success.
147
+ return;
148
+ }
149
+ // Wait for the child to exit, OR for graceMs to elapse.
150
+ const exited = await new Promise((resolve) => {
151
+ let resolved = false;
152
+ const finish = (didExit) => {
153
+ if (resolved)
154
+ return;
155
+ resolved = true;
156
+ clearTimeout(timer);
157
+ child.off("exit", onExit);
158
+ resolve(didExit);
159
+ };
160
+ const onExit = () => finish(true);
161
+ const timer = setTimeout(() => finish(false), graceMs);
162
+ child.once("exit", onExit);
163
+ });
164
+ // Stage 2: If still alive, escalate to SIGKILL.
165
+ if (!exited) {
166
+ try {
167
+ child.kill("SIGKILL");
168
+ }
169
+ catch {
170
+ // Child raced us — already dead.
171
+ }
172
+ }
173
+ }
174
+ /**
175
+ * Convenience: gracefully kill multiple children in parallel.
176
+ * All children get SIGTERM concurrently; each gets SIGKILL after graceMs
177
+ * if still running. Resolves when all have settled.
178
+ */
179
+ export async function gracefulKillAll(children, graceMs = 1500) {
180
+ const tasks = [];
181
+ for (const c of children)
182
+ tasks.push(gracefulKill(c, graceMs));
183
+ await Promise.all(tasks);
184
+ }
53
185
  //# sourceMappingURL=cleanup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/middleware/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,QAAQ,GAA2B,IAAI,GAAG,EAAE,CAAC;AACnD,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB,SAAS,cAAc;IACrB,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,EAAE,EAAE,CAAC;QACP,CAAC;QAAC,MAAM,CAAC;YACP,yCAAyC;QAC3C,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,cAAc;IACrB,IAAI,UAAU;QAAE,OAAO;IACvB,UAAU,GAAG,IAAI,CAAC;IAElB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,cAAc,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,cAAc,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,EAAa;IACxD,cAAc,EAAE,CAAC;IACjB,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/middleware/cleanup.ts"],"names":[],"mappings":"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"}
@@ -1,19 +1,62 @@
1
1
  /**
2
- * Fetch Utilities — Shared HTTP helpers with response size limits.
2
+ * Fetch Utilities — Shared HTTP helpers with protocol, timeout, redirect, and size limits.
3
3
  *
4
- * Used by the plugin registry, workflow marketplace, and any module
5
- * that fetches external resources. Enforces a maximum response body
6
- * size to prevent memory exhaustion from oversized or malicious responses.
4
+ * Used by the plugin registry, workflow marketplace, and any module that
5
+ * fetches external resources. Layered defenses against malicious or
6
+ * misconfigured external sources:
7
+ * - Protocol allowlist: https:// only (no http://, data:, file://, etc.).
8
+ * Validated on the INITIAL url AND on every redirect target. Defends
9
+ * against SSRF (file://, http://internal.rfc1918), plaintext credential
10
+ * leak (http://), and content injection via local-file or inline-data URLs.
11
+ * - Manual redirect handling: redirects are NOT auto-followed by the
12
+ * underlying fetch — we follow them ourselves with per-hop protocol
13
+ * re-validation and a 5-hop cap. Defends against a compromised or
14
+ * hijacked initial endpoint that 302-redirects to http://attacker.com
15
+ * or file:///etc/passwd, which the default `redirect: "follow"` would
16
+ * traverse silently and bypass the initial protocol check.
17
+ * - Request timeout: 30 seconds via AbortSignal.timeout() — shared across
18
+ * redirect hops, header arrival, AND body read. Total operation is
19
+ * bounded by one 30s window, not 30s × hops. Defends against slow-loris
20
+ * servers and slow-drip redirect chains.
21
+ * - Response size cap: 5 MB streaming check. Defends against memory
22
+ * exhaustion from oversized or pathological responses, even when the
23
+ * server omits Content-Length.
24
+ *
25
+ * Tests that need to exercise post-fetch parsing logic should mock
26
+ * fetchText/fetchJson at the function level rather than feeding non-https
27
+ * URLs through the real fetcher. Redirect-following behavior is validated
28
+ * during manual audit at release prep (per the test-supply-chain.mjs convention).
29
+ */
30
+ /**
31
+ * Throw if the URL does not begin with `https://`. Applied to the initial
32
+ * URL and re-applied to every redirect target so a `Location: http://...`
33
+ * header cannot bypass the protocol allowlist.
34
+ *
35
+ * Exported for unit testing.
7
36
  */
37
+ /**
38
+ * H1 note: case-sensitivity is INTENTIONAL. RFC 3986 says scheme is
39
+ * case-insensitive, so technically `HTTPS://example.com` is valid. But
40
+ * any URL produced by a real client library normalizes to lowercase, so
41
+ * an uppercase scheme in practice signals either (a) a hand-typed URL by
42
+ * an operator (the strict check catches typos), or (b) an adversarial
43
+ * input trying to bypass an allowlist check by using a non-canonical
44
+ * form. Strict-lowercase is the safer choice for an allowlist gate.
45
+ */
46
+ export declare function requireHttps(url: string, context: string): void;
8
47
  /**
9
48
  * Fetch a URL and parse the response as JSON.
10
- * Enforces a response size limit to prevent memory exhaustion.
49
+ * Enforces protocol, timeout, redirect, and size limits via fetchText().
11
50
  */
12
51
  export declare function fetchJson(url: string): Promise<unknown>;
13
52
  /**
14
53
  * Fetch a URL and return the response body as text.
15
- * Enforces a response size limit via streaming to prevent memory exhaustion,
16
- * even when the server omits the Content-Length header.
54
+ *
55
+ * Enforces:
56
+ * - https:// protocol on the INITIAL url AND every redirect target
57
+ * - At most 5 redirect hops (fail-closed beyond)
58
+ * - 30s total timeout via shared AbortSignal (covers hops + body read)
59
+ * - 5 MB body size cap via streaming size check
17
60
  */
18
61
  export declare function fetchText(url: string): Promise<string>;
19
62
  //# sourceMappingURL=fetch-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-utils.d.ts","sourceRoot":"","sources":["../../src/middleware/fetch-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;;GAGG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG7D;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2C5D"}
1
+ {"version":3,"file":"fetch-utils.d.ts","sourceRoot":"","sources":["../../src/middleware/fetch-utils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAcH;;;;;;GAMG;AACH;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAM/D;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAY7D;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoG5D"}
@@ -1,37 +1,143 @@
1
+ // Copyright 2026 Jason <fullread@github>
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  /**
2
- * Fetch Utilities — Shared HTTP helpers with response size limits.
4
+ * Fetch Utilities — Shared HTTP helpers with protocol, timeout, redirect, and size limits.
3
5
  *
4
- * Used by the plugin registry, workflow marketplace, and any module
5
- * that fetches external resources. Enforces a maximum response body
6
- * size to prevent memory exhaustion from oversized or malicious responses.
6
+ * Used by the plugin registry, workflow marketplace, and any module that
7
+ * fetches external resources. Layered defenses against malicious or
8
+ * misconfigured external sources:
9
+ * - Protocol allowlist: https:// only (no http://, data:, file://, etc.).
10
+ * Validated on the INITIAL url AND on every redirect target. Defends
11
+ * against SSRF (file://, http://internal.rfc1918), plaintext credential
12
+ * leak (http://), and content injection via local-file or inline-data URLs.
13
+ * - Manual redirect handling: redirects are NOT auto-followed by the
14
+ * underlying fetch — we follow them ourselves with per-hop protocol
15
+ * re-validation and a 5-hop cap. Defends against a compromised or
16
+ * hijacked initial endpoint that 302-redirects to http://attacker.com
17
+ * or file:///etc/passwd, which the default `redirect: "follow"` would
18
+ * traverse silently and bypass the initial protocol check.
19
+ * - Request timeout: 30 seconds via AbortSignal.timeout() — shared across
20
+ * redirect hops, header arrival, AND body read. Total operation is
21
+ * bounded by one 30s window, not 30s × hops. Defends against slow-loris
22
+ * servers and slow-drip redirect chains.
23
+ * - Response size cap: 5 MB streaming check. Defends against memory
24
+ * exhaustion from oversized or pathological responses, even when the
25
+ * server omits Content-Length.
26
+ *
27
+ * Tests that need to exercise post-fetch parsing logic should mock
28
+ * fetchText/fetchJson at the function level rather than feeding non-https
29
+ * URLs through the real fetcher. Redirect-following behavior is validated
30
+ * during manual audit at release prep (per the test-supply-chain.mjs convention).
7
31
  */
8
32
  /** Maximum response body size in bytes (5 MB). */
9
33
  const MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
34
+ /** Maximum time to wait for the entire fetch operation, in milliseconds (30 seconds).
35
+ * Covers initial request, all redirect hops, header arrival, and body read. */
36
+ const FETCH_TIMEOUT_MS = 30_000;
37
+ /** Maximum number of redirect hops to follow before failing closed.
38
+ * Matches the typical 5-hop limits in browsers (curl defaults to 50,
39
+ * but for a supply-chain fetch we want tight bounds). */
40
+ const MAX_REDIRECTS = 5;
41
+ /**
42
+ * Throw if the URL does not begin with `https://`. Applied to the initial
43
+ * URL and re-applied to every redirect target so a `Location: http://...`
44
+ * header cannot bypass the protocol allowlist.
45
+ *
46
+ * Exported for unit testing.
47
+ */
48
+ /**
49
+ * H1 note: case-sensitivity is INTENTIONAL. RFC 3986 says scheme is
50
+ * case-insensitive, so technically `HTTPS://example.com` is valid. But
51
+ * any URL produced by a real client library normalizes to lowercase, so
52
+ * an uppercase scheme in practice signals either (a) a hand-typed URL by
53
+ * an operator (the strict check catches typos), or (b) an adversarial
54
+ * input trying to bypass an allowlist check by using a non-canonical
55
+ * form. Strict-lowercase is the safer choice for an allowlist gate.
56
+ */
57
+ export function requireHttps(url, context) {
58
+ if (!url.startsWith("https://")) {
59
+ throw new Error(`Refusing to fetch non-https URL: ${url} (${context}). Only https:// URLs are permitted.`);
60
+ }
61
+ }
10
62
  /**
11
63
  * Fetch a URL and parse the response as JSON.
12
- * Enforces a response size limit to prevent memory exhaustion.
64
+ * Enforces protocol, timeout, redirect, and size limits via fetchText().
13
65
  */
14
66
  export async function fetchJson(url) {
15
67
  const text = await fetchText(url);
16
- return JSON.parse(text);
68
+ try {
69
+ return JSON.parse(text);
70
+ }
71
+ catch (err) {
72
+ // H2 fix: wrap JSON parse errors with the URL context so a misconfigured
73
+ // registry returning an HTML error page produces "Invalid JSON from
74
+ // https://... — Unexpected token <" instead of a bare parse error with
75
+ // no indication of which fetch failed.
76
+ const detail = err instanceof Error ? err.message : String(err);
77
+ throw new Error(`Invalid JSON from ${url} — ${detail}`, { cause: err });
78
+ }
17
79
  }
18
80
  /**
19
81
  * Fetch a URL and return the response body as text.
20
- * Enforces a response size limit via streaming to prevent memory exhaustion,
21
- * even when the server omits the Content-Length header.
82
+ *
83
+ * Enforces:
84
+ * - https:// protocol on the INITIAL url AND every redirect target
85
+ * - At most 5 redirect hops (fail-closed beyond)
86
+ * - 30s total timeout via shared AbortSignal (covers hops + body read)
87
+ * - 5 MB body size cap via streaming size check
22
88
  */
23
89
  export async function fetchText(url) {
24
- const response = await fetch(url);
90
+ requireHttps(url, "initial URL");
91
+ // Share one AbortSignal across all redirect hops, header arrival, and body
92
+ // read. The 30s budget is for the entire operation — a chain of slow-drip
93
+ // redirects cannot stack up to 30s × hops.
94
+ const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
95
+ // Manual redirect handling so we can re-validate the protocol of every
96
+ // hop. The default `redirect: "follow"` would silently traverse a
97
+ // https→http or https→file redirect from a compromised initial endpoint,
98
+ // defeating the protocol allowlist entirely.
99
+ let currentUrl = url;
100
+ let response = null;
101
+ for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
102
+ const r = await fetch(currentUrl, { signal, redirect: "manual" });
103
+ // 304 Not Modified is a 3xx that is NOT a redirect — treat as terminal.
104
+ if (r.status >= 300 && r.status < 400 && r.status !== 304) {
105
+ const location = r.headers.get("location");
106
+ if (!location) {
107
+ // 3xx without Location is unusual but possible (e.g., 305 Use Proxy
108
+ // without a Location header). Treat as a terminal response so the
109
+ // caller sees the raw status.
110
+ response = r;
111
+ break;
112
+ }
113
+ // Drain the redirect response body to free the connection eagerly.
114
+ r.body?.cancel().catch(() => { });
115
+ if (hop === MAX_REDIRECTS) {
116
+ throw new Error(`Too many redirects (max ${MAX_REDIRECTS}) starting from ${url}`);
117
+ }
118
+ // Resolve relative URLs against the current URL, then re-validate.
119
+ currentUrl = new URL(location, currentUrl).href;
120
+ requireHttps(currentUrl, `redirect hop ${hop + 1} from ${url}`);
121
+ continue;
122
+ }
123
+ response = r;
124
+ break;
125
+ }
126
+ if (!response) {
127
+ // Unreachable in practice — the loop either sets response or throws.
128
+ throw new Error(`Failed to fetch ${url}: no response obtained`);
129
+ }
25
130
  if (!response.ok) {
26
- throw new Error(`HTTP ${response.status}: ${response.statusText} — ${url}`);
131
+ throw new Error(`HTTP ${response.status}: ${response.statusText} — ${currentUrl}`);
27
132
  }
28
- // Fast-reject via Content-Length header if available
133
+ // Fast-reject via Content-Length header if available.
29
134
  const contentLength = response.headers.get("content-length");
30
135
  if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_BYTES) {
31
136
  throw new Error(`Response too large: ${contentLength} bytes (max ${MAX_RESPONSE_BYTES})`);
32
137
  }
33
- // Stream-read the body with incremental size checking.
34
- // This prevents memory exhaustion even when Content-Length is absent.
138
+ // Stream-read the body with incremental size checking. The AbortSignal
139
+ // attached to fetch above also applies to body reads, so a slow-drip server
140
+ // will trip the 30s timeout here rather than hanging until MAX_RESPONSE_BYTES.
35
141
  if (response.body) {
36
142
  const reader = response.body.getReader();
37
143
  const chunks = [];
@@ -54,7 +160,14 @@ export async function fetchText(url) {
54
160
  }
55
161
  return new TextDecoder().decode(Buffer.concat(chunks));
56
162
  }
57
- // Fallback for environments where response.body is null
163
+ // Fallback for environments where response.body is null (extremely rare
164
+ // in modern Node). H4 audit note: the function-level Content-Length
165
+ // pre-check at the top of this function already rejects declared-oversize
166
+ // bodies BEFORE we get here. The remaining unguarded case is a chunked
167
+ // response with no Content-Length AND null response.body — buffer below
168
+ // and post-read length check is the best we can do without a streaming
169
+ // body. Accept the unbounded read for this rare path; the post-check
170
+ // still catches the result.
58
171
  const text = await response.text();
59
172
  if (text.length > MAX_RESPONSE_BYTES) {
60
173
  throw new Error(`Response body too large: ${text.length} characters (max ${MAX_RESPONSE_BYTES})`);
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-utils.js","sourceRoot":"","sources":["../../src/middleware/fetch-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,kDAAkD;AAClD,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,MAAM,GAAG,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,qDAAqD;IACrD,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC7D,IAAI,aAAa,IAAI,QAAQ,CAAC,aAAa,EAAE,EAAE,CAAC,GAAG,kBAAkB,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,uBAAuB,aAAa,eAAe,kBAAkB,GAAG,CAAC,CAAC;IAC5F,CAAC;IAED,uDAAuD;IACvD,sEAAsE;IACtE,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QACzC,MAAM,MAAM,GAAiB,EAAE,CAAC;QAChC,IAAI,UAAU,GAAG,CAAC,CAAC;QAEnB,IAAI,CAAC;YACH,OAAO,IAAI,EAAE,CAAC;gBACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5C,IAAI,IAAI;oBAAE,MAAM;gBAChB,UAAU,IAAI,KAAK,CAAC,MAAM,CAAC;gBAC3B,IAAI,UAAU,GAAG,kBAAkB,EAAE,CAAC;oBACpC,MAAM,CAAC,MAAM,EAAE,CAAC;oBAChB,MAAM,IAAI,KAAK,CAAC,qCAAqC,kBAAkB,uBAAuB,CAAC,CAAC;gBAClG,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;QAED,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,wDAAwD;IACxD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,IAAI,CAAC,MAAM,GAAG,kBAAkB,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,MAAM,oBAAoB,kBAAkB,GAAG,CAAC,CAAC;IACpG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"fetch-utils.js","sourceRoot":"","sources":["../../src/middleware/fetch-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Filesystem Utilities - Shared helpers for safe on-disk state.
3
+ *
4
+ * Four primitives used across DeepADB tempDir-backed storage:
5
+ *
6
+ * 1. ensurePrivateDir(path) - Create a directory with restrictive
7
+ * permissions (0o700 on Unix; mode bits ignored on Windows). Use
8
+ * everywhere we create a subdirectory under tempDir. Defends against
9
+ * other-user reads on shared Unix hosts.
10
+ *
11
+ * 2. writeAtomicSync(path, data) - Write via a unique-per-call .tmp
12
+ * path then atomic rename. Defends against (a) mid-write crashes
13
+ * leaving corrupt data on disk, (b) concurrent readers seeing a
14
+ * partially-written file, (c) concurrent writers to the same path
15
+ * racing on .tmp filenames.
16
+ *
17
+ * 3. sanitizeFilenameComponent(name, maxLen?) - Replace any character
18
+ * outside [a-zA-Z0-9_-] with underscore. Optional maxLen truncates
19
+ * the result. Empty results are coerced to "_" so the caller never
20
+ * receives a zero-length component. Centralizes the pattern used
21
+ * across several tool modules for user-supplied identifier strings.
22
+ *
23
+ * 4. sanitizeFilenameComponentDotted(name, maxLen?) - Like (3) but
24
+ * ALSO allows "." in the output (for package names like
25
+ * "com.example.app"). Defangs pure-dot strings (".", "..", "...")
26
+ * by coercing them to "_", so a caller using the result directly
27
+ * as a path component cannot be steered into parent-directory
28
+ * traversal by an adversarial input.
29
+ *
30
+ * Note on existing directories: ensurePrivateDir only sets mode on
31
+ * directories it CREATES. Directories that already exist (from older
32
+ * DeepADB versions) keep their existing permissions. To remediate, the
33
+ * operator can chmod 700 the affected directories manually, or just
34
+ * delete them so they are recreated with the new mode.
35
+ */
36
+ /** Create directory with owner-only permissions. Idempotent. */
37
+ export declare function ensurePrivateDir(path: string): void;
38
+ /** Atomic write: unique .tmp + rename. Throws on failure (caller handles). */
39
+ export declare function writeAtomicSync(path: string, data: string | Buffer): void;
40
+ /**
41
+ * Sanitize a string for use as a filename component. Replaces every
42
+ * character outside [a-zA-Z0-9_-] with "_".
43
+ *
44
+ * Safe against path traversal because `.`, `/`, and `\\` are all outside
45
+ * the allowlist (they become `_`). For cases where rejection is preferable
46
+ * to silent replacement, validate the raw input against /^[a-zA-Z0-9_-]+$/
47
+ * before calling tools.
48
+ *
49
+ * @param name Raw user-supplied identifier.
50
+ * @param maxLen Optional length cap applied after sanitization. Callers
51
+ * that build long paths (Windows MAX_PATH-sensitive) should
52
+ * always pass this.
53
+ * @returns Sanitized string, guaranteed non-empty (empty input collapses
54
+ * to "_" so callers never receive a zero-length component).
55
+ */
56
+ export declare function sanitizeFilenameComponent(name: string, maxLen?: number): string;
57
+ /**
58
+ * Like sanitizeFilenameComponent but ALSO allows "." in the output.
59
+ * Intended for Android-style package identifiers ("com.example.app") and
60
+ * similar dotted names where stripping dots would damage the identifier.
61
+ *
62
+ * Pure-dot strings (".", "..", "...", etc.) are coerced to "_" so a
63
+ * caller that uses the result directly as a path component cannot be
64
+ * steered into parent-directory traversal by an adversarial input.
65
+ *
66
+ * @param name Raw user-supplied dotted identifier (e.g., package name).
67
+ * @param maxLen Optional length cap applied after sanitization.
68
+ * @returns Sanitized string, guaranteed non-empty AND not a pure-dot
69
+ * traversal sequence.
70
+ */
71
+ /**
72
+ * A8 note: a LEADING dot is preserved (only character-class filtering
73
+ * applies — the dot is in the allowed set). On Unix, a result like
74
+ * `.hidden` becomes an actual hidden file in the result-handle store and
75
+ * other directory listings. This is intentional under the design (callers
76
+ * who want to allow package names like com.example.app need the dot), but
77
+ * means operators browsing the temp directory should pass `ls -A` not
78
+ * bare `ls` to see all entries. If a future caller wants the no-leading-
79
+ * dot behavior, it should sanitize the input before calling this helper.
80
+ */
81
+ export declare function sanitizeFilenameComponentDotted(name: string, maxLen?: number): string;
82
+ /**
83
+ * Returns true iff `candidatePath`, after path resolution, is contained
84
+ * within `dir` — either equal to `dir` or strictly underneath it.
85
+ *
86
+ * Containment uses a path-separator boundary check, NOT a string prefix.
87
+ * Without the boundary, `candidatePath = /parent/tempDir_evil/x` would
88
+ * pass containment against `dir = /parent/tempDir` because the absolute
89
+ * path string-prefix-matches — even though the two are sibling directories.
90
+ *
91
+ * Both POSIX `/` and Windows `\\` separators are recognized so a candidate
92
+ * resolved on either platform is handled correctly.
93
+ *
94
+ * Usage:
95
+ * if (!isWithinDir(userOutputDir, ctx.config.tempDir)) {
96
+ * throw new Error("Output directory must be inside tempDir");
97
+ * }
98
+ *
99
+ * This is the centralized successor to the inline pattern that previously
100
+ * lived in tools/qemu.ts (verifyContainment, correctly using a separator
101
+ * boundary) and tools/split-apk.ts / tools/registry.ts (which used a bare
102
+ * string-prefix check and were vulnerable to the sibling-directory bypass).
103
+ * Lifting to shared middleware fixes the bypass at both call sites.
104
+ */
105
+ export declare function isWithinDir(candidatePath: string, dir: string): boolean;
106
+ /**
107
+ * Read and JSON-parse a file. On any failure (missing, unreadable, malformed),
108
+ * log a warning via the supplied logger and return null. Used by tools that
109
+ * walk a directory of JSON files (fingerprints, baselines, marketplace
110
+ * workflows, plugin metadata) and want to skip-but-surface corruption rather
111
+ * than abort the whole walk OR silently drop the entry.
112
+ *
113
+ * Closes audit findings AE8 / AO6 / AU6 / BJ7 / BN7 — the recurring silent
114
+ * `} catch { /* skip corrupt * / }` pattern. Surfaces corruption to the
115
+ * operator so a quietly-shrinking handle count doesn't go unnoticed.
116
+ *
117
+ * The logger is optional so this is safe to call from contexts without a
118
+ * Logger in scope (some loadFingerprints / listing functions don't take a
119
+ * ToolContext). When absent, the warning goes to stderr — non-silent.
120
+ *
121
+ * Returns null on any failure; caller should skip the entry.
122
+ */
123
+ export declare function tryReadJsonOrWarn<T = unknown>(filePath: string, context: string, logger?: {
124
+ warn: (msg: string) => void;
125
+ }): T | null;
126
+ //# sourceMappingURL=fs-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-utils.d.ts","sourceRoot":"","sources":["../../src/middleware/fs-utils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAMH,gEAAgE;AAChE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4BzE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAW/E;AAED;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;GASG;AACH,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBrF;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAcvE;AACD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,OAAO,EAC3C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACvC,CAAC,GAAG,IAAI,CAUV"}