@pan-sec/notebooklm-mcp 2026.3.3 → 2026.4.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 (466) hide show
  1. package/dist/auth/auth-manager.d.ts +0 -1
  2. package/dist/auth/auth-manager.js +0 -1
  3. package/dist/auth/mcp-auth.d.ts +0 -1
  4. package/dist/auth/mcp-auth.js +0 -1
  5. package/dist/compliance/alert-manager.d.ts +6 -2
  6. package/dist/compliance/alert-manager.js +40 -10
  7. package/dist/compliance/breach-detection.d.ts +0 -1
  8. package/dist/compliance/breach-detection.js +0 -1
  9. package/dist/compliance/change-log.d.ts +13 -1
  10. package/dist/compliance/change-log.js +82 -16
  11. package/dist/compliance/compliance-logger.d.ts +29 -3
  12. package/dist/compliance/compliance-logger.js +90 -27
  13. package/dist/compliance/compliance-tools.d.ts +0 -1
  14. package/dist/compliance/compliance-tools.js +0 -1
  15. package/dist/compliance/consent-manager.d.ts +0 -1
  16. package/dist/compliance/consent-manager.js +0 -1
  17. package/dist/compliance/dashboard.d.ts +4 -3
  18. package/dist/compliance/dashboard.js +11 -8
  19. package/dist/compliance/data-classification.d.ts +0 -1
  20. package/dist/compliance/data-classification.js +0 -1
  21. package/dist/compliance/data-erasure.d.ts +0 -1
  22. package/dist/compliance/data-erasure.js +0 -1
  23. package/dist/compliance/data-export.d.ts +0 -1
  24. package/dist/compliance/data-export.js +0 -1
  25. package/dist/compliance/data-inventory.d.ts +0 -1
  26. package/dist/compliance/data-inventory.js +0 -1
  27. package/dist/compliance/dsar-handler.d.ts +0 -1
  28. package/dist/compliance/dsar-handler.js +0 -1
  29. package/dist/compliance/evidence-collector.d.ts +0 -1
  30. package/dist/compliance/evidence-collector.js +4 -2
  31. package/dist/compliance/health-monitor.d.ts +0 -1
  32. package/dist/compliance/health-monitor.js +0 -1
  33. package/dist/compliance/incident-manager.d.ts +0 -1
  34. package/dist/compliance/incident-manager.js +0 -1
  35. package/dist/compliance/index.d.ts +0 -1
  36. package/dist/compliance/index.js +0 -1
  37. package/dist/compliance/policy-docs.d.ts +0 -1
  38. package/dist/compliance/policy-docs.js +0 -1
  39. package/dist/compliance/privacy-notice-text.d.ts +0 -1
  40. package/dist/compliance/privacy-notice-text.js +0 -1
  41. package/dist/compliance/privacy-notice.d.ts +0 -1
  42. package/dist/compliance/privacy-notice.js +0 -1
  43. package/dist/compliance/report-generator.d.ts +7 -1
  44. package/dist/compliance/report-generator.js +116 -34
  45. package/dist/compliance/retention-engine.d.ts +0 -1
  46. package/dist/compliance/retention-engine.js +0 -1
  47. package/dist/compliance/siem-exporter.d.ts +26 -2
  48. package/dist/compliance/siem-exporter.js +89 -24
  49. package/dist/compliance/types.d.ts +0 -1
  50. package/dist/compliance/types.js +0 -1
  51. package/dist/config.d.ts +0 -1
  52. package/dist/config.js +2 -3
  53. package/dist/errors.d.ts +0 -1
  54. package/dist/errors.js +0 -1
  55. package/dist/events/event-emitter.d.ts +9 -1
  56. package/dist/events/event-emitter.js +47 -8
  57. package/dist/events/event-types.d.ts +0 -1
  58. package/dist/events/event-types.js +8 -2
  59. package/dist/gemini/gemini-client.d.ts +0 -1
  60. package/dist/gemini/gemini-client.js +237 -45
  61. package/dist/gemini/index.d.ts +0 -1
  62. package/dist/gemini/index.js +0 -1
  63. package/dist/gemini/pdf-chunker.d.ts +0 -1
  64. package/dist/gemini/pdf-chunker.js +60 -35
  65. package/dist/gemini/types.d.ts +0 -1
  66. package/dist/gemini/types.js +0 -1
  67. package/dist/index.d.ts +0 -1
  68. package/dist/index.js +60 -7
  69. package/dist/library/notebook-library.d.ts +30 -2
  70. package/dist/library/notebook-library.js +345 -85
  71. package/dist/library/types.d.ts +0 -1
  72. package/dist/library/types.js +0 -1
  73. package/dist/logging/index.d.ts +0 -1
  74. package/dist/logging/index.js +0 -1
  75. package/dist/logging/query-logger.d.ts +20 -1
  76. package/dist/logging/query-logger.js +104 -21
  77. package/dist/notebook-creation/audio-manager.d.ts +0 -1
  78. package/dist/notebook-creation/audio-manager.js +111 -20
  79. package/dist/notebook-creation/browser-options.d.ts +0 -1
  80. package/dist/notebook-creation/browser-options.js +0 -1
  81. package/dist/notebook-creation/data-table-manager.d.ts +7 -1
  82. package/dist/notebook-creation/data-table-manager.js +59 -3
  83. package/dist/notebook-creation/dom-scripts.d.ts +0 -1
  84. package/dist/notebook-creation/dom-scripts.js +0 -1
  85. package/dist/notebook-creation/errors.d.ts +0 -1
  86. package/dist/notebook-creation/errors.js +0 -1
  87. package/dist/notebook-creation/index.d.ts +0 -1
  88. package/dist/notebook-creation/index.js +0 -1
  89. package/dist/notebook-creation/notebook-creator.d.ts +9 -1
  90. package/dist/notebook-creation/notebook-creator.js +50 -1
  91. package/dist/notebook-creation/notebook-nav.d.ts +0 -1
  92. package/dist/notebook-creation/notebook-nav.js +21 -6
  93. package/dist/notebook-creation/notebook-sync.d.ts +14 -2
  94. package/dist/notebook-creation/notebook-sync.js +124 -35
  95. package/dist/notebook-creation/selectors.d.ts +0 -1
  96. package/dist/notebook-creation/selectors.js +6 -4
  97. package/dist/notebook-creation/source-manager.d.ts +29 -2
  98. package/dist/notebook-creation/source-manager.js +0 -0
  99. package/dist/notebook-creation/types.d.ts +0 -1
  100. package/dist/notebook-creation/types.js +0 -1
  101. package/dist/notebook-creation/video-manager.d.ts +0 -1
  102. package/dist/notebook-creation/video-manager.js +91 -15
  103. package/dist/observability/metrics.d.ts +0 -1
  104. package/dist/observability/metrics.js +0 -1
  105. package/dist/quota/index.d.ts +0 -1
  106. package/dist/quota/index.js +0 -1
  107. package/dist/quota/quota-manager.d.ts +59 -4
  108. package/dist/quota/quota-manager.js +195 -46
  109. package/dist/resources/resource-handlers.d.ts +0 -1
  110. package/dist/resources/resource-handlers.js +33 -3
  111. package/dist/session/browser-session.d.ts +0 -1
  112. package/dist/session/browser-session.js +0 -1
  113. package/dist/session/session-manager.d.ts +0 -1
  114. package/dist/session/session-manager.js +0 -1
  115. package/dist/session/session-timeout.d.ts +0 -1
  116. package/dist/session/session-timeout.js +0 -1
  117. package/dist/session/shared-context-manager.d.ts +0 -1
  118. package/dist/session/shared-context-manager.js +0 -1
  119. package/dist/tools/annotations.d.ts +0 -1
  120. package/dist/tools/annotations.js +0 -1
  121. package/dist/tools/definitions/ask-question.d.ts +6 -3
  122. package/dist/tools/definitions/ask-question.js +12 -8
  123. package/dist/tools/definitions/chat-history.d.ts +0 -1
  124. package/dist/tools/definitions/chat-history.js +1 -1
  125. package/dist/tools/definitions/data-tables.d.ts +0 -1
  126. package/dist/tools/definitions/data-tables.js +4 -1
  127. package/dist/tools/definitions/gemini.d.ts +0 -1
  128. package/dist/tools/definitions/gemini.js +14 -7
  129. package/dist/tools/definitions/notebook-management.d.ts +0 -1
  130. package/dist/tools/definitions/notebook-management.js +7 -2
  131. package/dist/tools/definitions/query-history.d.ts +0 -1
  132. package/dist/tools/definitions/query-history.js +0 -1
  133. package/dist/tools/definitions/session-management.d.ts +0 -1
  134. package/dist/tools/definitions/session-management.js +0 -1
  135. package/dist/tools/definitions/system.d.ts +0 -1
  136. package/dist/tools/definitions/system.js +32 -12
  137. package/dist/tools/definitions/video.d.ts +0 -1
  138. package/dist/tools/definitions/video.js +6 -3
  139. package/dist/tools/definitions.d.ts +0 -1
  140. package/dist/tools/definitions.js +0 -1
  141. package/dist/tools/handlers/ask-question.d.ts +0 -1
  142. package/dist/tools/handlers/ask-question.js +47 -18
  143. package/dist/tools/handlers/audio-video.d.ts +0 -1
  144. package/dist/tools/handlers/audio-video.js +0 -1
  145. package/dist/tools/handlers/auth.d.ts +0 -1
  146. package/dist/tools/handlers/auth.js +0 -1
  147. package/dist/tools/handlers/error-utils.d.ts +0 -1
  148. package/dist/tools/handlers/error-utils.js +0 -1
  149. package/dist/tools/handlers/gemini.d.ts +0 -1
  150. package/dist/tools/handlers/gemini.js +0 -1
  151. package/dist/tools/handlers/index.d.ts +0 -1
  152. package/dist/tools/handlers/index.js +0 -1
  153. package/dist/tools/handlers/notebook-creation.d.ts +0 -1
  154. package/dist/tools/handlers/notebook-creation.js +16 -1
  155. package/dist/tools/handlers/notebook-management.d.ts +0 -1
  156. package/dist/tools/handlers/notebook-management.js +7 -2
  157. package/dist/tools/handlers/session-management.d.ts +0 -1
  158. package/dist/tools/handlers/session-management.js +0 -1
  159. package/dist/tools/handlers/system.d.ts +0 -1
  160. package/dist/tools/handlers/system.js +0 -1
  161. package/dist/tools/handlers/types.d.ts +0 -1
  162. package/dist/tools/handlers/types.js +0 -1
  163. package/dist/tools/handlers/webhooks.d.ts +0 -1
  164. package/dist/tools/handlers/webhooks.js +0 -1
  165. package/dist/tools/icons.d.ts +0 -1
  166. package/dist/tools/icons.js +0 -1
  167. package/dist/tools/index.d.ts +0 -1
  168. package/dist/tools/index.js +0 -1
  169. package/dist/types.d.ts +0 -1
  170. package/dist/types.js +0 -1
  171. package/dist/utils/audit-logger.d.ts +11 -1
  172. package/dist/utils/audit-logger.js +189 -21
  173. package/dist/utils/cleanup-manager.d.ts +0 -1
  174. package/dist/utils/cleanup-manager.js +0 -1
  175. package/dist/utils/cli-handler.d.ts +0 -1
  176. package/dist/utils/cli-handler.js +0 -1
  177. package/dist/utils/crypto.d.ts +18 -9
  178. package/dist/utils/crypto.js +93 -28
  179. package/dist/utils/file-lock.d.ts +15 -1
  180. package/dist/utils/file-lock.js +67 -59
  181. package/dist/utils/file-permissions.d.ts +0 -1
  182. package/dist/utils/file-permissions.js +35 -7
  183. package/dist/utils/logger.d.ts +0 -1
  184. package/dist/utils/logger.js +0 -1
  185. package/dist/utils/page-utils.d.ts +0 -1
  186. package/dist/utils/page-utils.js +32 -28
  187. package/dist/utils/response-validator.d.ts +0 -1
  188. package/dist/utils/response-validator.js +18 -15
  189. package/dist/utils/secrets-scanner.d.ts +0 -1
  190. package/dist/utils/secrets-scanner.js +32 -7
  191. package/dist/utils/secure-memory.d.ts +34 -16
  192. package/dist/utils/secure-memory.js +40 -25
  193. package/dist/utils/security.d.ts +0 -1
  194. package/dist/utils/security.js +66 -39
  195. package/dist/utils/settings-manager.d.ts +9 -1
  196. package/dist/utils/settings-manager.js +45 -2
  197. package/dist/utils/stealth-utils.d.ts +0 -1
  198. package/dist/utils/stealth-utils.js +11 -9
  199. package/dist/webhooks/index.d.ts +0 -1
  200. package/dist/webhooks/index.js +0 -1
  201. package/dist/webhooks/types.d.ts +0 -1
  202. package/dist/webhooks/types.js +0 -1
  203. package/dist/webhooks/webhook-dispatcher.d.ts +0 -1
  204. package/dist/webhooks/webhook-dispatcher.js +0 -1
  205. package/package.json +5 -4
  206. package/dist/auth/auth-manager.d.ts.map +0 -1
  207. package/dist/auth/auth-manager.js.map +0 -1
  208. package/dist/auth/mcp-auth.d.ts.map +0 -1
  209. package/dist/auth/mcp-auth.js.map +0 -1
  210. package/dist/compliance/alert-manager.d.ts.map +0 -1
  211. package/dist/compliance/alert-manager.js.map +0 -1
  212. package/dist/compliance/breach-detection.d.ts.map +0 -1
  213. package/dist/compliance/breach-detection.js.map +0 -1
  214. package/dist/compliance/change-log.d.ts.map +0 -1
  215. package/dist/compliance/change-log.js.map +0 -1
  216. package/dist/compliance/compliance-logger.d.ts.map +0 -1
  217. package/dist/compliance/compliance-logger.js.map +0 -1
  218. package/dist/compliance/compliance-tools.d.ts.map +0 -1
  219. package/dist/compliance/compliance-tools.js.map +0 -1
  220. package/dist/compliance/consent-manager.d.ts.map +0 -1
  221. package/dist/compliance/consent-manager.js.map +0 -1
  222. package/dist/compliance/dashboard.d.ts.map +0 -1
  223. package/dist/compliance/dashboard.js.map +0 -1
  224. package/dist/compliance/data-classification.d.ts.map +0 -1
  225. package/dist/compliance/data-classification.js.map +0 -1
  226. package/dist/compliance/data-erasure.d.ts.map +0 -1
  227. package/dist/compliance/data-erasure.js.map +0 -1
  228. package/dist/compliance/data-export.d.ts.map +0 -1
  229. package/dist/compliance/data-export.js.map +0 -1
  230. package/dist/compliance/data-inventory.d.ts.map +0 -1
  231. package/dist/compliance/data-inventory.js.map +0 -1
  232. package/dist/compliance/dsar-handler.d.ts.map +0 -1
  233. package/dist/compliance/dsar-handler.js.map +0 -1
  234. package/dist/compliance/evidence-collector.d.ts.map +0 -1
  235. package/dist/compliance/evidence-collector.js.map +0 -1
  236. package/dist/compliance/health-monitor.d.ts.map +0 -1
  237. package/dist/compliance/health-monitor.js.map +0 -1
  238. package/dist/compliance/incident-manager.d.ts.map +0 -1
  239. package/dist/compliance/incident-manager.js.map +0 -1
  240. package/dist/compliance/index.d.ts.map +0 -1
  241. package/dist/compliance/index.js.map +0 -1
  242. package/dist/compliance/policy-docs.d.ts.map +0 -1
  243. package/dist/compliance/policy-docs.js.map +0 -1
  244. package/dist/compliance/privacy-notice-text.d.ts.map +0 -1
  245. package/dist/compliance/privacy-notice-text.js.map +0 -1
  246. package/dist/compliance/privacy-notice.d.ts.map +0 -1
  247. package/dist/compliance/privacy-notice.js.map +0 -1
  248. package/dist/compliance/report-generator.d.ts.map +0 -1
  249. package/dist/compliance/report-generator.js.map +0 -1
  250. package/dist/compliance/retention-engine.d.ts.map +0 -1
  251. package/dist/compliance/retention-engine.js.map +0 -1
  252. package/dist/compliance/siem-exporter.d.ts.map +0 -1
  253. package/dist/compliance/siem-exporter.js.map +0 -1
  254. package/dist/compliance/types.d.ts.map +0 -1
  255. package/dist/compliance/types.js.map +0 -1
  256. package/dist/config.d.ts.map +0 -1
  257. package/dist/config.js.map +0 -1
  258. package/dist/errors.d.ts.map +0 -1
  259. package/dist/errors.js.map +0 -1
  260. package/dist/events/event-emitter.d.ts.map +0 -1
  261. package/dist/events/event-emitter.js.map +0 -1
  262. package/dist/events/event-types.d.ts.map +0 -1
  263. package/dist/events/event-types.js.map +0 -1
  264. package/dist/gemini/gemini-client.d.ts.map +0 -1
  265. package/dist/gemini/gemini-client.js.map +0 -1
  266. package/dist/gemini/index.d.ts.map +0 -1
  267. package/dist/gemini/index.js.map +0 -1
  268. package/dist/gemini/pdf-chunker.d.ts.map +0 -1
  269. package/dist/gemini/pdf-chunker.js.map +0 -1
  270. package/dist/gemini/types.d.ts.map +0 -1
  271. package/dist/gemini/types.js.map +0 -1
  272. package/dist/index.d.ts.map +0 -1
  273. package/dist/index.js.map +0 -1
  274. package/dist/library/notebook-library.d.ts.map +0 -1
  275. package/dist/library/notebook-library.js.map +0 -1
  276. package/dist/library/types.d.ts.map +0 -1
  277. package/dist/library/types.js.map +0 -1
  278. package/dist/logging/index.d.ts.map +0 -1
  279. package/dist/logging/index.js.map +0 -1
  280. package/dist/logging/query-logger.d.ts.map +0 -1
  281. package/dist/logging/query-logger.js.map +0 -1
  282. package/dist/notebook-creation/audio-manager.d.ts.map +0 -1
  283. package/dist/notebook-creation/audio-manager.js.map +0 -1
  284. package/dist/notebook-creation/browser-options.d.ts.map +0 -1
  285. package/dist/notebook-creation/browser-options.js.map +0 -1
  286. package/dist/notebook-creation/data-table-manager.d.ts.map +0 -1
  287. package/dist/notebook-creation/data-table-manager.js.map +0 -1
  288. package/dist/notebook-creation/discover-creation-flow.d.ts +0 -2
  289. package/dist/notebook-creation/discover-creation-flow.d.ts.map +0 -1
  290. package/dist/notebook-creation/discover-creation-flow.js +0 -177
  291. package/dist/notebook-creation/discover-creation-flow.js.map +0 -1
  292. package/dist/notebook-creation/discover-quota.d.ts +0 -2
  293. package/dist/notebook-creation/discover-quota.d.ts.map +0 -1
  294. package/dist/notebook-creation/discover-quota.js +0 -194
  295. package/dist/notebook-creation/discover-quota.js.map +0 -1
  296. package/dist/notebook-creation/discover-source-dialog.d.ts +0 -8
  297. package/dist/notebook-creation/discover-source-dialog.d.ts.map +0 -1
  298. package/dist/notebook-creation/discover-source-dialog.js +0 -134
  299. package/dist/notebook-creation/discover-source-dialog.js.map +0 -1
  300. package/dist/notebook-creation/discover-sources.d.ts +0 -8
  301. package/dist/notebook-creation/discover-sources.d.ts.map +0 -1
  302. package/dist/notebook-creation/discover-sources.js +0 -272
  303. package/dist/notebook-creation/discover-sources.js.map +0 -1
  304. package/dist/notebook-creation/discover-text-input.d.ts +0 -7
  305. package/dist/notebook-creation/discover-text-input.d.ts.map +0 -1
  306. package/dist/notebook-creation/discover-text-input.js +0 -135
  307. package/dist/notebook-creation/discover-text-input.js.map +0 -1
  308. package/dist/notebook-creation/dom-scripts.d.ts.map +0 -1
  309. package/dist/notebook-creation/dom-scripts.js.map +0 -1
  310. package/dist/notebook-creation/errors.d.ts.map +0 -1
  311. package/dist/notebook-creation/errors.js.map +0 -1
  312. package/dist/notebook-creation/index.d.ts.map +0 -1
  313. package/dist/notebook-creation/index.js.map +0 -1
  314. package/dist/notebook-creation/notebook-creator.d.ts.map +0 -1
  315. package/dist/notebook-creation/notebook-creator.js.map +0 -1
  316. package/dist/notebook-creation/notebook-nav.d.ts.map +0 -1
  317. package/dist/notebook-creation/notebook-nav.js.map +0 -1
  318. package/dist/notebook-creation/notebook-sync.d.ts.map +0 -1
  319. package/dist/notebook-creation/notebook-sync.js.map +0 -1
  320. package/dist/notebook-creation/run-discovery.d.ts +0 -11
  321. package/dist/notebook-creation/run-discovery.d.ts.map +0 -1
  322. package/dist/notebook-creation/run-discovery.js +0 -151
  323. package/dist/notebook-creation/run-discovery.js.map +0 -1
  324. package/dist/notebook-creation/selector-discovery.d.ts +0 -65
  325. package/dist/notebook-creation/selector-discovery.d.ts.map +0 -1
  326. package/dist/notebook-creation/selector-discovery.js +0 -414
  327. package/dist/notebook-creation/selector-discovery.js.map +0 -1
  328. package/dist/notebook-creation/selectors.d.ts.map +0 -1
  329. package/dist/notebook-creation/selectors.js.map +0 -1
  330. package/dist/notebook-creation/selectors.ts +0 -112
  331. package/dist/notebook-creation/source-manager.d.ts.map +0 -1
  332. package/dist/notebook-creation/source-manager.js.map +0 -1
  333. package/dist/notebook-creation/test-create.d.ts +0 -8
  334. package/dist/notebook-creation/test-create.d.ts.map +0 -1
  335. package/dist/notebook-creation/test-create.js +0 -72
  336. package/dist/notebook-creation/test-create.js.map +0 -1
  337. package/dist/notebook-creation/types.d.ts.map +0 -1
  338. package/dist/notebook-creation/types.js.map +0 -1
  339. package/dist/notebook-creation/video-manager.d.ts.map +0 -1
  340. package/dist/notebook-creation/video-manager.js.map +0 -1
  341. package/dist/observability/metrics.d.ts.map +0 -1
  342. package/dist/observability/metrics.js.map +0 -1
  343. package/dist/quota/index.d.ts.map +0 -1
  344. package/dist/quota/index.js.map +0 -1
  345. package/dist/quota/quota-manager.d.ts.map +0 -1
  346. package/dist/quota/quota-manager.js.map +0 -1
  347. package/dist/resources/resource-handlers.d.ts.map +0 -1
  348. package/dist/resources/resource-handlers.js.map +0 -1
  349. package/dist/session/browser-session.d.ts.map +0 -1
  350. package/dist/session/browser-session.js.map +0 -1
  351. package/dist/session/session-manager.d.ts.map +0 -1
  352. package/dist/session/session-manager.js.map +0 -1
  353. package/dist/session/session-timeout.d.ts.map +0 -1
  354. package/dist/session/session-timeout.js.map +0 -1
  355. package/dist/session/shared-context-manager.d.ts.map +0 -1
  356. package/dist/session/shared-context-manager.js.map +0 -1
  357. package/dist/tools/annotations.d.ts.map +0 -1
  358. package/dist/tools/annotations.js.map +0 -1
  359. package/dist/tools/definitions/ask-question.d.ts.map +0 -1
  360. package/dist/tools/definitions/ask-question.js.map +0 -1
  361. package/dist/tools/definitions/chat-history.d.ts.map +0 -1
  362. package/dist/tools/definitions/chat-history.js.map +0 -1
  363. package/dist/tools/definitions/data-tables.d.ts.map +0 -1
  364. package/dist/tools/definitions/data-tables.js.map +0 -1
  365. package/dist/tools/definitions/gemini.d.ts.map +0 -1
  366. package/dist/tools/definitions/gemini.js.map +0 -1
  367. package/dist/tools/definitions/notebook-management.d.ts.map +0 -1
  368. package/dist/tools/definitions/notebook-management.js.map +0 -1
  369. package/dist/tools/definitions/query-history.d.ts.map +0 -1
  370. package/dist/tools/definitions/query-history.js.map +0 -1
  371. package/dist/tools/definitions/session-management.d.ts.map +0 -1
  372. package/dist/tools/definitions/session-management.js.map +0 -1
  373. package/dist/tools/definitions/system.d.ts.map +0 -1
  374. package/dist/tools/definitions/system.js.map +0 -1
  375. package/dist/tools/definitions/video.d.ts.map +0 -1
  376. package/dist/tools/definitions/video.js.map +0 -1
  377. package/dist/tools/definitions.d.ts.map +0 -1
  378. package/dist/tools/definitions.js.map +0 -1
  379. package/dist/tools/handlers/ask-question.d.ts.map +0 -1
  380. package/dist/tools/handlers/ask-question.js.map +0 -1
  381. package/dist/tools/handlers/audio-video.d.ts.map +0 -1
  382. package/dist/tools/handlers/audio-video.js.map +0 -1
  383. package/dist/tools/handlers/auth.d.ts.map +0 -1
  384. package/dist/tools/handlers/auth.js.map +0 -1
  385. package/dist/tools/handlers/error-utils.d.ts.map +0 -1
  386. package/dist/tools/handlers/error-utils.js.map +0 -1
  387. package/dist/tools/handlers/gemini.d.ts.map +0 -1
  388. package/dist/tools/handlers/gemini.js.map +0 -1
  389. package/dist/tools/handlers/index.d.ts.map +0 -1
  390. package/dist/tools/handlers/index.js.map +0 -1
  391. package/dist/tools/handlers/notebook-creation.d.ts.map +0 -1
  392. package/dist/tools/handlers/notebook-creation.js.map +0 -1
  393. package/dist/tools/handlers/notebook-management.d.ts.map +0 -1
  394. package/dist/tools/handlers/notebook-management.js.map +0 -1
  395. package/dist/tools/handlers/session-management.d.ts.map +0 -1
  396. package/dist/tools/handlers/session-management.js.map +0 -1
  397. package/dist/tools/handlers/system.d.ts.map +0 -1
  398. package/dist/tools/handlers/system.js.map +0 -1
  399. package/dist/tools/handlers/types.d.ts.map +0 -1
  400. package/dist/tools/handlers/types.js.map +0 -1
  401. package/dist/tools/handlers/webhooks.d.ts.map +0 -1
  402. package/dist/tools/handlers/webhooks.js.map +0 -1
  403. package/dist/tools/handlers.d.ts +0 -666
  404. package/dist/tools/handlers.d.ts.map +0 -1
  405. package/dist/tools/handlers.js +0 -2929
  406. package/dist/tools/handlers.js.map +0 -1
  407. package/dist/tools/icons.d.ts.map +0 -1
  408. package/dist/tools/icons.js.map +0 -1
  409. package/dist/tools/index.d.ts.map +0 -1
  410. package/dist/tools/index.js.map +0 -1
  411. package/dist/types.d.ts.map +0 -1
  412. package/dist/types.js.map +0 -1
  413. package/dist/utils/audit-logger.d.ts.map +0 -1
  414. package/dist/utils/audit-logger.js.map +0 -1
  415. package/dist/utils/cert-pinning.d.ts +0 -97
  416. package/dist/utils/cert-pinning.d.ts.map +0 -1
  417. package/dist/utils/cert-pinning.js +0 -328
  418. package/dist/utils/cert-pinning.js.map +0 -1
  419. package/dist/utils/cleanup-manager.d.ts.map +0 -1
  420. package/dist/utils/cleanup-manager.js.map +0 -1
  421. package/dist/utils/cli-handler.d.ts.map +0 -1
  422. package/dist/utils/cli-handler.js.map +0 -1
  423. package/dist/utils/crypto.d.ts.map +0 -1
  424. package/dist/utils/crypto.js.map +0 -1
  425. package/dist/utils/file-lock.d.ts.map +0 -1
  426. package/dist/utils/file-lock.js.map +0 -1
  427. package/dist/utils/file-permissions.d.ts.map +0 -1
  428. package/dist/utils/file-permissions.js.map +0 -1
  429. package/dist/utils/logger.d.ts.map +0 -1
  430. package/dist/utils/logger.js.map +0 -1
  431. package/dist/utils/page-utils.d.ts.map +0 -1
  432. package/dist/utils/page-utils.js.map +0 -1
  433. package/dist/utils/response-validator.d.ts.map +0 -1
  434. package/dist/utils/response-validator.js.map +0 -1
  435. package/dist/utils/secrets-scanner.d.ts.map +0 -1
  436. package/dist/utils/secrets-scanner.js.map +0 -1
  437. package/dist/utils/secure-memory.d.ts.map +0 -1
  438. package/dist/utils/secure-memory.js.map +0 -1
  439. package/dist/utils/security.d.ts.map +0 -1
  440. package/dist/utils/security.js.map +0 -1
  441. package/dist/utils/settings-manager.d.ts.map +0 -1
  442. package/dist/utils/settings-manager.js.map +0 -1
  443. package/dist/utils/stealth-utils.d.ts.map +0 -1
  444. package/dist/utils/stealth-utils.js.map +0 -1
  445. package/dist/utils/tool-validation.d.ts +0 -93
  446. package/dist/utils/tool-validation.d.ts.map +0 -1
  447. package/dist/utils/tool-validation.js +0 -277
  448. package/dist/utils/tool-validation.js.map +0 -1
  449. package/dist/webhooks/index.d.ts.map +0 -1
  450. package/dist/webhooks/index.js.map +0 -1
  451. package/dist/webhooks/types.d.ts.map +0 -1
  452. package/dist/webhooks/types.js.map +0 -1
  453. package/dist/webhooks/webhook-dispatcher.d.ts.map +0 -1
  454. package/dist/webhooks/webhook-dispatcher.js.map +0 -1
  455. package/docs/COMPLIANCE-SPEC.md +0 -1452
  456. package/docs/MCP-DIRECTORY-LISTINGS.md +0 -91
  457. package/docs/SECURITY-FORK-OPPORTUNITIES.md +0 -79
  458. package/docs/SECURITY_IMPLEMENTATION_PLAN.md +0 -437
  459. package/docs/archive/ISSUES-legacy-2026-04-24.md +0 -644
  460. package/docs/configuration.md +0 -94
  461. package/docs/dependency-risk.md +0 -25
  462. package/docs/improvement-sprint-2026.2.10.md +0 -210
  463. package/docs/testing-runbook.md +0 -166
  464. package/docs/tools.md +0 -34
  465. package/docs/troubleshooting.md +0 -59
  466. package/docs/usage-guide.md +0 -246
