codex-multi-auth 2.17.2 → 2.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +83 -428
  3. package/dist/index.d.ts +0 -1
  4. package/dist/lib/account-policy.d.ts +18 -2
  5. package/dist/lib/account-policy.js +97 -40
  6. package/dist/lib/accounts/rate-limits.d.ts +0 -1
  7. package/dist/lib/accounts.d.ts +0 -1
  8. package/dist/lib/accounts.js +4 -5
  9. package/dist/lib/api-route-store.d.ts +0 -1
  10. package/dist/lib/audit.d.ts +0 -1
  11. package/dist/lib/audit.js +43 -12
  12. package/dist/lib/auth/account-access.d.ts +0 -1
  13. package/dist/lib/auth/account-access.js +5 -1
  14. package/dist/lib/auth/auth.d.ts +4 -3
  15. package/dist/lib/auth/auth.js +220 -28
  16. package/dist/lib/auth/browser.d.ts +0 -1
  17. package/dist/lib/auth/callback-guidance.d.ts +0 -1
  18. package/dist/lib/auth/device-auth.d.ts +0 -1
  19. package/dist/lib/auth/index.d.ts +0 -1
  20. package/dist/lib/auth/org-override.d.ts +0 -1
  21. package/dist/lib/auth/server.d.ts +1 -2
  22. package/dist/lib/auth/server.js +100 -53
  23. package/dist/lib/auth/token-utils.d.ts +0 -1
  24. package/dist/lib/auth-rate-limit.d.ts +0 -1
  25. package/dist/lib/budget-guard.d.ts +16 -2
  26. package/dist/lib/budget-guard.js +123 -43
  27. package/dist/lib/capability-policy.d.ts +0 -1
  28. package/dist/lib/circuit-breaker.d.ts +20 -1
  29. package/dist/lib/circuit-breaker.js +24 -5
  30. package/dist/lib/cli.d.ts +0 -1
  31. package/dist/lib/codex-cli/index.d.ts +0 -1
  32. package/dist/lib/codex-cli/observability.d.ts +0 -1
  33. package/dist/lib/codex-cli/state.d.ts +0 -1
  34. package/dist/lib/codex-cli/sync.d.ts +0 -1
  35. package/dist/lib/codex-cli/writer.d.ts +0 -1
  36. package/dist/lib/codex-manager/account-credentials.d.ts +0 -1
  37. package/dist/lib/codex-manager/account-manager-commands.d.ts +0 -1
  38. package/dist/lib/codex-manager/account-pool-write.d.ts +0 -1
  39. package/dist/lib/codex-manager/active-account-sync.d.ts +6 -0
  40. package/dist/lib/codex-manager/active-account-sync.js +336 -0
  41. package/dist/lib/codex-manager/api-login-menu.d.ts +0 -1
  42. package/dist/lib/codex-manager/backend-category-entry.d.ts +0 -1
  43. package/dist/lib/codex-manager/backend-category-helpers.d.ts +0 -1
  44. package/dist/lib/codex-manager/backend-category-prompt.d.ts +0 -1
  45. package/dist/lib/codex-manager/backend-settings-controller.d.ts +0 -1
  46. package/dist/lib/codex-manager/backend-settings-entry.d.ts +0 -1
  47. package/dist/lib/codex-manager/backend-settings-helpers.d.ts +0 -1
  48. package/dist/lib/codex-manager/backend-settings-prompt.d.ts +0 -1
  49. package/dist/lib/codex-manager/backend-settings-schema.d.ts +0 -1
  50. package/dist/lib/codex-manager/behavior-settings-panel.d.ts +0 -1
  51. package/dist/lib/codex-manager/commands/account.d.ts +8 -3
  52. package/dist/lib/codex-manager/commands/account.js +46 -34
  53. package/dist/lib/codex-manager/commands/best.d.ts +0 -1
  54. package/dist/lib/codex-manager/commands/bridge.d.ts +0 -1
  55. package/dist/lib/codex-manager/commands/budget.d.ts +8 -3
  56. package/dist/lib/codex-manager/commands/budget.js +6 -4
  57. package/dist/lib/codex-manager/commands/check.d.ts +0 -1
  58. package/dist/lib/codex-manager/commands/config-explain.d.ts +0 -1
  59. package/dist/lib/codex-manager/commands/debug-bundle.d.ts +0 -1
  60. package/dist/lib/codex-manager/commands/forecast.d.ts +0 -1
  61. package/dist/lib/codex-manager/commands/history.d.ts +0 -1
  62. package/dist/lib/codex-manager/commands/init-config.d.ts +0 -1
  63. package/dist/lib/codex-manager/commands/integrations.d.ts +0 -1
  64. package/dist/lib/codex-manager/commands/limits.d.ts +0 -1
  65. package/dist/lib/codex-manager/commands/models.d.ts +0 -1
  66. package/dist/lib/codex-manager/commands/monitor.d.ts +0 -1
  67. package/dist/lib/codex-manager/commands/report.d.ts +0 -1
  68. package/dist/lib/codex-manager/commands/resets.d.ts +0 -1
  69. package/dist/lib/codex-manager/commands/resets.js +9 -1
  70. package/dist/lib/codex-manager/commands/rotation.d.ts +0 -1
  71. package/dist/lib/codex-manager/commands/status.d.ts +0 -1
  72. package/dist/lib/codex-manager/commands/switch.d.ts +0 -1
  73. package/dist/lib/codex-manager/commands/uninstall.d.ts +0 -1
  74. package/dist/lib/codex-manager/commands/unpin.d.ts +0 -1
  75. package/dist/lib/codex-manager/commands/usage.d.ts +0 -1
  76. package/dist/lib/codex-manager/commands/verify.d.ts +0 -1
  77. package/dist/lib/codex-manager/commands/why-selected.d.ts +0 -1
  78. package/dist/lib/codex-manager/commands/workspace.d.ts +0 -1
  79. package/dist/lib/codex-manager/dashboard-display-panel.d.ts +0 -1
  80. package/dist/lib/codex-manager/dashboard-formatters.d.ts +0 -1
  81. package/dist/lib/codex-manager/dashboard-settings-controller.d.ts +0 -1
  82. package/dist/lib/codex-manager/dashboard-settings-data.d.ts +0 -1
  83. package/dist/lib/codex-manager/dashboard-settings-entry.d.ts +0 -1
  84. package/dist/lib/codex-manager/experimental-settings-entry.d.ts +0 -1
  85. package/dist/lib/codex-manager/experimental-settings-prompt.d.ts +0 -1
  86. package/dist/lib/codex-manager/experimental-settings-schema.d.ts +0 -1
  87. package/dist/lib/codex-manager/experimental-sync-target-entry.d.ts +0 -1
  88. package/dist/lib/codex-manager/experimental-sync-target.d.ts +0 -1
  89. package/dist/lib/codex-manager/forecast-report-shared.d.ts +0 -1
  90. package/dist/lib/codex-manager/formatters/account-formatters.d.ts +0 -1
  91. package/dist/lib/codex-manager/formatters/index.d.ts +0 -1
  92. package/dist/lib/codex-manager/formatters/model-formatters.d.ts +0 -1
  93. package/dist/lib/codex-manager/formatters/quota-formatters.d.ts +0 -1
  94. package/dist/lib/codex-manager/formatters/text-style.d.ts +0 -1
  95. package/dist/lib/codex-manager/health-check.d.ts +0 -1
  96. package/dist/lib/codex-manager/help.d.ts +0 -1
  97. package/dist/lib/codex-manager/login-action-panel.d.ts +0 -1
  98. package/dist/lib/codex-manager/login-flow.d.ts +0 -1
  99. package/dist/lib/codex-manager/login-menu-actions.d.ts +0 -1
  100. package/dist/lib/codex-manager/login-menu-data.d.ts +0 -1
  101. package/dist/lib/codex-manager/login-oauth.d.ts +0 -1
  102. package/dist/lib/codex-manager/login-workspace-choice.d.ts +0 -1
  103. package/dist/lib/codex-manager/manual-callback.d.ts +0 -1
  104. package/dist/lib/codex-manager/persist-selected-account.d.ts +0 -1
  105. package/dist/lib/codex-manager/quota-cache-helpers.d.ts +0 -1
  106. package/dist/lib/codex-manager/rate-limit-markers.d.ts +0 -1
  107. package/dist/lib/codex-manager/repair-commands.d.ts +0 -1
  108. package/dist/lib/codex-manager/settings-hub/backend.d.ts +0 -1
  109. package/dist/lib/codex-manager/settings-hub/dashboard.d.ts +0 -1
  110. package/dist/lib/codex-manager/settings-hub/experimental.d.ts +0 -1
  111. package/dist/lib/codex-manager/settings-hub/index.d.ts +0 -1
  112. package/dist/lib/codex-manager/settings-hub/shared.d.ts +0 -1
  113. package/dist/lib/codex-manager/settings-hub-entry.d.ts +0 -1
  114. package/dist/lib/codex-manager/settings-hub-menu.d.ts +0 -1
  115. package/dist/lib/codex-manager/settings-hub-prompt.d.ts +0 -1
  116. package/dist/lib/codex-manager/settings-hub.d.ts +0 -1
  117. package/dist/lib/codex-manager/settings-panels.d.ts +0 -1
  118. package/dist/lib/codex-manager/settings-persist-utils.d.ts +0 -1
  119. package/dist/lib/codex-manager/settings-preview.d.ts +0 -1
  120. package/dist/lib/codex-manager/settings-write-queue.d.ts +0 -1
  121. package/dist/lib/codex-manager/statusline-settings-panel.d.ts +0 -1
  122. package/dist/lib/codex-manager/theme-settings-panel.d.ts +0 -1
  123. package/dist/lib/codex-manager/unified-settings-controller.d.ts +0 -1
  124. package/dist/lib/codex-manager/unified-settings-entry.d.ts +0 -1
  125. package/dist/lib/codex-manager.d.ts +1 -2
  126. package/dist/lib/codex-manager.js +5 -95
  127. package/dist/lib/concurrency.d.ts +0 -1
  128. package/dist/lib/config-schema.d.ts +0 -1
  129. package/dist/lib/config.d.ts +0 -1
  130. package/dist/lib/config.js +46 -241
  131. package/dist/lib/constants.d.ts +0 -1
  132. package/dist/lib/context-budget/model-context-windows.d.ts +0 -1
  133. package/dist/lib/context-budget-guard.d.ts +0 -1
  134. package/dist/lib/context-budget-response.d.ts +0 -1
  135. package/dist/lib/context-overflow.d.ts +0 -1
  136. package/dist/lib/dashboard-settings.d.ts +0 -1
  137. package/dist/lib/entitlement-cache.d.ts +0 -1
  138. package/dist/lib/env-parsing.d.ts +13 -1
  139. package/dist/lib/env-parsing.js +21 -0
  140. package/dist/lib/errors.d.ts +0 -1
  141. package/dist/lib/forecast.d.ts +0 -1
  142. package/dist/lib/fs-retry.d.ts +0 -1
  143. package/dist/lib/health.d.ts +0 -1
  144. package/dist/lib/index.d.ts +0 -1
  145. package/dist/lib/integration-generators.d.ts +0 -1
  146. package/dist/lib/live-account-sync.d.ts +0 -1
  147. package/dist/lib/local-bridge.d.ts +0 -1
  148. package/dist/lib/local-bridge.js +4 -1
  149. package/dist/lib/local-client-tokens.d.ts +0 -1
  150. package/dist/lib/local-client-tokens.js +125 -30
  151. package/dist/lib/logger.d.ts +0 -1
  152. package/dist/lib/logger.js +11 -1
  153. package/dist/lib/model-capability-matrix.d.ts +0 -1
  154. package/dist/lib/model-route-policy.d.ts +1 -2
  155. package/dist/lib/named-backup-export.d.ts +0 -1
  156. package/dist/lib/oc-chatgpt-import-adapter.d.ts +0 -1
  157. package/dist/lib/oc-chatgpt-orchestrator.d.ts +0 -1
  158. package/dist/lib/oc-chatgpt-target-detection.d.ts +0 -1
  159. package/dist/lib/parallel-probe.d.ts +0 -1
  160. package/dist/lib/policy/runtime-policy-cache.d.ts +80 -0
  161. package/dist/lib/policy/runtime-policy-cache.js +310 -0
  162. package/dist/lib/policy/runtime-policy.d.ts +1 -2
  163. package/dist/lib/policy/runtime-policy.js +46 -19
  164. package/dist/lib/preemptive-quota-scheduler.d.ts +0 -1
  165. package/dist/lib/proactive-refresh.d.ts +0 -1
  166. package/dist/lib/prompts/codex-host-bridge.d.ts +0 -1
  167. package/dist/lib/prompts/codex.d.ts +0 -1
  168. package/dist/lib/prompts/fetch-utils.d.ts +0 -1
  169. package/dist/lib/prompts/host-codex-prompt.d.ts +0 -1
  170. package/dist/lib/quota-cache.d.ts +0 -1
  171. package/dist/lib/quota-cache.js +60 -36
  172. package/dist/lib/quota-probe.d.ts +0 -1
  173. package/dist/lib/quota-probe.js +5 -2
  174. package/dist/lib/quota-readiness.d.ts +0 -1
  175. package/dist/lib/recovery/constants.d.ts +0 -1
  176. package/dist/lib/recovery/storage.d.ts +0 -1
  177. package/dist/lib/recovery/types.d.ts +0 -1
  178. package/dist/lib/recovery.d.ts +0 -1
  179. package/dist/lib/redaction.d.ts +0 -1
  180. package/dist/lib/refresh-guardian.d.ts +0 -1
  181. package/dist/lib/refresh-lease.d.ts +0 -1
  182. package/dist/lib/refresh-queue.d.ts +0 -1
  183. package/dist/lib/request/client-cancellation.d.ts +0 -1
  184. package/dist/lib/request/error-classification.d.ts +0 -1
  185. package/dist/lib/request/failover-config.d.ts +0 -1
  186. package/dist/lib/request/failure-policy.d.ts +0 -1
  187. package/dist/lib/request/fetch-helpers.d.ts +0 -1
  188. package/dist/lib/request/headers.d.ts +0 -1
  189. package/dist/lib/request/helpers/input-utils.d.ts +0 -1
  190. package/dist/lib/request/helpers/input-utils.js +88 -34
  191. package/dist/lib/request/helpers/model-map.d.ts +0 -1
  192. package/dist/lib/request/helpers/tool-utils.d.ts +0 -1
  193. package/dist/lib/request/helpers/tool-utils.js +421 -4
  194. package/dist/lib/request/index.d.ts +0 -1
  195. package/dist/lib/request/rate-limit-backoff.d.ts +0 -1
  196. package/dist/lib/request/rate-limit-decision.d.ts +0 -1
  197. package/dist/lib/request/request-attempt-budget.d.ts +0 -1
  198. package/dist/lib/request/request-init.d.ts +0 -1
  199. package/dist/lib/request/request-resilience.d.ts +0 -1
  200. package/dist/lib/request/request-transformer.d.ts +0 -1
  201. package/dist/lib/request/request-transformer.js +50 -25
  202. package/dist/lib/request/response-compaction.d.ts +0 -1
  203. package/dist/lib/request/response-handler.d.ts +0 -1
  204. package/dist/lib/request/response-metadata.d.ts +0 -1
  205. package/dist/lib/request/response-outcome.d.ts +0 -1
  206. package/dist/lib/request/stream-failover-runtime.d.ts +0 -1
  207. package/dist/lib/request/stream-failover.d.ts +0 -1
  208. package/dist/lib/request/stream-failover.js +28 -4
  209. package/dist/lib/request/token-refresh.d.ts +0 -1
  210. package/dist/lib/request/url-rewriting.d.ts +0 -1
  211. package/dist/lib/request/wait-utils.d.ts +0 -1
  212. package/dist/lib/rotation.d.ts +0 -1
  213. package/dist/lib/routing-mutex.d.ts +0 -1
  214. package/dist/lib/routing-profiles.d.ts +16 -2
  215. package/dist/lib/routing-profiles.js +119 -44
  216. package/dist/lib/runtime/account-check-helpers.d.ts +0 -1
  217. package/dist/lib/runtime/account-check-types.d.ts +0 -1
  218. package/dist/lib/runtime/account-check.d.ts +0 -1
  219. package/dist/lib/runtime/account-manager-cache-entry.d.ts +0 -1
  220. package/dist/lib/runtime/account-manager-cache.d.ts +0 -1
  221. package/dist/lib/runtime/account-model-catalog.d.ts +1 -2
  222. package/dist/lib/runtime/account-pool.d.ts +0 -1
  223. package/dist/lib/runtime/account-reset-credits.d.ts +0 -1
  224. package/dist/lib/runtime/account-reset-credits.js +8 -2
  225. package/dist/lib/runtime/account-scope.d.ts +0 -1
  226. package/dist/lib/runtime/account-select-event.d.ts +0 -1
  227. package/dist/lib/runtime/account-selection.d.ts +0 -1
  228. package/dist/lib/runtime/account-state.d.ts +0 -1
  229. package/dist/lib/runtime/account-status.d.ts +0 -1
  230. package/dist/lib/runtime/account-storage-scope-entry.d.ts +0 -1
  231. package/dist/lib/runtime/api-model-capabilities.d.ts +0 -1
  232. package/dist/lib/runtime/api-model-runtime.d.ts +0 -1
  233. package/dist/lib/runtime/app-bind.d.ts +0 -1
  234. package/dist/lib/runtime/app-helper-selection.d.ts +0 -1
  235. package/dist/lib/runtime/auth-facade.d.ts +0 -1
  236. package/dist/lib/runtime/automatic-account-checks.d.ts +0 -1
  237. package/dist/lib/runtime/automatic-subscription-checks.d.ts +0 -1
  238. package/dist/lib/runtime/browser-oauth-flow.d.ts +0 -1
  239. package/dist/lib/runtime/capability-boost.d.ts +0 -1
  240. package/dist/lib/runtime/catalog-capabilities.d.ts +0 -1
  241. package/dist/lib/runtime/config-toml.d.ts +0 -1
  242. package/dist/lib/runtime/context-budget-settings.d.ts +0 -1
  243. package/dist/lib/runtime/event-handler.d.ts +0 -1
  244. package/dist/lib/runtime/first-run.d.ts +0 -1
  245. package/dist/lib/runtime/flagged-verify-types.d.ts +0 -1
  246. package/dist/lib/runtime/hydrate-emails.d.ts +0 -1
  247. package/dist/lib/runtime/inference-activity.d.ts +0 -1
  248. package/dist/lib/runtime/live-sync-entry.d.ts +0 -1
  249. package/dist/lib/runtime/live-sync.d.ts +0 -1
  250. package/dist/lib/runtime/loader-setup.d.ts +0 -1
  251. package/dist/lib/runtime/login-menu-accounts.d.ts +0 -1
  252. package/dist/lib/runtime/manual-oauth-flow.d.ts +0 -1
  253. package/dist/lib/runtime/model-discovery-status.d.ts +2 -3
  254. package/dist/lib/runtime/native-account-storage.d.ts +0 -1
  255. package/dist/lib/runtime/native-account-sync.d.ts +0 -1
  256. package/dist/lib/runtime/native-binding-lock.d.ts +0 -1
  257. package/dist/lib/runtime/native-client-auth.d.ts +0 -1
  258. package/dist/lib/runtime/native-provider-config.d.ts +0 -1
  259. package/dist/lib/runtime/native-rate-limits.d.ts +2 -1
  260. package/dist/lib/runtime/native-rate-limits.js +23 -6
  261. package/dist/lib/runtime/preemptive-quota.d.ts +0 -1
  262. package/dist/lib/runtime/quota-headers.d.ts +0 -1
  263. package/dist/lib/runtime/quota-probe.d.ts +0 -1
  264. package/dist/lib/runtime/quota-settings.d.ts +0 -1
  265. package/dist/lib/runtime/refresh-guardian-entry.d.ts +0 -1
  266. package/dist/lib/runtime/refresh-guardian.d.ts +0 -1
  267. package/dist/lib/runtime/request-init.d.ts +0 -1
  268. package/dist/lib/runtime/reset-credit-routing.d.ts +0 -1
  269. package/dist/lib/runtime/reset-credits.d.ts +0 -1
  270. package/dist/lib/runtime/response-output-history.d.ts +0 -1
  271. package/dist/lib/runtime/responses-websocket.d.ts +0 -1
  272. package/dist/lib/runtime/resume-picker.d.ts +0 -1
  273. package/dist/lib/runtime/rotation-account-selection.d.ts +0 -1
  274. package/dist/lib/runtime/rotation-proxy-state.d.ts +0 -1
  275. package/dist/lib/runtime/rotation-server-types.d.ts +0 -1
  276. package/dist/lib/runtime/rotation-storage-meta.d.ts +0 -1
  277. package/dist/lib/runtime/rotation-token-refresh.d.ts +0 -1
  278. package/dist/lib/runtime/runtime-capability-failures.d.ts +0 -1
  279. package/dist/lib/runtime/runtime-current-account.d.ts +0 -1
  280. package/dist/lib/runtime/runtime-observability.d.ts +0 -1
  281. package/dist/lib/runtime/runtime-services.d.ts +0 -1
  282. package/dist/lib/runtime/session-affinity-entry.d.ts +0 -1
  283. package/dist/lib/runtime/session-recovery.d.ts +0 -1
  284. package/dist/lib/runtime/status-marker.d.ts +0 -1
  285. package/dist/lib/runtime/storage-scope.d.ts +0 -1
  286. package/dist/lib/runtime/subscription-first-use.d.ts +0 -1
  287. package/dist/lib/runtime/subscription-quota-event.d.ts +0 -1
  288. package/dist/lib/runtime/subscription-quota-order.d.ts +0 -1
  289. package/dist/lib/runtime/toast.d.ts +0 -1
  290. package/dist/lib/runtime/ui-runtime-entry.d.ts +0 -1
  291. package/dist/lib/runtime/ui-runtime.d.ts +0 -1
  292. package/dist/lib/runtime/verify-flagged.d.ts +0 -1
  293. package/dist/lib/runtime/workspace-model-scopes.d.ts +0 -1
  294. package/dist/lib/runtime-constants.d.ts +0 -1
  295. package/dist/lib/runtime-paths.d.ts +0 -1
  296. package/dist/lib/runtime-rotation-proxy.d.ts +1 -1
  297. package/dist/lib/runtime-rotation-proxy.js +133 -0
  298. package/dist/lib/schemas.d.ts +35 -13
  299. package/dist/lib/schemas.js +19 -2
  300. package/dist/lib/session-affinity.d.ts +0 -1
  301. package/dist/lib/shutdown.d.ts +0 -1
  302. package/dist/lib/storage/account-clear-entry.d.ts +0 -1
  303. package/dist/lib/storage/account-clear.d.ts +0 -1
  304. package/dist/lib/storage/account-match-utils.d.ts +0 -1
  305. package/dist/lib/storage/account-persistence.d.ts +0 -1
  306. package/dist/lib/storage/account-port.d.ts +0 -1
  307. package/dist/lib/storage/account-save-entry.d.ts +0 -1
  308. package/dist/lib/storage/account-save.d.ts +9 -2
  309. package/dist/lib/storage/account-save.js +4 -1
  310. package/dist/lib/storage/account-snapshot.d.ts +0 -1
  311. package/dist/lib/storage/backup-metadata-builder.d.ts +0 -1
  312. package/dist/lib/storage/backup-metadata.d.ts +0 -1
  313. package/dist/lib/storage/backup-paths.d.ts +0 -1
  314. package/dist/lib/storage/backup-restore.d.ts +0 -1
  315. package/dist/lib/storage/cache-artifacts.d.ts +0 -1
  316. package/dist/lib/storage/credential-sidecars.d.ts +0 -1
  317. package/dist/lib/storage/error-hints.d.ts +0 -1
  318. package/dist/lib/storage/file-lock.d.ts +0 -1
  319. package/dist/lib/storage/file-lock.js +33 -1
  320. package/dist/lib/storage/file-paths.d.ts +0 -1
  321. package/dist/lib/storage/fixture-guards.d.ts +0 -1
  322. package/dist/lib/storage/flagged-entry.d.ts +0 -1
  323. package/dist/lib/storage/flagged-load-entry.d.ts +0 -1
  324. package/dist/lib/storage/flagged-save-entry.d.ts +0 -1
  325. package/dist/lib/storage/flagged-storage-file.d.ts +0 -1
  326. package/dist/lib/storage/flagged-storage-io.d.ts +0 -1
  327. package/dist/lib/storage/flagged-storage.d.ts +0 -1
  328. package/dist/lib/storage/gitignore.d.ts +0 -1
  329. package/dist/lib/storage/hash.d.ts +0 -1
  330. package/dist/lib/storage/health.d.ts +0 -1
  331. package/dist/lib/storage/identity.d.ts +0 -1
  332. package/dist/lib/storage/import-export.d.ts +0 -1
  333. package/dist/lib/storage/json-store-lock.d.ts +105 -0
  334. package/dist/lib/storage/json-store-lock.js +259 -0
  335. package/dist/lib/storage/metadata-section.d.ts +0 -1
  336. package/dist/lib/storage/migrations.d.ts +0 -1
  337. package/dist/lib/storage/migrations.js +31 -18
  338. package/dist/lib/storage/named-backup-entry.d.ts +0 -1
  339. package/dist/lib/storage/named-backups-entry.d.ts +0 -1
  340. package/dist/lib/storage/named-backups.d.ts +0 -1
  341. package/dist/lib/storage/path-state.d.ts +0 -1
  342. package/dist/lib/storage/paths.d.ts +0 -1
  343. package/dist/lib/storage/pending-auth.d.ts +9 -3
  344. package/dist/lib/storage/pending-auth.js +30 -8
  345. package/dist/lib/storage/project-migration.d.ts +0 -1
  346. package/dist/lib/storage/public-types.d.ts +0 -1
  347. package/dist/lib/storage/record-identity.d.ts +0 -1
  348. package/dist/lib/storage/record-utils.d.ts +0 -1
  349. package/dist/lib/storage/restore-assessment.d.ts +0 -1
  350. package/dist/lib/storage/restore-backup-entry.d.ts +0 -1
  351. package/dist/lib/storage/restore-metadata.d.ts +0 -1
  352. package/dist/lib/storage/save-retry.d.ts +0 -1
  353. package/dist/lib/storage/snapshot-inspectors.d.ts +0 -1
  354. package/dist/lib/storage/snapshot-merge.d.ts +0 -1
  355. package/dist/lib/storage/storage-parser.d.ts +2 -1
  356. package/dist/lib/storage/storage-parser.js +10 -2
  357. package/dist/lib/storage/transactions.d.ts +0 -1
  358. package/dist/lib/storage.d.ts +15 -2
  359. package/dist/lib/storage.js +612 -25
  360. package/dist/lib/synthetic-response.d.ts +0 -1
  361. package/dist/lib/table-formatter.d.ts +0 -1
  362. package/dist/lib/temp-path.d.ts +0 -1
  363. package/dist/lib/tools/hashline-tools.d.ts +0 -1
  364. package/dist/lib/types.d.ts +0 -1
  365. package/dist/lib/ui/ansi.d.ts +0 -1
  366. package/dist/lib/ui/auth-menu-builder.d.ts +0 -1
  367. package/dist/lib/ui/auth-menu.d.ts +0 -1
  368. package/dist/lib/ui/check-progress.d.ts +0 -1
  369. package/dist/lib/ui/confirm.d.ts +0 -1
  370. package/dist/lib/ui/display-width.d.ts +0 -1
  371. package/dist/lib/ui/format.d.ts +0 -1
  372. package/dist/lib/ui/index.d.ts +0 -1
  373. package/dist/lib/ui/runtime.d.ts +0 -1
  374. package/dist/lib/ui/select.d.ts +0 -1
  375. package/dist/lib/ui/theme.d.ts +0 -1
  376. package/dist/lib/ui/ui-copy.d.ts +0 -1
  377. package/dist/lib/unified-settings.d.ts +13 -11
  378. package/dist/lib/unified-settings.js +22 -14
  379. package/dist/lib/update-notice.d.ts +0 -1
  380. package/dist/lib/usage/index.d.ts +0 -1
  381. package/dist/lib/usage/ledger.d.ts +0 -1
  382. package/dist/lib/usage/pricing.d.ts +0 -1
  383. package/dist/lib/usage/redaction.d.ts +0 -1
  384. package/dist/lib/usage/stream-usage-deferral.d.ts +0 -1
  385. package/dist/lib/usage/types.d.ts +0 -1
  386. package/dist/lib/usage/usage-extraction.d.ts +0 -1
  387. package/dist/lib/utils.d.ts +21 -1
  388. package/dist/lib/utils.js +31 -0
  389. package/dist/lib/wsl.d.ts +0 -1
  390. package/package.json +11 -5
  391. package/scripts/codex-bin-resolver.js +20 -1
  392. package/scripts/codex-multi-auth.js +7 -1
  393. package/scripts/codex.js +288 -7
  394. package/skills/codex-auth-setup/SKILL.md +73 -0
