openai 0.84.0 → 0.85.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 (267) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +1 -1
  4. data/SECURITY.md +10 -0
  5. data/docs/architecture/security-model.md +267 -0
  6. data/lib/openai/client.rb +4 -0
  7. data/lib/openai/helpers/streaming/chat_completion_stream.rb +17 -8
  8. data/lib/openai/helpers/streaming/response_stream.rb +13 -8
  9. data/lib/openai/helpers/structured_output/array_of.rb +2 -2
  10. data/lib/openai/helpers/structured_output/base_model.rb +19 -5
  11. data/lib/openai/helpers/structured_output/chat_completion_parser.rb +6 -3
  12. data/lib/openai/helpers/structured_output/json_schema_converter.rb +34 -11
  13. data/lib/openai/internal/vector_store_file_uploader.rb +1 -1
  14. data/lib/openai/models/admin/organization/usage_costs_params.rb +1 -1
  15. data/lib/openai/models/batch.rb +2 -2
  16. data/lib/openai/models/beta/beta_custom_tool.rb +13 -1
  17. data/lib/openai/models/beta/beta_function_tool.rb +8 -1
  18. data/lib/openai/models/beta/beta_namespace_tool.rb +10 -1
  19. data/lib/openai/models/beta/beta_response.rb +20 -9
  20. data/lib/openai/models/beta/beta_response_configuration_update_item.rb +103 -0
  21. data/lib/openai/models/beta/beta_response_configuration_update_item_param.rb +104 -0
  22. data/lib/openai/models/beta/beta_response_custom_tool_call.rb +9 -1
  23. data/lib/openai/models/beta/beta_response_custom_tool_call_item.rb +3 -1
  24. data/lib/openai/models/beta/beta_response_error.rb +96 -1
  25. data/lib/openai/models/beta/beta_response_function_tool_call.rb +9 -1
  26. data/lib/openai/models/beta/beta_response_function_tool_call_item.rb +3 -1
  27. data/lib/openai/models/beta/beta_response_incomplete_event.rb +4 -0
  28. data/lib/openai/models/beta/beta_response_inject_event.rb +2 -2
  29. data/lib/openai/models/beta/beta_response_inject_failed_event.rb +2 -2
  30. data/lib/openai/models/beta/beta_response_input_item.rb +6 -1
  31. data/lib/openai/models/beta/beta_response_item.rb +5 -1
  32. data/lib/openai/models/beta/beta_response_steer_accepted_event.rb +81 -0
  33. data/lib/openai/models/beta/beta_response_steer_error_code.rb +61 -0
  34. data/lib/openai/models/beta/beta_response_steer_event.rb +75 -0
  35. data/lib/openai/models/beta/beta_response_steer_failed_event.rb +151 -0
  36. data/lib/openai/models/beta/beta_response_steer_input.rb +360 -0
  37. data/lib/openai/models/beta/beta_response_steer_input_content.rb +28 -0
  38. data/lib/openai/models/beta/beta_response_steer_pending_event.rb +113 -0
  39. data/lib/openai/models/beta/beta_response_steer_pending_reason.rb +26 -0
  40. data/lib/openai/models/beta/beta_response_steer_required_input.rb +215 -0
  41. data/lib/openai/models/beta/beta_response_stream_event.rb +4 -0
  42. data/lib/openai/models/beta/beta_responses_client_event.rb +40 -7
  43. data/lib/openai/models/beta/beta_responses_server_event.rb +164 -2
  44. data/lib/openai/models/beta/response_compact_params.rb +9 -6
  45. data/lib/openai/models/beta/response_create_params.rb +9 -6
  46. data/lib/openai/models/beta/responses/beta_response_item_list.rb +2 -2
  47. data/lib/openai/models/beta/responses/input_token_count_params.rb +3 -3
  48. data/lib/openai/models/chat/completion_create_params.rb +4 -4
  49. data/lib/openai/models/chat_model.rb +1 -0
  50. data/lib/openai/models/conversations/conversation_create_params.rb +2 -2
  51. data/lib/openai/models/conversations/conversation_item.rb +5 -1
  52. data/lib/openai/models/conversations/conversation_item_list.rb +2 -2
  53. data/lib/openai/models/conversations/item_create_params.rb +2 -2
  54. data/lib/openai/models/error_object.rb +90 -1
  55. data/lib/openai/models/eval_create_response.rb +1 -1
  56. data/lib/openai/models/eval_list_response.rb +1 -1
  57. data/lib/openai/models/eval_retrieve_response.rb +1 -1
  58. data/lib/openai/models/eval_update_response.rb +1 -1
  59. data/lib/openai/models/evals/create_eval_completions_run_data_source.rb +2 -2
  60. data/lib/openai/models/responses/custom_tool.rb +13 -1
  61. data/lib/openai/models/responses/function_tool.rb +8 -1
  62. data/lib/openai/models/responses/input_token_count_params.rb +3 -3
  63. data/lib/openai/models/responses/namespace_tool.rb +10 -1
  64. data/lib/openai/models/responses/response.rb +16 -8
  65. data/lib/openai/models/responses/response_compact_params.rb +9 -6
  66. data/lib/openai/models/responses/response_configuration_update_item.rb +56 -0
  67. data/lib/openai/models/responses/response_configuration_update_item_param.rb +57 -0
  68. data/lib/openai/models/responses/response_create_params.rb +5 -5
  69. data/lib/openai/models/responses/response_custom_tool_call.rb +9 -1
  70. data/lib/openai/models/responses/response_custom_tool_call_item.rb +3 -1
  71. data/lib/openai/models/responses/response_error.rb +96 -1
  72. data/lib/openai/models/responses/response_function_tool_call.rb +9 -1
  73. data/lib/openai/models/responses/response_function_tool_call_item.rb +3 -1
  74. data/lib/openai/models/responses/response_incomplete_event.rb +4 -0
  75. data/lib/openai/models/responses/response_input_item.rb +6 -1
  76. data/lib/openai/models/responses/response_item.rb +5 -1
  77. data/lib/openai/models/responses/response_item_list.rb +2 -2
  78. data/lib/openai/models/responses/response_steer_accepted_event.rb +79 -0
  79. data/lib/openai/models/responses/response_steer_error_code.rb +59 -0
  80. data/lib/openai/models/responses/response_steer_event.rb +73 -0
  81. data/lib/openai/models/responses/response_steer_failed_event.rb +149 -0
  82. data/lib/openai/models/responses/response_steer_input.rb +304 -0
  83. data/lib/openai/models/responses/response_steer_input_content.rb +26 -0
  84. data/lib/openai/models/responses/response_steer_pending_event.rb +111 -0
  85. data/lib/openai/models/responses/response_steer_pending_reason.rb +24 -0
  86. data/lib/openai/models/responses/response_steer_required_input.rb +213 -0
  87. data/lib/openai/models/responses/response_stream_event.rb +4 -0
  88. data/lib/openai/models/responses/responses_client_event.rb +872 -769
  89. data/lib/openai/models/responses/responses_server_event.rb +162 -2
  90. data/lib/openai/models/safety/alert_retrieve_params.rb +24 -0
  91. data/lib/openai/models/safety/safety_alert.rb +96 -0
  92. data/lib/openai/models/video_create_error.rb +93 -1
  93. data/lib/openai/models/webhooks/safety_alert_created_webhook_event.rb +63 -0
  94. data/lib/openai/models/webhooks/safety_org_alert_created_webhook_event.rb +63 -0
  95. data/lib/openai/models/webhooks/unwrap_webhook_event.rb +7 -1
  96. data/lib/openai/models.rb +2 -0
  97. data/lib/openai/net_http_client.rb +1 -1
  98. data/lib/openai/providers/bedrock.rb +20 -12
  99. data/lib/openai/resources/beta/responses/input_items.rb +1 -1
  100. data/lib/openai/resources/beta/responses/input_tokens.rb +1 -1
  101. data/lib/openai/resources/beta/responses.rb +6 -6
  102. data/lib/openai/resources/chat/completions.rb +2 -2
  103. data/lib/openai/resources/conversations/items.rb +3 -3
  104. data/lib/openai/resources/conversations.rb +1 -1
  105. data/lib/openai/resources/responses/input_items.rb +1 -1
  106. data/lib/openai/resources/responses/input_tokens.rb +1 -1
  107. data/lib/openai/resources/responses.rb +6 -6
  108. data/lib/openai/resources/safety/alerts.rb +37 -0
  109. data/lib/openai/resources/safety.rb +18 -0
  110. data/lib/openai/resources/webhooks.rb +1 -1
  111. data/lib/openai/version.rb +1 -1
  112. data/lib/openai.rb +28 -0
  113. data/rbi/openai/client.rbi +3 -0
  114. data/rbi/openai/models/admin/organization/usage_costs_params.rbi +2 -2
  115. data/rbi/openai/models/batch.rbi +2 -2
  116. data/rbi/openai/models/beta/beta_custom_tool.rbi +15 -0
  117. data/rbi/openai/models/beta/beta_function_tool.rbi +11 -0
  118. data/rbi/openai/models/beta/beta_namespace_tool.rbi +15 -0
  119. data/rbi/openai/models/beta/beta_response.rbi +14 -6
  120. data/rbi/openai/models/beta/beta_response_configuration_update_item.rbi +200 -0
  121. data/rbi/openai/models/beta/beta_response_configuration_update_item_param.rbi +207 -0
  122. data/rbi/openai/models/beta/beta_response_custom_tool_call.rbi +13 -0
  123. data/rbi/openai/models/beta/beta_response_custom_tool_call_item.rbi +6 -0
  124. data/rbi/openai/models/beta/beta_response_error.rbi +161 -3
  125. data/rbi/openai/models/beta/beta_response_function_tool_call.rbi +13 -0
  126. data/rbi/openai/models/beta/beta_response_function_tool_call_item.rbi +6 -0
  127. data/rbi/openai/models/beta/beta_response_incomplete_event.rbi +4 -0
  128. data/rbi/openai/models/beta/beta_response_inject_event.rbi +3 -0
  129. data/rbi/openai/models/beta/beta_response_inject_failed_event.rbi +3 -0
  130. data/rbi/openai/models/beta/beta_response_input_item.rbi +1 -0
  131. data/rbi/openai/models/beta/beta_response_item.rbi +1 -0
  132. data/rbi/openai/models/beta/beta_response_steer_accepted_event.rbi +148 -0
  133. data/rbi/openai/models/beta/beta_response_steer_error_code.rbi +65 -0
  134. data/rbi/openai/models/beta/beta_response_steer_event.rbi +118 -0
  135. data/rbi/openai/models/beta/beta_response_steer_failed_event.rbi +267 -0
  136. data/rbi/openai/models/beta/beta_response_steer_input.rbi +602 -0
  137. data/rbi/openai/models/beta/beta_response_steer_input_content.rbi +31 -0
  138. data/rbi/openai/models/beta/beta_response_steer_pending_event.rbi +222 -0
  139. data/rbi/openai/models/beta/beta_response_steer_pending_reason.rbi +35 -0
  140. data/rbi/openai/models/beta/beta_response_steer_required_input.rbi +379 -0
  141. data/rbi/openai/models/beta/beta_responses_client_event.rbi +8 -3
  142. data/rbi/openai/models/beta/beta_responses_server_event.rbi +206 -3
  143. data/rbi/openai/models/beta/response_compact_params.rbi +4 -3
  144. data/rbi/openai/models/beta/response_create_params.rbi +4 -3
  145. data/rbi/openai/models/beta/responses/beta_response_item_list.rbi +1 -0
  146. data/rbi/openai/models/chat/completion_create_params.rbi +3 -3
  147. data/rbi/openai/models/chat_model.rbi +1 -0
  148. data/rbi/openai/models/conversations/conversation_create_params.rbi +3 -0
  149. data/rbi/openai/models/conversations/conversation_item.rbi +1 -0
  150. data/rbi/openai/models/conversations/conversation_item_list.rbi +1 -0
  151. data/rbi/openai/models/conversations/item_create_params.rbi +3 -0
  152. data/rbi/openai/models/error_object.rbi +156 -3
  153. data/rbi/openai/models/eval_create_response.rbi +1 -1
  154. data/rbi/openai/models/eval_list_response.rbi +1 -1
  155. data/rbi/openai/models/eval_retrieve_response.rbi +1 -1
  156. data/rbi/openai/models/eval_update_response.rbi +1 -1
  157. data/rbi/openai/models/evals/create_eval_completions_run_data_source.rbi +2 -2
  158. data/rbi/openai/models/responses/custom_tool.rbi +15 -0
  159. data/rbi/openai/models/responses/function_tool.rbi +11 -0
  160. data/rbi/openai/models/responses/namespace_tool.rbi +15 -0
  161. data/rbi/openai/models/responses/response.rbi +12 -5
  162. data/rbi/openai/models/responses/response_compact_params.rbi +4 -3
  163. data/rbi/openai/models/responses/response_configuration_update_item.rbi +112 -0
  164. data/rbi/openai/models/responses/response_configuration_update_item_param.rbi +117 -0
  165. data/rbi/openai/models/responses/response_create_params.rbi +2 -2
  166. data/rbi/openai/models/responses/response_custom_tool_call.rbi +13 -0
  167. data/rbi/openai/models/responses/response_custom_tool_call_item.rbi +6 -0
  168. data/rbi/openai/models/responses/response_error.rbi +161 -3
  169. data/rbi/openai/models/responses/response_function_tool_call.rbi +13 -0
  170. data/rbi/openai/models/responses/response_function_tool_call_item.rbi +6 -0
  171. data/rbi/openai/models/responses/response_incomplete_event.rbi +4 -0
  172. data/rbi/openai/models/responses/response_input_item.rbi +1 -0
  173. data/rbi/openai/models/responses/response_item.rbi +1 -0
  174. data/rbi/openai/models/responses/response_item_list.rbi +1 -0
  175. data/rbi/openai/models/responses/response_steer_accepted_event.rbi +146 -0
  176. data/rbi/openai/models/responses/response_steer_error_code.rbi +63 -0
  177. data/rbi/openai/models/responses/response_steer_event.rbi +119 -0
  178. data/rbi/openai/models/responses/response_steer_failed_event.rbi +268 -0
  179. data/rbi/openai/models/responses/response_steer_input.rbi +474 -0
  180. data/rbi/openai/models/responses/response_steer_input_content.rbi +29 -0
  181. data/rbi/openai/models/responses/response_steer_pending_event.rbi +220 -0
  182. data/rbi/openai/models/responses/response_steer_pending_reason.rbi +33 -0
  183. data/rbi/openai/models/responses/response_steer_required_input.rbi +377 -0
  184. data/rbi/openai/models/responses/responses_client_event.rbi +1204 -1036
  185. data/rbi/openai/models/responses/responses_server_event.rbi +202 -3
  186. data/rbi/openai/models/safety/alert_retrieve_params.rbi +55 -0
  187. data/rbi/openai/models/safety/safety_alert.rbi +147 -0
  188. data/rbi/openai/models/video_create_error.rbi +149 -3
  189. data/rbi/openai/models/webhooks/safety_alert_created_webhook_event.rbi +130 -0
  190. data/rbi/openai/models/webhooks/safety_org_alert_created_webhook_event.rbi +130 -0
  191. data/rbi/openai/models/webhooks/unwrap_webhook_event.rbi +3 -1
  192. data/rbi/openai/models.rbi +2 -0
  193. data/rbi/openai/resources/admin/organization/usage.rbi +1 -1
  194. data/rbi/openai/resources/beta/responses.rbi +3 -3
  195. data/rbi/openai/resources/chat/completions.rbi +2 -2
  196. data/rbi/openai/resources/conversations/items.rbi +1 -0
  197. data/rbi/openai/resources/conversations.rbi +1 -0
  198. data/rbi/openai/resources/responses.rbi +3 -3
  199. data/rbi/openai/resources/safety/alerts.rbi +30 -0
  200. data/rbi/openai/resources/safety.rbi +18 -0
  201. data/rbi/openai/resources/webhooks.rbi +3 -1
  202. data/sig/openai/client.rbs +2 -0
  203. data/sig/openai/models/beta/beta_custom_tool.rbs +7 -0
  204. data/sig/openai/models/beta/beta_function_tool.rbs +7 -0
  205. data/sig/openai/models/beta/beta_namespace_tool.rbs +7 -0
  206. data/sig/openai/models/beta/beta_response.rbs +6 -2
  207. data/sig/openai/models/beta/beta_response_configuration_update_item.rbs +91 -0
  208. data/sig/openai/models/beta/beta_response_configuration_update_item_param.rbs +87 -0
  209. data/sig/openai/models/beta/beta_response_custom_tool_call.rbs +7 -0
  210. data/sig/openai/models/beta/beta_response_custom_tool_call_item.rbs +3 -0
  211. data/sig/openai/models/beta/beta_response_error.rbs +82 -3
  212. data/sig/openai/models/beta/beta_response_function_tool_call.rbs +7 -0
  213. data/sig/openai/models/beta/beta_response_function_tool_call_item.rbs +3 -0
  214. data/sig/openai/models/beta/beta_response_input_item.rbs +1 -0
  215. data/sig/openai/models/beta/beta_response_item.rbs +1 -0
  216. data/sig/openai/models/beta/beta_response_steer_accepted_event.rbs +53 -0
  217. data/sig/openai/models/beta/beta_response_steer_error_code.rbs +31 -0
  218. data/sig/openai/models/beta/beta_response_steer_event.rbs +34 -0
  219. data/sig/openai/models/beta/beta_response_steer_failed_event.rbs +102 -0
  220. data/sig/openai/models/beta/beta_response_steer_input.rbs +214 -0
  221. data/sig/openai/models/beta/beta_response_steer_input_content.rbs +18 -0
  222. data/sig/openai/models/beta/beta_response_steer_pending_event.rbs +63 -0
  223. data/sig/openai/models/beta/beta_response_steer_pending_reason.rbs +16 -0
  224. data/sig/openai/models/beta/beta_response_steer_required_input.rbs +161 -0
  225. data/sig/openai/models/beta/beta_responses_client_event.rbs +4 -1
  226. data/sig/openai/models/beta/beta_responses_server_event.rbs +80 -3
  227. data/sig/openai/models/beta/response_compact_params.rbs +3 -1
  228. data/sig/openai/models/beta/response_create_params.rbs +3 -1
  229. data/sig/openai/models/chat_model.rbs +3 -1
  230. data/sig/openai/models/conversations/conversation_item.rbs +1 -0
  231. data/sig/openai/models/error_object.rbs +82 -3
  232. data/sig/openai/models/responses/custom_tool.rbs +7 -0
  233. data/sig/openai/models/responses/function_tool.rbs +7 -0
  234. data/sig/openai/models/responses/namespace_tool.rbs +7 -0
  235. data/sig/openai/models/responses/response.rbs +3 -1
  236. data/sig/openai/models/responses/response_compact_params.rbs +3 -1
  237. data/sig/openai/models/responses/response_configuration_update_item.rbs +46 -0
  238. data/sig/openai/models/responses/response_configuration_update_item_param.rbs +46 -0
  239. data/sig/openai/models/responses/response_custom_tool_call.rbs +7 -0
  240. data/sig/openai/models/responses/response_custom_tool_call_item.rbs +3 -0
  241. data/sig/openai/models/responses/response_error.rbs +79 -3
  242. data/sig/openai/models/responses/response_function_tool_call.rbs +7 -0
  243. data/sig/openai/models/responses/response_function_tool_call_item.rbs +3 -0
  244. data/sig/openai/models/responses/response_input_item.rbs +1 -0
  245. data/sig/openai/models/responses/response_item.rbs +1 -0
  246. data/sig/openai/models/responses/response_steer_accepted_event.rbs +51 -0
  247. data/sig/openai/models/responses/response_steer_error_code.rbs +29 -0
  248. data/sig/openai/models/responses/response_steer_event.rbs +32 -0
  249. data/sig/openai/models/responses/response_steer_failed_event.rbs +100 -0
  250. data/sig/openai/models/responses/response_steer_input.rbs +182 -0
  251. data/sig/openai/models/responses/response_steer_input_content.rbs +16 -0
  252. data/sig/openai/models/responses/response_steer_pending_event.rbs +61 -0
  253. data/sig/openai/models/responses/response_steer_pending_reason.rbs +13 -0
  254. data/sig/openai/models/responses/response_steer_required_input.rbs +159 -0
  255. data/sig/openai/models/responses/responses_client_event.rbs +355 -344
  256. data/sig/openai/models/responses/responses_server_event.rbs +80 -3
  257. data/sig/openai/models/safety/alert_retrieve_params.rbs +22 -0
  258. data/sig/openai/models/safety/safety_alert.rbs +81 -0
  259. data/sig/openai/models/video_create_error.rbs +87 -3
  260. data/sig/openai/models/webhooks/safety_alert_created_webhook_event.rbs +52 -0
  261. data/sig/openai/models/webhooks/safety_org_alert_created_webhook_event.rbs +52 -0
  262. data/sig/openai/models/webhooks/unwrap_webhook_event.rbs +2 -0
  263. data/sig/openai/models.rbs +2 -0
  264. data/sig/openai/resources/safety/alerts.rbs +14 -0
  265. data/sig/openai/resources/safety.rbs +9 -0
  266. data/sig/openai/resources/webhooks.rbs +3 -1
  267. metadata +86 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03f5297b1df9de7c2e182a5f76a72a5a15f2e9a34468b554eb92eb7e8022182b