@@ -28,6 +28,20 @@ import { CONFIG } from "../config.js";
28
28
  import { log } from "./logger.js";
29
29
  import { audit } from "./audit-logger.js";
30
30
  import { mkdirSecure, writeFileSecure, PERMISSION_MODES, } from "./file-permissions.js";
31
+ /**
32
+ * Thrown when an encrypted file exists but cannot be decrypted (e.g. wrong
33
+ * key after rotation, corruption, or tampering). Distinct from a genuinely
34
+ * absent file (load() returns null), so callers can avoid overwriting
35
+ * good-but-undecryptable state.
36
+ */
37
+ export class DecryptionError extends Error {
38
+ file;
39
+ constructor(message, file) {
40
+ super(message);
41
+ this.name = "DecryptionError";
42
+ this.file = file;
43
+ }
44
+ }
31
45
  /**
32
46
  * Constants
33
47
  */
@@ -64,25 +78,6 @@ function hkdfDerive(ikm, salt, info, length) {
64
78
  export function deriveKey(passphrase, salt, iterations = DEFAULT_PBKDF2_ITERATIONS) {
65
79
  return crypto.pbkdf2Sync(passphrase, salt, iterations, KEY_LENGTH, "sha256");
66
80
  }
67
- /**
68
- * Generate a machine-derived key based on hardware/OS identifiers
69
- *
70
- * Note: This provides obscurity, not true security. It's a fallback
71
- * when no user key is provided.
72
- */
73
- export function getMachineKey() {
74
- const components = [
75
- os.hostname(),
76
- os.platform(),
77
- os.arch(),
78
- os.cpus()[0]?.model || "unknown",
79
- os.homedir(),
80
- ];
81
- // Create a deterministic key from machine components
82
- const combined = components.join("|");
83
- const hash = crypto.createHash("sha256").update(combined).digest("hex");
84
- return hash;
85
- }
86
81
  export function getOrCreateMachineKey(keyPath) {
87
82
  if (fs.existsSync(keyPath)) {
88
83
  const existingKey = fs.readFileSync(keyPath);
@@ -178,8 +173,10 @@ export function decryptPQ(encryptedData, recipientSecretKey) {
178
173
  * Classical ChaCha20-Poly1305 encryption (fallback)
179
174
  */
180
175
  export function encryptClassical(data, key) {
176
+ // No salt: the caller-supplied 256-bit key is used directly. The random
177
+ // 96-bit nonce provides per-ciphertext uniqueness. A salt was previously
178
+ // stored but never fed to any KDF (L3), so it was decorative and is dropped.
181
179
  const nonce = crypto.randomBytes(NONCE_LENGTH);
182
- const salt = crypto.randomBytes(SALT_LENGTH);
183
180
  const cipher = crypto.createCipheriv(ALGORITHM, key, nonce, {
184
181
  authTagLength: 16,
185
182
  });
@@ -192,7 +189,6 @@ export function encryptClassical(data, key) {
192
189
  version: CLASSICAL_VERSION,
193
190
  algorithm: ALGORITHM,
194
191
  nonce: nonce.toString("base64"),
195
- salt: salt.toString("base64"),
196
192
  ciphertext: ciphertextWithTag.toString("base64"),
197
193
  };
198
194
  }
@@ -214,8 +210,24 @@ export function decryptClassical(encryptedData, key) {
214
210
  const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
215
211
  return decrypted;
216
212
  }
213
+ // ---------------------------------------------------------------------------
214
+ // LEGACY AES-GCM MIGRATION SHIM (L7) — bounded, remove after migration window.
215
+ //
216
+ // This block (LegacyAESEncryptedData, decryptLegacyAES, isLegacyFormat) exists
217
+ // ONLY to transparently upgrade pre-fork v1/v2 files that were encrypted with
218
+ // AES-GCM (detected by the presence of `iv` + `tag` fields) to the current
219
+ // ChaCha20-Poly1305 format on first read. It is intentionally isolated to these
220
+ // three symbols and adds one branch on the load/init paths.
221
+ //
222
+ // TODO(remove-after-migration-window): once all deployed installs have read
223
+ // their state at least once under a ChaCha20-Poly1305 build (so no AES-GCM
224
+ // files remain on disk), delete this shim along with the `isLegacyFormat`
225
+ // branches in initializePQKeys()/load(). No new data is ever written in this
226
+ // format — decrypt-only, one direction.
227
+ // ---------------------------------------------------------------------------
217
228
  /**
218
- * Decrypt legacy AES-GCM encrypted data (for migration)
229
+ * Decrypt legacy AES-GCM encrypted data (for migration). See migration shim
230
+ * note above — decrypt-only, scheduled for removal after the migration window.
219
231
  */
220
232
  function decryptLegacyAES(encryptedData, key, pqSecretKey) {
221
233
  let aesKey;
@@ -261,8 +273,16 @@ export class SecureStorage {
261
273
  pqKeyPair = null;
262
274
  initialized = false;
263
275
  keyStorePath;
276
+ /**
277
+ * Captured at construction, before config.enabled can be mutated by
278
+ * initialize()/initializeClassicalKey(). Records whether the caller
279
+ * actually intended encryption. Used to fail closed: if encryption was
280
+ * expected but is unavailable, we refuse to write plaintext.
281
+ */
282
+ encryptionExpected;
264
283
  constructor(config) {
265
284
  this.config = { ...getEncryptionConfig(), ...config };
285
+ this.encryptionExpected = this.config.enabled;
266
286
  this.keyStorePath = path.join(process.env.NLMCP_CONFIG_DIR || CONFIG.configDir || path.join(os.homedir(), ".notebooklm-mcp"), "pq-keys.enc");
267
287
  }
268
288
  /**
@@ -287,9 +307,12 @@ export class SecureStorage {
287
307
  this.initialized = true;
288
308
  }
289
309
  catch (error) {
310
+ // Fail closed: encryption was requested but initialization failed.
311
+ // Do NOT silently disable and fall through to plaintext writes —
312
+ // callers believe this data is encrypted.
290
313
  log.error(` ❌ Failed to initialize encryption: ${error}`);
291
- this.config.enabled = false;
292
314
  await audit.security("encryption_init_failed", "error", { error: String(error) });
315
+ throw error;
293
316
  }
294
317
  }
295
318
  /**
@@ -404,6 +427,13 @@ export class SecureStorage {
404
427
  // Ensure directory exists with secure permissions
405
428
  mkdirSecure(dir, PERMISSION_MODES.OWNER_FULL);
406
429
  if (!this.config.enabled) {
430
+ // Fail closed: only write plaintext when encryption was genuinely
431
+ // never expected (e.g. NLMCP_ENCRYPTION_ENABLED=false). If encryption
432
+ // was expected but got disabled by an init failure, refuse.
433
+ if (this.encryptionExpected) {
434
+ throw new Error(`Refusing to save ${path.basename(filePath)} as plaintext: ` +
435
+ `encryption was expected but is unavailable (initialization failed)`);
436
+ }
407
437
  // Save unencrypted
408
438
  writeFileSecure(filePath, dataStr, PERMISSION_MODES.OWNER_READ_WRITE);
409
439
  log.info(`📝 Saved (unencrypted): ${path.basename(filePath)}`);
@@ -423,13 +453,43 @@ export class SecureStorage {
423
453
  log.info(`🔐 Saved with ChaCha20-Poly1305: ${path.basename(encryptedPath)}`);
424
454
  }
425
455
  else {
456
+ // Fail closed: no encryption keys are available. If encryption was
457
+ // expected, refuse rather than silently writing plaintext.
458
+ if (this.encryptionExpected) {
459
+ throw new Error(`Refusing to save ${path.basename(filePath)} as plaintext: ` +
460
+ `encryption was expected but no keys are available`);
461
+ }
426
462
  // Save unencrypted as fallback
427
463
  writeFileSecure(filePath, dataStr, PERMISSION_MODES.OWNER_READ_WRITE);
428
464
  log.warning(`⚠️ Saved unencrypted (no keys): ${path.basename(filePath)}`);
429
465
  return;
430
466
  }
431
- writeFileSecure(encryptedPath, JSON.stringify(encrypted, null, 2), PERMISSION_MODES.OWNER_READ_WRITE);
432
- // Remove unencrypted and other encrypted versions if they exist
467
+ // L8: Write atomically via temp-file + rename. save() is invoked from
468
+ // inside load()'s migrate-on-read path (and callers may already hold the
469
+ // file lock on this same path, so we cannot re-acquire it here without
470
+ // self-deadlock). An atomic rename guarantees a crash or a concurrent
471
+ // reader never observes a partially written / truncated encrypted file:
472
+ // the destination either has the complete old contents or the complete
473
+ // new contents, never an intermediate state.
474
+ const tmpPath = `${encryptedPath}.tmp-${process.pid}-${crypto.randomBytes(6).toString("hex")}`;
475
+ try {
476
+ writeFileSecure(tmpPath, JSON.stringify(encrypted, null, 2), PERMISSION_MODES.OWNER_READ_WRITE);
477
+ fs.renameSync(tmpPath, encryptedPath);
478
+ }
479
+ catch (error) {
480
+ // Best-effort cleanup of the temp file so we don't leave litter behind.
481
+ try {
482
+ if (fs.existsSync(tmpPath))
483
+ fs.unlinkSync(tmpPath);
484
+ }
485
+ catch {
486
+ /* ignore cleanup failure */
487
+ }
488
+ throw error;
489
+ }
490
+ // Remove unencrypted and other encrypted versions if they exist. This runs
491
+ // only after the new file is durably in place, so a crash here leaves a
492
+ // valid (if duplicated) encrypted file rather than losing data.
433
493
  const extensions = ["", ".enc", ".pqenc"];
434
494
  for (const ext of extensions) {
435
495
  const oldPath = filePath + ext;
@@ -471,7 +531,10 @@ export class SecureStorage {
471
531
  type: "post-quantum",
472
532
  error: String(error),
473
533
  });
474
- return null;
534
+ // Fail closed: the file exists but could not be decrypted. Throw a
535
+ // typed error so callers don't mistake this for a missing file and
536
+ // overwrite good-but-undecryptable state (e.g. after key rotation).
537
+ throw new DecryptionError(`Failed to decrypt ${pqEncryptedPath}: ${error instanceof Error ? error.message : String(error)}`, pqEncryptedPath);
475
538
  }
476
539
  }
477
540
  // Check for classical encrypted version
@@ -509,7 +572,10 @@ export class SecureStorage {
509
572
  type: "classical",
510
573
  error: String(error),
511
574
  });
512
- return null;
575
+ // Fail closed: the file exists but could not be decrypted. Throw a
576
+ // typed error so callers don't mistake this for a missing file and
577
+ // overwrite good-but-undecryptable state (e.g. after key rotation).
578
+ throw new DecryptionError(`Failed to decrypt ${classicalEncryptedPath}: ${error instanceof Error ? error.message : String(error)}`, classicalEncryptedPath);
513
579
  }