@@ -4,10 +4,9 @@ import { basename, join } from "node:path";
4
4
  import { logWarn } from "./logger.js";
5
5
  import { getCodexMultiAuthDir } from "./runtime-paths.js";
6
6
  import { isRecord, sleep } from "./utils.js";
7
- import { tempPathFor } from "./temp-path.js";
7
+ import { getJsonStoreFileMtimeMs, resetJsonStoreWriteQueuesForTests, stampUpdatedAt, withJsonStoreCasRetry, withJsonStoreFileLock, withJsonStoreWriteQueue, writeJsonStoreFileAtomicWithRetry, } from "./storage/json-store-lock.js";
8
8
  const ACCOUNT_POLICY_FILE_NAME = "account-policies.json";
9
9
  const RETRYABLE_FS_CODES = new Set(["EBUSY", "EPERM"]);
10
- let writeQueue = Promise.resolve();
11
10
  function isRetryableFsError(error) {
12
11
  const code = error?.code;
13
12
  return typeof code === "string" && RETRYABLE_FS_CODES.has(code);
@@ -113,45 +112,103 @@ export async function loadAccountPolicyStore() {
113
112
  return emptyStore();
114
113
  }
115
114
  }
116
- export async function saveAccountPolicyStore(store) {
117
- const path = getAccountPolicyPath();
118
- const payload = normalizeStore(store);
119
- const task = async () => {
120
- await fs.mkdir(getCodexMultiAuthDir(), { recursive: true, mode: 0o700 });
121
- const tempPath = tempPathFor(path);
122
- let moved = false;
123
- try {
124
- await fs.writeFile(tempPath, `${JSON.stringify(payload, null, 2)}\n`, {
125
- encoding: "utf8",
126
- mode: 0o600,
127
- });
128
- for (let attempt = 0; attempt < 5; attempt += 1) {
129
- try {
130
- await fs.rename(tempPath, path);
131
- moved = true;
132
- return;
133
- }
134
- catch (error) {
135
- if (!isRetryableFsError(error) || attempt >= 4)
136
- throw error;
137
- await sleep(10 * 2 ** attempt);
138
- }
115
+ /**
116
+ * Per-key merge of the caller's normalized store over the freshest on-disk
117
+ * store. Policies are keyed by the account's hashed identity (`sha256:`) and
118
+ * carry `updatedAt`; the newer entry wins per key so a save built on a stale
119
+ * snapshot cannot resurrect an older policy over a concurrent process's
120
+ * newer one. Keys the caller does not carry are preserved (upsert-only store
121
+ * — nothing deletes entries), matching the config save's
122
+ * patch-over-fresh-read semantics.
123
+ *
124
+ * When `baseline` (the caller's pre-edit snapshot) is supplied, entries
125
+ * identical to it are untouched carried copies and skipped entirely, so a
126
+ * stale snapshot row can never be re-stamped over a raced write. Entries
127
+ * that differ are deliberate writes and must not lose purely on timestamp:
128
+ * the upsert-time floor stamps against the caller's snapshot, but a
129
+ * concurrent writer can land a still-newer entry before this merge runs, so
130
+ * the merge re-stamps against the freshest on-disk entry instead of
131
+ * silently dropping the edit. Without a baseline every supplied entry is a
132
+ * plain `updatedAt >=` upsert.
133
+ */
134
+ function mergeAccountPolicies(current, incoming, baseline) {
135
+ const merged = {
136
+ version: 1,
137
+ accounts: { ...current.accounts },
138
+ };
139
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
140
+ for (const [key, policy] of Object.entries(incoming.accounts)) {
141
+ const existing = merged.accounts[key];
142
+ if (!baseline) {
143
+ if (!existing || policy.updatedAt >= existing.updatedAt) {
144
+ merged.accounts[key] = policy;
139
145
  }
146
+ continue;
140
147
  }
141
- finally {
142
- if (!moved) {
143
- try {
144
- await fs.unlink(tempPath);
145
- }
146
- catch {
147
- // Best-effort temp cleanup.
148
- }
149
- }
148
+ if (same(policy, baseline.accounts[key]))
149
+ continue;
150
+ policy.updatedAt = stampUpdatedAt(policy.updatedAt, existing?.updatedAt);
151
+ merged.accounts[key] = policy;
152
+ }
153
+ return merged;
154
+ }
155
+ export async function saveAccountPolicyStore(store, baseline) {
156
+ const path = getAccountPolicyPath();
157
+ const incoming = normalizeStore(store);
158
+ // Normalize at invocation like `incoming`: later caller-side mutation of
159
+ // `baseline` must not shift which entries the merge counts as deliberate.
160
+ const prior = baseline ? normalizeStore(baseline) : undefined;
161
+ // Per-path promise queue serializes writers inside THIS process; the
162
+ // lock directory closes the same window against OTHER processes. Inside the lock
163
+ // every attempt re-stats (CAS), re-reads the freshest on-disk store, and
164
+ // merges this call's entries over it — so a save that raced a concurrent
165
+ // write reloads-and-retries rather than blindly overwriting it.
166
+ await withJsonStoreWriteQueue(path, async () => {
167
+ await withJsonStoreFileLock(path, async () => {
168
+ await withJsonStoreCasRetry(async () => {
169
+ const expectedMtimeMs = await getJsonStoreFileMtimeMs(path);
170
+ const current = await loadAccountPolicyStore();
171
+ const merged = mergeAccountPolicies(current, incoming, prior);
172
+ await fs.mkdir(getCodexMultiAuthDir(), {
173
+ recursive: true,
174
+ mode: 0o700,
175
+ });
176
+ await writeJsonStoreFileAtomicWithRetry(path, merged, {
177
+ expectedMtimeMs,
178
+ mode: 0o600,
179
+ });
180
+ });
181
+ });
182
+ });
183
+ }
184
+ /**
185
+ * Run a policy-store read→mutate→write inside the write queue (in-process
186
+ * serialization) AND the cross-process lock directory, with mtime CAS retry:
187
+ * every attempt re-reads the freshest on-disk store and re-applies `mutate`
188
+ * before writing — so two commands editing DIFFERENT fields of the same
189
+ * policy (e.g. `weight` vs `tags`) both land, which the whole-record
190
+ * `updatedAt` merge in {@link saveAccountPolicyStore} cannot guarantee: a
191
+ * stale snapshot's record would either clobber a newer record wholesale or be
192
+ * discarded entirely. `mutate` must be re-appliable across retries (apply
193
+ * precomputed work onto the store it is handed, don't capture mutated state).
194
+ * Prefer this over load→upsert→save whenever the caller is mutating rather
195
+ * than importing a foreign store.
196
+ */
197
+ export async function updateAccountPolicyStore(mutate) {
198
+ const path = getAccountPolicyPath();
199
+ return withJsonStoreWriteQueue(path, () => withJsonStoreFileLock(path, () => withJsonStoreCasRetry(async () => {
200
+ const expectedMtimeMs = await getJsonStoreFileMtimeMs(path);
201
+ const store = await loadAccountPolicyStore();
202
+ const { result, dirty } = mutate(store);
203
+ if (dirty) {
204
+ await fs.mkdir(getCodexMultiAuthDir(), {
205
+ recursive: true,
206
+ mode: 0o700,
207
+ });
208
+ await writeJsonStoreFileAtomicWithRetry(path, normalizeStore(store), { expectedMtimeMs, mode: 0o600 });
150
209
  }
151
- };
152
- const queued = writeQueue.catch(() => undefined).then(task);
153
- writeQueue = queued.then(() => undefined, () => undefined);
154
- await queued;
210
+ return result;
211
+ })));
155
212
  }
156
213
  /** Tiers are integers 0-9; anything else (including legacy absence) is tier 1. */
157
214
  function normalizePriority(value) {
@@ -168,7 +225,7 @@ export function upsertAccountPolicy(store, accountKey, mutate, now = Date.now())
168
225
  next.weight = normalizeWeight(next.weight);
169
226
  next.priority = normalizePriority(next.priority);
170
227
  next.autoPrime = next.autoPrime === true;
171
- next.updatedAt = now;
228
+ next.updatedAt = stampUpdatedAt(now, store.accounts[accountKey]?.updatedAt);
172
229
  store.accounts[accountKey] = next;
173
230
  return next;
174
231
  }
@@ -176,5 +233,5 @@ export function normalizeAccountPolicyTag(value) {
176
233
  return normalizeTag(value);
177
234
  }
178
235
  export function resetAccountPolicyWriteQueueForTests() {
179
- writeQueue = Promise.resolve();
236
+ resetJsonStoreWriteQueuesForTests(getAccountPolicyPath());
180
237
  }
@@ -27,4 +27,3 @@ export declare function clearAllRateLimits(entity: RateLimitedEntity): void;
27
27
  export declare function isRateLimitedForQuotaKey(entity: RateLimitedEntity, key: QuotaKey): boolean;
28
28
  export declare function isRateLimitedForFamily(entity: RateLimitedEntity, family: ModelFamily, model?: string | null): boolean;
29
29
  export declare function formatWaitTime(ms: number): string;
30
- //# sourceMappingURL=rate-limits.d.ts.map
@@ -385,4 +385,3 @@ export declare function formatCooldown(account: {
385
385
  coolingDownUntil?: number;
386
386
  cooldownReason?: string;
387
387
  }, now?: number): string | null;
388
- //# sourceMappingURL=accounts.d.ts.map
@@ -922,16 +922,15 @@ export class AccountManager {
922
922
  if (shouldConsumeToken && !tokenTracker.tryConsume(trackerKey, quotaKey)) {
923
923
  return { ok: false, reason: "token-exhausted" };
924
924
  }
925
- try {
926
- getCircuitBreaker(getAccountCircuitKey(account)).canExecute();
927
- return { ok: true };
928
- }
929
- catch {
925
+ // Non-throwing probe keeps circuit-open storms off the exception path.
926
+ const admission = getCircuitBreaker(getAccountCircuitKey(account)).tryCanExecute();
927
+ if (!admission.ok) {
930
928
  if (shouldConsumeToken) {
931
929
  tokenTracker.refundToken(trackerKey, quotaKey);
932
930
  }
933
931
  return { ok: false, reason: "circuit-open" };
934
932
  }
933
+ return { ok: true };
935
934
  }
936
935
  /**
937
936
  * Refund a token consumed within the refund window (30 seconds).
@@ -21,4 +21,3 @@ export declare function loadApiRoutes(path?: string): Promise<ApiRouteCredential
21
21
  export declare function saveApiRoutes(routes: ApiRouteCredential[], path?: string, expected?: ApiRouteCredential[]): Promise<void>;
22
22
  export declare function updateApiModelDiscovery(route: ApiRouteCredential, models: string[], path?: string): Promise<ApiRouteCredential>;
23
23
  export {};
24
- //# sourceMappingURL=api-route-store.d.ts.map
@@ -33,4 +33,3 @@ export declare function getAuditConfig(): AuditConfig;
33
33
  export declare function auditLog(action: AuditAction, actor: string, resource: string, outcome: AuditOutcome, metadata?: Record<string, unknown>): void;
34
34
  export declare function getAuditLogPath(): string;
35
35
  export declare function listAuditLogFiles(): string[];
36
- //# sourceMappingURL=audit.d.ts.map
package/dist/lib/audit.js CHANGED
@@ -1,4 +1,4 @@
1
- import { writeFileSync, mkdirSync, existsSync, statSync, renameSync, readdirSync, unlinkSync } from "node:fs";
1
+ import { writeFileSync, mkdirSync, existsSync, statSync, chmodSync, renameSync, readdirSync, unlinkSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { getCorrelationId, maskEmail } from "./logger.js";
4
4
  import { getCodexLogDir } from "./runtime-paths.js";
@@ -49,11 +49,32 @@ function ensureLogDir() {
49
49
  function getLogFilePath() {
50
50
  return join(auditConfig.logDir, "audit.log");
51
51
  }
52
+ // POSIX-only permission re-assertion. The append-mode `mode` option applies
53
+ // only when the file is created, so an audit log that already exists with
54
+ // permissive bits (a shared/custom logDir, or one written before this
55
+ // hardening) would stay readable by other local users, and rotation would
56
+ // carry those bits onto the backups. On Windows chmod cannot establish the
57
+ // equivalent ACL — the logDir's ACLs govern access there — so this is skipped
58
+ // on win32 rather than half-applied.
59
+ function restrictLogPermissions(path, currentMode) {
60
+ if (process.platform === "win32")
61
+ return;
62
+ try {
63
+ const mode = currentMode ?? statSync(path).mode;
64
+ if ((mode & 0o777) !== 0o600) {
65
+ chmodSync(path, 0o600);
66
+ }
67
+ }
68
+ catch {
69
+ // Best-effort hardening: audit logging must never break the application.
70
+ }
71
+ }
52
72
  function rotateLogsIfNeeded() {
53
73
  const logPath = getLogFilePath();
54
74
  if (!existsSync(logPath))
55
75
  return;
56
76
  const stats = statSync(logPath);
77
+ restrictLogPermissions(logPath, stats.mode);
57
78
  if (stats.size < auditConfig.maxFileSizeBytes)
58
79
  return;
59
80
  for (let i = auditConfig.maxFiles - 1; i >= 1; i--) {
@@ -64,26 +85,34 @@ function rotateLogsIfNeeded() {
64
85
  }
65
86
  if (existsSync(newer)) {
66
87
  renameSync(newer, older);
88
+ restrictLogPermissions(older);
67
89
  }
68
90
  }
69
91
  }
70
- function sanitizeActor(actor) {
71
- if (actor.includes("@")) {
72
- return maskEmail(actor);
73
- }
74
- return actor;
92
+ // Same email shape logger.ts scrubs, applied as a SUBSTRING masker. Feeding
93
+ // the whole value to maskEmail would treat any "@" as an email separator and
94
+ // mangle non-email identifiers that legitimately carry one — "@scope/package",
95
+ // "@user" handles, URLs with embedded credentials — leaving the audit entry
96
+ // unable to identify the resource. Only real email-shaped spans are masked.
97
+ const EMAIL_SUBSTRING_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
98
+ function maskAuditField(value) {
99
+ return value.replace(EMAIL_SUBSTRING_PATTERN, maskEmail);
75
100
  }
101
+ // Substring markers matched against the lowercased metadata key. Covers
102
+ // token/secret/password plus credential-carrying spellings: "authorization"
103
+ // (raw headers), "api_key"/"api-key"/"apiKey" (api[-_]?key), and "credential".
104
+ const SENSITIVE_KEY_PATTERN = /token|secret|password|authorization|api[-_]?key|credential/;
76
105
  function sanitizeMetadata(metadata) {
77
106
  if (!metadata)
78
107
  return undefined;
79
108
  const sanitized = {};
80
109
  for (const [key, value] of Object.entries(metadata)) {
81
110
  const lowerKey = key.toLowerCase();
82
- if (lowerKey.includes("token") || lowerKey.includes("secret") || lowerKey.includes("password")) {
111
+ if (SENSITIVE_KEY_PATTERN.test(lowerKey)) {
83
112
  sanitized[key] = "***REDACTED***";
84
113
  }
85
- else if (typeof value === "string" && value.includes("@")) {
86
- sanitized[key] = maskEmail(value);
114
+ else if (typeof value === "string") {
115
+ sanitized[key] = maskAuditField(value);
87
116
  }
88
117
  else if (typeof value === "object" && value !== null) {
89
118
  sanitized[key] = sanitizeMetadata(value);
@@ -104,14 +133,16 @@ export function auditLog(action, actor, resource, outcome, metadata) {
104
133
  timestamp: new Date().toISOString(),
105
134
  correlationId: getCorrelationId(),
106
135
  action,
107
- actor: sanitizeActor(actor),
108
- resource,
136
+ actor: maskAuditField(actor),
137
+ resource: maskAuditField(resource),
109
138
  outcome,
110
139
  metadata: sanitizeMetadata(metadata),
111
140
  };
112
141
  const logPath = getLogFilePath();
113
142
  const line = JSON.stringify(entry) + "\n";
114
- writeFileSync(logPath, line, { flag: "a" });
143
+ // mode applies only at creation; rotateLogsIfNeeded already re-asserted
144
+ // 0o600 on an existing file (and on every backup it renamed).
145
+ writeFileSync(logPath, line, { flag: "a", mode: 0o600 });
115
146
  }
116
147
  catch {
117
148
  // Audit logging should never break the application
@@ -112,4 +112,3 @@ export interface StoredAccountIdentity {
112
112
  * "manual" is an explicit `login --org` binding this must not override.
113
113
  */
114
114
  export declare function reboundUnauthorizedAccountIdentity(account: StoredAccountIdentity, accessToken: string, options?: FetchAuthorizedAccountsOptions): Promise<ConstrainedSelection | null>;
115
- //# sourceMappingURL=account-access.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { CODEX_BASE_URL } from "../constants.js";
2
+ import { combineSignals } from "../utils.js";
2
3
  const ACCOUNTS_CHECK_PATH = "/wham/accounts/check";
3
4
  const ACCOUNTS_CHECK_TIMEOUT_MS = 10_000;
4
5
  function readString(value) {
@@ -50,8 +51,11 @@ export async function fetchAuthorizedAccounts(accessToken, options = {}) {
50
51
  return null;
51
52
  const doFetch = options.fetch ?? globalThis.fetch;
52
53
  const timeout = AbortSignal.timeout(ACCOUNTS_CHECK_TIMEOUT_MS);
54
+ // combineSignals wraps AbortSignal.any (available since Node 18.17, the
55
+ // engines floor) with null-tolerance; the native composite also detaches
56
+ // from a long-lived caller signal once this request completes.
53
57
  const signal = options.signal
54
- ? AbortSignal.any([options.signal, timeout])
58
+ ? combineSignals(options.signal, timeout)
55
59
  : timeout;
56
60
  try {
57
61
  const response = await doFetch(`${CODEX_BASE_URL}${ACCOUNTS_CHECK_PATH}`, {
@@ -40,8 +40,10 @@ export declare const AUTH_REDIRECT: Readonly<{
40
40
  */
41
41
  export declare function sanitizeOAuthResponseBodyForLog(rawBody: string): string;
42
42
  /**
43
- * Redacts sensitive OAuth query parameters for safe logging.
44
- * Returns the original string when parsing fails.
43
+ * Redacts sensitive OAuth parameters for safe logging: query keys match
44
+ * case-insensitively, fragment and nested-URL params are covered, userinfo is
45
+ * masked, and unparseable input collapses to `<unparseable-url>` so the raw
46
+ * string can never reach a log verbatim.
45
47
  */
46
48
  export declare function redactOAuthUrlForLog(rawUrl: string): string;
47
49
  /**
@@ -92,4 +94,3 @@ export interface AuthorizationFlowOptions {
92
94
  */
93
95
  export declare function createAuthorizationFlow(options?: AuthorizationFlowOptions): Promise<AuthorizationFlow>;
94
96
  export {};
95
- //# sourceMappingURL=auth.d.ts.map
@@ -33,6 +33,26 @@ const OAUTH_SENSITIVE_QUERY_PARAMS = [
33
33
  "code",
34
34
  "code_challenge",
35
35
  "code_verifier",
36
+ "access_token",
37
+ "id_token",
38
+ "refresh_token",
39
+ "token",
40
+ "device_auth_id",
41
+ "user_code",
42
+ "authorization_code",
43
+ "client_secret",
44
+ "password",
45
+ // Credential-carrying names the logger's SENSITIVE_KEYS already masks:
46
+ // keep the query-key set aligned so they cannot reach a log either.
47
+ "api_key",
48
+ "apikey",
49
+ "authorization",
50
+ "credential",
51
+ "credentials",
52
+ "secret",
53
+ "assertion",
54
+ "client_assertion",
55
+ "id_token_hint",
36
56
  ];
37
57
  function getOAuthResponseLogMetadata(rawResponse) {
38
58
  if (Array.isArray(rawResponse)) {
@@ -55,13 +75,118 @@ const OAUTH_SENSITIVE_BODY_KEYS = [
55
75
  "id_token",
56
76
  "idToken",
57
77
  "codeVerifier",
78
+ "code_verifier",
79
+ "code_challenge",
80
+ "codeChallenge",
58
81
  "token",
59
82
  "code",
60
- "code_verifier",
83
+ "state",
84
+ "device_auth_id",
85
+ "deviceAuthId",
86
+ "user_code",
87
+ "userCode",
88
+ "authorization_code",
89
+ "authorizationCode",
90
+ "client_secret",
91
+ "clientSecret",
92
+ "password",
93
+ // Credential-carrying names the logger's SENSITIVE_KEYS already masks:
94
+ // keep the body-key set aligned so they cannot reach a log either.
95
+ "api_key",
96
+ "apiKey",
97
+ "apikey",
98
+ "authorization",
99
+ "credential",
100
+ "credentials",
101
+ "secret",
102
+ "assertion",
103
+ "client_assertion",
104
+ "clientAssertion",
105
+ "id_token_hint",
106
+ "idTokenHint",
107
+ ];
108
+ function normalizeOAuthSensitiveKey(key) {
109
+ return key.toLowerCase().replace(/[\s_-]/g, "");
110
+ }
111
+ const OAUTH_SENSITIVE_KEY_SET = new Set([...OAUTH_SENSITIVE_BODY_KEYS, ...OAUTH_SENSITIVE_QUERY_PARAMS].map(normalizeOAuthSensitiveKey));
112
+ // A normalized key is also sensitive when it *ends* with a compound secret
113
+ // name — `x-api-key`, `x-client-secret`, `my-refresh-token`,
114
+ // `x-ms-token-aad-id-token` are still credentials. Bare `code`/`state`/`token`
115
+ // are excluded so `error_code`, `csrf_token`, `session_state`, `valid_token`
116
+ // and `api_keys` keep their documented over-redaction protection.
117
+ const OAUTH_SENSITIVE_KEY_SUFFIXES = [
118
+ "accesstoken",
119
+ "refreshtoken",
120
+ "idtoken",
121
+ "clientsecret",
122
+ "clientassertion",
123
+ "apikey",
124
+ "authorizationcode",
125
+ "codechallenge",
126
+ "codeverifier",
127
+ "deviceauthid",
128
+ "usercode",
129
+ "idtokenhint",
130
+ "authorization",
131
+ "credential",
132
+ "credentials",
133
+ "assertion",
134
+ "password",
135
+ "passwords",
136
+ "secret",
61
137
  ];
138
+ function isOAuthSensitiveKey(key) {
139
+ const normalized = normalizeOAuthSensitiveKey(key);
140
+ return (OAUTH_SENSITIVE_KEY_SET.has(normalized) ||
141
+ OAUTH_SENSITIVE_KEY_SUFFIXES.some((suffix) => normalized.endsWith(suffix)));
142
+ }
143
+ // Separator inside a compound key spelling: whitespace, `_`, `-`, `+`, and the
144
+ // percent-encoded space all collapse under normalizeOAuthSensitiveKey, so
145
+ // "client-secret", "ACCESS TOKEN" and "access%20token" must match too.
146
+ const OAUTH_KEY_SEPARATOR = "(?:[\\s_+-]|%20)*";
147
+ // Free-text key shapes. `code`/`state`/`token` need a non-word boundary so
148
+ // longer names like `error_code`/`device_code`/`csrf_token` are untouched.
149
+ const OAUTH_TEXT_SECRET_KEY_PATTERN = [
150
+ `(?:refresh|access|id)${OAUTH_KEY_SEPARATOR}token`,
151
+ `code${OAUTH_KEY_SEPARATOR}(?:challenge|verifier)`,
152
+ `device${OAUTH_KEY_SEPARATOR}auth${OAUTH_KEY_SEPARATOR}id`,
153
+ `user${OAUTH_KEY_SEPARATOR}code`,
154
+ `authorization${OAUTH_KEY_SEPARATOR}code`,
155
+ `client${OAUTH_KEY_SEPARATOR}secret`,
156
+ `api${OAUTH_KEY_SEPARATOR}key`,
157
+ `client${OAUTH_KEY_SEPARATOR}assertion`,
158
+ `id${OAUTH_KEY_SEPARATOR}token${OAUTH_KEY_SEPARATOR}hint`,
159
+ "authorization",
160
+ "credentials?",
161
+ "assertion",
162
+ "password",
163
+ "secret",
164
+ "state",
165
+ "token",
166
+ "code",
167
+ ].join("|");
62
168
  function scrubTokenLikeSubstrings(value) {
63
- let scrubbed = value.replace(/(\b(?:refresh|access|id)[_-]?token\s*[:=]\s*)([^\s,;"'}]{8,})/gi, (_match, prefix) => `${prefix}***REDACTED***`);
64
- scrubbed = scrubbed.replace(/\b(?:RT|AT)_ch_[A-Za-z0-9_-]{20,}\b/g, "***REDACTED***");
169
+ // Bearer runs first: in a "key: Bearer <jwt>" shape the key pass below
170
+ // would otherwise consume the literal word "Bearer" as its value and
171
+ // strand the token behind it.
172
+ let scrubbed = value.replace(/\bBearer(?:[\s+]|%20)+[A-Za-z0-9._~+/=-]{8,4096}/gi, "Bearer ***REDACTED***");
173
+ // `key <sep> value` in free text. Quoted keys and encoded separator gaps
174
+ // (%20/%22/%27) are tolerated; percent-encoded `:`/`=` (%3A/%3D, plus the
175
+ // double-encoded %253A/%253D) count as separators, and a key directly
176
+ // behind a %XX escape still counts as boundary (the escape itself is the
177
+ // delimiter). `_`/`-` are deliberately absent from the gap classes so
178
+ // `error_code`/`csrf-token` style names still survive. Value length is
179
+ // capped so a megabyte-scale run cannot exhaust the regex backtrack
180
+ // stack; the entropy pass below absorbs any leftover tail.
181
+ scrubbed = scrubbed.replace(new RegExp(`((?:(?<![A-Za-z0-9_])|(?<=%[0-9a-zA-Z]{2}))(?:${OAUTH_TEXT_SECRET_KEY_PATTERN})(?:[\\s'"+]|%2[027])*(?:[:=]|%3[ad]|%253[ad])(?:[\\s'"+]|%2[027])*)([^\\s,;"'{}&=*()<>?]{4,4096})`, "gi"), (_match, prefix) => `${prefix}***REDACTED***`);
182
+ // Bare high-entropy slugs (32-512 chars of token charset) with no key
183
+ // context. No word boundaries: consecutive chunks of a giant run each
184
+ // match, so a huge homogeneous value is consumed piecewise instead of
185
+ // overflowing the backtrack stack on one giant quantified match.
186
+ scrubbed = scrubbed.replace(/[A-Za-z0-9_-]{32,512}/g, "***REDACTED***");
187
+ // ChatGPT opaque token shapes — catches shorter RT_/AT_ values the entropy
188
+ // rule misses. No leading boundary: they may be glued inside identifiers.
189
+ scrubbed = scrubbed.replace(/(?:RT|AT)_ch_[A-Za-z0-9_-]{20,512}/g, "***REDACTED***");
65
190
  return scrubbed;
66
191
  }
67
192
  function redactSensitiveFields(value) {
@@ -69,14 +194,17 @@ function redactSensitiveFields(value) {
69
194
  return value.map((item) => redactSensitiveFields(item));
70
195
  }
71
196
  if (value !== null && typeof value === "object") {
72
- const out = {};
73
- const sensitiveSet = new Set(OAUTH_SENSITIVE_BODY_KEYS);
197
+ // Null-prototype map so a `__proto__` key stays ordinary data instead of
198
+ // munging the output object's prototype.
199
+ const out = Object.create(null);
74
200
  for (const [k, v] of Object.entries(value)) {
75
- if (sensitiveSet.has(k)) {
201
+ if (isOAuthSensitiveKey(k)) {
76
202
  out[k] = "***REDACTED***";
77
203
  }
78
204
  else {
79
- out[k] = redactSensitiveFields(v);
205
+ // The key name itself can smuggle a token (an error response that
206
+ // echoes request fields verbatim) — scrub token shapes from it.
207
+ out[scrubTokenLikeSubstrings(k)] = redactSensitiveFields(v);
80
208
  }
81
209
  }
82
210
  return out;
@@ -120,34 +248,98 @@ export function sanitizeOAuthResponseBodyForLog(rawBody) {
120
248
  // Fall through to regex scrub for malformed JSON.
121
249
  }
122
250
  }
251
+ // Single-pass alternation over the normalized key shapes so case- and
252
+ // separator-varied spellings ("client-secret", "ACCESS TOKEN",
253
+ // "access%20token") still match when the body is not parseable JSON.
123
254
  let scrubbed = rawBody;
124
- for (const key of OAUTH_SENSITIVE_BODY_KEYS) {
125
- // "key":"value" style (JSON-like text)
126
- const jsonPattern = new RegExp(`("${key}"\\s*:\\s*)"[^"]*"`, "g");
127
- scrubbed = scrubbed.replace(jsonPattern, `$1"***REDACTED***"`);
128
- // key=value style (urlencoded / query-string)
129
- const urlPattern = new RegExp(`(^|[?&\\s])(${key}=)[^&\\s]+`, "g");
130
- scrubbed = scrubbed.replace(urlPattern, `$1$2***REDACTED***`);
255
+ // "key":"value" / 'key':'value' style (JSON-like text); a stray separator
256
+ // may sit before the closing quote ("access_token ":).
257
+ scrubbed = scrubbed.replace(new RegExp(`(["'](?:${OAUTH_TEXT_SECRET_KEY_PATTERN})${OAUTH_KEY_SEPARATOR}["']\\s*:\\s*)["'][^"']*["']`, "gi"), `$1"***REDACTED***"`);
258
+ // key<sep>value style (urlencoded / query-string). The boundary class
259
+ // excludes `_` and alphanumerics so `device_code`/`error_code`/
260
+ // `csrf_token`/`opcode` are not redacted; a leading %XX escape does count
261
+ // as a boundary since it decodes to a delimiter.
262
+ scrubbed = scrubbed.replace(new RegExp(`(^|[^A-Za-z0-9_]|%[0-9a-zA-Z]{2})((?:${OAUTH_TEXT_SECRET_KEY_PATTERN})${OAUTH_KEY_SEPARATOR}(?:[:=]|%3[ad]|%253[ad]))[^&\\s]+`, "gi"), "$1$2***REDACTED***");
263
+ return scrubTokenLikeSubstrings(scrubbed);
264
+ }
265
+ function redactUrlSearchParamsForLog(params, depth) {
266
+ for (const key of new Set(params.keys())) {
267
+ if (isOAuthSensitiveKey(key)) {
268
+ params.set(key, "<redacted>");
269
+ continue;
270
+ }
271
+ // Non-sensitive keys still get a free-text scrub at every depth:
272
+ // `msg=refresh_token%3D...` or `error_description=Bearer ...` would
273
+ // otherwise carry embedded secrets straight into the log. URL values
274
+ // recurse structurally while depth remains, then fall back to the
275
+ // flat scrub so deeper nesting still loses its secrets.
276
+ const values = params.getAll(key);
277
+ const redactedValues = values.map((value) => /^https?:\/\//i.test(value) && depth > 0
278
+ ? redactOAuthUrlForLogDepth(value, depth - 1)
279
+ : scrubTokenLikeSubstrings(value));
280
+ if (redactedValues.some((v, i) => v !== values[i])) {
281
+ params.delete(key);
282
+ for (const v of redactedValues)
283
+ params.append(key, v);
284
+ }
131
285
  }
132
- return scrubbed;
133
286
  }
134
- /**
135
- * Redacts sensitive OAuth query parameters for safe logging.
136
- * Returns the original string when parsing fails.
137
- */
138
- export function redactOAuthUrlForLog(rawUrl) {
287
+ function redactOAuthUrlForLogDepth(rawUrl, depth) {
288
+ let parsed;
139
289
  try {
140
- const parsed = new URL(rawUrl);
141
- for (const key of OAUTH_SENSITIVE_QUERY_PARAMS) {
142
- if (parsed.searchParams.has(key)) {
143
- parsed.searchParams.set(key, "<redacted>");
144
- }
145
- }
146
- return parsed.toString();
290
+ parsed = new URL(rawUrl);
147
291
  }
148
292
  catch {
149
- return rawUrl;
293
+ return "<unparseable-url>";
150
294
  }
295
+ redactUrlSearchParamsForLog(parsed.searchParams, depth);
296
+ // Fragment params (#access_token=...) live in `hash`, not `searchParams`.
297
+ // A route-style fragment (#/path?code=...) keeps its prefix, which is
298
+ // itself free text that can carry secrets like `#code=...?` — scrub it
299
+ // rather than copying it back verbatim. The body is decoded first so a
300
+ // fully percent-encoded fragment cannot hide `?`/`=` from the splitter.
301
+ let hashBody = parsed.hash.startsWith("#") ? parsed.hash.slice(1) : "";
302
+ if (hashBody.includes("%")) {
303
+ try {
304
+ hashBody = decodeURIComponent(hashBody);
305
+ }
306
+ catch {
307
+ // Malformed escape — keep the raw fragment body.
308
+ }
309
+ }
310
+ if (hashBody.includes("=")) {
311
+ const qIndex = hashBody.indexOf("?");
312
+ const fragPath = qIndex >= 0 ? hashBody.slice(0, qIndex + 1) : "";
313
+ const fragParams = qIndex >= 0 ? hashBody.slice(qIndex + 1) : hashBody;
314
+ const scrubbedPath = scrubTokenLikeSubstrings(fragPath);
315
+ const params = new URLSearchParams(fragParams);
316
+ redactUrlSearchParamsForLog(params, depth);
317
+ const rebuilt = params.toString();
318
+ if (rebuilt !== fragParams || scrubbedPath !== fragPath) {
319
+ parsed.hash = `${scrubbedPath}${rebuilt}`;
320
+ }
321
+ }
322
+ else if (hashBody.length > 0) {
323
+ parsed.hash = scrubTokenLikeSubstrings(hashBody);
324
+ }
325
+ if (parsed.username)
326
+ parsed.username = "***";
327
+ if (parsed.password)
328
+ parsed.password = "***";
329
+ const scrubbedHost = scrubTokenLikeSubstrings(parsed.hostname);
330
+ if (scrubbedHost !== parsed.hostname)
331
+ parsed.hostname = scrubbedHost;
332
+ parsed.pathname = scrubTokenLikeSubstrings(parsed.pathname);
333
+ return parsed.toString();
334
+ }
335
+ /**
336
+ * Redacts sensitive OAuth parameters for safe logging: query keys match
337
+ * case-insensitively, fragment and nested-URL params are covered, userinfo is
338
+ * masked, and unparseable input collapses to `<unparseable-url>` so the raw
339
+ * string can never reach a log verbatim.
340
+ */
341
+ export function redactOAuthUrlForLog(rawUrl) {
342
+ return redactOAuthUrlForLogDepth(rawUrl, 1);
151
343
  }
152
344
  /**
153
345
  * Generate a random state value for OAuth flow