shieldcortex 4.31.2 → 4.32.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 (346) hide show
  1. package/README.md +78 -2
  2. package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
  3. package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
  4. package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
  5. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
  6. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
  7. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +2 -2
  13. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +1 -1
  14. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  15. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  17. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  20. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.html +1 -1
  21. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.rsc +1 -1
  22. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk/admin/__PAGE__.segment.rsc +1 -1
  23. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk/admin.segment.rsc +1 -1
  24. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  25. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_full.segment.rsc +1 -1
  26. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  27. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_index.segment.rsc +1 -1
  28. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
  29. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.html +1 -1
  30. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.rsc +1 -1
  31. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk/cloud/__PAGE__.segment.rsc +1 -1
  32. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk/cloud.segment.rsc +1 -1
  33. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  34. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_full.segment.rsc +1 -1
  35. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_head.segment.rsc +1 -1
  36. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_index.segment.rsc +1 -1
  37. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_tree.segment.rsc +1 -1
  38. package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
  39. package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +1 -1
  40. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  41. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +1 -1
  42. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
  43. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +1 -1
  44. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  45. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.html +1 -1
  46. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.rsc +1 -1
  47. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory/capture/__PAGE__.segment.rsc +1 -1
  48. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory/capture.segment.rsc +1 -1
  49. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  50. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  51. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_full.segment.rsc +1 -1
  52. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_head.segment.rsc +1 -1
  53. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_index.segment.rsc +1 -1
  54. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_tree.segment.rsc +1 -1
  55. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.html +1 -1
  56. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.rsc +1 -1
  57. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory/graph/__PAGE__.segment.rsc +1 -1
  58. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory/graph.segment.rsc +1 -1
  59. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  60. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  61. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_full.segment.rsc +1 -1
  62. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_head.segment.rsc +1 -1
  63. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_index.segment.rsc +1 -1
  64. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_tree.segment.rsc +1 -1
  65. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.html +1 -1
  66. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.rsc +1 -1
  67. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory/recall/__PAGE__.segment.rsc +1 -1
  68. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory/recall.segment.rsc +1 -1
  69. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  70. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  71. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_full.segment.rsc +1 -1
  72. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_head.segment.rsc +1 -1
  73. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_index.segment.rsc +1 -1
  74. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_tree.segment.rsc +1 -1
  75. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.html +2 -2
  76. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.rsc +1 -1
  77. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory/replay/__PAGE__.segment.rsc +1 -1
  78. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory/replay.segment.rsc +1 -1
  79. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  80. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  81. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_full.segment.rsc +1 -1
  82. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_head.segment.rsc +1 -1
  83. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_index.segment.rsc +1 -1
  84. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_tree.segment.rsc +1 -1
  85. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.html +1 -1
  86. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.rsc +1 -1
  87. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory/review/__PAGE__.segment.rsc +1 -1
  88. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory/review.segment.rsc +1 -1
  89. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  90. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  91. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_full.segment.rsc +1 -1
  92. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_head.segment.rsc +1 -1
  93. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_index.segment.rsc +1 -1
  94. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_tree.segment.rsc +1 -1
  95. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.html +1 -1
  96. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.rsc +1 -1
  97. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory/timeline/__PAGE__.segment.rsc +1 -1
  98. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory/timeline.segment.rsc +1 -1
  99. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  100. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  101. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_full.segment.rsc +1 -1
  102. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_head.segment.rsc +1 -1
  103. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_index.segment.rsc +1 -1
  104. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_tree.segment.rsc +1 -1
  105. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.html +2 -2
  106. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.rsc +1 -1
  107. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk/memory/__PAGE__.segment.rsc +1 -1
  108. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  109. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  110. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_full.segment.rsc +1 -1
  111. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_head.segment.rsc +1 -1
  112. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_index.segment.rsc +1 -1
  113. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_tree.segment.rsc +1 -1
  114. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.html +2 -2
  115. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.rsc +1 -1
  116. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk/overview/__PAGE__.segment.rsc +1 -1
  117. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk/overview.segment.rsc +1 -1
  118. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  119. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_full.segment.rsc +1 -1
  120. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_head.segment.rsc +1 -1
  121. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_index.segment.rsc +1 -1
  122. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_tree.segment.rsc +1 -1
  123. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.html +1 -1
  124. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.rsc +1 -1
  125. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection/audit/__PAGE__.segment.rsc +1 -1
  126. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection/audit.segment.rsc +1 -1
  127. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  128. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  129. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_full.segment.rsc +1 -1
  130. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_head.segment.rsc +1 -1
  131. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_index.segment.rsc +1 -1
  132. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_tree.segment.rsc +1 -1
  133. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.html +1 -1
  134. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.rsc +1 -1
  135. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection/intercepts/__PAGE__.segment.rsc +1 -1
  136. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection/intercepts.segment.rsc +1 -1
  137. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  138. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  139. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_full.segment.rsc +1 -1
  140. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_head.segment.rsc +1 -1
  141. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_index.segment.rsc +1 -1
  142. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_tree.segment.rsc +1 -1
  143. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.html +1 -1
  144. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.rsc +1 -1
  145. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection/iron-dome/__PAGE__.segment.rsc +1 -1
  146. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection/iron-dome.segment.rsc +1 -1
  147. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  148. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  149. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_full.segment.rsc +1 -1
  150. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_head.segment.rsc +1 -1
  151. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_index.segment.rsc +1 -1
  152. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_tree.segment.rsc +1 -1
  153. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.html +1 -1
  154. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.rsc +1 -1
  155. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection/policies/__PAGE__.segment.rsc +1 -1
  156. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection/policies.segment.rsc +1 -1
  157. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  158. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  159. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_full.segment.rsc +1 -1
  160. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_head.segment.rsc +1 -1
  161. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_index.segment.rsc +1 -1
  162. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_tree.segment.rsc +1 -1
  163. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.html +1 -1
  164. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.rsc +1 -1
  165. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection/quarantine/__PAGE__.segment.rsc +1 -1
  166. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection/quarantine.segment.rsc +1 -1
  167. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  168. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  169. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_full.segment.rsc +1 -1
  170. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_head.segment.rsc +1 -1
  171. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_index.segment.rsc +1 -1
  172. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_tree.segment.rsc +1 -1
  173. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.html +2 -2
  174. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.rsc +1 -1
  175. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk/protection/__PAGE__.segment.rsc +1 -1
  176. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  177. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  178. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_full.segment.rsc +1 -1
  179. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_head.segment.rsc +1 -1
  180. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_index.segment.rsc +1 -1
  181. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_tree.segment.rsc +1 -1
  182. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.html +2 -2
  183. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.rsc +1 -1
  184. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk/settings/__PAGE__.segment.rsc +1 -1
  185. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk/settings.segment.rsc +1 -1
  186. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  187. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  188. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  189. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  190. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  191. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.html +1 -1
  192. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.rsc +1 -1
  193. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain/xray/__PAGE__.segment.rsc +1 -1
  194. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain/xray.segment.rsc +1 -1
  195. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain.segment.rsc +1 -1
  196. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  197. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_full.segment.rsc +1 -1
  198. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_head.segment.rsc +1 -1
  199. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_index.segment.rsc +1 -1
  200. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_tree.segment.rsc +1 -1
  201. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.html +1 -1
  202. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.rsc +1 -1
  203. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk/supply-chain/__PAGE__.segment.rsc +1 -1
  204. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk/supply-chain.segment.rsc +1 -1
  205. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  206. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_full.segment.rsc +1 -1
  207. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_head.segment.rsc +1 -1
  208. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_index.segment.rsc +1 -1
  209. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_tree.segment.rsc +1 -1
  210. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.html +2 -2
  211. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.rsc +1 -1
  212. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk/xray/__PAGE__.segment.rsc +1 -1
  213. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk/xray.segment.rsc +1 -1
  214. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  215. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_full.segment.rsc +1 -1
  216. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_head.segment.rsc +1 -1
  217. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_index.segment.rsc +1 -1
  218. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_tree.segment.rsc +1 -1
  219. package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +2 -2
  220. package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
  221. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
  222. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
  223. package/dist/api/control.d.ts +2 -0
  224. package/dist/api/control.js +119 -2
  225. package/dist/api/routes/memories.js +19 -14
  226. package/dist/api/routes/system.js +2 -3
  227. package/dist/api/visualization-server.d.ts +13 -1
  228. package/dist/api/visualization-server.js +57 -1
  229. package/dist/audit/env-scanner.js +5 -2
  230. package/dist/audit/index.d.ts +4 -1
  231. package/dist/audit/index.js +2 -1
  232. package/dist/audit/mcp-config-scanner.d.ts +23 -0
  233. package/dist/audit/mcp-config-scanner.js +110 -0
  234. package/dist/audit/mcp-tools-scanner.d.ts +112 -0
  235. package/dist/audit/mcp-tools-scanner.js +299 -0
  236. package/dist/cli/audit.d.ts +1 -0
  237. package/dist/cli/audit.js +12 -1
  238. package/dist/cli/mcp.d.ts +13 -0
  239. package/dist/cli/mcp.js +0 -0
  240. package/dist/cli/remember.d.ts +75 -0
  241. package/dist/cli/remember.js +195 -0
  242. package/dist/cloud/config.d.ts +23 -1
  243. package/dist/cloud/config.js +453 -193
  244. package/dist/cloud/quarantine-sync.d.ts +12 -2
  245. package/dist/cloud/quarantine-sync.js +28 -6
  246. package/dist/cloud/sync-queue.d.ts +21 -2
  247. package/dist/cloud/sync-queue.js +124 -29
  248. package/dist/database/better-sqlite3-guard.d.ts +21 -2
  249. package/dist/database/better-sqlite3-guard.js +29 -5
  250. package/dist/database/init.js +68 -16
  251. package/dist/database/inline-schema.js +35 -1
  252. package/dist/database/migrations.js +104 -8
  253. package/dist/database/schema.sql +39 -1
  254. package/dist/defence/audit/queries.d.ts +10 -2
  255. package/dist/defence/audit/queries.js +30 -4
  256. package/dist/defence/audit/retention.d.ts +50 -0
  257. package/dist/defence/audit/retention.js +161 -0
  258. package/dist/defence/credential-leak/entropy.d.ts +11 -0
  259. package/dist/defence/credential-leak/entropy.js +27 -0
  260. package/dist/defence/credential-leak/index.js +27 -1
  261. package/dist/defence/credential-leak/patterns.d.ts +9 -0
  262. package/dist/defence/credential-leak/patterns.js +21 -0
  263. package/dist/defence/custom-patterns/store.js +8 -1
  264. package/dist/defence/custom-rules/store.d.ts +18 -0
  265. package/dist/defence/custom-rules/store.js +63 -0
  266. package/dist/defence/firewall/confusables.d.ts +30 -0
  267. package/dist/defence/firewall/confusables.js +87 -0
  268. package/dist/defence/firewall/encoding-detector.js +23 -9
  269. package/dist/defence/firewall/index.d.ts +11 -1
  270. package/dist/defence/firewall/index.js +34 -1
  271. package/dist/defence/firewall/instruction-detector.js +18 -7
  272. package/dist/defence/firewall/markdown-image-detector.d.ts +34 -0
  273. package/dist/defence/firewall/markdown-image-detector.js +83 -0
  274. package/dist/defence/fragmentation/entity-extractor.js +17 -6
  275. package/dist/defence/index.d.ts +5 -0
  276. package/dist/defence/index.js +8 -0
  277. package/dist/defence/iron-dome/index.js +7 -1
  278. package/dist/defence/pipeline.js +62 -10
  279. package/dist/defence/scan-windows.d.ts +41 -0
  280. package/dist/defence/scan-windows.js +61 -0
  281. package/dist/defence/semantic/attack-corpus.d.ts +22 -0
  282. package/dist/defence/semantic/attack-corpus.js +75 -0
  283. package/dist/defence/semantic/index.d.ts +67 -0
  284. package/dist/defence/semantic/index.js +138 -0
  285. package/dist/defence/skill-scanner/deep-scan.js +35 -15
  286. package/dist/defence/skill-scanner/patterns.d.ts +1 -1
  287. package/dist/defence/skill-scanner/patterns.js +8 -7
  288. package/dist/defence/tool-response-scanner.d.ts +21 -5
  289. package/dist/defence/tool-response-scanner.js +111 -22
  290. package/dist/defence/types.d.ts +11 -1
  291. package/dist/index.d.ts +29 -0
  292. package/dist/index.js +104 -20
  293. package/dist/memory/consolidate.js +1 -1
  294. package/dist/memory/decay.js +3 -1
  295. package/dist/memory/embedding.d.ts +18 -2
  296. package/dist/memory/embedding.js +32 -11
  297. package/dist/memory/expiry.js +1 -1
  298. package/dist/memory/search-recall.js +107 -49
  299. package/dist/memory/search.d.ts +19 -3
  300. package/dist/memory/search.js +25 -10
  301. package/dist/memory/store.d.ts +13 -2
  302. package/dist/memory/store.js +115 -11
  303. package/dist/scan-only.d.ts +64 -0
  304. package/dist/scan-only.js +173 -0
  305. package/dist/server.d.ts +5 -0
  306. package/dist/server.js +6 -4
  307. package/dist/setup/claude-md.js +39 -34
  308. package/dist/setup/codex.js +9 -2
  309. package/dist/setup/copilot.js +160 -47
  310. package/dist/setup/json-config.d.ts +99 -0
  311. package/dist/setup/json-config.js +167 -0
  312. package/dist/setup/migrate.js +1 -1
  313. package/dist/setup/settings-hooks.js +8 -13
  314. package/dist/setup/uninstall.js +1 -21
  315. package/dist/tools/context.d.ts +8 -8
  316. package/dist/tools/forget.d.ts +9 -8
  317. package/dist/tools/forget.js +17 -4
  318. package/dist/tools/recall.d.ts +13 -13
  319. package/dist/tools/remember.d.ts +16 -16
  320. package/dist/tools/remember.js +19 -8
  321. package/dist/worker/brain-worker.d.ts +1 -0
  322. package/dist/worker/brain-worker.js +79 -16
  323. package/dist/worker/types.d.ts +8 -0
  324. package/dist/worker/types.js +8 -0
  325. package/dist/xray/dir-scanner.d.ts +18 -0
  326. package/dist/xray/dir-scanner.js +23 -1
  327. package/dist/xray/file-scanner.js +16 -1
  328. package/dist/xray/findings-store.js +9 -1
  329. package/dist/xray/index.d.ts +2 -0
  330. package/dist/xray/index.js +10 -1
  331. package/dist/xray/npm-inspector.d.ts +31 -0
  332. package/dist/xray/npm-inspector.js +135 -29
  333. package/dist/xray/patterns.d.ts +1 -1
  334. package/dist/xray/patterns.js +20 -23
  335. package/dist/xray/sarif.d.ts +78 -0
  336. package/dist/xray/sarif.js +166 -0
  337. package/dist/xray/watch.d.ts +1 -0
  338. package/dist/xray/watch.js +10 -1
  339. package/hooks/openclaw/cortex-memory/handler.ts +122 -18
  340. package/hooks/openclaw/cortex-memory/runtime.mjs +10 -4
  341. package/package.json +10 -3
  342. package/dist/memory/embedding-cache.d.ts +0 -20
  343. package/dist/memory/embedding-cache.js +0 -91
  344. /package/dashboard/.next/standalone/dashboard/.next/static/{_j4TeMpss-w79QtNNWqZw → Ox9scglBehbbCk7DD8-Vn}/_buildManifest.js +0 -0
  345. /package/dashboard/.next/standalone/dashboard/.next/static/{_j4TeMpss-w79QtNNWqZw → Ox9scglBehbbCk7DD8-Vn}/_clientMiddlewareManifest.json +0 -0
  346. /package/dashboard/.next/standalone/dashboard/.next/static/{_j4TeMpss-w79QtNNWqZw → Ox9scglBehbbCk7DD8-Vn}/_ssgManifest.js +0 -0