514
580
  }
515
581
  // Fall back to unencrypted version
@@ -613,4 +679,3 @@ export function getSecureStorage() {
613
679
  }
614
680
  return globalSecureStorage;
615
681
  }
616
- //# sourceMappingURL=crypto.js.map
@@ -26,6 +26,21 @@ export interface LockOptions {
26
26
  /** Lock considered stale after this time (ms) */
27
27
  staleThreshold?: number;
28
28
  }
29
+ /**
30
+ * Single shared stale-lock threshold (L15).
31
+ *
32
+ * The async FileLock util and the synchronous shutdown-flush path in
33
+ * audit-logger.ts (writeWithSyncLock) lock the SAME audit-*.jsonl files, so they
34
+ * MUST agree on when a lock is stale. They previously diverged (900_000ms here vs
35
+ * a hardcoded 30_000ms in the sync path): the sync path could steal a lock at 30s
36
+ * that the async owner still considered live (900s), and both would then write the
37
+ * same audit file concurrently — corrupting the very log the lock protects.
38
+ *
39
+ * Unified UP to 900_000ms (15 min): losing a best-effort shutdown flush (the sync
40
+ * path only waits 10s total anyway) is strictly less bad than corrupting the audit
41
+ * log. Overridable via NLMCP_LOCK_STALE_MS.
42
+ */
43
+ export declare const STALE_LOCK_THRESHOLD_MS: number;
29
44
  /**
30
45
  * File Lock Class
31
46
  *
@@ -76,4 +91,3 @@ export declare function isLocked(filePath: string, staleThreshold?: number): boo
76
91
  * Only use when you're certain the lock is orphaned.
77
92
  */
