@nathapp/nax-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (398) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/dist/coding-tools/coding-tool-bash.d.ts +46 -0
  5. package/dist/coding-tools/coding-tool-bash.js +44 -0
  6. package/dist/coding-tools/coding-tool-extras.d.ts +35 -0
  7. package/dist/coding-tools/coding-tool-extras.js +56 -0
  8. package/dist/coding-tools/coding-tool-sandbox.d.ts +74 -0
  9. package/dist/coding-tools/coding-tool-sandbox.js +215 -0
  10. package/dist/coding-tools/coding-tool-support.d.ts +132 -0
  11. package/dist/coding-tools/coding-tool-support.js +159 -0
  12. package/dist/coding-tools/universal-coding-tools.d.ts +11 -0
  13. package/dist/coding-tools/universal-coding-tools.js +5 -0
  14. package/dist/command-interceptor/index.d.ts +55 -0
  15. package/dist/command-interceptor/index.js +60 -0
  16. package/dist/command-interceptor/shell.d.ts +36 -0
  17. package/dist/command-interceptor/shell.js +137 -0
  18. package/dist/command-safety/build.d.ts +19 -0
  19. package/dist/command-safety/build.js +47 -0
  20. package/dist/command-safety/guard.d.ts +32 -0
  21. package/dist/command-safety/guard.js +179 -0
  22. package/dist/command-safety/identifiers.d.ts +24 -0
  23. package/dist/command-safety/identifiers.js +20 -0
  24. package/dist/command-safety/index.d.ts +17 -0
  25. package/dist/command-safety/index.js +11 -0
  26. package/dist/command-safety/questions.d.ts +19 -0
  27. package/dist/command-safety/questions.js +60 -0
  28. package/dist/command-safety/row.d.ts +2 -0
  29. package/dist/command-safety/row.js +14 -0
  30. package/dist/command-safety/rule-scorer.d.ts +9 -0
  31. package/dist/command-safety/rule-scorer.js +391 -0
  32. package/dist/command-safety/shadow.d.ts +33 -0
  33. package/dist/command-safety/shadow.js +188 -0
  34. package/dist/command-safety/systemone-client.d.ts +16 -0
  35. package/dist/command-safety/systemone-client.js +92 -0
  36. package/dist/command-safety/tap.d.ts +37 -0
  37. package/dist/command-safety/tap.js +77 -0
  38. package/dist/command-safety/temp-only.d.ts +6 -0
  39. package/dist/command-safety/temp-only.js +215 -0
  40. package/dist/command-safety/tmp-write.d.ts +2 -0
  41. package/dist/command-safety/tmp-write.js +112 -0
  42. package/dist/command-safety/types.d.ts +185 -0
  43. package/dist/command-safety/types.js +21 -0
  44. package/dist/config/bash-approval.d.ts +33 -0
  45. package/dist/config/bash-approval.js +30 -0
  46. package/dist/config/catalog-overrides.d.ts +94 -0
  47. package/dist/config/catalog-overrides.js +7 -0
  48. package/dist/config/native-agent/index.d.ts +8 -0
  49. package/dist/config/native-agent/index.js +8 -0
  50. package/dist/config/schemas-sandbox.d.ts +31 -0
  51. package/dist/config/schemas-sandbox.js +45 -0
  52. package/dist/cost/core/index.d.ts +8 -0
  53. package/dist/cost/core/index.js +7 -0
  54. package/dist/cost/estimate.d.ts +34 -0
  55. package/dist/cost/estimate.js +65 -0
  56. package/dist/cost/model-spec.d.ts +28 -0
  57. package/dist/cost/model-spec.js +29 -0
  58. package/dist/cost/standard-types.d.ts +9 -0
  59. package/dist/cost/standard-types.js +1 -0
  60. package/dist/cost/usage-math.d.ts +18 -0
  61. package/dist/cost/usage-math.js +46 -0
  62. package/dist/index.d.ts +26 -0
  63. package/dist/index.js +21 -0
  64. package/dist/infra/agent-logger.d.ts +22 -0
  65. package/dist/infra/agent-logger.js +19 -0
  66. package/dist/infra/credentials-config.d.ts +20 -0
  67. package/dist/infra/credentials-config.js +15 -0
  68. package/dist/infra/errors.d.ts +9 -0
  69. package/dist/infra/errors.js +11 -0
  70. package/dist/infra/index.d.ts +7 -0
  71. package/dist/infra/index.js +7 -0
  72. package/dist/infra/nax-error.d.ts +10 -0
  73. package/dist/infra/nax-error.js +16 -0
  74. package/dist/infra/spin-breaker/hash.d.ts +2 -0
  75. package/dist/infra/spin-breaker/hash.js +5 -0
  76. package/dist/infra/spin-breaker/index.d.ts +120 -0
  77. package/dist/infra/spin-breaker/index.js +340 -0
  78. package/dist/internal/agent-output-env.d.ts +44 -0
  79. package/dist/internal/agent-output-env.js +57 -0
  80. package/dist/internal/argv-exec.d.ts +58 -0
  81. package/dist/internal/argv-exec.js +224 -0
  82. package/dist/internal/bounded-io.d.ts +24 -0
  83. package/dist/internal/bounded-io.js +51 -0
  84. package/dist/internal/command-spec/index.d.ts +29 -0
  85. package/dist/internal/command-spec/index.js +36 -0
  86. package/dist/internal/describe-value-type.d.ts +8 -0
  87. package/dist/internal/describe-value-type.js +14 -0
  88. package/dist/internal/exec-framing.d.ts +18 -0
  89. package/dist/internal/exec-framing.js +22 -0
  90. package/dist/internal/file-lock.d.ts +91 -0
  91. package/dist/internal/file-lock.js +212 -0
  92. package/dist/internal/file-size.d.ts +2 -0
  93. package/dist/internal/file-size.js +12 -0
  94. package/dist/internal/git-add.d.ts +43 -0
  95. package/dist/internal/git-add.js +59 -0
  96. package/dist/internal/git-env.d.ts +59 -0
  97. package/dist/internal/git-env.js +93 -0
  98. package/dist/internal/git-exec.d.ts +58 -0
  99. package/dist/internal/git-exec.js +115 -0
  100. package/dist/internal/path-file-lock.d.ts +23 -0
  101. package/dist/internal/path-file-lock.js +25 -0
  102. package/dist/internal/process-alive.d.ts +42 -0
  103. package/dist/internal/process-alive.js +56 -0
  104. package/dist/internal/process-kill.d.ts +27 -0
  105. package/dist/internal/process-kill.js +54 -0
  106. package/dist/internal/realpath.d.ts +29 -0
  107. package/dist/internal/realpath.js +52 -0
  108. package/dist/internal/redact.d.ts +40 -0
  109. package/dist/internal/redact.js +161 -0
  110. package/dist/internal/shell-quote.d.ts +2 -0
  111. package/dist/internal/shell-quote.js +4 -0
  112. package/dist/internal/sort.d.ts +20 -0
  113. package/dist/internal/sort.js +20 -0
  114. package/dist/internal/strip-control-chars.d.ts +19 -0
  115. package/dist/internal/strip-control-chars.js +38 -0
  116. package/dist/internal/thenable.d.ts +10 -0
  117. package/dist/internal/thenable.js +15 -0
  118. package/dist/internal.d.ts +91 -0
  119. package/dist/internal.js +91 -0
  120. package/dist/native/adapter-deps.d.ts +46 -0
  121. package/dist/native/adapter-deps.js +43 -0
  122. package/dist/native/auth-types.d.ts +70 -0
  123. package/dist/native/auth-types.js +13 -0
  124. package/dist/native/auth.d.ts +133 -0
  125. package/dist/native/auth.js +362 -0
  126. package/dist/native/client.d.ts +50 -0
  127. package/dist/native/client.js +174 -0
  128. package/dist/native/complete.d.ts +33 -0
  129. package/dist/native/complete.js +88 -0
  130. package/dist/native/credentials/chained-store.d.ts +30 -0
  131. package/dist/native/credentials/chained-store.js +68 -0
  132. package/dist/native/credentials/change-guard.d.ts +51 -0
  133. package/dist/native/credentials/change-guard.js +136 -0
  134. package/dist/native/credentials/exec-source.d.ts +50 -0
  135. package/dist/native/credentials/exec-source.js +222 -0
  136. package/dist/native/credentials/fingerprint.d.ts +26 -0
  137. package/dist/native/credentials/fingerprint.js +174 -0
  138. package/dist/native/credentials/helper-process.d.ts +79 -0
  139. package/dist/native/credentials/helper-process.js +259 -0
  140. package/dist/native/credentials/helper-protocol.d.ts +36 -0
  141. package/dist/native/credentials/helper-protocol.js +79 -0
  142. package/dist/native/credentials/index.d.ts +60 -0
  143. package/dist/native/credentials/index.js +184 -0
  144. package/dist/native/errors.d.ts +34 -0
  145. package/dist/native/errors.js +109 -0
  146. package/dist/native/index.d.ts +25 -0
  147. package/dist/native/index.js +23 -0
  148. package/dist/native/model-resolver.d.ts +57 -0
  149. package/dist/native/model-resolver.js +73 -0
  150. package/dist/native/models.d.ts +116 -0
  151. package/dist/native/models.js +183 -0
  152. package/dist/native/session/ask-human.d.ts +15 -0
  153. package/dist/native/session/ask-human.js +22 -0
  154. package/dist/native/session/compaction.d.ts +94 -0
  155. package/dist/native/session/compaction.js +170 -0
  156. package/dist/native/session/handle-invalid-tool-call.d.ts +111 -0
  157. package/dist/native/session/handle-invalid-tool-call.js +159 -0
  158. package/dist/native/session/loop-events/cache-boundary.d.ts +11 -0
  159. package/dist/native/session/loop-events/cache-boundary.js +29 -0
  160. package/dist/native/session/loop-events/external-handler.d.ts +38 -0
  161. package/dist/native/session/loop-events/external-handler.js +193 -0
  162. package/dist/native/session/loop-events/index.d.ts +16 -0
  163. package/dist/native/session/loop-events/index.js +15 -0
  164. package/dist/native/session/loop-events/payload-guard.d.ts +9 -0
  165. package/dist/native/session/loop-events/payload-guard.js +31 -0
  166. package/dist/native/session/loop-events/registry.d.ts +84 -0
  167. package/dist/native/session/loop-events/registry.js +305 -0
  168. package/dist/native/session/loop-events/types.d.ts +251 -0
  169. package/dist/native/session/loop-events/types.js +14 -0
  170. package/dist/native/session/loop-handlers.d.ts +73 -0
  171. package/dist/native/session/loop-handlers.js +137 -0
  172. package/dist/native/session/nudge.d.ts +29 -0
  173. package/dist/native/session/nudge.js +35 -0
  174. package/dist/native/session/rate-provenance.d.ts +17 -0
  175. package/dist/native/session/rate-provenance.js +39 -0
  176. package/dist/native/session/session.d.ts +139 -0
  177. package/dist/native/session/session.js +208 -0
  178. package/dist/native/session/tool-input-exemplar.d.ts +2 -0
  179. package/dist/native/session/tool-input-exemplar.js +66 -0
  180. package/dist/native/session/tool-input-validation.d.ts +15 -0
  181. package/dist/native/session/tool-input-validation.js +146 -0
  182. package/dist/native/session/tool-mapping.d.ts +18 -0
  183. package/dist/native/session/tool-mapping.js +27 -0
  184. package/dist/native/session/tool-result.d.ts +37 -0
  185. package/dist/native/session/tool-result.js +25 -0
  186. package/dist/native/session/transcript-store.d.ts +77 -0
  187. package/dist/native/session/transcript-store.js +200 -0
  188. package/dist/native/session/truncation-handler.d.ts +47 -0
  189. package/dist/native/session/truncation-handler.js +83 -0
  190. package/dist/native/session/turn-accumulator.d.ts +45 -0
  191. package/dist/native/session/turn-accumulator.js +67 -0
  192. package/dist/native/session/turn-ask-human.d.ts +23 -0
  193. package/dist/native/session/turn-ask-human.js +41 -0
  194. package/dist/native/session/turn-compaction-step.d.ts +74 -0
  195. package/dist/native/session/turn-compaction-step.js +128 -0
  196. package/dist/native/session/turn-complete-step.d.ts +77 -0
  197. package/dist/native/session/turn-complete-step.js +159 -0
  198. package/dist/native/session/turn-end-event.d.ts +3 -0
  199. package/dist/native/session/turn-end-event.js +17 -0
  200. package/dist/native/session/turn-events.d.ts +63 -0
  201. package/dist/native/session/turn-events.js +48 -0
  202. package/dist/native/session/turn-loop-round-trip.d.ts +116 -0
  203. package/dist/native/session/turn-loop-round-trip.js +318 -0
  204. package/dist/native/session/turn-loop.d.ts +23 -0
  205. package/dist/native/session/turn-loop.js +250 -0
  206. package/dist/native/session/turn-result.d.ts +39 -0
  207. package/dist/native/session/turn-result.js +64 -0
  208. package/dist/native/session/turn-retry.d.ts +120 -0
  209. package/dist/native/session/turn-retry.js +182 -0
  210. package/dist/native/session/turn-tool-batch.d.ts +61 -0
  211. package/dist/native/session/turn-tool-batch.js +259 -0
  212. package/dist/native/session/turn-types.d.ts +164 -0
  213. package/dist/native/session/turn-types.js +36 -0
  214. package/dist/native/session-adapter.d.ts +56 -0
  215. package/dist/native/session-adapter.js +369 -0
  216. package/dist/native/session-affinity.d.ts +36 -0
  217. package/dist/native/session-affinity.js +43 -0
  218. package/dist/native/tier-providers.d.ts +18 -0
  219. package/dist/native/tier-providers.js +20 -0
  220. package/dist/permissions/approval-audit.d.ts +10 -0
  221. package/dist/permissions/approval-audit.js +22 -0
  222. package/dist/permissions/approvals-link.d.ts +20 -0
  223. package/dist/permissions/approvals-link.js +62 -0
  224. package/dist/permissions/approvals-store.d.ts +168 -0
  225. package/dist/permissions/approvals-store.js +259 -0
  226. package/dist/permissions/approvals-taint.d.ts +33 -0
  227. package/dist/permissions/approvals-taint.js +138 -0
  228. package/dist/permissions/ask-chain.d.ts +69 -0
  229. package/dist/permissions/ask-chain.js +32 -0
  230. package/dist/permissions/ask.d.ts +23 -0
  231. package/dist/permissions/ask.js +25 -0
  232. package/dist/permissions/bash-lex.d.ts +59 -0
  233. package/dist/permissions/bash-lex.js +279 -0
  234. package/dist/permissions/grammar.d.ts +12 -0
  235. package/dist/permissions/grammar.js +24 -0
  236. package/dist/permissions/index.d.ts +11 -0
  237. package/dist/permissions/index.js +9 -0
  238. package/dist/permissions/secret-spans.d.ts +19 -0
  239. package/dist/permissions/secret-spans.js +88 -0
  240. package/dist/permissions/types.d.ts +85 -0
  241. package/dist/permissions/types.js +1 -0
  242. package/dist/runtime/index.d.ts +3 -0
  243. package/dist/runtime/index.js +2 -0
  244. package/dist/runtime/node-glob.d.ts +3 -0
  245. package/dist/runtime/node-glob.js +83 -0
  246. package/dist/runtime/node-runtime.d.ts +3 -0
  247. package/dist/runtime/node-runtime.js +126 -0
  248. package/dist/runtime/slot.d.ts +11 -0
  249. package/dist/runtime/slot.js +17 -0
  250. package/dist/runtime/types.d.ts +51 -0
  251. package/dist/runtime/types.js +1 -0
  252. package/dist/runtime/which.d.ts +8 -0
  253. package/dist/runtime/which.js +32 -0
  254. package/dist/sandbox/argv-quote.d.ts +11 -0
  255. package/dist/sandbox/argv-quote.js +13 -0
  256. package/dist/sandbox/defaults.d.ts +27 -0
  257. package/dist/sandbox/defaults.js +47 -0
  258. package/dist/sandbox/git-guards.d.ts +64 -0
  259. package/dist/sandbox/git-guards.js +161 -0
  260. package/dist/sandbox/index.d.ts +11 -0
  261. package/dist/sandbox/index.js +10 -0
  262. package/dist/sandbox/launcher.d.ts +37 -0
  263. package/dist/sandbox/launcher.js +160 -0
  264. package/dist/sandbox/messages.d.ts +17 -0
  265. package/dist/sandbox/messages.js +46 -0
  266. package/dist/sandbox/policy-builder.d.ts +44 -0
  267. package/dist/sandbox/policy-builder.js +149 -0
  268. package/dist/sandbox/policy-inputs.d.ts +53 -0
  269. package/dist/sandbox/policy-inputs.js +76 -0
  270. package/dist/sandbox/probe.d.ts +8 -0
  271. package/dist/sandbox/probe.js +66 -0
  272. package/dist/sandbox/registry.d.ts +17 -0
  273. package/dist/sandbox/registry.js +57 -0
  274. package/dist/sandbox/session-tmp.d.ts +20 -0
  275. package/dist/sandbox/session-tmp.js +95 -0
  276. package/dist/sandbox/srt-backend.d.ts +21 -0
  277. package/dist/sandbox/srt-backend.js +99 -0
  278. package/dist/sandbox/types.d.ts +102 -0
  279. package/dist/sandbox/types.js +1 -0
  280. package/dist/session/adapter-failure.d.ts +60 -0
  281. package/dist/session/adapter-failure.js +11 -0
  282. package/dist/session/agent-stream-events.d.ts +103 -0
  283. package/dist/session/agent-stream-events.js +6 -0
  284. package/dist/session/interaction-handler.d.ts +53 -0
  285. package/dist/session/interaction-handler.js +2 -0
  286. package/dist/session/no-op-interaction-handler.d.ts +9 -0
  287. package/dist/session/no-op-interaction-handler.js +11 -0
  288. package/dist/session/protocol-types.d.ts +12 -0
  289. package/dist/session/protocol-types.js +1 -0
  290. package/dist/session/session-types.d.ts +456 -0
  291. package/dist/session/session-types.js +64 -0
  292. package/dist/session/tool-descriptor.d.ts +23 -0
  293. package/dist/session/tool-descriptor.js +5 -0
  294. package/dist/session/turn-deadline.d.ts +17 -0
  295. package/dist/session/turn-deadline.js +23 -0
  296. package/dist/tools/ask-request.d.ts +35 -0
  297. package/dist/tools/ask-request.js +57 -0
  298. package/dist/tools/bash-cwd.d.ts +82 -0
  299. package/dist/tools/bash-cwd.js +55 -0
  300. package/dist/tools/bash.d.ts +69 -0
  301. package/dist/tools/bash.js +283 -0
  302. package/dist/tools/delete.d.ts +57 -0
  303. package/dist/tools/delete.js +154 -0
  304. package/dist/tools/denial-redirect.d.ts +42 -0
  305. package/dist/tools/denial-redirect.js +266 -0
  306. package/dist/tools/deny-paths.d.ts +35 -0
  307. package/dist/tools/deny-paths.js +76 -0
  308. package/dist/tools/edit-region.d.ts +50 -0
  309. package/dist/tools/edit-region.js +90 -0
  310. package/dist/tools/edit.d.ts +25 -0
  311. package/dist/tools/edit.js +114 -0
  312. package/dist/tools/exec-allowlist-text.d.ts +18 -0
  313. package/dist/tools/exec-allowlist-text.js +24 -0
  314. package/dist/tools/exec-guard.d.ts +69 -0
  315. package/dist/tools/exec-guard.js +151 -0
  316. package/dist/tools/git-commit.d.ts +59 -0
  317. package/dist/tools/git-commit.js +220 -0
  318. package/dist/tools/git-flags/index.d.ts +10 -0
  319. package/dist/tools/git-flags/index.js +10 -0
  320. package/dist/tools/git.d.ts +88 -0
  321. package/dist/tools/git.js +397 -0
  322. package/dist/tools/glob.d.ts +29 -0
  323. package/dist/tools/glob.js +153 -0
  324. package/dist/tools/grep.d.ts +21 -0
  325. package/dist/tools/grep.js +162 -0
  326. package/dist/tools/index.d.ts +36 -0
  327. package/dist/tools/index.js +28 -0
  328. package/dist/tools/narrow-grants.d.ts +19 -0
  329. package/dist/tools/narrow-grants.js +54 -0
  330. package/dist/tools/nax-owned-writes.d.ts +135 -0
  331. package/dist/tools/nax-owned-writes.js +259 -0
  332. package/dist/tools/package-managers-table.d.ts +146 -0
  333. package/dist/tools/package-managers-table.js +325 -0
  334. package/dist/tools/package-managers-types.d.ts +36 -0
  335. package/dist/tools/package-managers-types.js +6 -0
  336. package/dist/tools/package-managers.d.ts +85 -0
  337. package/dist/tools/package-managers.js +391 -0
  338. package/dist/tools/path-list.d.ts +38 -0
  339. package/dist/tools/path-list.js +62 -0
  340. package/dist/tools/policy-bash-raw.d.ts +19 -0
  341. package/dist/tools/policy-bash-raw.js +210 -0
  342. package/dist/tools/policy-bash.d.ts +64 -0
  343. package/dist/tools/policy-bash.js +232 -0
  344. package/dist/tools/policy-command-branch.d.ts +32 -0
  345. package/dist/tools/policy-command-branch.js +63 -0
  346. package/dist/tools/policy-input.d.ts +2 -0
  347. package/dist/tools/policy-input.js +10 -0
  348. package/dist/tools/policy-match.d.ts +75 -0
  349. package/dist/tools/policy-match.js +117 -0
  350. package/dist/tools/policy-paths-branch.d.ts +97 -0
  351. package/dist/tools/policy-paths-branch.js +314 -0
  352. package/dist/tools/policy.d.ts +65 -0
  353. package/dist/tools/policy.js +232 -0
  354. package/dist/tools/protected-paths.d.ts +26 -0
  355. package/dist/tools/protected-paths.js +8 -0
  356. package/dist/tools/provider-adapt.d.ts +4 -0
  357. package/dist/tools/provider-adapt.js +32 -0
  358. package/dist/tools/provider-advertise.d.ts +39 -0
  359. package/dist/tools/provider-advertise.js +50 -0
  360. package/dist/tools/provider-grants.d.ts +36 -0
  361. package/dist/tools/provider-grants.js +72 -0
  362. package/dist/tools/provider-sanitize.d.ts +19 -0
  363. package/dist/tools/provider-sanitize.js +53 -0
  364. package/dist/tools/provider-types.d.ts +41 -0
  365. package/dist/tools/provider-types.js +26 -0
  366. package/dist/tools/read-continuation.d.ts +147 -0
  367. package/dist/tools/read-continuation.js +176 -0
  368. package/dist/tools/read-file.d.ts +32 -0
  369. package/dist/tools/read-file.js +85 -0
  370. package/dist/tools/read.d.ts +25 -0
  371. package/dist/tools/read.js +182 -0
  372. package/dist/tools/registry.d.ts +125 -0
  373. package/dist/tools/registry.js +67 -0
  374. package/dist/tools/request-capability.d.ts +13 -0
  375. package/dist/tools/request-capability.js +23 -0
  376. package/dist/tools/run-command-exec.d.ts +14 -0
  377. package/dist/tools/run-command-exec.js +161 -0
  378. package/dist/tools/run-command.d.ts +107 -0
  379. package/dist/tools/run-command.js +398 -0
  380. package/dist/tools/runtime-calltool.d.ts +146 -0
  381. package/dist/tools/runtime-calltool.js +223 -0
  382. package/dist/tools/runtime.d.ts +149 -0
  383. package/dist/tools/runtime.js +345 -0
  384. package/dist/tools/scratchpad.d.ts +20 -0
  385. package/dist/tools/scratchpad.js +197 -0
  386. package/dist/tools/spill.d.ts +81 -0
  387. package/dist/tools/spill.js +251 -0
  388. package/dist/tools/tool-audit.d.ts +171 -0
  389. package/dist/tools/tool-audit.js +183 -0
  390. package/dist/tools/truncate.d.ts +98 -0
  391. package/dist/tools/truncate.js +297 -0
  392. package/dist/tools/types.d.ts +145 -0
  393. package/dist/tools/types.js +24 -0
  394. package/dist/tools/verb-denial-argv-hint.d.ts +35 -0
  395. package/dist/tools/verb-denial-argv-hint.js +40 -0
  396. package/dist/tools/write.d.ts +9 -0
  397. package/dist/tools/write.js +45 -0
  398. package/package.json +54 -0
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Generic path-keyed file lock.
3
+ *
4
+ * Keyed on an arbitrary file path (e.g. `metrics.json`) instead of the queue
5
+ * file. Used to serialize read-modify-write sequences across processes so a
6
+ * concurrent `saveRunMetrics` cannot race with another and drop one run's
7
+ * append (BUG-6).
8
+ *
9
+ * Implementation is the shared single-file exclusive-create lock in
10
+ * `file-lock.ts` (see its module doc for the #1731 correctness argument and
11
+ * the BUG-10 / BUG-25 staleness rules). The lock file lives next to the
12
+ * target as `<target>.lock`.
13
+ *
14
+ * Failure mode: the default 5s timeout aborts with a descriptive error —
15
+ * it never enters over a possible holder (fail closed).
16
+ */
17
+ export interface PathFileLockOptions {
18
+ /** Milliseconds between acquisition retries. Default 10. */
19
+ retryMs?: number;
20
+ /** Maximum time to wait for the lock before throwing. Default 5000. */
21
+ timeoutMs?: number;
22
+ }
23
+ export declare function withPathFileLock<T>(targetPath: string, operation: () => Promise<T>, options?: PathFileLockOptions): Promise<T>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Generic path-keyed file lock.
3
+ *
4
+ * Keyed on an arbitrary file path (e.g. `metrics.json`) instead of the queue
5
+ * file. Used to serialize read-modify-write sequences across processes so a
6
+ * concurrent `saveRunMetrics` cannot race with another and drop one run's
7
+ * append (BUG-6).
8
+ *
9
+ * Implementation is the shared single-file exclusive-create lock in
10
+ * `file-lock.ts` (see its module doc for the #1731 correctness argument and
11
+ * the BUG-10 / BUG-25 staleness rules). The lock file lives next to the
12
+ * target as `<target>.lock`.
13
+ *
14
+ * Failure mode: the default 5s timeout aborts with a descriptive error —
15
+ * it never enters over a possible holder (fail closed).
16
+ */
17
+ import { withFileLock } from "./file-lock.js";
18
+ export async function withPathFileLock(targetPath, operation, options = {}) {
19
+ return withFileLock(`${targetPath}.lock`, operation, {
20
+ retryMs: options.retryMs,
21
+ timeoutMs: options.timeoutMs,
22
+ lockName: "path",
23
+ errorPrefix: "[path-lock]",
24
+ });
25
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Process Liveness Probe — SSOT
3
+ *
4
+ * Answers "is this PID still running?" for lock holders, queue holders, and
5
+ * spawned children. This is the only place in the codebase that interprets a
6
+ * `process.kill(pid, 0)` probe.
7
+ */
8
+ /**
9
+ * Check whether a process with the given PID is still alive.
10
+ *
11
+ * `process.kill(pid, 0)` sends no signal — it only performs the permission and
12
+ * existence checks the kernel would run for a real signal, then throws on
13
+ * failure. Interpreting that throw is the whole subtlety:
14
+ *
15
+ * | Outcome | Meaning | Alive? |
16
+ * |:---------------|:-------------------------------------------|:-------|
17
+ * | no throw | Probe succeeded | yes |
18
+ * | `ESRCH` | No such process | no |
19
+ * | `EPERM` | Process EXISTS, caller may not signal it | yes |
20
+ * | any other/none | Probe inconclusive | yes |
21
+ *
22
+ * Only `ESRCH` proves absence. A bare `catch { return false }` — the shape this
23
+ * helper replaces — misreads `EPERM` as "dead", so a live run owned by another
24
+ * user or another privilege level looks stale. That is a correctness bug for
25
+ * every caller that then reclaims the resource: `nax unlock` deletes a running
26
+ * process's lock, and lock acquisition lets a second run start on top of the
27
+ * first. Inconclusive errors therefore fail **safe** (report alive) rather than
28
+ * fail convenient.
29
+ *
30
+ * @param pid - Process ID to probe. Non-positive and non-finite values are
31
+ * never valid process IDs and report dead without probing — passing a
32
+ * negative PID to `process.kill` would address a process *group*.
33
+ * @returns true if the process exists (or its existence cannot be ruled out)
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * if (isProcessAlive(lockPid)) {
38
+ * throw new NaxError(`nax is already running (PID ${lockPid})`, "LOCK_ACQUISITION_FAILED", { stage: "file-lock" });
39
+ * }
40
+ * ```
41
+ */
42
+ export declare function isProcessAlive(pid: number): boolean;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Process Liveness Probe — SSOT
3
+ *
4
+ * Answers "is this PID still running?" for lock holders, queue holders, and
5
+ * spawned children. This is the only place in the codebase that interprets a
6
+ * `process.kill(pid, 0)` probe.
7
+ */
8
+ /**
9
+ * Check whether a process with the given PID is still alive.
10
+ *
11
+ * `process.kill(pid, 0)` sends no signal — it only performs the permission and
12
+ * existence checks the kernel would run for a real signal, then throws on
13
+ * failure. Interpreting that throw is the whole subtlety:
14
+ *
15
+ * | Outcome | Meaning | Alive? |
16
+ * |:---------------|:-------------------------------------------|:-------|
17
+ * | no throw | Probe succeeded | yes |
18
+ * | `ESRCH` | No such process | no |
19
+ * | `EPERM` | Process EXISTS, caller may not signal it | yes |
20
+ * | any other/none | Probe inconclusive | yes |
21
+ *
22
+ * Only `ESRCH` proves absence. A bare `catch { return false }` — the shape this
23
+ * helper replaces — misreads `EPERM` as "dead", so a live run owned by another
24
+ * user or another privilege level looks stale. That is a correctness bug for
25
+ * every caller that then reclaims the resource: `nax unlock` deletes a running
26
+ * process's lock, and lock acquisition lets a second run start on top of the
27
+ * first. Inconclusive errors therefore fail **safe** (report alive) rather than
28
+ * fail convenient.
29
+ *
30
+ * @param pid - Process ID to probe. Non-positive and non-finite values are
31
+ * never valid process IDs and report dead without probing — passing a
32
+ * negative PID to `process.kill` would address a process *group*.
33
+ * @returns true if the process exists (or its existence cannot be ruled out)
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * if (isProcessAlive(lockPid)) {
38
+ * throw new NaxError(`nax is already running (PID ${lockPid})`, "LOCK_ACQUISITION_FAILED", { stage: "file-lock" });
39
+ * }
40
+ * ```
41
+ */
42
+ export function isProcessAlive(pid) {
43
+ if (!Number.isInteger(pid) || pid <= 0)
44
+ return false;
45
+ try {
46
+ process.kill(pid, 0);
47
+ return true;
48
+ }
49
+ catch (error) {
50
+ // Only ESRCH proves absence. EPERM, any other errno, and a throw carrying
51
+ // no errno at all all mean the same thing — the process was not proven
52
+ // gone — so they report alive. Reclaiming callers (lock steal, unlock
53
+ // delete) must never act on an inconclusive probe.
54
+ return error.code !== "ESRCH";
55
+ }
56
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Process Group Kill Utility
3
+ *
4
+ * Shared helper for killing process groups, preventing orphaned child processes.
5
+ * Uses process.kill(-pid, signal) to kill the entire process group.
6
+ */
7
+ /**
8
+ * Kill a process group by PID, falling back to single process if group kill fails.
9
+ *
10
+ * Process groups are spawned with the negative PID. This function:
11
+ * 1. Attempts to kill the entire process group (negative PID)
12
+ * 2. Falls back to killing the single process if group kill fails
13
+ *
14
+ * @param pid - Process ID (positive, not negative)
15
+ * @param signal - Signal to send (SIGTERM, SIGKILL, etc.)
16
+ * @returns true if kill was sent successfully, false if process already exited
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * if (killProcessGroup(proc.pid, "SIGTERM")) {
21
+ * console.log("Process group killed");
22
+ * } else {
23
+ * console.log("Process already exited");
24
+ * }
25
+ * ```
26
+ */
27
+ export declare function killProcessGroup(pid: number, signal: NodeJS.Signals | number): boolean;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Process Group Kill Utility
3
+ *
4
+ * Shared helper for killing process groups, preventing orphaned child processes.
5
+ * Uses process.kill(-pid, signal) to kill the entire process group.
6
+ */
7
+ /**
8
+ * Kill a process group by PID, falling back to single process if group kill fails.
9
+ *
10
+ * Process groups are spawned with the negative PID. This function:
11
+ * 1. Attempts to kill the entire process group (negative PID)
12
+ * 2. Falls back to killing the single process if group kill fails
13
+ *
14
+ * @param pid - Process ID (positive, not negative)
15
+ * @param signal - Signal to send (SIGTERM, SIGKILL, etc.)
16
+ * @returns true if kill was sent successfully, false if process already exited
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * if (killProcessGroup(proc.pid, "SIGTERM")) {
21
+ * console.log("Process group killed");
22
+ * } else {
23
+ * console.log("Process already exited");
24
+ * }
25
+ * ```
26
+ */
27
+ export function killProcessGroup(pid, signal) {
28
+ // A pid that is not a real child reaches other processes: 0 is our own group,
29
+ // 1 (as -1) is every process we may signal, and a negative pid is init or a
30
+ // foreign group. A spawn that failed has no pid, so refuse rather than signal.
31
+ if (!Number.isInteger(pid) || pid <= 1)
32
+ return false;
33
+ // Try process group kill first (negative PID)
34
+ try {
35
+ process.kill(-pid, signal);
36
+ return true;
37
+ }
38
+ catch (error) {
39
+ const err = error;
40
+ // ESRCH = no such process — process group doesn't exist, try single process
41
+ if (err.code === "ESRCH") {
42
+ try {
43
+ process.kill(pid, signal);
44
+ return true;
45
+ }
46
+ catch {
47
+ // Process already exited
48
+ return false;
49
+ }
50
+ }
51
+ // Other errors (EPERM, etc.) — signal was likely sent, return true
52
+ return true;
53
+ }
54
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Symlink-tolerant path normalisation for comparing paths from different sources.
3
+ *
4
+ * `getGitRoot` returns git's realpath — `git rev-parse --show-toplevel` run from
5
+ * `/tmp/repo` answers `/private/tmp/repo` — while paths threaded through config,
6
+ * pipeline context, or a story record keep whatever spelling the caller supplied.
7
+ * On macOS `/tmp` is a symlink to `/private/tmp` and worktrees are created under
8
+ * temp directories, so the two spellings meet constantly. Comparing them
9
+ * unresolved makes equal paths look different, and every containment check
10
+ * (`startsWith(root)`) silently fails closed.
11
+ *
12
+ * Pure apart from the `realpathSync` probe, and never throws.
13
+ */
14
+ /**
15
+ * Absolute, symlink-resolved form of `p`.
16
+ *
17
+ * A path that does not exist cannot be resolved directly, so this walks up to
18
+ * the nearest ancestor that DOES exist, resolves that, and re-attaches the
19
+ * remaining segments. Resolving only the immediate parent is not enough: a
20
+ * changed file that has since been deleted, or one under a directory that was
21
+ * never created, leaves several missing segments — and a half-resolved answer
22
+ * compares unequal to a fully-resolved root, turning every containment check
23
+ * into a silent false negative.
24
+ *
25
+ * Falls back to the lexical absolute form when no ancestor resolves.
26
+ */
27
+ export declare function realOrRaw(p: string): string;
28
+ /** Is `filePath` inside `root`'s subtree, symlinks resolved on both sides? */
29
+ export declare function isInside(root: string, filePath: string): boolean;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Symlink-tolerant path normalisation for comparing paths from different sources.
3
+ *
4
+ * `getGitRoot` returns git's realpath — `git rev-parse --show-toplevel` run from
5
+ * `/tmp/repo` answers `/private/tmp/repo` — while paths threaded through config,
6
+ * pipeline context, or a story record keep whatever spelling the caller supplied.
7
+ * On macOS `/tmp` is a symlink to `/private/tmp` and worktrees are created under
8
+ * temp directories, so the two spellings meet constantly. Comparing them
9
+ * unresolved makes equal paths look different, and every containment check
10
+ * (`startsWith(root)`) silently fails closed.
11
+ *
12
+ * Pure apart from the `realpathSync` probe, and never throws.
13
+ */
14
+ import { realpathSync } from "node:fs";
15
+ import { basename, dirname, join, resolve, sep } from "node:path";
16
+ /**
17
+ * Absolute, symlink-resolved form of `p`.
18
+ *
19
+ * A path that does not exist cannot be resolved directly, so this walks up to
20
+ * the nearest ancestor that DOES exist, resolves that, and re-attaches the
21
+ * remaining segments. Resolving only the immediate parent is not enough: a
22
+ * changed file that has since been deleted, or one under a directory that was
23
+ * never created, leaves several missing segments — and a half-resolved answer
24
+ * compares unequal to a fully-resolved root, turning every containment check
25
+ * into a silent false negative.
26
+ *
27
+ * Falls back to the lexical absolute form when no ancestor resolves.
28
+ */
29
+ export function realOrRaw(p) {
30
+ const abs = resolve(p);
31
+ const trailing = [];
32
+ let current = abs;
33
+ for (;;) {
34
+ try {
35
+ return trailing.length === 0 ? realpathSync(current) : join(realpathSync(current), ...trailing);
36
+ }
37
+ catch {
38
+ const parent = dirname(current);
39
+ // `dirname` is a fixed point at the filesystem root — nothing resolved.
40
+ if (parent === current)
41
+ return abs;
42
+ trailing.unshift(basename(current));
43
+ current = parent;
44
+ }
45
+ }
46
+ }
47
+ /** Is `filePath` inside `root`'s subtree, symlinks resolved on both sides? */
48
+ export function isInside(root, filePath) {
49
+ const base = realOrRaw(root);
50
+ const target = realOrRaw(filePath);
51
+ return target === base || target.startsWith(`${base}${sep}`);
52
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Secret-redaction helper for the JSONL logger write path.
3
+ *
4
+ * Two layers of protection:
5
+ * 1. Key-based: any field whose name matches SECRET_KEY_PATTERN gets its value
6
+ * replaced with "[REDACTED]" regardless of content.
7
+ * 2. Pattern-based: free-text strings (in values that did NOT trigger layer 1)
8
+ * are scanned for token-shaped substrings (API keys, PATs, etc.).
9
+ */
10
+ /**
11
+ * Patterns are reset via `re.lastIndex = 0` before every call because they carry
12
+ * the `/g` flag (required for `String.replace`). Resetting prevents the stale
13
+ * `lastIndex` bug that skips matches on subsequent calls.
14
+ */
15
+ /** One secret-value pattern and the kind label a masked span shows. */
16
+ export interface SecretValuePattern {
17
+ readonly kind: string;
18
+ readonly re: RegExp;
19
+ }
20
+ export declare const SECRET_VALUE_PATTERNS: readonly SecretValuePattern[];
21
+ /**
22
+ * Recursively walk `input` and return a new value with secrets removed.
23
+ * - Primitive non-strings are returned as-is.
24
+ * - Object keys matching SECRET_KEY_PATTERN → value replaced with "[REDACTED]".
25
+ * - String values (not key-redacted) → token-pattern scan.
26
+ * - Arrays and nested objects are walked recursively.
27
+ */
28
+ export declare function redactSecrets<T>(input: T): T;
29
+ /**
30
+ * Redact a whole log entry — both the free-text `message` and the structured
31
+ * `data` payload.
32
+ *
33
+ * `message` matters as much as `data`: callers routinely interpolate shell
34
+ * commands, agent stderr, and error text into it, any of which can carry a
35
+ * credential. Redacting only `data` left those in cleartext.
36
+ */
37
+ export declare function redactEntry<T extends {
38
+ message: string;
39
+ data?: Record<string, unknown>;
40
+ }>(entry: T): T;
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Secret-redaction helper for the JSONL logger write path.
3
+ *
4
+ * Two layers of protection:
5
+ * 1. Key-based: any field whose name matches SECRET_KEY_PATTERN gets its value
6
+ * replaced with "[REDACTED]" regardless of content.
7
+ * 2. Pattern-based: free-text strings (in values that did NOT trigger layer 1)
8
+ * are scanned for token-shaped substrings (API keys, PATs, etc.).
9
+ */
10
+ // TOKEN(?!s\b) prevents matching plural metric keys like "tokens", "inputTokens",
11
+ // "totalTokens" (which are counts, not credentials) while still matching "token",
12
+ // "GITHUB_TOKEN", "accessToken", etc.
13
+ // SEC-1 (Round 2 review): URL/URI/DSN/_URL/CONNECTIONSTRING were missing —
14
+ // DATABASE_URL=postgres://admin:s3cret@db/prod passed through both redaction
15
+ // layers (the KEY=value regex required SECRET|TOKEN|... before `=`, which
16
+ // DATABASE_URL doesn't satisfy; the key pattern didn't match either).
17
+ // SEC-3: AUTHORIZATION/COOKIE/CREDENTIAL/PASSWD were missing. Deliberately
18
+ // narrowed — this is an unanchored substring test, so AUTH(?:ORIZATION)?
19
+ // would also match author/authorName, and SESSION would match
20
+ // sessionName/sessionId/sessionScratchDir, the run log's primary correlation
21
+ // key (see prompt-auditor.ts:300-302). COOKIE already covers SET-COOKIE and
22
+ // setCookie; sessionToken stays covered by the TOKEN branch.
23
+ const SECRET_KEY_PATTERN = /(SECRET|TOKEN(?!s\b)|API_?KEY|PASSWORD|PRIVATE_?KEY|ACCESS_?KEY|WEBHOOK|AUTHORIZATION|COOKIE|CREDENTIAL|PASSWD|(?:\w+)?_URL|\w+_URI|\w+_DSN|CONNECTION\s*STRING)/i;
24
+ export const SECRET_VALUE_PATTERNS = [
25
+ { kind: "openai", re: /sk-[A-Za-z0-9_-]{16,}/g },
26
+ { kind: "github", re: /ghp_[A-Za-z0-9]{16,}/g },
27
+ { kind: "github", re: /gh[opsu]_[A-Za-z0-9]{16,}/g },
28
+ // LOG-1: GitHub fine-grained PATs — gh[opsu]_ above never matches "github_pat_"
29
+ // ("gh" + "i" is not one of [opsu]).
30
+ { kind: "github-pat", re: /github_pat_[A-Za-z0-9_]{20,}/g },
31
+ { kind: "npm", re: /npm_[A-Za-z0-9]{8,}/g },
32
+ { kind: "aws", re: /AKIA[0-9A-Z]{16}/g },
33
+ { kind: "slack", re: /xox[baprs]-[A-Za-z0-9-]{10,}/g },
34
+ // LOG-1: Telegram bot tokens ("<bot-id>:<35-char secret>").
35
+ { kind: "telegram", re: /\b\d{6,}:[A-Za-z0-9_-]{30,}\b/g },
36
+ // KEY=value assignments inside strings (e.g. "NPM_TOKEN=somevalue")
37
+ { kind: "assignment", re: /(?:SECRET|TOKEN|API_?KEY|PASSWORD|PRIVATE_?KEY|ACCESS_?KEY|WEBHOOK)=[^\s"',]+/gi },
38
+ // MED-01: PEM-encoded key/cert blocks (private keys, certificates, incl.
39
+ // PGP's " ... BLOCK" suffix). The gap between BEGIN/END is bounded to
40
+ // 64KB — generous enough for any realistic single key or bundled
41
+ // certificate chain, while still capping the rescan cost of an
42
+ // unterminated "BEGIN" marker in a pathologically large payload on this
43
+ // synchronous logger write path (a small bound like a few KB risks
44
+ // missing legitimate multi-cert chain bundles entirely).
45
+ {
46
+ kind: "pem",
47
+ re: /-----BEGIN [A-Z ]*(?:PRIVATE KEY|CERTIFICATE)(?: BLOCK)?-----[\s\S]{0,65536}?-----END [A-Z ]*(?:PRIVATE KEY|CERTIFICATE)(?: BLOCK)?-----/g,
48
+ },
49
+ // MED-01: JWTs (header.payload.signature, base64url segments).
50
+ { kind: "jwt", re: /eyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}/g },
51
+ // MED-01: Authorization header values (Bearer/Basic schemes). Requires a
52
+ // credential-shaped value (>=8 chars, at least one digit/symbol) so
53
+ // ordinary prose like "Basic authentication failed" or "Bearer token
54
+ // scheme" isn't swallowed — real bearer tokens and base64 basic creds
55
+ // almost always contain a non-alphabetic character; English words
56
+ // essentially never do. Length floor kept at 8 (not raised to 16) so
57
+ // short-but-real base64 credentials (e.g. "user:pass" -> ~16 raw chars,
58
+ // shorter inputs shorter still) aren't under-redacted.
59
+ { kind: "bearer", re: /\bBearer\s+(?=[A-Za-z0-9\-._~+/]*[0-9+/_-])[A-Za-z0-9\-._~+/]{8,}={0,2}/gi },
60
+ // LOG-1: Basic creds are base64(user:pass) and are frequently pure-alphabetic
61
+ // (no digit/symbol), which the digit-requiring lookahead above misses. Require
62
+ // mixed case instead — real base64 output almost always mixes upper/lower,
63
+ // while English prose like "Basic authentication" does not. No `i` flag here
64
+ // (unlike Bearer): case-insensitivity would make [A-Z]/[a-z] equivalent,
65
+ // defeating the mixed-case check — so the "basic" keyword is spelled out
66
+ // char-by-char to stay case-insensitive for the scheme name only (the
67
+ // scheme is case-insensitive per RFC 7617; "basic"/"BASIC" are as valid as
68
+ // "Basic") while the payload lookaheads remain case-sensitive.
69
+ {
70
+ kind: "basic",
71
+ re: /\b[Bb][Aa][Ss][Ii][Cc]\s+(?=[A-Za-z0-9+/]*[A-Z])(?=[A-Za-z0-9+/]*[a-z])[A-Za-z0-9+/]{8,}={0,2}/g,
72
+ },
73
+ // MED-01: header-style key:value / key=value pairs for api-key headers
74
+ // that SECRET_KEY_PATTERN's object-key check can't reach because the
75
+ // key/value are both embedded in one free-text string (e.g. raw HTTP logs).
76
+ { kind: "api-key-header", re: /(?:x-api-key|api[_-]?key)\s*[:=]\s*[^\s"',]+/gi },
77
+ // SEC-3: Cookie / Set-Cookie headers interpolated into a free-text string
78
+ // (agent stderr is the common path) — SECRET_KEY_PATTERN only sees object
79
+ // keys, not header text. Consumes the rest of the line; cookie values are
80
+ // opaque and line-delimited in header dumps, so a narrower character class
81
+ // would only risk under-redacting the tail of a multi-cookie header.
82
+ { kind: "cookie", re: /\b(?:Set-)?Cookie\s*:\s*[^\r\n]+/gi },
83
+ // SEC-1 (Round 2 review): URL-embedded credentials (scheme://user:pass@host).
84
+ // Matches a scheme name (lowercase letters, digits, +, ., -), then ://, then
85
+ // optional user:password (no /, whitespace, or @), then @. The colon + @
86
+ // is the load-bearing signal that a credential is present; credential-less
87
+ // URLs like "https://example.com/foo" are intentionally left alone because
88
+ // they have no colon between :// and @.
89
+ // Examples:
90
+ // postgres://admin:s3cret@db.internal:5432/prod → "postgres://admin:s3cret@"
91
+ // redis://:hunter2@cache.internal:6379/0 → "redis://:hunter2@" (empty user)
92
+ // mongodb://root:mongoPwd@mongo.internal:27017 → "mongodb://root:mongoPwd@"
93
+ { kind: "url-credentials", re: /\b[a-z][a-z0-9+.-]*:\/\/(?:[^/\s@]*:[^/\s@]+)@/gi },
94
+ ];
95
+ const REDACTED = "[REDACTED]";
96
+ function redactString(value) {
97
+ let out = value;
98
+ for (const { re } of SECRET_VALUE_PATTERNS) {
99
+ re.lastIndex = 0;
100
+ out = out.replace(re, REDACTED);
101
+ }
102
+ return out;
103
+ }
104
+ /**
105
+ * Recursively walk `input` and return a new value with secrets removed.
106
+ * - Primitive non-strings are returned as-is.
107
+ * - Object keys matching SECRET_KEY_PATTERN → value replaced with "[REDACTED]".
108
+ * - String values (not key-redacted) → token-pattern scan.
109
+ * - Arrays and nested objects are walked recursively.
110
+ */
111
+ export function redactSecrets(input) {
112
+ return redactValue(input);
113
+ }
114
+ /**
115
+ * Redact a whole log entry — both the free-text `message` and the structured
116
+ * `data` payload.
117
+ *
118
+ * `message` matters as much as `data`: callers routinely interpolate shell
119
+ * commands, agent stderr, and error text into it, any of which can carry a
120
+ * credential. Redacting only `data` left those in cleartext.
121
+ */
122
+ export function redactEntry(entry) {
123
+ return {
124
+ ...entry,
125
+ message: redactString(entry.message),
126
+ ...(entry.data ? { data: redactValue(entry.data) } : {}),
127
+ };
128
+ }
129
+ /** Depth guard is a backstop against pathological (non-circular but very deep) input; the WeakSet is what actually stops cycles. */
130
+ const MAX_REDACT_DEPTH = 100;
131
+ const CIRCULAR_REF_MARKER = "[Circular]";
132
+ /**
133
+ * MED-02: unguarded recursion here threw a RangeError (stack overflow) out
134
+ * of every logger call whenever a data payload contained a circular
135
+ * reference — a single bad log call could crash whatever code path was
136
+ * trying to log. `seen` tracks objects/arrays currently on the recursion
137
+ * stack (removed on the way back out, so the same object appearing twice at
138
+ * different, non-nested positions is still redacted normally) and
139
+ * `MAX_REDACT_DEPTH` bounds pathologically deep-but-acyclic input.
140
+ */
141
+ function redactValue(input, seen = new WeakSet(), depth = 0) {
142
+ if (typeof input === "string")
143
+ return redactString(input);
144
+ if (input === null || typeof input !== "object")
145
+ return input;
146
+ if (depth >= MAX_REDACT_DEPTH || seen.has(input))
147
+ return CIRCULAR_REF_MARKER;
148
+ seen.add(input);
149
+ try {
150
+ if (Array.isArray(input))
151
+ return input.map((item) => redactValue(item, seen, depth + 1));
152
+ const out = {};
153
+ for (const [key, value] of Object.entries(input)) {
154
+ out[key] = SECRET_KEY_PATTERN.test(key) ? REDACTED : redactValue(value, seen, depth + 1);
155
+ }
156
+ return out;
157
+ }
158
+ finally {
159
+ seen.delete(input);
160
+ }
161
+ }
@@ -0,0 +1,2 @@
1
+ /** POSIX single-quote a shell argument so it survives `/bin/sh -c`. */
2
+ export declare function shellQuoteArg(arg: string): string;
@@ -0,0 +1,4 @@
1
+ /** POSIX single-quote a shell argument so it survives `/bin/sh -c`. */
2
+ export function shellQuoteArg(arg) {
3
+ return `'${arg.replaceAll("'", `'\\''`)}'`;
4
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Comparators for `Array.prototype.sort`.
3
+ *
4
+ * A bare `.sort()` coerces every element to a string and orders by UTF-16 code
5
+ * unit. That is correct for a `string[]` and silently wrong for a `number[]`
6
+ * (`[2, 10]` sorts to `[10, 2]`), so `suspicious/useArraySortCompare` bans the
7
+ * bare form outright. These are the comparators it should resolve to.
8
+ */
9
+ /**
10
+ * Code-point ordering for strings.
11
+ *
12
+ * Byte-identical to what a bare `.sort()` already does on a `string[]`, so
13
+ * adopting it never changes an existing ordering. Preferred over
14
+ * `localeCompare` wherever the order is part of a contract: locale-aware
15
+ * collation is not stable across locales or ICU versions (CTX-5 — see
16
+ * `src/context/engine/digest.ts`).
17
+ */
18
+ export declare const byCodePoint: (a: string, b: string) => number;
19
+ /** Ascending numeric ordering — the comparison a bare `.sort()` does *not* do. */
20
+ export declare const byNumber: (a: number, b: number) => number;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Comparators for `Array.prototype.sort`.
3
+ *
4
+ * A bare `.sort()` coerces every element to a string and orders by UTF-16 code
5
+ * unit. That is correct for a `string[]` and silently wrong for a `number[]`
6
+ * (`[2, 10]` sorts to `[10, 2]`), so `suspicious/useArraySortCompare` bans the
7
+ * bare form outright. These are the comparators it should resolve to.
8
+ */
9
+ /**
10
+ * Code-point ordering for strings.
11
+ *
12
+ * Byte-identical to what a bare `.sort()` already does on a `string[]`, so
13
+ * adopting it never changes an existing ordering. Preferred over
14
+ * `localeCompare` wherever the order is part of a contract: locale-aware
15
+ * collation is not stable across locales or ICU versions (CTX-5 — see
16
+ * `src/context/engine/digest.ts`).
17
+ */
18
+ export const byCodePoint = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
19
+ /** Ascending numeric ordering — the comparison a bare `.sort()` does *not* do. */
20
+ export const byNumber = (a, b) => a - b;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * SEC-09: strip ANSI/escape sequences and other C0 control characters from
3
+ * agent-controlled or PRD-authored display strings before they reach a
4
+ * terminal renderer.
5
+ *
6
+ * Ink's <Text> does not sanitize its children, and the headless console
7
+ * formatter writes raw strings straight to stdout. A crafted prd.json field
8
+ * (story title, failure reason) or agent output containing ESC (\x1b)
9
+ * sequences can move the cursor, clear the screen, or (via OSC 52) write to
10
+ * the clipboard in a terminal showing the user's session — defense-in-depth
11
+ * against a prompt-injected repo or a malicious PRD, not a primary trust
12
+ * boundary (the LLM already writes code that runs).
13
+ */
14
+ /**
15
+ * Removes ESC-initiated sequences (CSI, OSC, and other ANSI escape forms)
16
+ * and other C0 control characters, while leaving normal whitespace
17
+ * (tab, newline, carriage return) untouched.
18
+ */
19
+ export declare function stripControlChars(input: string): string;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * SEC-09: strip ANSI/escape sequences and other C0 control characters from
3
+ * agent-controlled or PRD-authored display strings before they reach a
4
+ * terminal renderer.
5
+ *
6
+ * Ink's <Text> does not sanitize its children, and the headless console
7
+ * formatter writes raw strings straight to stdout. A crafted prd.json field
8
+ * (story title, failure reason) or agent output containing ESC (\x1b)
9
+ * sequences can move the cursor, clear the screen, or (via OSC 52) write to
10
+ * the clipboard in a terminal showing the user's session — defense-in-depth
11
+ * against a prompt-injected repo or a malicious PRD, not a primary trust
12
+ * boundary (the LLM already writes code that runs).
13
+ */
14
+ // C0 control range 0x00-0x1F and DEL (0x7F), excluding tab (\x09), newline
15
+ // (\x0A), and carriage return (\x0D) — display strings are frequently
16
+ // multi-line and stripping those would corrupt legitimate formatting for no
17
+ // security benefit.
18
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: this IS the control-character stripper
19
+ const CONTROL_CHARS_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
20
+ /**
21
+ * Removes ESC-initiated sequences (CSI, OSC, and other ANSI escape forms)
22
+ * and other C0 control characters, while leaving normal whitespace
23
+ * (tab, newline, carriage return) untouched.
24
+ */
25
+ export function stripControlChars(input) {
26
+ if (!input)
27
+ return input;
28
+ // ESC-initiated sequences: OSC "\x1b]...BEL-or-ST", CSI "\x1b[...<final-byte>",
29
+ // and any other "\x1b" + single byte (short escapes like \x1bc, \x1b=).
30
+ const withoutEscapes = input
31
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: matching a real ESC/BEL/ST sequence to strip
32
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "") // OSC ... BEL | OSC ... ST
33
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: matching a real ESC (CSI) sequence to strip
34
+ .replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "") // CSI ... final byte
35
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: matching a real ESC sequence to strip
36
+ .replace(/\x1b[ -~]/g, ""); // other short two-byte escapes (ESC + one printable byte, e.g. ESC c, ESC =, ESC 7)
37
+ return withoutEscapes.replace(CONTROL_CHARS_RE, "");
38
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Is `value` a promise-like — something `await` would suspend on?
3
+ *
4
+ * Two callers need the SAME answer rather than two near-copies: the loop-event
5
+ * dispatcher decides with it whether an `await` is warranted at all (a
6
+ * synchronous answer must not pay a needless microtask yield), and the plugin
7
+ * stager uses it to detect an `async register()` contract violation. A single
8
+ * predicate keeps those from diverging on an exotic function-thenable.
9
+ */
10
+ export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Is `value` a promise-like — something `await` would suspend on?
3
+ *
4
+ * Two callers need the SAME answer rather than two near-copies: the loop-event
5
+ * dispatcher decides with it whether an `await` is warranted at all (a
6
+ * synchronous answer must not pay a needless microtask yield), and the plugin
7
+ * stager uses it to detect an `async register()` contract violation. A single
8
+ * predicate keeps those from diverging on an exotic function-thenable.
9
+ */
10
+ export function isThenable(value) {
11
+ if (value === null)
12
+ return false;
13
+ const type = typeof value;
14
+ return (type === "object" || type === "function") && typeof value.then === "function";
15
+ }