4
- data.tar.gz: 414ac43f94cee9591533b94c02d1d904cdd6a6ba0beb8694e88745b1b30dfad6
3
+ metadata.gz: 37f60ef5c84fcaef53fccd86fa30e1cc0988b4033abdd3e14711247e082de5d4
4
+ data.tar.gz: 97cd2b216e53d22574e00031f94b0d2975393a9128a9d2528ea735679bb04f8f
5
5
  SHA512:
6
- metadata.gz: a2dda8c58eb47a26113b553b1428d23502d0c6740808954b740c0a5cf5dac361a798f6a14f963cff9d1230256cd800295017dc07489f7c6798f50a59c4066b1c
7
- data.tar.gz: 72d931bd9cc9fa963b2f9d0d6f6e66078eb778456097d041850013b814ec20c3b5c9661a4b68c9fa2ea56a829aaaccc657e544a8cb68863f6c9ae4dd653aeb01
6
+ metadata.gz: 1493b225ac23056cda189b6af2bdd547aeb3441fef8f85c9454ae2d2d51e87e84743807cd86875f85a1d055eeb1f8c30ab684b7a11f76e46f86fbcd47c3b0995
7
+ data.tar.gz: 702dc478f26dff8454563e36b2d3ed4113ced5f04f104d34ab399091b290bf34ca3cccbb1ad8f44b2dae132d40053e33b9d0a86dc20f3a66316e0aed9378459d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.85.0](https://github.com/openai/openai-ruby/compare/v0.84.0...v0.85.0) (2026-09-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * **api:** add gpt-6-astra and related features ([#596](https://github.com/openai/openai-ruby/issues/596)) ([a08d882](https://github.com/openai/openai-ruby/commit/a08d882c2575e0743a18eb5a4eae7ef3f94b0314))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **ci:** restore Castiron statuses for fork PRs ([#572](https://github.com/openai/openai-ruby/issues/572)) ([b78af5a](https://github.com/openai/openai-ruby/commit/b78af5acf775627c8eddf42da41bef7ea7325235))
14
+ * correct StringIO request content length ([#583](https://github.com/openai/openai-ruby/issues/583)) ([5baeac8](https://github.com/openai/openai-ruby/commit/5baeac8131511141491fc41abdb2e4e4c596f3de))
15
+ * emit done events for terminal chat tool calls ([#573](https://github.com/openai/openai-ruby/issues/573)) ([891936c](https://github.com/openai/openai-ruby/commit/891936ce58db613a61d270792f40da400ea44b97))
16
+ * handle nullable structured-output literals ([#568](https://github.com/openai/openai-ruby/issues/568)) ([c4fee85](https://github.com/openai/openai-ruby/commit/c4fee85338aa283d6cec80538e4524a406fa8b7d))
17
+ * isolate chat stream logprob chunks ([#585](https://github.com/openai/openai-ruby/issues/585)) ([0daedb2](https://github.com/openai/openai-ruby/commit/0daedb2878373da899dac8b029fdb0523a26c6ef))
18
+ * preserve initial chat stream system fingerprint ([#580](https://github.com/openai/openai-ruby/issues/580)) ([a31c1ce](https://github.com/openai/openai-ruby/commit/a31c1cef9ee8e1fc5ed7a9b29f23171260a101e4))
19
+ * preserve late chat completion service tier ([#567](https://github.com/openai/openai-ruby/issues/567)) ([52a0c64](https://github.com/openai/openai-ruby/commit/52a0c64fbb04535d4c1250986a5116298048764e))
20
+ * preserve nested array schema definitions ([#581](https://github.com/openai/openai-ruby/issues/581)) ([bbfbe29](https://github.com/openai/openai-ruby/commit/bbfbe29f53018b4a3a1c61edab355ad901bb0239))
21
+ * preserve recursive structured output refs ([#584](https://github.com/openai/openai-ruby/issues/584)) ([b23fc75](https://github.com/openai/openai-ruby/commit/b23fc752952314de279a32175d98b7ca7fd33f8c))
22
+ * preserve retained Responses stream events ([#577](https://github.com/openai/openai-ruby/issues/577)) ([149e685](https://github.com/openai/openai-ruby/commit/149e685c64f72052d19d9389ba07d40a14c4c070))
23
+ * preserve string-tagged structured output models ([#582](https://github.com/openai/openai-ruby/issues/582)) ([983eaf9](https://github.com/openai/openai-ruby/commit/983eaf9c240f6888d2872abddd555fa3cdd5af95))
24
+ * raise finish errors for initial chat stream chunks ([#579](https://github.com/openai/openai-ruby/issues/579)) ([c923659](https://github.com/openai/openai-ruby/commit/c923659aeddd2ed5e928830babd2f75ea6cbae3d))
25
+ * short-circuit Bedrock profile credentials ([#588](https://github.com/openai/openai-ruby/issues/588)) ([1c7ed46](https://github.com/openai/openai-ruby/commit/1c7ed46782288a882aed9ccedcf69ecc53fd9c6d))
26
+
27
+
28
+ ### Documentation
29
+
30
+ * clarify commit prefix user impact ([#575](https://github.com/openai/openai-ruby/issues/575)) ([73752ad](https://github.com/openai/openai-ruby/commit/73752ad281e7d3fde9c4b63bd1efa17b151ff5a6))
31
+ * establish canonical security model ([#565](https://github.com/openai/openai-ruby/issues/565)) ([2ded215](https://github.com/openai/openai-ruby/commit/2ded21565f969d026c93027d1d7264a8081d6242))
32
+
3
33
  ## [0.84.0](https://github.com/openai/openai-ruby/compare/v0.83.0...v0.84.0) (2026-09-02)
4
34
 
5
35
 
data/README.md CHANGED
@@ -15,7 +15,7 @@ To use this gem, install via Bundler by adding the following to your application
15
15
  <!-- x-release-please-start-version -->
16
16
 
17
17
  ```ruby
18
- gem "openai", "~> 0.84.0"
18
+ gem "openai", "~> 0.85.0"
19
19
  ```
20
20
 
21
21
  <!-- x-release-please-end -->
data/SECURITY.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Security Policy
2
2
 
3
+ ## Threat model authority
4
+
5
+ This file governs vulnerability disclosure and reportability. The repository's
6
+ canonical detailed threat model, trust boundaries, security invariants, and
7
+ severity calibration are defined in
8
+ [docs/architecture/security-model.md](docs/architecture/security-model.md).
9
+ Codex Security scans and reviewers should use that document from the scanned
10
+ revision rather than maintaining a second pasted threat model here or in scan
11
+ configuration.
12
+
3
13
  ## Reporting a vulnerability
4
14
 
5
15
  Please report potential security vulnerabilities through OpenAI's
@@ -0,0 +1,267 @@
1
+ # OpenAI Ruby SDK Security Model
2
+
3
+ This is the canonical detailed threat model for this repository. `SECURITY.md`
4
+ remains the authority for disclosure and reportability; this document is the
5
+ authority for architecture, trust boundaries, security invariants, attack
6
+ surface, and severity calibration. Codex Security scans must load this file
7
+ from the scanned revision instead of relying on a duplicated cloud-configured
8
+ model.
9
+
10
+ ## 1. Overview
11
+
12
+ The OpenAI Ruby gem is a caller-embedded Ruby 3.3+ library, not a hosted
13
+ service. It converts caller-supplied configuration, parameters, files, and
14
+ credentials into authenticated REST, SSE, and optional Realtime WebSocket
15
+ requests, then parses remote responses into typed Ruby values
16
+ ([README.md](../../README.md#L1-L3),
17
+ [README.md](../../README.md#L38-L59)).
18
+
19
+ | Component | Role | Evidence |
20
+ | --- | --- | --- |
21
+ | `OpenAI::Client` and generated resources | Public REST entry points, credentials, retries, request options | `lib/openai/client.rb:5-41`, `lib/openai/client.rb:129-170` |
22
+ | Base transport, HTTP client, and multipart encoder | URL construction, redirects, retries, default Net::HTTP TLS/pooling, multipart headers/files, serialization, response decoding | `lib/openai/internal/transport/base_client.rb:13-27`, `lib/openai/net_http_client.rb:55-71`, `lib/openai/net_http_client.rb:139-205`, `lib/openai/net_http_client.rb:241-345`, `lib/openai/file_part.rb:33-43`, `lib/openai/file_part.rb:110-119`, `lib/openai/internal/util.rb:533-565`, `lib/openai/http_client.rb:138-237` |
23
+ | Streaming and Realtime helpers | Construct authenticated WebSocket handshakes, enforce transport policy, and parse SSE/WebSocket JSON into typed events | `lib/openai/internal/stream.rb:19-60`, `lib/openai/helpers/realtime/client_extension.rb:167-280`, `lib/openai/helpers/realtime/transports/async_websocket.rb:185-308`, `lib/openai/helpers/realtime/connection.rb:47-100` |
24
+ | Webhook helper | Verify timestamp and HMAC before parsing webhook events | `lib/openai/resources/webhooks.rb:15-119` |
25
+ | Providers and workload identity | Azure, Bedrock, built-in subject-token acquisition, exchange, and X.509 credential paths | `lib/openai/providers/azure.rb:40-58`, `lib/openai/auth/subject_token_providers/gcp_id_token_provider.rb:9-67`, `lib/openai/auth/subject_token_providers/azure_managed_identity_token_provider.rb:9-92`, `lib/openai/auth/subject_token_providers/k8s_service_account_token_provider.rb:9-37`, `lib/openai/auth/workload_identity_auth.rb:280-330` |
26
+ | CI and release workflows | Run repository code, upload CodeQL results, publish trusted Castiron results, live checks/examples, and releases | `.github/workflows/ci.yml:18-32`, `.github/workflows/codeql.yml:3-53`, `.github/workflows/castiron-custom-code-comment.yml:4-38`, `.github/workflows/examples-e2e.yml:1-43`, `.github/workflows/create-releases.yml:14-79` |
27
+
28
+ ```mermaid
29
+ flowchart LR
30
+ Caller[Embedding application] --> SDK[OpenAI Ruby SDK]
31
+ SDK --> API[Configured API or provider origin]
32
+ API --> Parser[JSON / SSE / WebSocket parsers]
33
+ Parser --> Caller
34
+ Webhook[Webhook sender] --> Verify[HMAC + timestamp verifier]
35
+ Verify --> Caller
36
+ PR[Candidate checkout] --> CI[Ordinary read-only CI]
37
+ PR --> CodeQL[CodeQL analysis]
38
+ CodeQL --> SecurityEvents[Code-scanning results]
39
+ PR --> Castiron[Candidate Castiron workflow]
40
+ Castiron --> Trusted[Main-sourced workflow_run publisher]
41
+ Trusted --> GitHub[Statuses and PR comments]
42
+ Main[Main revision] --> Live[Live / release workflows]
43
+ Live --> Secrets[Referenced credentials and publishing authority]
44
+ ```
45
+
46
+ | Deployment or workflow | Resource or capability | Configuration and precedence | Safe effective value or location | Readers, writers, or recipients | Enforcing control | Evidence or unknowns |
47
+ | --- | --- | --- | --- | --- | --- | --- |
48
+ | Default SDK | API/admin bearer credential and default Net::HTTP connection | Explicit option, then environment fallback; explicit/data-residency base URL, then `OPENAI_BASE_URL`, then default | Bearer credential to configured origin; default `https://api.openai.com/v1`; pooled connection keyed by origin | Configured API origin | Configured-origin request construction; cross-origin redirects strip credentials and reject bodies; HTTPS downgrade rejected; default Net::HTTP selects TLS from the URL, loads default trust roots, pools by origin, requires configurators to leave new connections unstarted, requires validators to leave previously unstarted connections unstarted, and checks `use_ssl?` against the URL scheme | `lib/openai/client.rb:634-645`, `lib/openai/client.rb:669-693`, `lib/openai/helpers/data_residency.rb:16-33`, `lib/openai/internal/transport/base_client.rb:418-452`, `lib/openai/internal/transport/base_client.rb:153-215`, `lib/openai/net_http_client.rb:55-71`, `lib/openai/net_http_client.rb:139-205`, `lib/openai/net_http_client.rb:241-345` |
49
+ | Webhook handler | Webhook secret | Explicit argument, then client option, then `OPENAI_WEBHOOK_SECRET` | In-process HMAC key | Local verifier only | Required headers, freshness window, HMAC, timing-safe comparison before `unwrap` parses JSON | `lib/openai/resources/webhooks.rb:15-119` |
50
+ | Workload identity | Built-in GCP/Azure/Kubernetes subject token and exchanged access token | GCP provider uses fixed metadata host/path plus configurable audience/timeout; Azure managed identity uses fixed IMDS endpoint plus configurable resource, identity selectors, API version, and timeout; both metadata fetches inherit Net::HTTP environment-proxy routing; Kubernetes provider uses configured/default token path; exchange uses identity/service-account IDs and default issuer URL | GCP GET to `http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity`; Azure GET to `http://169.254.169.254/metadata/identity/oauth2/token`; Kubernetes token read from configured/default path; subject token sent to `https://auth.openai.com/oauth/token`; returned token cached in memory | Local GCP/Azure metadata service, caller-owned environment proxy, or token-file path; issuer; configured API origin | Fixed GCP host/path and `Metadata-Flavor` header; fixed Azure IMDS endpoint and `Metadata` header; configured open/read timeouts; Azure JSON parsing and `access_token` extraction; Kubernetes file read/error mapping; token-type mapping, exchange timeout/deadline handling, coordinated refresh; fixed metadata URLs do not bypass caller-owned proxy configuration | `lib/openai/auth/subject_token_providers/gcp_id_token_provider.rb:9-67`, `lib/openai/auth/subject_token_providers/azure_managed_identity_token_provider.rb:9-92`, `lib/openai/auth/subject_token_providers/k8s_service_account_token_provider.rb:9-37`, `lib/openai/auth/workload_identity_auth.rb:13-15`, `lib/openai/auth/workload_identity_auth.rb:280-330` |
51
+ | X.509 workload identity | Attested mTLS transport and detached exchanged bearer token | Client uses the identity-configured transport when present, otherwise a caller-supplied attested `http_client` for a detached identity, and selects `X509TokenExchange` before token acquisition | Exchange POST to `https://mtls.auth.openai.com/oauth/token`; API requests use the selected transport's attested global/US/EU mTLS API origin | OpenAI mTLS issuer and matching attested API origin | Exact issuer endpoint and attested API-origin/header checks, TLS peer/hostname verification, proxy policy, no redirects | `lib/openai/client.rb:616-644`, `lib/openai/client.rb:739-756`, `test/openai/auth/x509_client_test.rb:16-34`, `lib/openai/auth/x509_transport.rb:5-10`, `lib/openai/auth/x509_transport.rb:14-26`, `lib/openai/auth/x509_transport.rb:174-237`, `lib/openai/auth/x509_transport.rb:243-315` |
52
+ | Azure provider | API key or Entra bearer token | Explicit endpoint/credential, then Azure environment fallback | Credential attached only to normalized Azure origin | Configured Azure endpoint | Origin validation before credential attachment; conflicting auth rejected | `lib/openai/providers/azure.rb:47-58`, `lib/openai/providers/azure.rb:141-147` |
53
+ | Bedrock provider | Bearer token or SigV4 authority | Explicit mode, environment, then AWS credential chain | Credential/signature to configured Bedrock origin | Configured Bedrock endpoint | Origin and endpoint/region validation; SigV4 disables redirects | `lib/openai/providers/bedrock.rb:105-168`, `lib/openai/providers/bedrock.rb:398-427` |
54
+ | Realtime WebSocket transport | API/workload bearer credential, WebSocket destination, TLS/proxy policy, and trace confidentiality | Client handshake builder applies bearer auth and optional workload-token refresh; caller may select `websocket_base_url` only within provider restrictions | Authenticated `ws://` or `wss://` handshake to configured Realtime origin; proxy credentials stay on CONNECT | Configured Realtime endpoint and optional proxy | Base URL validation, provider/X.509 restrictions, proxy-authorization stripping, WSS peer/hostname verification, trace redaction | `lib/openai/helpers/realtime/client_extension.rb:167-280`, `lib/openai/helpers/realtime/transports/async_websocket.rb:185-224`, `lib/openai/helpers/realtime/transports/async_websocket.rb:294-378` |
55
+ | Multipart uploads | Caller file contents, effective filename, content type, and multipart header parameters | Caller supplies `Pathname`, `IO`, or `FilePart`; optional filename/content type | File bytes to configured API origin; effective filename reduced to basename; header parameters escaped | Configured API origin | Media-type validation; `FilePart` basename reduction; encoder basename reduction for raw paths; quote/backslash escaping and CR/LF removal for multipart header parameters | `lib/openai/file_part.rb:33-43`, `lib/openai/file_part.rb:110-119`, `lib/openai/internal/util.rb:533-565` |
56
+ | Diagnostic logging | Request/response diagnostics | Explicit log level, then `OPENAI_LOG`; supplied logger or stderr | Sanitized metadata and structural body summaries | Caller logger or stderr | Credential/query redaction; multipart, streaming, binary, and large JSON bodies omitted | `lib/openai/internal/logging.rb:10-26`, `lib/openai/internal/logging.rb:299-343` |
57
+ | Dependency acquisition and installation | Gem source, locked revision, transitive gem, native extension, and install/build-script execution | Contributor/CI `Gemfile` selects `syntax_tree-rbs` from GitHub branch `main` and `Gemfile.lock` pins revision `247832988a850b8df050cf207f652872fda49973`; Bedrock CI selects `aws-sdk-core` through `gemfiles/bedrock.gemfile` and its lock; docs lockfiles and gemspec dependencies select other installed code | Locked RubyGems and Git sources plus package metadata from the reviewed revision | CI runners and consumer Bundler/RubyGems installations | Human/source review of dependency and lockfile changes; CI verifies the reviewed checkout but does not make an unreviewed dependency trustworthy | `AGENTS.md:39-42`, `Gemfile:3-15`, `Gemfile.lock:1-20`, `gemfiles/bedrock.gemfile:1-5`, `gemfiles/bedrock.gemfile.lock:1-58`, `.github/workflows/ci-checks.yml:124-144`, `openai.gemspec:18-48`, `docs/Gemfile:1-8`, `docs/Gemfile.lock:1-21` |
58
+ | Ordinary PR CI | Candidate checkout execution | Checked-out PR revision runs lint, typecheck, build, and tests | Candidate code executes with repository-code authority | Ephemeral CI runner | `permissions: {}`, `contents: read`, `persist-credentials: false` | `.github/workflows/ci.yml:18-32`, `.github/workflows/ci-checks.yml:100-120` |
59
+ | CodeQL PR analysis | Candidate checkout and code-scanning result upload | Pull requests, main pushes, and merge-group checks run action and Ruby analysis | Candidate source produces SARIF under runner temp; `security-events: write` uploads code-scanning results | GitHub code scanning | Top-level `permissions: {}`; job grants only `actions: read`, `contents: read`, and `security-events: write`; pinned checkout/CodeQL actions; `persist-credentials: false`; checked SARIF must exist and contain no findings | `.github/workflows/codeql.yml:3-28`, `.github/workflows/codeql.yml:37-53`, `.github/workflows/codeql.yml:55-96` |
60
+ | Trusted Castiron workflow-run publisher | Commit-status and PR-comment writes | Candidate-associated `workflow_run` metadata -> main-sourced reporter recomputes from current Git objects for statuses and successful report comments; fallback failure comments use event/path, current-head, and monotonic-replacement checks | `statuses: write` and `pull-requests: write` only in publisher jobs | GitHub commit statuses and PR comments | Status path: no candidate artifact, run identity validation, exact head/base checks; successful comment path: trusted report artifact; fallback failure-comment path: main-sourced code, event/path filtering, current-head matching, monotonic replacement | `.github/workflows/castiron-custom-code-comment.yml:4-38`, `.github/workflows/castiron-custom-code-comment.yml:40-98`, `.github/workflows/castiron-custom-code-comment.yml:116-171`, `.github/workflows/castiron-custom-code-comment.yml:174-237` |
61
+ | Live/release workflows | API keys, X.509 material, GitHub App private key, generated GitHub App token, RubyGems OIDC | Main-only conditions plus named environments; release job passes `OPENAI_SDKS_APP_PRIVATE_KEY` to pinned token action, then passes generated token to Release Please | Referenced long-lived private key as action input; generated short-lived release token; publishing OIDC | Main revision, token action, OpenAI API, GitHub, RubyGems | Repository/ref conditions, separate named environments, least privilege, pinned actions, generated token handoff, OIDC only for publishing | `.github/workflows/live-smoke.yml:17-92`, `.github/workflows/create-releases.yml:17-79` |
62
+ | Examples E2E workflow | Live-example API credential and uploaded reports | Manual dispatch; main/repository condition; named `ci` environment; `OPENAI_API_KEY` secret reference | Credential only in the live-example step; allowlisted `report.json` and `summary.md` under runner temp | OpenAI API and GitHub artifact readers | `permissions: {}`, `contents: read`, checkout without persisted credentials, main/repository condition, allowlisted reports, 14-day retention, focused confidentiality/isolation tests | `.github/workflows/examples-e2e.yml:1-43`, `test/scripts/examples_e2e_test.rb:202-234`, `test/scripts/examples_e2e_test.rb:306-327` |
63
+
64
+ ## 2. Threat Model, Trust Boundaries, and Assumptions
65
+
66
+ Protected assets include caller API/admin/provider credentials, workload subject
67
+ tokens and exchanged access tokens, webhook secrets, X.509 private-key
68
+ material, uploaded file contents, request destinations and headers, parsed
69
+ response integrity, diagnostic confidentiality, GitHub App private-key
70
+ material, and release/publishing integrity.
71
+
72
+ Important boundaries:
73
+
74
+ - Caller-controlled parameters, headers, files, base URLs, callbacks, loggers,
75
+ and custom transports cross into SDK request construction. These remain
76
+ caller authority inside the embedding process; the SDK is not a sandbox
77
+ (`lib/openai/http_client.rb:138-142`,
78
+ `lib/openai/internal/transport/base_client.rb:293-339`).
79
+ - Configured origin to redirect destination is a network boundary. Credential
80
+ headers must not reach a different origin, request bodies must not cross
81
+ origins, and HTTPS must not downgrade to HTTP
82
+ (`lib/openai/internal/transport/base_client.rb:153-215`).
83
+ - The default Net::HTTP implementation is the ordinary REST TLS and connection-
84
+ reuse boundary. It derives TLS from the requested scheme, installs default
85
+ trust roots, keys pools by origin, requires configurators to leave new
86
+ connections unstarted, requires validators to leave previously unstarted
87
+ connections unstarted, and checks `use_ssl?` against the URL scheme each time
88
+ (`lib/openai/net_http_client.rb:55-71`,
89
+ `lib/openai/net_http_client.rb:139-205`,
90
+ `lib/openai/net_http_client.rb:241-345`).
91
+ - API, SSE, and WebSocket bytes cross parser/type-conversion boundaries. They
92
+ are parsed as data, not evaluated as code
93
+ (`lib/openai/internal/stream.rb:21-60`,
94
+ `lib/openai/helpers/realtime/connection.rb:61-84`).
95
+ - Raw webhook payloads and headers cross an authenticity boundary. `unwrap`
96
+ verifies freshness and HMAC before parsing or coercion
97
+ (`lib/openai/resources/webhooks.rb:15-23`,
98
+ `lib/openai/resources/webhooks.rb:75-119`).
99
+ - Built-in subject-token sources, the generic token issuer, and Azure/Bedrock
100
+ endpoints are credential-brokering boundaries. GCP acquisition uses the fixed
101
+ plaintext metadata host/path and metadata header with configurable audience
102
+ and timeout; Azure managed-identity acquisition uses the fixed plaintext IMDS
103
+ endpoint and metadata header with configurable resource, identity selectors,
104
+ API version, and timeout, then parses the returned JSON token. Both metadata
105
+ fetches inherit Net::HTTP environment-proxy routing, so a caller-owned proxy
106
+ can mediate the plaintext request/response and fixed URLs alone do not prove
107
+ direct source binding. Kubernetes acquisition reads its configured/default
108
+ token path and maps file failures. Credentials must remain bound to their
109
+ intended source, proxy, issuer, and destination
110
+ (`lib/openai/auth/subject_token_providers/gcp_id_token_provider.rb:9-67`,
111
+ `lib/openai/auth/subject_token_providers/azure_managed_identity_token_provider.rb:9-92`,
112
+ `lib/openai/auth/subject_token_providers/k8s_service_account_token_provider.rb:9-37`,
113
+ `lib/openai/auth/workload_identity_auth.rb:280-330`,
114
+ `lib/openai/providers/azure.rb:141-147`,
115
+ `lib/openai/providers/bedrock.rb:398-427`).
116
+ - X.509 workload identity is a separate credential boundary: the exchange uses
117
+ the fixed mTLS issuer while API requests use the selected transport's attested
118
+ matching mTLS origin. The certificate identity and exchanged bearer token
119
+ remain detached; controls enforce the exact issuer endpoint and approved API
120
+ origin/header/TLS/proxy/no-redirect policy
121
+ (`lib/openai/client.rb:616-644`,
122
+ `lib/openai/client.rb:739-756`,
123
+ `test/openai/auth/x509_client_test.rb:16-34`,
124
+ `lib/openai/auth/x509_transport.rb:5-10`,
125
+ `lib/openai/auth/x509_transport.rb:174-315`).
126
+ - Authenticated Realtime handshakes are a transport boundary distinct from
127
+ event parsing. Bearer credentials, caller-selected WebSocket destinations,
128
+ TLS, proxy credentials, and traces must remain bound to their intended
129
+ origin and diagnostic audience
130
+ (`lib/openai/helpers/realtime/client_extension.rb:167-280`,
131
+ `lib/openai/helpers/realtime/transports/async_websocket.rb:185-224`,
132
+ `lib/openai/helpers/realtime/transports/async_websocket.rb:294-378`).
133
+ - Multipart upload serialization is a file-path and header-integrity boundary.
134
+ `FilePart` validates media types and reduces explicit or inferred filenames to
135
+ basenames; the encoder again basenames raw paths and escapes quote, backslash,
136
+ CR, and LF header-parameter characters before bytes reach the API origin
137
+ (`lib/openai/file_part.rb:33-43`,
138
+ `lib/openai/file_part.rb:110-119`,
139
+ `lib/openai/internal/util.rb:533-565`).
140
+ - Runtime/API data crossing into logs, exceptions, retained metadata objects, or
141
+ CI artifacts is a sensitive sink boundary. Logging redacts or omits sensitive
142
+ bodies; derived HTTP status messages are bounded and sanitized when no
143
+ top-level scalar or explicit message is supplied; top-level scalar and
144
+ explicit SSE error messages remain remote data; Realtime connection errors
145
+ redact call IDs from URLs; response metadata omits retained bodies from
146
+ inspection and serialization
147
+ (`lib/openai/internal/logging.rb:299-343`,
148
+ `lib/openai/errors.rb:259-409`,
149
+ `lib/openai/internal/stream.rb:35-40`,
150
+ `lib/openai/helpers/realtime/errors.rb:26-70`,
151
+ `lib/openai/http_client.rb:46-79`,
152
+ `CONTRIBUTING.md:42-50`).
153
+ - Candidate PR code reaching sensitive live/release credentials or repository
154
+ write/publishing authority is a genuine boundary
155
+ (`.github/workflows/live-smoke.yml:17-92`,
156
+ `.github/workflows/examples-e2e.yml:1-43`,
157
+ `.github/workflows/create-releases.yml:17-79`).
158
+ - The release GitHub App private key is distinct from the generated app token.
159
+ The main/repository-gated release job passes the referenced private key only
160
+ to a pinned token action, which mints the narrower token passed to Release
161
+ Please; disclosure of the private key can outlive one generated token
162
+ (`.github/workflows/create-releases.yml:17-46`).
163
+ - Candidate source and generated SARIF crossing into CodeQL result upload are a
164
+ separate PR-adjacent write boundary. The workflow scopes the token to
165
+ `security-events: write` plus read-only metadata/content, uses pinned actions
166
+ without persisted checkout credentials, and validates that SARIF exists and
167
+ has no findings before the job succeeds
168
+ (`.github/workflows/codeql.yml:3-28`,
169
+ `.github/workflows/codeql.yml:37-53`,
170
+ `.github/workflows/codeql.yml:55-96`).
171
+ - The manually dispatched Examples E2E job is a separate live-data boundary:
172
+ its main/repository-gated job references `OPENAI_API_KEY`, runs examples, and
173
+ always uploads only two allowlisted reports. Tests enforce report
174
+ confidentiality and workflow isolation
175
+ (`.github/workflows/examples-e2e.yml:1-43`,
176
+ `test/scripts/examples_e2e_test.rb:202-234`,
177
+ `test/scripts/examples_e2e_test.rb:306-327`).
178
+ - Candidate-associated `workflow_run` metadata and Git objects crossing into
179
+ the trusted Castiron publisher are a genuine write-capable boundary. The
180
+ status path must preserve run-identity and exact-head/base checks; successful
181
+ report comments must remain bound to the trusted report artifact. The
182
+ fallback failure-comment path has narrower main-sourced event/path,
183
+ current-head, and monotonic-replacement checks that scans must assess
184
+ independently
185
+ (`.github/workflows/castiron-custom-code-comment.yml:34-98`,
186
+ `.github/workflows/castiron-custom-code-comment.yml:116-171`,
187
+ `.github/workflows/castiron-custom-code-comment.yml:174-237`).
188
+ - Dependency declarations and locks cross a supply-chain boundary when Bundler
189
+ or RubyGems fetches and executes selected package code in CI or consumer
190
+ applications. A changed source, Git revision, transitive gem, native
191
+ extension, or install/build script needs explicit dependency review. The root
192
+ contributor/CI bundle currently selects `syntax_tree-rbs` from GitHub branch
193
+ `main` and locks revision `247832988a850b8df050cf207f652872fda49973`; the
194
+ Bedrock CI job separately selects `aws-sdk-core` through
195
+ `gemfiles/bedrock.gemfile` and its lock
196
+ (`AGENTS.md:39-42`, `Gemfile:3-15`, `Gemfile.lock:1-20`,
197
+ `gemfiles/bedrock.gemfile:1-5`, `gemfiles/bedrock.gemfile.lock:1-58`,
198
+ `.github/workflows/ci-checks.yml:124-144`,
199
+ `openai.gemspec:18-48`,
200
+ `docs/Gemfile:1-8`, `docs/Gemfile.lock:1-21`).
201
+
202
+ Checked-in executable source has repository-code authority. Reviewed tracked
203
+ examples, tests, fixtures, Rake tasks, build scripts, generators, and other
204
+ checkout files are intentionally executable by local development and ordinary
205
+ PR CI. A contributor who can modify those tracked files does not gain a new
206
+ privilege merely because CI executes them. Do not report that execution alone
207
+ as a security finding.
208
+
209
+ That rule does not suppress a real finding when independently mutable
210
+ lower-trust input crosses a parser/evaluator boundary, untrusted runtime/API/
211
+ network data reaches a sensitive sink, candidate code can reach sensitive
212
+ secrets or write/publishing authority, or shipped production code violates a
213
+ security invariant.
214
+
215
+ Assumptions:
216
+
217
+ - The embedding application owns its local filesystem, network, process,
218
+ callback, proxy, and credential authority.
219
+ - Custom transports, token providers, and loggers are caller-owned extension
220
+ points, not less-trusted sandboxed plugins.
221
+ - Azure and Bedrock custom HTTP endpoints are caller-selected; HTTPS outside
222
+ local/private testing is a caller obligation where the provider permits HTTP.
223
+ - External branch protection, environment approval rules, secret configuration,
224
+ and RubyGems trusted-publisher bindings are not proven by repository files.
225
+ - Large JSON bodies and SSE events are supported API contracts; arbitrary fixed
226
+ rejection limits are not an acceptable security fix.
227
+
228
+ ## 3. Attack Surface, Mitigations, and Attacker Stories
229
+
230
+ The following are hypotheses and review guidance, not confirmed findings.
231
+
232
+ | Priority | Scenario and capability gain | Prerequisites | Impact | Existing controls | Mitigation | Evidence |
233
+ | --- | --- | --- | --- | --- | --- | --- |
234
+ | High | Redirect, TLS, or pooled-connection confusion sends credentials or a body to an unintended origin | Remote endpoint controls redirect or caller/provider origin handling is bypassed, or default connection validation/pooling regresses | Credential or payload disclosure | Cross-origin credential stripping, body rejection, HTTPS downgrade rejection, provider origin validation, default trust roots, origin-keyed pools, return-time unstarted postconditions for new connections, scheme-matched `use_ssl?` checks | Preserve origin, redirect, TLS, and pool controls | `lib/openai/internal/transport/base_client.rb:153-215`, `lib/openai/providers/azure.rb:141-147`, `lib/openai/net_http_client.rb:55-71`, `lib/openai/net_http_client.rb:139-205`, `lib/openai/net_http_client.rb:241-345` |
235
+ | High | Candidate PR code reaches live/release credentials or publishing authority | Privileged workflow executes candidate code or exposes secrets/write tokens | Credential theft or release compromise | Main-only conditions, named environments, least-privilege permissions, trusted publisher separation | Keep candidate and live/release workflows isolated | `.github/workflows/live-smoke.yml:17-92`, `.github/workflows/create-releases.yml:17-79` |
236
+ | High | Release GitHub App private key reaches an unintended action, log, or artifact | Main/repository release gate or pinned token-action input boundary regresses | Attacker can mint GitHub App tokens beyond one workflow token lifetime | Main/repository condition, named release environment, empty job permissions, pinned token action, generated token passed separately to Release Please | Keep the private key confined to token minting and preserve generated-token handoff | `.github/workflows/create-releases.yml:17-46` |
237
+ | Medium | Candidate source or generated SARIF influences the write-capable CodeQL upload outside its intended code-scanning result path | PR-adjacent CodeQL action or token scoping regresses | Forged or unintended code-scanning results | Top-level empty permissions; scoped `security-events: write`; read-only content/metadata; pinned actions; no persisted checkout credentials; SARIF existence/shape/finding checks | Preserve least privilege and keep upload input limited to CodeQL output | `.github/workflows/codeql.yml:3-28`, `.github/workflows/codeql.yml:37-53`, `.github/workflows/codeql.yml:55-96` |
238
+ | High | Candidate-associated workflow metadata or Git objects influence the write-capable Castiron publisher without the path-specific trusted validation | Attacker controls a PR head or associated workflow metadata and a status, successful-comment, or fallback-comment check regresses | Forged or stale commit statuses and PR comments | Statuses use main-sourced recomputation plus run/head/base checks; successful comments use a trusted report artifact; fallback comments rely on main-sourced event/path, current-head, and monotonic-replacement checks | Preserve each path's distinct recomputation, freshness, artifact-binding, or fallback-isolation checks before its write | `.github/workflows/castiron-custom-code-comment.yml:34-98`, `.github/workflows/castiron-custom-code-comment.yml:116-171`, `.github/workflows/castiron-custom-code-comment.yml:174-237` |
239
+ | High | A dependency declaration or lockfile selects malicious package code, native extension, or install/build script | Lower-trust change alters a source, Git revision, transitive dependency, or executable package hook that CI or a consumer installs; the contributor/CI bundle currently includes Git-sourced `syntax_tree-rbs` locked at `247832988a850b8df050cf207f652872fda49973`; Bedrock CI separately installs its `aws-sdk-core` bundle | Code execution in CI or consumer applications | Reviewed lockfiles and dependency policy; ordinary and Bedrock CI execute selected dependencies with runner authority | Review direct/transitive dependencies, sources, locked revisions, native extensions, and scripts before acceptance | `AGENTS.md:39-42`, `Gemfile:3-15`, `Gemfile.lock:1-20`, `gemfiles/bedrock.gemfile:1-5`, `gemfiles/bedrock.gemfile.lock:1-58`, `.github/workflows/ci-checks.yml:124-144`, `openai.gemspec:18-48`, `docs/Gemfile:1-8`, `docs/Gemfile.lock:1-21` |
240
+ | High | Built-in subject-token acquisition or exchange sends a token to an unintended source, proxy, issuer, or origin | Lower-trust configuration, caller-owned environment proxy, local token path, metadata response, or endpoint handling is bypassed | Credential disclosure or forged authentication | Fixed GCP metadata host/path/header with configurable audience/timeout; fixed Azure IMDS endpoint/header with configurable resource/selectors/API version/timeout and JSON token extraction; inherited Net::HTTP environment-proxy routing remains caller-owned; configured/default Kubernetes path with mapped failures; fixed/default issuer paths, origin checks, X.509 destination restrictions | Preserve strict source, proxy, destination, timeout, redirect, and response-parsing handling | `lib/openai/auth/subject_token_providers/gcp_id_token_provider.rb:9-67`, `lib/openai/auth/subject_token_providers/azure_managed_identity_token_provider.rb:9-92`, `lib/openai/auth/subject_token_providers/k8s_service_account_token_provider.rb:9-37`, `lib/openai/auth/workload_identity_auth.rb:303-330`, `lib/openai/providers/bedrock.rb:398-427` |
241
+ | High | Realtime bearer credentials, proxy credentials, or trace data reach an unintended destination or audience | Lower-trust destination, proxy, TLS, or tracing behavior bypasses handshake controls | Credential disclosure or authenticated connection to the wrong peer | Base-URL/provider restrictions, proxy-header stripping, WSS verification, proxy handling, trace redaction | Preserve destination/TLS/proxy/redaction controls independently from event parsing | `lib/openai/helpers/realtime/client_extension.rb:167-280`, `lib/openai/helpers/realtime/transports/async_websocket.rb:185-224`, `lib/openai/helpers/realtime/transports/async_websocket.rb:294-378` |
242
+ | Medium | Malformed or adversarial API/SSE/WebSocket data causes sensitive leakage, type confusion, or availability failure | Victim consumes untrusted remote data | Data exposure, incorrect application behavior, or DoS | JSON parsing, typed coercion, protocol errors, safe unknown events | Keep parsers data-only, incremental where applicable, and redact errors/logs | `lib/openai/internal/stream.rb:21-60`, `lib/openai/helpers/realtime/connection.rb:61-84` |
243
+ | Medium | Multipart filename, content type, or field name reaches headers without normalization | Caller-supplied path or header parameter crosses multipart serialization | Absolute local path disclosure or multipart header injection | Media-type validation, basename reduction for `FilePart` and raw path/IO values, quote/backslash escaping, CR/LF removal | Preserve basename and header-parameter controls before upload | `lib/openai/file_part.rb:33-43`, `lib/openai/file_part.rb:110-119`, `lib/openai/internal/util.rb:533-565` |
244
+ | Medium | Forged webhook, or replay of a captured valid webhook, becomes an accepted typed event | For forgery, attacker lacks the webhook secret; for replay, attacker can resend a valid signed payload within the tolerance window | Application accepts an unauthentic or repeated event | Required headers, HMAC, and timing-safe comparison reject forgery; timestamp validation bounds replay age but does not deduplicate `webhook-id` | Verify before parsing; callers that need replay prevention must deduplicate `webhook-id` | `lib/openai/resources/webhooks.rb:15-119` |
245
+ | Medium | Sensitive runtime or live-example data leaks through diagnostics, exceptions, retained metadata, or artifacts | Untrusted data reaches logger/error/object serialization or a live-example report | Credential, prompt, response, file, header, or call-identifier disclosure | Redacted headers/queries; unsafe body categories omitted; derived HTTP errors are bounded/sanitized when no top-level scalar or explicit message is supplied; top-level scalar and explicit SSE messages remain remote data; Realtime URL call-ID redaction; retained bodies omitted from inspect/YAML/Marshal; Examples E2E reports are allowlisted and tested for sensitive output | Preserve redaction, bounded derived errors, metadata serialization controls, and allowlisted artifacts; treat preserved remote messages as remote data | `lib/openai/internal/logging.rb:299-343`, `lib/openai/errors.rb:259-409`, `lib/openai/internal/stream.rb:35-40`, `lib/openai/helpers/realtime/errors.rb:26-70`, `lib/openai/http_client.rb:46-79`, `.github/workflows/examples-e2e.yml:29-43`, `test/scripts/examples_e2e_test.rb:202-234`, `test/scripts/examples_e2e_test.rb:306-327`, `CONTRIBUTING.md:42-50` |
246
+ | Not a finding by itself | Contributor changes a tracked test/example/fixture/build script and ordinary CI runs it | Contributor already controls candidate checkout code | No new authority | Read-only ordinary CI without referenced secrets or write authority | Require a separate privilege or boundary violation before reporting | `.github/workflows/ci.yml:18-32`, `.github/workflows/ci-checks.yml:100-120` |
247
+
248
+ ## 4. Severity Calibration
249
+
250
+ - Critical: unauthorized release/publishing compromise, protected credential
251
+ theft with broad impact, or a cross-account/tenant boundary failure.
252
+ - High: realistic credential disclosure, authenticated request redirection,
253
+ protected CI/release boundary bypass, or forged authentication with meaningful
254
+ account impact.
255
+ - Medium: production-reachable parser, webhook, retry, logging, or availability
256
+ failures with bounded impact or meaningful prerequisites.
257
+ - Low: narrowly reachable confidentiality/integrity defects with limited impact
258
+ and strong mitigating prerequisites.
259
+ - Out of scope without additional authority: ordinary execution of
260
+ contributor-controlled tracked checkout code, caller-chosen dangerous
261
+ configuration, caller-owned callbacks/custom transports, purely local
262
+ self-effects, and hypothetical stories without a realistic lower-trust route
263
+ to a sensitive operation.
264
+
265
+ Severity depends on the attacker's initial authority, the new capability gained,
266
+ reachability, deployment prerequisites, and effective controls. Missing evidence
267
+ is an explicit uncertainty, not proof that a control succeeds or fails.
data/lib/openai/client.rb CHANGED
@@ -86,6 +86,9 @@ module OpenAI
86
86
  # @return [OpenAI::Resources::VectorStores]
87
87
  attr_reader :vector_stores
88
88
 
89
+ # @return [OpenAI::Resources::Safety]
90
+ attr_reader :safety
91
+
89
92
  # @return [OpenAI::Resources::Webhooks]
90
93
  attr_reader :webhooks
91
94
 
@@ -806,6 +809,7 @@ module OpenAI
806
809
  @fine_tuning = OpenAI::Resources::FineTuning.new(client: self)
807
810
  @graders = OpenAI::Resources::Graders.new(client: self)
808
811
  @vector_stores = OpenAI::Resources::VectorStores.new(client: self)
812
+ @safety = OpenAI::Resources::Safety.new(client: self)
809
813
  @webhooks = OpenAI::Resources::Webhooks.new(client: self)
810
814
  @beta = OpenAI::Resources::Beta.new(client: self)
811
815
  @batches = OpenAI::Resources::Batches.new(client: self)
@@ -133,6 +133,12 @@ module OpenAI
133
133
  index_choice_snapshot!(choice_snapshot)
134
134
  end
135
135
 
136
+ @current_completion_snapshot = completion_snapshot
137
+
138
+ chunk.choices.each do |choice|
139
+ handle_finish_reason(choice.finish_reason, completion_snapshot) if choice.finish_reason
140
+ end
141
+
136
142
  return completion_snapshot
137
143
  end
138
144
 
@@ -144,6 +150,7 @@ module OpenAI
144
150
 
145
151
  completion_snapshot.usage = chunk.usage if chunk.usage
146
152
  completion_snapshot.system_fingerprint = chunk.system_fingerprint if chunk.system_fingerprint
153
+ completion_snapshot.service_tier = chunk.service_tier if chunk.service_tier
147
154
 
148
155
  completion_snapshot
149
156
  end
@@ -332,8 +339,8 @@ module OpenAI
332
339
 
333
340
  if choice_snapshot.logprobs.nil?
334
341
  choice_snapshot.logprobs = OpenAI::Chat::ChatCompletionChunk::Choice::Logprobs.new(
335
- content: choice_logprobs.content,
336
- refusal: choice_logprobs.refusal
342
+ content: choice_logprobs.content&.dup,
343
+ refusal: choice_logprobs.refusal&.dup
337
344
  )
338
345
  else
339
346
  if choice_logprobs.content
@@ -527,7 +534,7 @@ module OpenAI
527
534
  model: data[:model],
528
535
  choices: choices,
529
536
  usage: data[:usage],
530
- system_fingerprint: nil,
537
+ system_fingerprint: data[:system_fingerprint],
531
538
  service_tier: data[:service_tier]
532
539
  }
533
540
  )
@@ -647,11 +654,6 @@ module OpenAI
647
654
 
648
655
  if choice_snapshot.finish_reason
649
656
  events.concat(content_done_events(choice_snapshot, response_format))
650
-
651
- if @current_tool_call_index && !@done_tool_calls.include?(@current_tool_call_index)
652
- event = tool_done_event(tool_calls_by_index, @current_tool_call_index)
653
- events << event if event
654
- end
655
657
  end
656
658
 
657
659
  Array(choice_chunk.delta.tool_calls).each do |tool_call|
@@ -667,6 +669,13 @@ module OpenAI
667
669
  @current_tool_call_index = tool_call.index
668
670
  end
669
671
 
672
+ if choice_snapshot.finish_reason &&
673
+ @current_tool_call_index &&
674
+ !@done_tool_calls.include?(@current_tool_call_index)
675
+ event = tool_done_event(tool_calls_by_index, @current_tool_call_index)
676
+ events << event if event
677
+ end
678
+
670
679
  events
671
680
  end
672
681
 
@@ -190,12 +190,7 @@ module OpenAI
190
190
  def accumulate_event(event:, current_snapshot:)
191
191
  if current_snapshot.nil?
192
192
  if event.is_a?(OpenAI::Models::Responses::ResponseCreatedEvent)
193
- # Use the converter to create a new, isolated copy of the response object.
194
- # This ensures proper type validation and prevents shared object references.
195
- return OpenAI::Internal::Type::Converter.coerce(
196
- OpenAI::Models::Responses::Response,
197
- event.response
198
- )
193
+ return isolated_value(event.response)
199
194
  end
200
195
 
201
196
  unless @resumed
@@ -212,12 +207,12 @@ module OpenAI
212
207
 
213
208
  case event
214
209
  when OpenAI::Models::Responses::ResponseOutputItemAddedEvent
215
- current_snapshot.output.push(event.item)
210
+ current_snapshot.output.push(isolated_value(event.item))
216
211
 
217
212
  when OpenAI::Models::Responses::ResponseContentPartAddedEvent
218
213
  output = current_snapshot.output[event.output_index]
219
214
  if output && output.type == :message
220
- output.content.push(event.part)
215
+ output.content.push(isolated_value(event.part))
221
216
  current_snapshot.output[event.output_index] = output
222
217
  end
223
218
 
@@ -248,6 +243,16 @@ module OpenAI
248
243
 
249
244
  private
250
245
 
246
+ # Materialize fresh containers before snapshot accumulation mutates them.
247
+ # Coercing an already-typed model returns it unchanged, so first use the
248
+ # model's recursive raw representation. Unknown union fallbacks stay raw.
249
+ def isolated_value(value)
250
+ raw = OpenAI::Internal::Type::BaseModel.recursively_to_h(value, convert: false)
251
+ return raw unless value.is_a?(OpenAI::Internal::Type::BaseModel)
252
+
253
+ OpenAI::Internal::Type::Converter.coerce(value.class, raw)
254
+ end
255
+
251
256
  def assert_type(object, expected_type)
252
257
  return if object && object.type == expected_type
253
258
  actual_type = object ? object.type : "nil"
@@ -24,10 +24,10 @@ module OpenAI
24
24
  # @return [Hash{Symbol=>Object}]
25
25
  def to_json_schema_inner(state:)
26
26
  OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.cache_def!(state, type: self) do
27
- state.fetch(:path) << "[]"
27
+ new_state = {**state, path: [*state.fetch(:path), "[]"]}
28
28
  items = OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.to_json_schema_inner(
29
29
  item_type,
30
- state: state
30
+ state: new_state
31
31
  )
32
32
  items = OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.to_nilable(items) if nilable?
33
33
  OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.assoc_meta!(items, meta: @meta)
@@ -32,13 +32,27 @@ module OpenAI
32
32
  OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.cache_def!(state, type: self) do
33
33
  path = state.fetch(:path)
34
34
  properties = field_values.to_h do |field|
35
- api_name, type, nilable, meta = field.fetch_values(:api_name, :type, :nilable, :meta)
35
+ api_name, type, nilable, const, meta = field.fetch_values(:api_name, :type, :nilable, :const, :meta)
36
36
  new_state = {**state, path: [*path, ".#{api_name}"]}
37
37
 
38
- schema = OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.to_json_schema_inner(
39
- type,
40
- state: new_state
41
- )
38
+ literal_const = case const
39
+ when Integer, TrueClass, FalseClass
40
+ true
41
+ when Float
42
+ const.finite?
43
+ else
44
+ false
45
+ end
46
+
47
+ schema = if literal_const
48
+ {const: const}
49
+ else
50
+ OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.to_json_schema_inner(
51
+ type,
52
+ state: new_state
53
+ )
54
+ end
55
+
42
56
  schema = OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.to_nilable(schema) if nilable
43
57
  OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.assoc_meta!(
44
58
  schema,
@@ -67,7 +67,10 @@ module OpenAI
67
67
  }
68
68
  )
69
69
  in {
70
- response_format: {type: :json_schema, json_schema: OpenAI::StructuredOutput::JsonSchemaConverter => model}
70
+ response_format: {
71
+ type: :json_schema | "json_schema",
72
+ json_schema: OpenAI::StructuredOutput::JsonSchemaConverter => model
73
+ }
71
74
  }
72
75
  parsed.fetch(:response_format).update(
73
76
  json_schema: {
@@ -78,7 +81,7 @@ module OpenAI
78
81
  )
79
82
  in {
80
83
  response_format: {
81
- type: :json_schema,
84
+ type: :json_schema | "json_schema",
82
85
  json_schema: {schema: OpenAI::StructuredOutput::JsonSchemaConverter => model}
83
86
  }
84
87
  }
@@ -124,7 +127,7 @@ module OpenAI
124
127
  return [] if tools.nil?
125
128
 
126
129
  tools.map do |tool|
127
- next tool unless tool[:type] == :function
130
+ next tool unless [:function, "function"].include?(tool[:type])
128
131
 
129
132
  function_name = tool.dig(:function, :name)
130
133
  model = tool_models[function_name]