78
93
  export declare function forceUnlock(filePath: string): boolean;
79
- //# sourceMappingURL=file-lock.d.ts.map
@@ -26,13 +26,28 @@ function parseIntegerEnv(name, fallback) {
26
26
  const parsed = Number.parseInt(raw, 10);
27
27
  return Number.isFinite(parsed) ? parsed : fallback;
28
28
  }
29
+ /**
30
+ * Single shared stale-lock threshold (L15).
31
+ *
32
+ * The async FileLock util and the synchronous shutdown-flush path in
33
+ * audit-logger.ts (writeWithSyncLock) lock the SAME audit-*.jsonl files, so they
34
+ * MUST agree on when a lock is stale. They previously diverged (900_000ms here vs
35
+ * a hardcoded 30_000ms in the sync path): the sync path could steal a lock at 30s
36
+ * that the async owner still considered live (900s), and both would then write the
37
+ * same audit file concurrently — corrupting the very log the lock protects.
38
+ *
39
+ * Unified UP to 900_000ms (15 min): losing a best-effort shutdown flush (the sync
40
+ * path only waits 10s total anyway) is strictly less bad than corrupting the audit
41
+ * log. Overridable via NLMCP_LOCK_STALE_MS.
42
+ */
43
+ export const STALE_LOCK_THRESHOLD_MS = parseIntegerEnv("NLMCP_LOCK_STALE_MS", 900000);
29
44
  /**
30
45
  * Default lock options
31
46
  */