@@ -1,4 +1,4 @@
1
- import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from 'fs';
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, statSync, renameSync, rmSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir, hostname } from 'os';
4
4
  import { randomUUID, randomBytes, createHmac, timingSafeEqual } from 'crypto';
@@ -42,6 +42,15 @@ const DEFAULT_REVIEW_COPILOT_CONFIG = {
42
42
  // Cache to avoid repeated file reads
43
43
  let cachedConfig = null;
44
44
  let cachedConfigFile = null;
45
+ // Raw-config cache keyed on (file path, mtimeMs). The defence pipeline reads
46
+ // the config on every scan via getDefenceMode() and ~30 other accessors all
47
+ // funnel through readRawConfig(); without this each call re-read + re-parsed +
48
+ // re-HMAC-verified the file. The mtime key means an external write (any other
49
+ // process) is picked up on its next stat, while a hot loop in one process pays
50
+ // for the read+parse+verify exactly once per actual file change.
51
+ let cachedRawConfig = null;
52
+ let cachedRawConfigFile = null;
53
+ let cachedRawConfigMtimeMs = null;
45
54
  // ── Config Integrity (HMAC) ──────────────────────────────
46
55
  let cachedIntegrityKey = null;
47
56
  let cachedIntegrityKeyFile = null;
@@ -74,6 +83,25 @@ function getIntegrityKey() {
74
83
  function signConfig(jsonContent) {
75
84
  return createHmac('sha256', getIntegrityKey()).update(jsonContent, 'utf-8').digest('hex');
76
85
  }
86
+ /**
87
+ * The exact byte string the HMAC is computed over for the embedded-`_sig`
88
+ * scheme. Defined once and used by BOTH the write path and the verify path so
89
+ * they can never diverge: the canonical body is the config object with `_sig`
90
+ * stripped, serialised with `JSON.stringify(rest, null, 2)`. Whatever the
91
+ * writer signs, the verifier recomputes over the identical bytes.
92
+ */
93
+ function canonicalBodyForSig(obj) {
94
+ const rest = { ...obj };
95
+ delete rest._sig;
96
+ return JSON.stringify(rest, null, 2);
97
+ }
98
+ function constantTimeEqualHex(storedSig, computedSig) {
99
+ const a = Buffer.from(storedSig, 'utf-8');
100
+ const b = Buffer.from(computedSig, 'utf-8');
101
+ if (a.length !== b.length)
102
+ return false;
103
+ return timingSafeEqual(a, b);
104
+ }
77
105
  function writeConfigSignature(jsonContent) {
78
106
  const sig = signConfig(jsonContent);
79
107
  const sigFile = getSigFile();
@@ -83,21 +111,41 @@ function writeConfigSignature(jsonContent) {
83
111
  }
84
112
  catch { /* best-effort */ }
85
113
  }
86
- function verifyConfigIntegrity(jsonContent) {
114
+ /**
115
+ * Verify a parsed config object's integrity.
116
+ *
117
+ * Two on-disk formats are supported for backward compatibility:
118
+ * - **Embedded (new, v4.32+):** the object carries a top-level `_sig` field;
119
+ * verify it against the HMAC of `canonicalBodyForSig(obj)`.
120
+ * - **Legacy (pre-v4.32):** no `_sig` field — the signature lives in a
121
+ * separate `.config-sig` file, computed over the EXACT file bytes
122
+ * (`rawFileContent`). This is the original scheme; we keep reading it so an
123
+ * install that hasn't been rewritten yet never false-tampers. The next
124
+ * write upgrades it to the embedded format.
125
+ *
126
+ * `rawFileContent` is the literal bytes read from disk (needed for the legacy
127
+ * whole-file signature). On a missing legacy sig file we adopt the current
128
+ * content as trusted (first run after upgrade), matching prior behaviour.
129
+ */
130
+ function verifyConfigIntegrity(parsed, rawFileContent) {
87
131
  try {
132
+ if (typeof parsed._sig === 'string') {
133
+ // Embedded scheme: recompute over the canonical body (object minus _sig).
134
+ const computed = signConfig(canonicalBodyForSig(parsed));
135
+ return constantTimeEqualHex(parsed._sig, computed);
136
+ }
137
+ // Legacy scheme: separate .config-sig file signed over the whole file.
88
138
  const sigFile = getSigFile();
89
139
  if (!existsSync(sigFile)) {
90
- // First run after upgrade — create signature, don't flag tamper
91
- writeConfigSignature(jsonContent);
140
+ // First run after upgrade with no sig yet adopt as trusted, write a
141
+ // legacy sig so a subsequent read (before the next write upgrades it)
142
+ // still verifies. Matches the prior behaviour to avoid a false tamper.
143
+ writeConfigSignature(rawFileContent);
92
144
  return true;
93
145
  }
94
146
  const storedSig = readFileSync(sigFile, 'utf-8').trim();
95
- const computedSig = signConfig(jsonContent);
96
- const a = Buffer.from(storedSig, 'utf-8');
97
- const b = Buffer.from(computedSig, 'utf-8');
98
- if (a.length !== b.length)
99
- return false;
100
- return timingSafeEqual(a, b);
147
+ const computedSig = signConfig(rawFileContent);
148
+ return constantTimeEqualHex(storedSig, computedSig);
101
149
  }
102
150
  catch {
103
151
  return false;
@@ -111,55 +159,42 @@ export function getCloudConfig() {
111
159
  const configFile = getConfigFile();
112
160
  if (cachedConfig && cachedConfigFile === configFile)
113
161
  return cachedConfig;
114
- try {
115
- if (!existsSync(configFile)) {
116
- return { cloudApiKey: null, cloudBaseUrl: DEFAULT_BASE_URL, cloudEnabled: false };
117
- }
118
- const raw = JSON.parse(readFileSync(configFile, 'utf-8'));
119
- cachedConfig = {
120
- cloudApiKey: raw.cloudApiKey ?? null,
121
- cloudBaseUrl: raw.cloudBaseUrl ?? DEFAULT_BASE_URL,
122
- cloudEnabled: raw.cloudEnabled ?? false,
123
- };
124
- cachedConfigFile = configFile;
125
- return cachedConfig;
126
- }
127
- catch {
128
- return { cloudApiKey: null, cloudBaseUrl: DEFAULT_BASE_URL, cloudEnabled: false };
129
- }
162
+ // Route through the shared, mtime-cached, integrity-verified read so the
163
+ // cloud config reflects the same view (and `_sig` stripping) as every other
164
+ // accessor no second bespoke parse path that could drift.
165
+ const raw = readRawConfig();
166
+ cachedConfig = {
167
+ cloudApiKey: raw.cloudApiKey ?? null,
168
+ cloudBaseUrl: raw.cloudBaseUrl ?? DEFAULT_BASE_URL,
169
+ cloudEnabled: raw.cloudEnabled ?? false,
170
+ };
171
+ cachedConfigFile = configFile;
172
+ return cachedConfig;
130
173
  }
131
174
  export function setCloudConfig(updates) {
132
- let existing = {};
133
- try {
134
- const configFile = getConfigFile();
135
- if (existsSync(configFile)) {
136
- existing = JSON.parse(readFileSync(configFile, 'utf-8'));
137
- }
138
- }
139
- catch {
140
- // Start fresh if parse fails
141
- }
142
- if (updates.cloudApiKey !== undefined)
143
- existing.cloudApiKey = updates.cloudApiKey;
144
- if (updates.cloudBaseUrl !== undefined)
145
- existing.cloudBaseUrl = updates.cloudBaseUrl;
146
- if (updates.cloudEnabled !== undefined)
147
- existing.cloudEnabled = updates.cloudEnabled;
148
- const configDir = getConfigDir();
149
- const configFile = getConfigFile();
150
- mkdirSync(configDir, { recursive: true });
151
- const content = JSON.stringify(existing, null, 2) + '\n';
152
- writeFileSync(configFile, content);
153
- writeConfigSignature(content);
154
- // Invalidate cache
175
+ // Read-modify-write through mutateRawConfig: preserves every other field
176
+ // (defenceMode, deviceId, sync controls…) and writes atomically with the
177
+ // embedded `_sig`. On a corrupt/unreadable file we do NOT silently start
178
+ // fresh — that would wipe a credential the user is still relying on; the
179
+ // helper throws so the caller surfaces the problem rather than losing data.
180
+ mutateRawConfig((existing) => {
181
+ if (updates.cloudApiKey !== undefined)
182
+ existing.cloudApiKey = updates.cloudApiKey;
183
+ if (updates.cloudBaseUrl !== undefined)
184
+ existing.cloudBaseUrl = updates.cloudBaseUrl;
185
+ if (updates.cloudEnabled !== undefined)
186
+ existing.cloudEnabled = updates.cloudEnabled;
187
+ });
188
+ // Invalidate the derived cloud-config cache (writeRawConfig already cleared
189
+ // the raw cache and tamper flag).
155
190
  cachedConfig = null;
156
191
  cachedConfigFile = null;
157
- configTampered = false;
158
192
  }
159
193
  /** Reset the in-memory cache (useful for testing) */
160
194
  export function clearCloudConfigCache() {
161
195
  cachedConfig = null;
162
196
  cachedConfigFile = null;
197
+ invalidateRawConfigCache();
163
198
  }
164
199
  function normalizeProjectList(value) {
165
200
  if (!Array.isArray(value))
@@ -170,19 +205,31 @@ function normalizeProjectList(value) {
170
205
  .filter(Boolean))].sort((a, b) => a.localeCompare(b));
171
206
  }
172
207
  export function getCloudSyncControls() {
173
- const raw = readRawConfig();
208
+ // This is a READER. On a parse failure we must return the in-memory defaults
209
+ // and NOT write — readRawConfigState preserves `parseFailed` so the one-shot
210
+ // migration below never runs against a `{}` derived from a corrupt file
211
+ // (which would persist a near-empty, signed config and wipe cloudApiKey).
212
+ const { data: raw, parseFailed } = readRawConfigState();
174
213
  // One-shot v4.27 migration: pre-upgrade configs with cloud sync enabled
175
214
  // were silently shipping CONFIDENTIAL+ content because `excludeSensitive`
176
215
  // defaulted to false. Detect those configs (cloud on, no explicit setting,
177
216
  // no prior migration stamp) and rewrite them to the new safe default. If
178
217
  // the user later opts back in via `--cloud-include-sensitive`, that writes
179
218
  // `cloudSyncExcludeSensitive: false` explicitly and this branch is skipped.
180
- if (raw.cloudEnabled === true &&
219
+ // Skip entirely on parseFailed: never write through a reader path.
220
+ if (!parseFailed &&
221
+ raw.cloudEnabled === true &&
181
222
  typeof raw.cloudSyncExcludeSensitive !== 'boolean' &&
182
223
  typeof raw.cloudSyncDefaultsMigratedAt !== 'string') {
224
+ // Route the migration write through the guarded helper (skip-policy: a
225
+ // reader must never throw). The earlier !parseFailed guard makes the skip
226
+ // path unreachable here, but going through mutateRawConfig keeps the
227
+ // single-source-of-truth invariant: no bare writeRawConfig outside it.
228
+ mutateRawConfig((m) => {
229
+ m.cloudSyncExcludeSensitive = true;
230
+ m.cloudSyncDefaultsMigratedAt = new Date().toISOString();
231
+ }, 'skip');
183
232
  raw.cloudSyncExcludeSensitive = true;
184
- raw.cloudSyncDefaultsMigratedAt = new Date().toISOString();
185
- writeRawConfig(raw);
186
233
  }
187
234
  const projectMode = raw.cloudSyncProjectMode;
188
235
  const contentMode = raw.cloudSyncContentMode;
@@ -198,16 +245,16 @@ export function getCloudSyncControls() {
198
245
  };
199
246
  }
200
247
  export function setCloudSyncControls(updates) {
201
- const raw = readRawConfig();
202
- if (updates.projectMode !== undefined)
203
- raw.cloudSyncProjectMode = updates.projectMode;
204
- if (updates.projects !== undefined)
205
- raw.cloudSyncProjects = normalizeProjectList(updates.projects);
206
- if (updates.contentMode !== undefined)
207
- raw.cloudSyncContentMode = updates.contentMode;
208
- if (updates.excludeSensitive !== undefined)
209
- raw.cloudSyncExcludeSensitive = updates.excludeSensitive;
210
- writeRawConfig(raw);
248
+ mutateRawConfig((raw) => {
249
+ if (updates.projectMode !== undefined)
250
+ raw.cloudSyncProjectMode = updates.projectMode;
251
+ if (updates.projects !== undefined)
252
+ raw.cloudSyncProjects = normalizeProjectList(updates.projects);
253
+ if (updates.contentMode !== undefined)
254
+ raw.cloudSyncContentMode = updates.contentMode;
255
+ if (updates.excludeSensitive !== undefined)
256
+ raw.cloudSyncExcludeSensitive = updates.excludeSensitive;
257
+ });
211
258
  }
212
259
  export function shouldSyncProject(project, controls = getCloudSyncControls()) {
213
260
  const normalized = (project ?? '').trim();
@@ -222,70 +269,211 @@ export function isSensitiveLevel(level) {
222
269
  const normalized = level.trim().toUpperCase();
223
270
  return normalized.length > 0 && normalized !== 'PUBLIC' && normalized !== 'INTERNAL';
224
271
  }
225
- // ── Trusted Skills ──────────────────────────────────────
226
- export function readRawConfig() {
272
+ /**
273
+ * Read + verify the raw config, distinguishing "file absent/empty" (safe to
274
+ * write) from "file present but unparseable" (must stay read-only). Backed by
275
+ * an mtime cache so a hot path (per-scan getDefenceMode) doesn't re-read,
276
+ * re-parse and re-HMAC the file on every call.
277
+ */
278
+ function readRawConfigState() {
279
+ const configFile = getConfigFile();
280
+ // mtime cache: if the file is unchanged since the last successful read,
281
+ // return the cached parsed object without touching disk again.
282
+ try {
283
+ const mtimeMs = statSync(configFile).mtimeMs;
284
+ if (cachedRawConfig !== null &&
285
+ cachedRawConfigFile === configFile &&
286
+ cachedRawConfigMtimeMs === mtimeMs) {
287
+ // Return a shallow copy so callers can mutate freely without poisoning
288
+ // the cache (accessors push to arrays / set fields before writing back).
289
+ return { data: { ...cachedRawConfig }, parseFailed: false };
290
+ }
291
+ }
292
+ catch {
293
+ // stat failed (file likely absent) — fall through to the real read.
294
+ }
227
295
  try {
228
- const configFile = getConfigFile();
229
296
  if (existsSync(configFile)) {
297
+ // Capture mtime BEFORE reading the bytes. The cache key must describe the
298
+ // exact bytes we're about to read: if we stat AFTER readFileSync, a
299
+ // concurrent write between read and stat would cache the OLD bytes under
300
+ // the NEW mtime, so a later read at that mtime would serve stale content.
301
+ // Stat-before-read closes that window (a write after this stat bumps the
302
+ // mtime past the cached key, forcing a re-read next time).
303
+ let mtimeMsForCache = null;
304
+ try {
305
+ mtimeMsForCache = statSync(configFile).mtimeMs;
306
+ }
307
+ catch { /* best-effort */ }
230
308
  const content = readFileSync(configFile, 'utf-8');
231
- const data = JSON.parse(content);
232
- // Verify HMAC integrity
233
- if (!verifyConfigIntegrity(content)) {
309
+ // An empty file is treated as "no config yet", not a parse failure.
310
+ if (content.trim().length === 0) {
311
+ return { data: {}, parseFailed: false };
312
+ }
313
+ let data;
314
+ try {
315
+ data = JSON.parse(content);
316
+ }
317
+ catch {
318
+ // File exists but is corrupt/torn. Do NOT return {} as writable —
319
+ // signal parseFailed so persisters skip the write and we never clobber
320
+ // a momentarily-unreadable config.
321
+ return { data: {}, parseFailed: true };
322
+ }
323
+ // Verify HMAC integrity (embedded `_sig`, else legacy `.config-sig`).
324
+ if (!verifyConfigIntegrity(data, content)) {
234
325
  configTampered = true;
235
326
  console.error('[ShieldCortex] WARNING: Config file integrity check failed — possible tampering detected. Falling back to strict mode.');
236
327
  // Force strict mode on tampered config
237
328
  data.defenceMode = 'strict';
238
329
  }
239
- return data;
330
+ // `_sig` is an integrity artefact, never config data — strip it so it
331
+ // can't leak into CloudConfig or get re-serialised as a normal field.
332
+ delete data._sig;
333
+ // Populate the mtime cache from the parsed (sig-stripped) object, keyed on
334
+ // the mtime captured BEFORE the read (matches the bytes actually parsed).
335
+ if (mtimeMsForCache !== null) {
336
+ cachedRawConfig = { ...data };
337
+ cachedRawConfigFile = configFile;
338
+ cachedRawConfigMtimeMs = mtimeMsForCache;
339
+ }
340
+ return { data, parseFailed: false };
240
341
  }
241
342
  }
242
- catch { /* ignore */ }
243
- return {};
343
+ catch { /* ignore — treat as absent */ }
344
+ return { data: {}, parseFailed: false };
244
345
  }
346
+ export function readRawConfig() {
347
+ return readRawConfigState().data;
348
+ }
349
+ function invalidateRawConfigCache() {
350
+ cachedRawConfig = null;
351
+ cachedRawConfigFile = null;
352
+ cachedRawConfigMtimeMs = null;
353
+ }
354
+ /**
355
+ * Atomically persist the raw config with the HMAC embedded as a top-level
356
+ * `_sig` field, computed over `canonicalBodyForSig` (the object WITHOUT
357
+ * `_sig`). Because the signature lives inside the same file and the file is
358
+ * swapped in via `renameSync` (atomic on POSIX), a concurrent reader can never
359
+ * observe a half-written file or a config/signature pair that are out of step
360
+ * — the two torn-read cases the audit found.
361
+ */
245
362
  function writeRawConfig(raw) {
246
363
  const configDir = getConfigDir();
247
364
  const configFile = getConfigFile();
248
365
  mkdirSync(configDir, { recursive: true });
249
- const content = JSON.stringify(raw, null, 2) + '\n';
250
- writeFileSync(configFile, content);
251
- // Sign the config after writing
252
- writeConfigSignature(content);
366
+ // Never carry an inbound `_sig` through; we always recompute it.
367
+ const { _sig: _ignored, ...rest } = raw;
368
+ const body = canonicalBodyForSig(rest);
369
+ const sig = signConfig(body);
370
+ const out = JSON.stringify({ ...rest, _sig: sig }, null, 2) + '\n';
371
+ // Write to a unique temp file in the same dir, then atomically rename over
372
+ // the target. The original config survives a failed/partial write — we never
373
+ // truncate it. Same-dir tmp guarantees rename is a same-filesystem move.
374
+ const tmpFile = `${configFile}.tmp-${process.pid}-${randomBytes(6).toString('hex')}`;
375
+ try {
376
+ writeFileSync(tmpFile, out, { mode: 0o600 });
377
+ // On Windows, renameSync over an EXISTING target can throw EPERM if another
378
+ // process has the destination open concurrently. This is non-corrupting:
379
+ // the original config is left intact and the catch below removes the tmp
380
+ // file and rethrows, so a write may FAIL rather than damage the config.
381
+ renameSync(tmpFile, configFile);
382
+ }
383
+ catch (err) {
384
+ try {
385
+ if (existsSync(tmpFile))
386
+ rmSync(tmpFile, { force: true });
387
+ }
388
+ catch { /* best-effort */ }
389
+ throw err;
390
+ }
391
+ // The embedded `_sig` is now the source of truth. Delete the stale legacy
392
+ // `.config-sig` only after a successful embedded write, only if it exists —
393
+ // this completes the one-way upgrade from the legacy format.
394
+ try {
395
+ const sigFile = getSigFile();
396
+ if (existsSync(sigFile))
397
+ rmSync(sigFile, { force: true });
398
+ }
399
+ catch { /* best-effort */ }
253
400
  cachedConfig = null;
254
401
  cachedConfigFile = null;
402
+ invalidateRawConfigCache();
255
403
  // Clear tamper flag on legitimate write
256
404
  configTampered = false;
257
405
  }
406
+ /**
407
+ * The ONLY sanctioned read-modify-write path for config.json.
408
+ *
409
+ * EVERY mutating accessor MUST go through this helper. Do NOT call
410
+ * `writeRawConfig` with a `raw` object obtained from a bare `readRawConfig()`:
411
+ * `readRawConfig()` collapses a parse failure to `{}`, discarding the
412
+ * `parseFailed` flag, so mutating that `{}` and writing it back atomically
413
+ * persists a near-empty, validly-signed config — wiping `cloudApiKey` and every
414
+ * other setting the corrupt file still holds, with no error signal. This helper
415
+ * reads through `readRawConfigState()` (which preserves `parseFailed`) and
416
+ * refuses to write when the on-disk config is unreadable.
417
+ *
418
+ * `onParseFail`:
419
+ * - `'throw'` (default): surface corruption to user-facing/explicit setters so
420
+ * the caller sees the problem rather than silently losing credentials.
421
+ * - `'skip'`: silently no-op for automatic / hot-path writes that must NEVER
422
+ * throw (background sync, read-with-migration). Returns false so the caller
423
+ * can tell the write was skipped.
424
+ *
425
+ * Returns true if the mutation was applied and persisted, false if it was
426
+ * skipped because the config was unparseable (`onParseFail: 'skip'`).
427
+ */
428
+ function mutateRawConfig(fn, onParseFail = 'throw') {
429
+ const { data, parseFailed } = readRawConfigState();
430
+ if (parseFailed) {
431
+ console.error('[ShieldCortex] config.json is unreadable — refusing to overwrite (would wipe settings incl. cloudApiKey). Fix or remove the file.');
432
+ if (onParseFail === 'throw') {
433
+ throw new Error('ShieldCortex config.json is corrupt/unparseable; refusing to overwrite to avoid losing settings. ' +
434
+ 'Fix or remove ~/.shieldcortex/config.json and retry.');
435
+ }
436
+ return false;
437
+ }
438
+ fn(data);
439
+ writeRawConfig(data);
440
+ return true;
441
+ }
258
442
  export function getTrustedSkills() {
259
443
  const raw = readRawConfig();
260
444
  return Array.isArray(raw.trustedSkills) ? raw.trustedSkills : [];
261
445
  }
262
446
  export function addTrustedSkill(path) {
263
- const raw = readRawConfig();
264
- const list = Array.isArray(raw.trustedSkills) ? raw.trustedSkills : [];
265
- if (!list.includes(path)) {
266
- list.push(path);
447
+ // Throw-policy explicit setter: on a corrupt config mutateRawConfig throws
448
+ // (surfacing the corruption) rather than wiping it. The closure is itself a
449
+ // no-op write when the skill is already present, which is acceptable churn on
450
+ // a VALID config and avoids a second bare read just to skip a write — the one
451
+ // case we must not silently swallow is the corrupt one, which the helper
452
+ // handles by throwing.
453
+ mutateRawConfig((raw) => {
454
+ const list = Array.isArray(raw.trustedSkills) ? raw.trustedSkills : [];
455
+ if (!list.includes(path))
456
+ list.push(path);
267
457
  raw.trustedSkills = list;
268
- writeRawConfig(raw);
269
- }
458
+ });
270
459
  }
271
460
  export function removeTrustedSkill(path) {
272
- const raw = readRawConfig();
273
- const list = Array.isArray(raw.trustedSkills) ? raw.trustedSkills : [];
274
- const idx = list.indexOf(path);
275
- if (idx !== -1) {
276
- list.splice(idx, 1);
461
+ mutateRawConfig((raw) => {
462
+ const list = Array.isArray(raw.trustedSkills) ? raw.trustedSkills : [];
463
+ const idx = list.indexOf(path);
464
+ if (idx !== -1)
465
+ list.splice(idx, 1);
277
466
  raw.trustedSkills = list;
278
- writeRawConfig(raw);
279
- }
467
+ });
280
468
  }
281
469
  // ── Cloud Iron Dome Cache ─────────────────────────────
282
470
  /**
283
471
  * Persist cloud Iron Dome data (patterns + policy) to config.json with HMAC integrity.
284
472
  */
285
473
  export function setCloudIronDomeCache(data) {
286
- const raw = readRawConfig();
287
- raw.cloudIronDome = data;
288
- writeRawConfig(raw);
474
+ mutateRawConfig((raw) => {
475
+ raw.cloudIronDome = data;
476
+ });
289
477
  }
290
478
  /**
291
479
  * Read cached cloud Iron Dome data from config.json.
@@ -298,13 +486,66 @@ export function getCloudIronDomeCache() {
298
486
  return null;
299
487
  }
300
488
  // ── Sync Timestamp ────────────────────────────────────
489
+ // Debounce state for lastSyncAt. The sync queue, graph-sync and memory-sync all
490
+ // call updateLastSyncAt() after EVERY successful upload — a busy agent fired a
491
+ // full read-modify-write of config.json per record. We keep the latest value in
492
+ // memory and only persist at most once per LAST_SYNC_PERSIST_INTERVAL_MS, which
493
+ // collapses a burst to a single write while readers still see a fresh value.
494
+ const LAST_SYNC_PERSIST_INTERVAL_MS = 60_000;
495
+ let pendingLastSyncAt = null;
496
+ let lastSyncPersistedAtMs = 0;
301
497
  /**
302
- * Write lastSyncAt timestamp to config.json on successful cloud sync.
498
+ * Record a successful cloud sync. Always updates the in-memory timestamp;
499
+ * persists to config.json at most once per 60s (time-debounced). Callers
500
+ * (sync.ts / sync-queue.ts / memory-sync.ts) are unchanged — the debounce is
501
+ * internal.
303
502
  */
304
503
  export function updateLastSyncAt() {
504
+ const now = Date.now();
505
+ pendingLastSyncAt = new Date(now).toISOString();
506
+ if (now - lastSyncPersistedAtMs < LAST_SYNC_PERSIST_INTERVAL_MS) {
507
+ return; // within the debounce window — memory updated, disk left alone
508
+ }
509
+ persistLastSyncAt(now);
510
+ }
511
+ function persistLastSyncAt(nowMs) {
512
+ if (pendingLastSyncAt === null)
513
+ return;
514
+ // Automatic / hot path (fires after cloud uploads): MUST NOT throw. The
515
+ // 'skip' policy makes mutateRawConfig a silent no-op on an unreadable config
516
+ // (returns false) instead of clobbering it. Only stamp the debounce clock
517
+ // when the write actually landed, so a skipped write is retried next call.
518
+ const written = mutateRawConfig((raw) => {
519
+ raw.lastSyncAt = pendingLastSyncAt;
520
+ }, 'skip');
521
+ if (written)
522
+ lastSyncPersistedAtMs = nowMs;
523
+ }
524
+ /**
525
+ * Force-persist the latest in-memory lastSyncAt regardless of the debounce
526
+ * window, so the final sync timestamp isn't lost if the process exits inside
527
+ * the debounce window. Safe no-op if nothing is pending.
528
+ *
529
+ * Available for a shutdown path (not currently wired): the existing
530
+ * SIGINT/SIGTERM/exit handlers (src/index.ts, src/api/visualization-server.ts)
531
+ * are MCP-server / dashboard lifecycle concerns and don't obviously own the
532
+ * cloud-sync clock, so wiring is left to whichever shutdown owner adopts it
533
+ * rather than bolted onto an unrelated handler. Losing at most one debounced
534
+ * timestamp on abrupt exit is non-critical (the next sync re-stamps it).
535
+ */
536
+ export function flushLastSyncAt() {
537
+ persistLastSyncAt(Date.now());
538
+ }
539
+ /**
540
+ * Returns the most recent sync timestamp, preferring the in-memory value (which
541
+ * may be ahead of disk inside the debounce window) and falling back to the
542
+ * persisted config value.
543
+ */
544
+ export function getLastSyncAt() {
545
+ if (pendingLastSyncAt !== null)
546
+ return pendingLastSyncAt;
305
547
  const raw = readRawConfig();
306
- raw.lastSyncAt = new Date().toISOString();
307
- writeRawConfig(raw);
548
+ return typeof raw.lastSyncAt === 'string' ? raw.lastSyncAt : null;
308
549
  }
309
550
  const VALID_MODES = ['strict', 'balanced', 'permissive'];
310
551
  /**
@@ -325,9 +566,9 @@ export function setDefenceMode(mode) {
325
566
  if (!VALID_MODES.includes(mode)) {
326
567
  throw new Error(`Invalid defence mode: ${mode}. Must be one of: ${VALID_MODES.join(', ')}`);
327
568
  }
328
- const raw = readRawConfig();
329
- raw.defenceMode = mode;
330
- writeRawConfig(raw);
569
+ mutateRawConfig((raw) => {
570
+ raw.defenceMode = mode;
571
+ });
331
572
  }
332
573
  const DEFAULT_VERIFY_CONFIG = {
333
574
  verifyEnabled: false,
@@ -352,16 +593,16 @@ export function getVerifyConfig() {
352
593
  * Persists LLM verification config to ~/.shieldcortex/config.json.
353
594
  */
354
595
  export function setVerifyConfig(updates) {
355
- const raw = readRawConfig();
356
- if (updates.verifyEnabled !== undefined)
357
- raw.verifyEnabled = updates.verifyEnabled;
358
- if (updates.verifyMode !== undefined)
359
- raw.verifyMode = updates.verifyMode;
360
- if (updates.verifyTriggers !== undefined)
361
- raw.verifyTriggers = updates.verifyTriggers;
362
- if (updates.verifyTimeoutMs !== undefined)
363
- raw.verifyTimeoutMs = updates.verifyTimeoutMs;
364
- writeRawConfig(raw);
596
+ mutateRawConfig((raw) => {
597
+ if (updates.verifyEnabled !== undefined)
598
+ raw.verifyEnabled = updates.verifyEnabled;
599
+ if (updates.verifyMode !== undefined)
600
+ raw.verifyMode = updates.verifyMode;
601
+ if (updates.verifyTriggers !== undefined)
602
+ raw.verifyTriggers = updates.verifyTriggers;
603
+ if (updates.verifyTimeoutMs !== undefined)
604
+ raw.verifyTimeoutMs = updates.verifyTimeoutMs;
605
+ });
365
606
  }
366
607
  // ── Review Copilot Config ───────────────────────────────
367
608
  function readReviewCopilotRaw() {
@@ -400,15 +641,15 @@ export function getReviewCopilotConfig() {
400
641
  * Persists local Review Copilot config.
401
642
  */
402
643
  export function setReviewCopilotConfig(updates) {
403
- const raw = readRawConfig();
404
- const existing = raw.reviewCopilot && typeof raw.reviewCopilot === 'object'
405
- ? raw.reviewCopilot
406
- : {};
407
- raw.reviewCopilot = {
408
- ...existing,
409
- ...updates,
410
- };
411
- writeRawConfig(raw);
644
+ mutateRawConfig((raw) => {
645
+ const existing = raw.reviewCopilot && typeof raw.reviewCopilot === 'object'
646
+ ? raw.reviewCopilot
647
+ : {};
648
+ raw.reviewCopilot = {
649
+ ...existing,
650
+ ...updates,
651
+ };
652
+ });
412
653
  }
413
654
  // ── Ranker Config ─────────────────────────────────────
414
655
  const DEFAULT_RANKER_CONFIG = {
@@ -469,18 +710,18 @@ export function getRankerConfig() {
469
710
  * fields supplied in `updates` are written; other fields are preserved.
470
711
  */
471
712
  export function setRankerConfig(updates) {
472
- const raw = readRawConfig();
473
- const existing = raw.ranker && typeof raw.ranker === 'object'
474
- ? raw.ranker
475
- : {};
476
- if (updates.engine !== undefined)
477
- existing.engine = updates.engine;
478
- if (updates.rrfK !== undefined)
479
- existing.rrfK = updates.rrfK;
480
- if (updates.weights !== undefined)
481
- existing.weights = updates.weights;
482
- raw.ranker = existing;
483
- writeRawConfig(raw);
713
+ mutateRawConfig((raw) => {
714
+ const existing = raw.ranker && typeof raw.ranker === 'object'
715
+ ? raw.ranker
716
+ : {};
717
+ if (updates.engine !== undefined)
718
+ existing.engine = updates.engine;
719
+ if (updates.rrfK !== undefined)
720
+ existing.rrfK = updates.rrfK;
721
+ if (updates.weights !== undefined)
722
+ existing.weights = updates.weights;
723
+ raw.ranker = existing;
724
+ });
484
725
  }
485
726
  const DEFAULT_OPENCLAW_MEMORY_CONFIG = {
486
727
  autoMemory: true,
@@ -513,18 +754,18 @@ export function getOpenClawMemoryConfig() {
513
754
  * Persists OpenClaw memory integration config.
514
755
  */
515
756
  export function setOpenClawMemoryConfig(updates) {
516
- const raw = readRawConfig();
517
- if (updates.autoMemory !== undefined)
518
- raw.openclawAutoMemory = updates.autoMemory;
519
- if (updates.dedupe !== undefined)
520
- raw.openclawAutoMemoryDedupe = updates.dedupe;
521
- if (updates.noveltyThreshold !== undefined) {
522
- raw.openclawAutoMemoryNoveltyThreshold = clamp(updates.noveltyThreshold, 0.6, 0.99);
523
- }
524
- if (updates.maxRecent !== undefined) {
525
- raw.openclawAutoMemoryMaxRecent = Math.floor(clamp(updates.maxRecent, 50, 1000));
526
- }
527
- writeRawConfig(raw);
757
+ mutateRawConfig((raw) => {
758
+ if (updates.autoMemory !== undefined)
759
+ raw.openclawAutoMemory = updates.autoMemory;
760
+ if (updates.dedupe !== undefined)
761
+ raw.openclawAutoMemoryDedupe = updates.dedupe;
762
+ if (updates.noveltyThreshold !== undefined) {
763
+ raw.openclawAutoMemoryNoveltyThreshold = clamp(updates.noveltyThreshold, 0.6, 0.99);
764
+ }
765
+ if (updates.maxRecent !== undefined) {
766
+ raw.openclawAutoMemoryMaxRecent = Math.floor(clamp(updates.maxRecent, 50, 1000));
767
+ }
768
+ });
528
769
  }
529
770
  /**
530
771
  * Returns whether OpenClaw auto-memory extraction is enabled.
@@ -554,9 +795,9 @@ export function isProactiveRecallEnabled() {
554
795
  * Persists proactive recall preference to ~/.shieldcortex/config.json.
555
796
  */
556
797
  export function setProactiveRecall(enabled) {
557
- const raw = readRawConfig();
558
- raw.proactiveRecall = enabled;
559
- writeRawConfig(raw);
798
+ mutateRawConfig((raw) => {
799
+ raw.proactiveRecall = enabled;
800
+ });
560
801
  }
561
802
  /**
562
803
  * Restores the v4.10.x defaults for users who preferred the old behaviour.
@@ -564,32 +805,32 @@ export function setProactiveRecall(enabled) {
564
805
  * so the flip is a one-command undo.
565
806
  */
566
807
  export function restore410Defaults() {
567
- const raw = readRawConfig();
568
- raw.proactiveRecall = true;
569
- const existingInterceptor = (raw.interceptor && typeof raw.interceptor === 'object')
570
- ? raw.interceptor
571
- : {};
572
- const existingSeverity = (existingInterceptor.severityActions && typeof existingInterceptor.severityActions === 'object')
573
- ? existingInterceptor.severityActions
574
- : {};
575
- raw.interceptor = {
576
- ...existingInterceptor,
577
- severityActions: {
578
- ...existingSeverity,
579
- low: 'log',
580
- medium: 'warn',
581
- high: 'require_approval',
582
- critical: 'require_approval',
583
- },
584
- };
585
- const existingSessionStart = (raw.sessionStart && typeof raw.sessionStart === 'object')
586
- ? raw.sessionStart
587
- : {};
588
- raw.sessionStart = {
589
- ...existingSessionStart,
590
- preamble: 'minimal',
591
- };
592
- writeRawConfig(raw);
808
+ mutateRawConfig((raw) => {
809
+ raw.proactiveRecall = true;
810
+ const existingInterceptor = (raw.interceptor && typeof raw.interceptor === 'object')
811
+ ? raw.interceptor
812
+ : {};
813
+ const existingSeverity = (existingInterceptor.severityActions && typeof existingInterceptor.severityActions === 'object')
814
+ ? existingInterceptor.severityActions
815
+ : {};
816
+ raw.interceptor = {
817
+ ...existingInterceptor,
818
+ severityActions: {
819
+ ...existingSeverity,
820
+ low: 'log',
821
+ medium: 'warn',
822
+ high: 'require_approval',
823
+ critical: 'require_approval',
824
+ },
825
+ };
826
+ const existingSessionStart = (raw.sessionStart && typeof raw.sessionStart === 'object')
827
+ ? raw.sessionStart
828
+ : {};
829
+ raw.sessionStart = {
830
+ ...existingSessionStart,
831
+ preamble: 'minimal',
832
+ };
833
+ });
593
834
  }
594
835
  /**
595
836
  * Returns the resolved on/off state of the opt-in auto-memory hooks.
@@ -618,16 +859,16 @@ export function getAutoMemoryEnableConfig() {
618
859
  * runtime gate cannot disagree.
619
860
  */
620
861
  export function setAutoMemoryEnableConfig(updates) {
621
- const raw = readRawConfig();
622
- const existing = raw.autoMemory && typeof raw.autoMemory === 'object'
623
- ? raw.autoMemory
624
- : {};
625
- if (updates.enableStop !== undefined)
626
- existing.enableStop = updates.enableStop;
627
- if (updates.enableSessionEnd !== undefined)
628
- existing.enableSessionEnd = updates.enableSessionEnd;
629
- raw.autoMemory = existing;
630
- writeRawConfig(raw);
862
+ mutateRawConfig((raw) => {
863
+ const existing = raw.autoMemory && typeof raw.autoMemory === 'object'
864
+ ? raw.autoMemory
865
+ : {};
866
+ if (updates.enableStop !== undefined)
867
+ existing.enableStop = updates.enableStop;
868
+ if (updates.enableSessionEnd !== undefined)
869
+ existing.enableSessionEnd = updates.enableSessionEnd;
870
+ raw.autoMemory = existing;
871
+ });
631
872
  }
632
873
  const DEFAULT_TOOL_RESPONSE_SCAN_CONFIG = {
633
874
  scanToolResponses: true,
@@ -647,12 +888,12 @@ export function getToolResponseScanConfig() {
647
888
  * Persists tool response scanning config.
648
889
  */
649
890
  export function setToolResponseScanConfig(updates) {
650
- const raw = readRawConfig();
651
- if (updates.scanToolResponses !== undefined)
652
- raw.scanToolResponses = updates.scanToolResponses;
653
- if (updates.toolResponseMode !== undefined)
654
- raw.toolResponseMode = updates.toolResponseMode;
655
- writeRawConfig(raw);
891
+ mutateRawConfig((raw) => {
892
+ if (updates.scanToolResponses !== undefined)
893
+ raw.scanToolResponses = updates.scanToolResponses;
894
+ if (updates.toolResponseMode !== undefined)
895
+ raw.toolResponseMode = updates.toolResponseMode;
896
+ });
656
897
  }
657
898
  // ── Device Identity ────────────────────────────────────
658
899
  /**
@@ -660,13 +901,26 @@ export function setToolResponseScanConfig(updates) {
660
901
  * Generates and persists on first call; reads from config thereafter.
661
902
  */
662
903
  export function getDeviceId() {
663
- const raw = readRawConfig();
904
+ const { data: raw, parseFailed } = readRawConfigState();
664
905
  if (typeof raw.deviceId === 'string' && raw.deviceId) {
665
906
  return raw.deviceId;
666
907
  }
667
908
  const id = randomUUID();
668
- raw.deviceId = id;
669
- writeRawConfig(raw);
909
+ if (parseFailed) {
910
+ // The config file exists but is unreadable. Persisting now would write
911
+ // `{ deviceId }` over the corrupt bytes and destroy cloudApiKey and every
912
+ // other setting the file still holds. Return an ephemeral id this run and
913
+ // leave the file untouched — identity persists once the file is readable.
914
+ // (mutateRawConfig('skip') would no-op here too, but we want this specific
915
+ // diagnostic, so we short-circuit before calling it.)
916
+ console.error('[ShieldCortex] config.json unreadable — using an ephemeral device id this run; identity was NOT persisted.');
917
+ return id;
918
+ }
919
+ // Persist through the guarded helper so no bare writeRawConfig exists outside
920
+ // mutateRawConfig; skip-policy keeps this read-then-persist path non-throwing.
921
+ mutateRawConfig((m) => {
922
+ m.deviceId = id;
923
+ }, 'skip');
670
924
  return id;
671
925
  }
672
926
  /**
@@ -674,12 +928,18 @@ export function getDeviceId() {
674
928
  * Stores in config on first call; reads from config thereafter.
675
929
  */
676
930
  export function getDeviceName() {
677
- const raw = readRawConfig();
931
+ const { data: raw, parseFailed } = readRawConfigState();
678
932
  if (typeof raw.deviceName === 'string' && raw.deviceName) {
679
933
  return raw.deviceName;
680
934
  }
681
935
  const name = hostname();
682
- raw.deviceName = name;
683
- writeRawConfig(raw);
936
+ if (parseFailed) {
937
+ // See getDeviceId — never overwrite an unreadable config.
938
+ console.error('[ShieldCortex] config.json unreadable — using the live hostname this run; device name was NOT persisted.');
939
+ return name;
940
+ }
941
+ mutateRawConfig((m) => {
942
+ m.deviceName = name;
943
+ }, 'skip');
684
944
  return name;
685
945
  }