32
47
  const DEFAULT_OPTIONS = {
33
48
  timeout: parseIntegerEnv("NLMCP_LOCK_TIMEOUT_MS", 10000),
34
49
  retryInterval: 100,
35
- staleThreshold: parseIntegerEnv("NLMCP_LOCK_STALE_MS", 900000),
50
+ staleThreshold: STALE_LOCK_THRESHOLD_MS,
36
51
  };
37
52
  /**
38
53
  * Generate unique lock ID
@@ -69,33 +84,53 @@ export class FileLock {
69
84
  }
70
85
  while (Date.now() - startTime < opts.timeout) {
71
86
  try {
72
- // Check if stale lock exists
73
- if (fs.existsSync(this.lockPath)) {
87
+ // Check for a stale or corrupted lock. Never blind-unlink by mere
88
+ // existence: a concurrent process may have replaced a stale lock with
89
+ // its own fresh one. Use compare-and-delete — re-read the lock
90
+ // immediately before unlinking and only remove the EXACT bytes we
91
+ // observed. The atomic `wx` create below is the real backstop: if the
92
+ // file is recreated between our delete and our create, it EEXISTs and
93
+ // we simply loop.
94
+ let observed = null;
95
+ try {
96
+ observed = fs.readFileSync(this.lockPath, "utf-8");
97
+ }
98
+ catch (err) {
99
+ // ENOENT (no lock present) is the common, expected case — fall through
100
+ // to the create attempt below.
101
+ if (err.code !== "ENOENT") {
102
+ log.debug(`file-lock: reading lock file in acquire: ${err instanceof Error ? err.message : String(err)}`);
103
+ }
104
+ }
105
+ if (observed !== null) {
106
+ let removable = false;
74
107
  try {
75
- const content = fs.readFileSync(this.lockPath, "utf-8");
76
- const existing = JSON.parse(content);
108
+ const existing = JSON.parse(observed);
77
109
  const age = Date.now() - existing.timestamp;
78
110
  if (age > opts.staleThreshold) {
79
- // Lock is stale, remove it
80
111
  log.warning(`🔓 Removing stale lock (age: ${Math.round(age / 1000)}s, pid: ${existing.pid})`);
81
- try {
82
- fs.unlinkSync(this.lockPath);
83
- }
84
- catch (err) {
85
- log.debug(`file-lock: removing stale lock file in acquire: ${err instanceof Error ? err.message : String(err)}`);
86
- // Ignore if another process already removed it
87
- }
112
+ removable = true;
88
113
  }
89
114
  }
90
115
  catch (err) {
91
116
  log.debug(`file-lock: reading/parsing lock file content in acquire: ${err instanceof Error ? err.message : String(err)}`);
92
- // Corrupted lock file, try to remove it
117
+ // Corrupted lock file — eligible for removal, but still only if the
118
+ // exact corrupted bytes are unchanged when we re-read below.
119
+ removable = true;
120
+ }
121
+ if (removable) {
93
122
  try {
94
- fs.unlinkSync(this.lockPath);
123
+ // Compare-and-delete: re-read and confirm the content is byte-for-byte
124
+ // identical to what we observed before unlinking, so we never delete
125
+ // a different process's freshly-written lock.
126
+ const reread = fs.readFileSync(this.lockPath, "utf-8");
127
+ if (reread === observed) {
128
+ fs.unlinkSync(this.lockPath);
129
+ }
95
130
  }
96
131
  catch (err) {
97
- log.debug(`file-lock: removing corrupted lock file in acquire: ${err instanceof Error ? err.message : String(err)}`);
98
- // Ignore
132
+ log.debug(`file-lock: compare-and-delete stale lock in acquire: ${err instanceof Error ? err.message : String(err)}`);
133
+ // Another process already changed or removed it — let the wx create decide.
99
134
  }
100
135
  }
101
136
  }
@@ -140,54 +175,28 @@ export class FileLock {
140
175
  release() {
141
176
  if (!this.acquired)
142
177
  return;
143
- const tempPath = `${this.lockPath}.${this.lockId}.release`;
144
178
  try {
145
- if (fs.existsSync(this.lockPath)) {
146
- try {
147
- const content = fs.readFileSync(this.lockPath, "utf-8");
148
- const existing = JSON.parse(content);
149
- if (existing.lockId === this.lockId) {
150
- fs.writeFileSync(tempPath, content, { mode: 0o600 });
151
- const verifiedContent = fs.readFileSync(this.lockPath, "utf-8");
152
- const verified = JSON.parse(verifiedContent);
153
- if (verified.lockId === this.lockId) {
154
- fs.renameSync(tempPath, this.lockPath);
155
- const finalContent = fs.readFileSync(this.lockPath, "utf-8");
156
- const finalLock = JSON.parse(finalContent);
157
- if (finalLock.lockId === this.lockId) {
158
- fs.unlinkSync(this.lockPath);
159
- }
160
- else {
161
- log.warning(`⚠️ Lock changed before release completed, not deleting`);
162
- }
163
- }
164
- }
165
- else {
166
- log.warning(`⚠️ Lock owned by different process, not releasing`);
167
- }
168
- }
169
- catch (err) {
170
- log.debug(`file-lock: verifying and releasing lock file in release: ${err instanceof Error ? err.message : String(err)}`);
171
- // Ignore errors during release
172
- }
179
+ // Compare-and-delete: read the lock once and only remove it if it is
180
+ // still ours. If another process validly stole the lock after the stale
181
+ // threshold, we leave it untouched rather than clobbering it (the old
182
+ // temp-file rename dance could overwrite a newer owner's lock with our
183
+ // stale content).
184
+ const content = fs.readFileSync(this.lockPath, "utf-8");
185
+ const existing = JSON.parse(content);
186
+ if (existing.lockId === this.lockId) {
187
+ fs.unlinkSync(this.lockPath);
188
+ }
189
+ else {
190
+ log.warning(`⚠️ Lock owned by different process, not releasing`);
173
191
  }
174
192
  }
175
193
  catch (err) {
176
- log.debug(`file-lock: releasing lock file in release: ${err instanceof Error ? err.message : String(err)}`);
177
- // Ignore errors during release
194
+ // ENOENT (already gone), parse errors, or a concurrent unlink nothing to do.
195
+ log.debug(`file-lock: compare-and-delete in release: ${err instanceof Error ? err.message : String(err)}`);
178
196
  }
179
197
  finally {
180
- try {
181
- if (fs.existsSync(tempPath)) {
182
- fs.unlinkSync(tempPath);
183
- }
184
- }
185
- catch (err) {
186
- log.debug(`file-lock: removing temp file in release finally block: ${err instanceof Error ? err.message : String(err)}`);
187
- // Ignore temp cleanup errors
188
- }
198
+ this.acquired = false;
189
199
  }
190
- this.acquired = false;
191
200
  }
192
201
  /**
193
202
  * Check if lock is acquired
@@ -290,4 +299,3 @@ export function forceUnlock(filePath) {
290
299
  return false;
291
300
  }
292
301
  }
293
- //# sourceMappingURL=file-lock.js.map
@@ -84,4 +84,3 @@ export declare function getPlatformInfo(): {
84
84
  supportsUnixPermissions: boolean;
85
85
  supportsWindowsACLs: boolean;
86
86
  };
87
- //# sourceMappingURL=file-permissions.d.ts.map
@@ -249,13 +249,42 @@ export function writeFileSecure(filePath, content, mode = PERMISSION_MODES.OWNER
249
249
  * @param mode - Unix permission mode (default: 0o600)
250
250
  */
251
251
  export function appendFileSecure(filePath, content, mode = PERMISSION_MODES.OWNER_READ_WRITE) {
252
- if (!fs.existsSync(filePath)) {
253
- // If file doesn't exist, create with secure permissions
254
- writeFileSecure(filePath, content, mode);
255
- }
256
- else {
257
- // File exists, just append (permissions already set)
252
+ // Ensure parent directory exists (matches writeFileSecure behaviour)
253
+ mkdirSecure(path.dirname(filePath));
254
+ if (isWindows) {
255
+ // O_NOFOLLOW / uid ownership are Unix concepts. On Windows, fall back to
256
+ // the existing create-then-ACL path used by writeFileSecure.
257
+ const existed = fs.existsSync(filePath);
258
258
  fs.appendFileSync(filePath, content);
259
+ if (!existed) {
260
+ setWindowsFilePermissions(filePath, true);
261
+ }
262
+ return;
263
+ }
264
+ // Unix: open in append mode atomically (creates if absent) with O_NOFOLLOW
265
+ // so a pre-planted symlink at filePath causes the open to fail (ELOOP)
266
+ // rather than redirecting our write to an attacker-chosen target. Avoid
267
+ // existsSync entirely — it is itself a TOCTOU. The single open both creates
268
+ // (with `mode`) and appends, closing the create/append race.
269
+ const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_APPEND | fs.constants.O_NOFOLLOW;
270
+ const fd = fs.openSync(filePath, flags, mode);
271
+ try {
272
+ // fstat the open descriptor (not the path) — symlink-safe — and confirm we
273
+ // are writing to a regular file owned by the current user before writing.
274
+ const stat = fs.fstatSync(fd);
275
+ if (!stat.isFile()) {
276
+ throw new Error(`Refusing to append: ${filePath} is not a regular file`);
277
+ }
278
+ if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
279
+ throw new Error(`Refusing to append: ${filePath} is not owned by the current user`);
280
+ }
281
+ // Re-assert secure permissions on the fd (symlink-safe). `mode` only applies
282
+ // on creation, so this also tightens any weakened pre-existing permissions.
283
+ fs.fchmodSync(fd, mode);
284
+ fs.writeSync(fd, Buffer.isBuffer(content) ? content : Buffer.from(content));
285
+ }
286
+ finally {
287
+ fs.closeSync(fd);
259
288
  }
260
289
  }
261
290
  /**
@@ -271,4 +300,3 @@ export function getPlatformInfo() {
271
300
  supportsWindowsACLs: isWindows,
272
301
  };
273
302
  }
274
- //# sourceMappingURL=file-permissions.js.map
@@ -62,4 +62,3 @@ export declare const log: {
62
62
  withContext: <T>(context: LogContext, fn: () => T) => T;
63
63
  };
64
64
  export {};
65
- //# sourceMappingURL=logger.d.ts.map
@@ -105,4 +105,3 @@ export const log = {
105
105
  dim: (msg) => logger.dim(msg),
106
106
  withContext: (context, fn) => logger.withContext(context, fn),
107
107
  };
108
- //# sourceMappingURL=logger.js.map
@@ -64,4 +64,3 @@ declare const _default: {
64
64
  waitForLatestAnswer: typeof waitForLatestAnswer;
65
65
  };
66
66
  export default _default;
67
- //# sourceMappingURL=page-utils.d.ts.map
@@ -10,21 +10,23 @@
10
10
  * Based on the Python implementation from page_utils.py
11
11
  */
12
12
  import { log } from "./logger.js";
13
+ import { validateResponse } from "./response-validator.js";
13
14
  import { RESPONSE_SELECTORS, getSelectors } from "../notebook-creation/selectors.js";
14
15
  // ============================================================================
15
16
  // Helper Functions
16
17
  // ============================================================================
17
18
  /**
18
- * Simple string hash function (for efficient comparison)
19
+ * Sanitize untrusted assistant/document text before it is returned to the
20
+ * calling model. Notebook sources are arbitrary documents, so extracted text
21
+ * is a prompt-injection conduit; route it through the shared response
22
+ * validator (same mechanism used by the ask_question handler).
19
23
  */
20
- function hashString(str) {
21
- let hash = 0;
22
- for (let i = 0; i < str.length; i++) {
23
- const char = str.charCodeAt(i);
24
- hash = (hash << 5) - hash + char;
25
- hash = hash & hash; // Convert to 32bit integer
24
+ async function sanitizeExtractedText(text) {
25
+ const validation = await validateResponse(text);
26
+ if (!validation.safe) {
27
+ log.warning("🛡️ Suspicious content detected in extracted page text — sanitized");
26
28
  }
27
- return hash;
29
+ return validation.sanitized;
28
30
  }
29
31
  // ============================================================================
30
32
  // Main Functions
@@ -34,7 +36,8 @@ function hashString(str) {
34
36
  * Returns null if no response found
35
37
  */
36
38
  export async function snapshotLatestResponse(page) {
37
- return await extractLatestText(page, new Set(), false, 0);
39
+ const text = await extractLatestText(page, new Set(), false, 0);
40
+ return text === null ? null : await sanitizeExtractedText(text);
38
41
  }
39
42
  /**
40
43
  * Snapshot ALL existing assistant response texts
@@ -52,7 +55,7 @@ export async function snapshotAllResponses(page) {
52
55
  if (textElement) {
53
56
  const text = await textElement.innerText();
54
57
  if (text && text.trim()) {
55
- allTexts.push(text.trim());
58
+ allTexts.push(await sanitizeExtractedText(text.trim()));
56
59
  }
57
60
  }
58
61
  }
@@ -121,15 +124,17 @@ export async function waitForLatestAnswer(page, options = {}) {
121
124
  const { question = "", timeoutMs = 120000, pollIntervalMs = 1000, ignoreTexts = [], debug = false, } = options;
122
125
  const deadline = Date.now() + timeoutMs;
123
126
  const sanitizedQuestion = question.trim().toLowerCase();
124
- // Track ALL known texts as HASHES (memory efficient!)
125
- const knownHashes = new Set();
127
+ // Track ALL known texts by their trimmed string value. Storing the full
128
+ // strings (rather than a 32-bit hash) avoids hash collisions that could
129
+ // cause a genuinely new answer to be treated as "seen" and hang to timeout.
130
+ const knownTexts = new Set();
126
131
  for (const text of ignoreTexts) {
127
132
  if (typeof text === "string" && text.trim()) {
128
- knownHashes.add(hashString(text.trim()));
133
+ knownTexts.add(text.trim());
129
134
  }
130
135
  }
131
136
  if (debug) {
132
- log.debug(`🔍 [DEBUG] Waiting for NEW answer. Ignoring ${knownHashes.size} known responses`);
137
+ log.debug(`🔍 [DEBUG] Waiting for NEW answer. Ignoring ${knownTexts.size} known responses`);
133
138
  }
134
139
  let pollCount = 0;
135
140
  let lastCandidate = null;
@@ -156,7 +161,7 @@ export async function waitForLatestAnswer(page, options = {}) {
156
161
  // Ignore errors checking thinking state
157
162
  }
158
163
  // Extract latest NEW text
159
- const candidate = await extractLatestText(page, knownHashes, debug, pollCount);
164
+ const candidate = await extractLatestText(page, knownTexts, debug, pollCount);
160
165
  if (candidate) {
161
166
  const normalized = candidate.trim();
162
167
  if (normalized) {
@@ -166,7 +171,7 @@ export async function waitForLatestAnswer(page, options = {}) {
166
171
  if (debug) {
167
172
  log.debug("🔍 [DEBUG] Found question echo, ignoring");
168
173
  }
169
- knownHashes.add(hashString(normalized)); // Mark as seen
174
+ knownTexts.add(normalized); // Mark as seen
170
175
  await page.waitForTimeout(pollIntervalMs);
171
176
  continue;
172
177
  }
@@ -193,7 +198,7 @@ export async function waitForLatestAnswer(page, options = {}) {
193
198
  if (debug) {
194
199
  log.debug(`✅ [DEBUG] Returning stable answer (${normalized.length} chars)`);
195
200
  }
196
- return normalized;
201
+ return await sanitizeExtractedText(normalized);
197
202
  }
198
203
  }
199
204
  }
@@ -206,31 +211,31 @@ export async function waitForLatestAnswer(page, options = {}) {
206
211
  }
207
212
  /**
208
213
  * Extract the latest NEW response text from the page
209
- * Uses hash-based comparison for efficiency
214
+ * Compares against the set of already-seen trimmed response strings
210
215
  *
211
216
  * @param page Playwright page instance
212
- * @param knownHashes Set of hashes of already-seen response texts
217
+ * @param knownTexts Set of already-seen (trimmed) response texts
213
218
  * @param debug Enable debug logging
214
219
  * @param pollCount Current poll number (for conditional logging)
215
220
  * @returns First NEW response text found, or null
216
221
  */
217
- async function extractLatestText(page, knownHashes, debug, pollCount) {
222
+ async function extractLatestText(page, knownTexts, debug, pollCount) {
218
223
  // Try the primary selector first (most specific for NotebookLM)
219
224
  const primarySelector = ".to-user-container";
220
225
  try {
221
226
  const containers = await page.$$(primarySelector);
222
227
  const totalContainers = containers.length;
223
228
  // Early exit if no new containers possible
224
- if (totalContainers <= knownHashes.size) {
229
+ if (totalContainers <= knownTexts.size) {
225
230
  if (debug && pollCount % 5 === 0) {
226
- log.dim(`⏭️ [EXTRACT] No new containers (${totalContainers} total, ${knownHashes.size} known)`);
231
+ log.dim(`⏭️ [EXTRACT] No new containers (${totalContainers} total, ${knownTexts.size} known)`);
227
232
  }
228
233
  return null;
229
234
  }
230
235
  if (containers.length > 0) {
231
236
  // Only log every 5th poll to reduce noise
232
237
  if (debug && pollCount % 5 === 0) {
233
- log.dim(`🔍 [EXTRACT] Scanning ${totalContainers} containers (${knownHashes.size} known)`);
238
+ log.dim(`🔍 [EXTRACT] Scanning ${totalContainers} containers (${knownTexts.size} known)`);
234
239
  }
235
240
  let skipped = 0;
236
241
  let empty = 0;
@@ -242,9 +247,9 @@ async function extractLatestText(page, knownHashes, debug, pollCount) {
242
247
  if (textElement) {
243
248
  const text = await textElement.innerText();
244
249
  if (text && text.trim()) {
245
- // Hash-based comparison (faster & less memory)
246
- const textHash = hashString(text.trim());
247
- if (!knownHashes.has(textHash)) {
250
+ // Exact-string comparison against seen responses (no hash
251
+ // collisions that could drop a genuinely new answer).
252
+ if (!knownTexts.has(text.trim())) {
248
253
  log.success(`✅ [EXTRACT] Found NEW text in container[${idx}]: ${text.trim().length} chars`);
249
254
  return text.trim();
250
255
  }
@@ -305,7 +310,7 @@ async function extractLatestText(page, knownHashes, debug, pollCount) {
305
310
  container = element;
306
311
  }
307
312
  const text = await container.innerText();
308
- if (text && text.trim() && !knownHashes.has(hashString(text.trim()))) {
313
+ if (text && text.trim() && !knownTexts.has(text.trim())) {
309
314
  return text.trim();
310
315
  }
311
316
  }
@@ -424,4 +429,3 @@ export default {
424
429
  countResponseElements,
425
430
  waitForLatestAnswer,
426
431
  };
427
- //# sourceMappingURL=page-utils.js.map
@@ -95,4 +95,3 @@ export declare function getResponseValidator(): ResponseValidator;
95
95
  * Convenience function to validate a response
96
96
  */
97
97
  export declare function validateResponse(response: string): Promise<ValidationResult>;
98
- //# sourceMappingURL=response-validator.d.ts.map