codex-a2a 1.2.0__tar.gz → 1.3.0__tar.gz

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 (253) hide show
  1. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/PKG-INFO +2 -2
  2. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/README.md +1 -1
  3. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/compatibility.md +1 -1
  4. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/guide.md +61 -3
  5. codex_a2a-1.3.0/docs/inspection/README.md +17 -0
  6. codex_a2a-1.3.0/docs/inspection/error_redaction_policy.md +56 -0
  7. codex_a2a-1.3.0/docs/inspection/release_integrity_policy.md +33 -0
  8. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/publish_github_release.sh +23 -0
  9. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/manager.py +22 -4
  10. codex_a2a-1.3.0/src/codex_a2a/client/network_policy.py +156 -0
  11. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/config.py +69 -0
  12. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/contracts/extension_specs.py +6 -6
  13. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/contracts/extensions.py +9 -13
  14. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/exec_runtime.py +3 -2
  15. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/executor.py +3 -1
  16. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/application.py +28 -2
  17. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/discovery_payload_mapping.py +4 -9
  18. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/errors.py +14 -6
  19. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/metrics.py +6 -0
  20. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/profile/runtime.py +5 -1
  21. codex_a2a-1.3.0/src/codex_a2a/redact.py +79 -0
  22. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/agent_card.py +0 -3
  23. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/application.py +158 -5
  24. codex_a2a-1.3.0/src/codex_a2a/server/database.py +144 -0
  25. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/http_middlewares.py +183 -1
  26. codex_a2a-1.3.0/src/codex_a2a/server/runtime_limits.py +338 -0
  27. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/discovery_payloads.py +0 -3
  28. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/PKG-INFO +2 -2
  29. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/SOURCES.txt +10 -0
  30. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_client_manager.py +65 -3
  31. codex_a2a-1.3.0/tests/client/test_network_policy.py +210 -0
  32. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/config/test_settings.py +41 -0
  33. codex_a2a-1.3.0/tests/execution/test_error_redaction.py +123 -0
  34. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_codex_discovery_extension.py +49 -1
  35. codex_a2a-1.3.0/tests/jsonrpc/test_error_redaction.py +126 -0
  36. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_protocol_payload_mapping.py +12 -31
  37. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/test_publish_github_release_script.py +12 -0
  38. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/profile/test_profile.py +13 -0
  39. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_agent_card.py +16 -0
  40. codex_a2a-1.3.0/tests/server/test_database.py +223 -0
  41. codex_a2a-1.3.0/tests/server/test_http_boundary.py +282 -0
  42. codex_a2a-1.3.0/tests/server/test_runtime_limits.py +542 -0
  43. codex_a2a-1.3.0/tests/test_redact.py +84 -0
  44. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/test_codex_client_params.py +0 -12
  45. codex_a2a-1.2.0/src/codex_a2a/server/database.py +0 -47
  46. codex_a2a-1.2.0/src/codex_a2a/server/runtime_limits.py +0 -167
  47. codex_a2a-1.2.0/tests/server/test_database.py +0 -27
  48. codex_a2a-1.2.0/tests/server/test_runtime_limits.py +0 -122
  49. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.github/dependabot.yml +0 -0
  50. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.github/workflows/ci.yml +0 -0
  51. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.github/workflows/compatibility.yml +0 -0
  52. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.github/workflows/dependency-health.yml +0 -0
  53. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.github/workflows/publish.yml +0 -0
  54. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.gitignore +0 -0
  55. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.pre-commit-config.yaml +0 -0
  56. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.secrets.baseline +0 -0
  57. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/AGENTS.md +0 -0
  58. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/CONTRIBUTING.md +0 -0
  59. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/LICENSE +0 -0
  60. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/SECURITY.md +0 -0
  61. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/a2a-extension-baseline.md +0 -0
  62. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/architecture.md +0 -0
  63. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/conformance-triage.md +0 -0
  64. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/conformance.md +0 -0
  65. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/extension-specifications.md +0 -0
  66. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/maintainer-architecture.md +0 -0
  67. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/mypy.ini +0 -0
  68. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/pyproject.toml +0 -0
  69. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/README.md +0 -0
  70. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/audit_low_call_sites.py +0 -0
  71. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/check_dead_code.py +0 -0
  72. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/conformance.sh +0 -0
  73. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/dependency_health.sh +0 -0
  74. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/health_common.sh +0 -0
  75. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/smoke_test_built_cli.sh +0 -0
  76. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/smoke_test_live_codex.sh +0 -0
  77. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/sync_codex_docs.sh +0 -0
  78. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/tck_auth_plugin.py +0 -0
  79. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/validate_baseline.sh +0 -0
  80. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/validate_runtime_matrix.sh +0 -0
  81. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/setup.cfg +0 -0
  82. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/__init__.py +0 -0
  83. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/a2a_proto.py +0 -0
  84. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/auth.py +0 -0
  85. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/cli.py +0 -0
  86. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/__init__.py +0 -0
  87. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/agent_card.py +0 -0
  88. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/auth.py +0 -0
  89. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/client.py +0 -0
  90. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/config.py +0 -0
  91. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/errors.py +0 -0
  92. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/extension_negotiation.py +0 -0
  93. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/payload_text.py +0 -0
  94. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/request_context.py +0 -0
  95. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/contracts/__init__.py +0 -0
  96. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/contracts/extension_registry.py +0 -0
  97. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/contracts/runtime_output.py +0 -0
  98. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/__init__.py +0 -0
  99. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/discovery_runtime.py +0 -0
  100. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/output_mapping.py +0 -0
  101. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/request_metadata.py +0 -0
  102. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/request_overrides.py +0 -0
  103. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/response_emitter.py +0 -0
  104. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/review_runtime.py +0 -0
  105. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/session_runtime.py +0 -0
  106. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/stream_chunks.py +0 -0
  107. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/stream_interrupts.py +0 -0
  108. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/stream_processor.py +0 -0
  109. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/stream_state.py +0 -0
  110. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/streaming.py +0 -0
  111. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/thread_lifecycle_runtime.py +0 -0
  112. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/tool_call_payloads.py +0 -0
  113. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/watch_events.py +0 -0
  114. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/input_mapping.py +0 -0
  115. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/__init__.py +0 -0
  116. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/discovery_control.py +0 -0
  117. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/discovery_params.py +0 -0
  118. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/discovery_query.py +0 -0
  119. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/dispatch.py +0 -0
  120. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/exec_control.py +0 -0
  121. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/exec_control_params.py +0 -0
  122. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/hooks.py +0 -0
  123. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/interrupt_lifecycle.py +0 -0
  124. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/interrupt_params.py +0 -0
  125. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/interrupt_recovery.py +0 -0
  126. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/interrupt_recovery_params.py +0 -0
  127. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/interrupts.py +0 -0
  128. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/owner_guard.py +0 -0
  129. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/params.py +0 -0
  130. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/params_common.py +0 -0
  131. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/payload_mapping.py +0 -0
  132. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/query_params.py +0 -0
  133. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/request_models.py +0 -0
  134. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/review_control.py +0 -0
  135. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/review_control_params.py +0 -0
  136. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/session_query.py +0 -0
  137. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/thread_lifecycle_control.py +0 -0
  138. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/thread_lifecycle_params.py +0 -0
  139. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/turn_control.py +0 -0
  140. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/turn_control_params.py +0 -0
  141. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/logging_context.py +0 -0
  142. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/media_modes.py +0 -0
  143. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/payload_helpers.py +0 -0
  144. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/profile/__init__.py +0 -0
  145. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/protocol_versions.py +0 -0
  146. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/py.typed +0 -0
  147. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/__init__.py +0 -0
  148. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/call_context.py +0 -0
  149. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/migrations.py +0 -0
  150. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/openapi.py +0 -0
  151. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/openapi_contract_fragments.py +0 -0
  152. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/output_negotiation.py +0 -0
  153. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/push_config_store.py +0 -0
  154. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/request_handler.py +0 -0
  155. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/runtime_state.py +0 -0
  156. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/runtime_state_schema.py +0 -0
  157. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/task_store.py +0 -0
  158. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/__init__.py +0 -0
  159. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/client.py +0 -0
  160. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/conversation_facade.py +0 -0
  161. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/interrupt_bridge.py +0 -0
  162. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/interrupts.py +0 -0
  163. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/models.py +0 -0
  164. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/notification_mapping.py +0 -0
  165. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/request_mapping.py +0 -0
  166. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/startup.py +0 -0
  167. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/stream_bridge.py +0 -0
  168. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/transport.py +0 -0
  169. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/dependency_links.txt +0 -0
  170. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/entry_points.txt +0 -0
  171. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/requires.txt +0 -0
  172. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/top_level.txt +0 -0
  173. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/__init__.py +0 -0
  174. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/__init__.py +0 -0
  175. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_agent_card.py +0 -0
  176. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_cli.py +0 -0
  177. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_client_flow.py +0 -0
  178. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_errors.py +0 -0
  179. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_lifecycle.py +0 -0
  180. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_payload_text.py +0 -0
  181. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_request_context.py +0 -0
  182. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/config/__init__.py +0 -0
  183. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/contracts/__init__.py +0 -0
  184. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/contracts/test_extension_contract_consistency.py +0 -0
  185. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/contracts/test_extension_registry.py +0 -0
  186. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/__init__.py +0 -0
  187. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_agent_errors.py +0 -0
  188. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_cancellation.py +0 -0
  189. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_codex_agent_session_binding.py +0 -0
  190. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_directory_validation.py +0 -0
  191. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_discovery_exec_runtime.py +0 -0
  192. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_metrics.py +0 -0
  193. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_request_metadata.py +0 -0
  194. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_review_runtime.py +0 -0
  195. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_session_ownership.py +0 -0
  196. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_session_persistence.py +0 -0
  197. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_session_runtime.py +0 -0
  198. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_stream_chunks.py +0 -0
  199. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_stream_interrupts.py +0 -0
  200. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_streaming_output_contract.py +0 -0
  201. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_thread_lifecycle_runtime.py +0 -0
  202. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_tool_call_payloads.py +0 -0
  203. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/fixtures/codex_app_server/command_execution_output_delta.json +0 -0
  204. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/fixtures/codex_app_server/file_change_output_delta.json +0 -0
  205. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/__init__.py +0 -0
  206. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_codex_exec_extension.py +0 -0
  207. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_codex_session_extension.py +0 -0
  208. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_codex_thread_lifecycle_extension.py +0 -0
  209. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_codex_turn_review_extension.py +0 -0
  210. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_dispatch.py +0 -0
  211. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_guard_hooks.py +0 -0
  212. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_interrupt_lifecycle.py +0 -0
  213. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_interrupt_params.py +0 -0
  214. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_interrupt_protocol_handlers.py +0 -0
  215. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_jsonrpc_models.py +0 -0
  216. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/__init__.py +0 -0
  217. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/test_logging.py +0 -0
  218. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/test_release_distribution_contract.py +0 -0
  219. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/test_typing_contract.py +0 -0
  220. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/test_version.py +0 -0
  221. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/profile/__init__.py +0 -0
  222. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/scripts/__init__.py +0 -0
  223. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/scripts/test_audit_low_call_sites.py +0 -0
  224. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/scripts/test_script_health_contract.py +0 -0
  225. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/scripts/test_tck_auth_plugin.py +0 -0
  226. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/__init__.py +0 -0
  227. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_auth.py +0 -0
  228. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_call_context_builder.py +0 -0
  229. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_cli.py +0 -0
  230. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_database_app_persistence.py +0 -0
  231. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_migrations.py +0 -0
  232. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_output_negotiation.py +0 -0
  233. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_push_config_store.py +0 -0
  234. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_request_handler.py +0 -0
  235. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_runtime_state.py +0 -0
  236. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_task_store.py +0 -0
  237. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_transport_contract.py +0 -0
  238. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/__init__.py +0 -0
  239. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/context.py +0 -0
  240. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/dummy_clients.py +0 -0
  241. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/fixtures.py +0 -0
  242. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/http_auth.py +0 -0
  243. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/jsonrpc_errors.py +0 -0
  244. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/settings.py +0 -0
  245. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/test_a2a_proto.py +0 -0
  246. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/test_input_mapping.py +0 -0
  247. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/test_protocol_versions.py +0 -0
  248. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/__init__.py +0 -0
  249. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/test_interrupt_bridge.py +0 -0
  250. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/test_interrupt_persistence.py +0 -0
  251. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/test_modular_upstream_components.py +0 -0
  252. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/test_request_mapping.py +0 -0
  253. {codex_a2a-1.2.0 → codex_a2a-1.3.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-a2a
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Codex A2A runtime adapter
5
5
  Author: liujuanjuan1984@Intelligent-Internet
6
6
  License-Expression: Apache-2.0
@@ -164,7 +164,7 @@ Authenticated extended card:
164
164
  - JSON-RPC: `agent/getAuthenticatedExtendedCard`
165
165
  - HTTP: `GET /extendedAgentCard`
166
166
 
167
- Outbound peer auth is configured with `A2A_CLIENT_BEARER_TOKEN` or `A2A_CLIENT_BASIC_AUTH`; see the Usage Guide for the complete client-side matrix.
167
+ Outbound peer auth is configured with `A2A_CLIENT_BEARER_TOKEN` or `A2A_CLIENT_BASIC_AUTH`; credentials are only sent to hosts listed in `A2A_CLIENT_ALLOWED_HOSTS`. The embedded `a2a_call(...)` tool also rejects private/loopback targets unless `A2A_CLIENT_ALLOW_PRIVATE_HOSTS=true`; see the Usage Guide for the complete client-side matrix.
168
168
 
169
169
  ## When To Use It
170
170
 
@@ -117,7 +117,7 @@ Authenticated extended card:
117
117
  - JSON-RPC: `agent/getAuthenticatedExtendedCard`
118
118
  - HTTP: `GET /extendedAgentCard`
119
119
 
120
- Outbound peer auth is configured with `A2A_CLIENT_BEARER_TOKEN` or `A2A_CLIENT_BASIC_AUTH`; see the Usage Guide for the complete client-side matrix.
120
+ Outbound peer auth is configured with `A2A_CLIENT_BEARER_TOKEN` or `A2A_CLIENT_BASIC_AUTH`; credentials are only sent to hosts listed in `A2A_CLIENT_ALLOWED_HOSTS`. The embedded `a2a_call(...)` tool also rejects private/loopback targets unless `A2A_CLIENT_ALLOW_PRIVATE_HOSTS=true`; see the Usage Guide for the complete client-side matrix.
121
121
 
122
122
  ## When To Use It
123
123
 
@@ -101,7 +101,7 @@ Execution-environment boundary fields are also published through the runtime pro
101
101
  - Deployment-conditional methods must be declared as conditional rather than silently disappearing.
102
102
  - Rich input mapping is compatibility-sensitive across the core A2A message surface and `codex.turns.steer`. Changes to supported part types, `Part(url|raw)` image handling, or `Part(data)` mention/skill mapping should be treated as wire-level behavior changes.
103
103
  - `codex.exec.*` is compatibility-sensitive as the standalone interactive exec contract. Changes to handle shapes, task-stream delivery, or lifecycle method names should be treated as wire-level changes.
104
- - `codex.discovery.*` is compatibility-sensitive as the stable discovery contract for `skill.path` and `mention_path` identifiers. Changes to normalized item fields, plugin marketplace mapping, or discovery watch task payload kinds should be treated as wire-level changes.
104
+ - `codex.discovery.*` is compatibility-sensitive as the stable discovery contract for normalized skill/app/plugin summaries and `mention_path` identifiers. Local paths (`skill.path`, marketplace paths) and raw upstream records are intentionally excluded from discovery responses; changes to normalized item fields, plugin marketplace mapping, or discovery watch task payload kinds should be treated as wire-level changes.
105
105
  - `codex.threads.*` is compatibility-sensitive as the provider-private thread lifecycle contract. Changes to lifecycle method names, watch payload kinds, or watch-task bridge event names should be treated as wire-level changes.
106
106
  - `codex.turns.*` is compatibility-sensitive as the active-turn control contract. Changes to `expected_turn_id` semantics, same-turn rich-input handling, or rejected override fields should be treated as wire-level changes.
107
107
  - `codex.review.*` is compatibility-sensitive as the review control and watch contract. Changes to supported target types, `delivery` semantics, review watch payload kinds, or review watch event names should be treated as wire-level changes.
@@ -189,15 +189,49 @@ Use the grouped sections below as the deployment-first reading order:
189
189
  - `A2A_HOST`: bind host, default `127.0.0.1`
190
190
  - `A2A_PORT`: bind port, default `8000`
191
191
  - `A2A_PUBLIC_URL`: externally reachable A2A URL prefix, default `http://127.0.0.1:8000`
192
+ - `A2A_ALLOWED_ORIGINS`: comma-separated extra browser-origin allowlist for inbound requests. Requests carrying an `Origin` header must match the origin of `A2A_PUBLIC_URL` or an entry here; mismatches are rejected with `403` (CSRF guard). Requests without an `Origin` header (CLI/SDK clients) are unaffected.
193
+ - `A2A_ALLOWED_HOSTS`: comma-separated `Host` header allowlist (exact names or `*.example.com` wildcards). When configured, every inbound request must present a matching `Host` header. Binding to a non-loopback address without this allowlist logs a startup warning (DNS rebinding risk).
192
194
  - `A2A_DATABASE_URL`: SQLAlchemy async database URL. Defaults to SQLite under `${CODEX_WORKSPACE_ROOT}/.codex-a2a/codex-a2a.db`.
193
195
  - `A2A_DATABASE_URL` also owns the adapter-managed runtime-state schema lifecycle. On startup, `codex-a2a` auto-creates the runtime-state tables, records a schema version for the `runtime_state` scope, and applies in-place migrations for those tables only.
194
196
  - The adapter-managed runtime-state schema is limited to `a2a_session_bindings`, `a2a_session_owners`, `a2a_pending_session_claims`, `a2a_pending_interrupt_requests`, and `a2a_schema_version`. It does not own the A2A SDK task-store tables or any upstream Codex/provider-local state.
195
197
  - For deployment-specific durability constraints and migration-scope boundaries, see [compatibility.md](./compatibility.md).
198
+
199
+ ### SQLite Persistence Hardening
200
+
201
+ File-backed SQLite databases are hardened at startup and on every new connection on POSIX systems:
202
+
203
+ - the database file must be a regular file; symlinks are rejected and fail startup;
204
+ - the file must be owned by the user running `codex-a2a`; a database owned by another user fails startup;
205
+ - the database file is forced to mode `0600`, and existing SQLite sidecar files (`-wal`/`-shm`/`-journal`) are converged to the same mode;
206
+ - the parent directory is created with mode `0700` when it does not exist; keep the `.codex-a2a` directory private (no group/world access);
207
+ - if the database is replaced by a symlink, a special file, or a foreign-owned file while the service is running, the next connection fails closed.
208
+
209
+ `A2A_DATABASE_URL` values that do not map to a plain file path are exempt: `:memory:` and `file:` URI-style database components (including in-memory shared-cache databases) are not hardened. Prefer a plain absolute file path for deployments that need the startup guarantees. Non-POSIX platforms rely on the user account ACLs of the hosting directory; the same private-directory requirement applies there.
210
+
211
+ Deployment requirements:
212
+
213
+ - run `codex-a2a` as a dedicated user and keep the database directory outside world-readable workspace trees;
214
+ - do not place the SQLite file on network-mounted or sync-managed directories;
215
+ - when migrating an existing database, fix ownership and mode before startup: `chown <service-user> <db>` and `chmod 600 <db>`, and ensure the parent directory is not group/world accessible.
216
+
217
+ ### Inbound Origin and Host Boundary
218
+
219
+ Browsers store and automatically attach Basic credentials, and they send an `Origin` header with every request. Without an origin boundary, a malicious web page could cross-site trigger `SendMessage`, `CancelTask`, or task subscription against a Basic-protected service. The service therefore enforces:
220
+
221
+ - every request carrying an `Origin` header must match the origin of `A2A_PUBLIC_URL` (scheme, host, and non-default port) or an entry in `A2A_ALLOWED_ORIGINS`; mismatches return `403` before authentication. `Origin: null` (sandboxed iframes) is rejected unless explicitly allowlisted;
222
+ - requests without an `Origin` header — normal CLI/SDK peers — are not subject to origin checks;
223
+ - when `A2A_ALLOWED_HOSTS` is configured, every request must present a matching `Host` header (exact names, optional `host:port`, or `*.example.com` wildcards); mismatches return `403`. This also blocks DNS rebinding, where an attacker-controlled hostname resolves to the service address and the browser sends that hostname as `Host`;
224
+ - binding to a non-loopback address (`0.0.0.0`, `::`, or a LAN/interface address) without `A2A_ALLOWED_HOSTS` logs a startup warning: the service is then exposed to DNS rebinding and should only run behind a trusted network boundary or a reverse proxy that validates `Host`.
225
+
226
+ For browser-based clients, prefer the same origin as `A2A_PUBLIC_URL`, or explicitly allow the dashboard origin via `A2A_ALLOWED_ORIGINS` and treat `A2A_ALLOWED_HOSTS` as part of the deployment contract. Basic authentication should not be relied on as the sole browser-facing boundary: combine it with the origin/host checks above, short-lived credentials, and TLS at the public URL.
227
+
196
228
  - `A2A_LOG_LEVEL`: `DEBUG/INFO/WARNING/ERROR`, default `WARNING`
197
229
  - `A2A_LOG_PAYLOADS`: log A2A/Codex payload bodies, default `false`
198
230
  - `A2A_LOG_BODY_LIMIT`: payload log body size limit, default `0` (no truncation)
199
231
  - `A2A_REQUEST_BODY_MAX_BYTES`: hard HTTP request body limit, default `4194304` (4 MiB); set `0` to disable
200
232
  - `A2A_MAX_CONCURRENT_OPERATIONS`: process-local active POST and task-subscription limit, default `32`; set `0` to disable
233
+ - `A2A_RATE_LIMIT_ENABLED` / `A2A_RATE_LIMIT_WINDOW_SECONDS` / `A2A_RATE_LIMIT_MAX_REQUESTS`: per-credential sliding-window rate limiting (per-peer-IP for the unauthenticated Agent Card surface). Defaults: `true` / `60` seconds / `120` requests. Exceeding the window returns HTTP `429` with a `Retry-After` header; `A2A_RATE_LIMIT_ENABLED=false` disables the limiter. The limiter is process-local; multi-process deployments should place a shared gateway in front or rely on per-instance limits.
234
+ - `A2A_STREAM_MAX_BYTES` / `A2A_STREAM_MAX_DURATION_SECONDS` / `A2A_STREAM_IDLE_TIMEOUT_SECONDS`: streaming (SSE) response budgets for total serialized bytes, total duration, and idle gap. Defaults: `67108864` bytes / `3600` seconds / `120` seconds; `0` disables the respective budget. When a budget is exceeded the server ends the stream with an SSE `event: error` frame; if the very first event already exceeds the budget, REST requests are rejected with HTTP `429` `RESOURCE_EXHAUSTED` before the SSE stream starts.
201
235
  - `A2A_ENABLE_METRICS_ENDPOINT`: enable the authenticated Prometheus `/metrics` endpoint, default `true`
202
236
  - `A2A_TITLE`: agent name, default `Codex A2A`
203
237
  - `A2A_DESCRIPTION`: agent description exposed on Agent Card and docs surfaces
@@ -217,6 +251,18 @@ These settings affect peer calls made through `codex-a2a call` or the embedded `
217
251
  - `A2A_CLIENT_SUPPORTED_TRANSPORTS`: comma-separated outbound transport preference list, default `JSONRPC,HTTP+JSON`
218
252
  - `A2A_CLIENT_BEARER_TOKEN`: optional bearer token for the target peer service
219
253
  - `A2A_CLIENT_BASIC_AUTH`: optional Basic auth credential for the target peer service when bearer auth is not configured
254
+ - `A2A_CLIENT_ALLOWED_HOSTS`: comma-separated allowlist of outbound target hosts for `a2a_call(...)` (exact names or `*.example.com` wildcards). When configured, outbound calls are restricted to matching hosts, and outbound credentials (`A2A_CLIENT_BEARER_TOKEN` / `A2A_CLIENT_BASIC_AUTH`) are only sent to allowlisted hosts.
255
+ - `A2A_CLIENT_ALLOW_PRIVATE_HOSTS`: default `false`. When `false`, outbound `a2a_call(...)` targets that resolve to private, loopback, link-local, reserved, or multicast addresses are rejected (SSRF / DNS-rebinding guard). Set to `true` only when the deployment intentionally targets A2A peers on the local network.
256
+
257
+ ### Outbound Network Policy
258
+
259
+ The embedded `a2a_call(...)` tool lets the upstream model choose the target URL, so the adapter applies a fail-closed network policy before opening any connection:
260
+
261
+ - only `http`/`https` schemes are accepted, and URLs carrying userinfo credentials are rejected;
262
+ - when `A2A_CLIENT_ALLOWED_HOSTS` is set, the target host must match the allowlist (exact or `*.example.com` wildcard);
263
+ - unless `A2A_CLIENT_ALLOW_PRIVATE_HOSTS=true`, the resolved addresses must be public; private/loopback/link-local/reserved addresses are rejected even when the hostname matches the allowlist (DNS rebinding defense);
264
+ - outbound credentials are only attached to allowlisted hosts. If credentials are configured without an allowlist, they are never sent and a warning is logged;
265
+ - the `codex-a2a call` CLI remains a manual operator action and is not subject to the allowlist, but still rejects non-http(s) schemes through the shared client URL handling.
220
266
 
221
267
  ### Upstream Codex Configuration
222
268
 
@@ -237,6 +283,7 @@ These variables are forwarded to the local `codex app-server` subprocess.
237
283
  - `A2A_ENABLE_TURN_CONTROL`: expose `codex.turns.steer` on JSON-RPC extensions, default `true`
238
284
  - `A2A_ENABLE_REVIEW_CONTROL`: expose `codex.review.start` and `codex.review.watch` on JSON-RPC extensions, default `false`
239
285
  - `A2A_ENABLE_EXEC_CONTROL`: expose `codex.exec.*` on JSON-RPC extensions, default `false`
286
+ - `A2A_EXPOSE_WORKSPACE_ROOT_IN_CARD`: default `false`; controls whether the authenticated extended Agent Card includes the local `CODEX_WORKSPACE_ROOT` in the structured profile `runtime_context`. Keep disabled unless trusted callers explicitly need the host path.
240
287
  - `A2A_ALLOW_DIRECTORY_OVERRIDE`: allow `metadata.codex.directory` overrides within the configured workspace boundary, default `true`
241
288
  - `A2A_SESSION_CACHE_TTL_SECONDS`: in-memory TTL for session mapping, default `3600`
242
289
  - `A2A_SESSION_CACHE_MAXSIZE`: max local process session-cache entries, default `10000`
@@ -274,12 +321,20 @@ These variables are forwarded to the local `codex app-server` subprocess.
274
321
  | `A2A_HOST` | Bind host |
275
322
  | `A2A_PORT` | Bind port |
276
323
  | `A2A_PUBLIC_URL` | Public URL prefix |
324
+ | `A2A_ALLOWED_ORIGINS` | Inbound browser-origin allowlist (CSRF guard) |
325
+ | `A2A_ALLOWED_HOSTS` | Inbound Host allowlist (DNS rebinding guard) |
277
326
  | `A2A_DATABASE_URL` | Persistence DB URL |
278
327
  | `A2A_LOG_LEVEL` | Log level |
279
328
  | `A2A_LOG_PAYLOADS` | Log bodies |
280
329
  | `A2A_LOG_BODY_LIMIT` | Body log limit |
281
330
  | `A2A_REQUEST_BODY_MAX_BYTES` | HTTP request body limit |
282
331
  | `A2A_MAX_CONCURRENT_OPERATIONS` | Active operation capacity |
332
+ | `A2A_RATE_LIMIT_ENABLED` | Sliding-window rate limiter toggle |
333
+ | `A2A_RATE_LIMIT_WINDOW_SECONDS` | Rate-limit window length |
334
+ | `A2A_RATE_LIMIT_MAX_REQUESTS` | Max requests per window per key |
335
+ | `A2A_STREAM_MAX_BYTES` | Streaming byte budget |
336
+ | `A2A_STREAM_MAX_DURATION_SECONDS` | Streaming duration budget |
337
+ | `A2A_STREAM_IDLE_TIMEOUT_SECONDS` | Streaming idle timeout |
283
338
  | `A2A_TITLE` | Agent title |
284
339
  | `A2A_DESCRIPTION` | Agent description |
285
340
  | `A2A_VERSION` | Agent version |
@@ -302,6 +357,8 @@ These variables are forwarded to the local `codex app-server` subprocess.
302
357
  | `A2A_CLIENT_CARD_FETCH_TIMEOUT_SECONDS` | Card fetch timeout |
303
358
  | `A2A_CLIENT_USE_CLIENT_PREFERENCE` | Transport preference |
304
359
  | `A2A_CLIENT_SUPPORTED_TRANSPORTS` | Supported transports |
360
+ | `A2A_CLIENT_ALLOWED_HOSTS` | Outbound host allowlist (SSRF / credential binding) |
361
+ | `A2A_CLIENT_ALLOW_PRIVATE_HOSTS` | Allow private/loopback outbound targets |
305
362
  | `A2A_INTERRUPT_REQUEST_TTL_SECONDS` | Interrupt TTL |
306
363
  | `A2A_EXECUTION_SANDBOX_MODE` | Discovery sandbox mode |
307
364
  | `A2A_EXECUTION_SANDBOX_FILESYSTEM_SCOPE` | Discovery FS scope |
@@ -473,6 +530,7 @@ On the current npm global install layout for Linux x64, the command above resolv
473
530
  - On the core chat surface, the `application/json` input mode is intentionally narrower than arbitrary JSON: only `Part(data={"type":"mention"|"skill", ...})` is part of the declared stable contract.
474
531
  - Image input maps to upstream `turn/start.input[].type=input_image`.
475
532
  - `mention.path` and `skill.path` are forwarded verbatim. The service does not guess app or plugin identifiers from display names.
533
+ - Discovery responses no longer expose local `skill.path` values; callers that need rich-input skill items must supply the path from their own context.
476
534
  - `local_image` is not part of the current declared stable rich-input contract.
477
535
  - Session query projections currently use the upstream Codex `session_id` as the A2A `contextId`. In that projection surface, `contextId` is the canonical session identity and the adapter does not duplicate the same value under `metadata.shared.session.id`.
478
536
  - Completed chat turns are persisted as `completed`; `input-required` is reserved for active interrupt asks that still need a reply.
@@ -654,15 +712,15 @@ This service exposes read-only Codex discovery methods through JSON-RPC:
654
712
 
655
713
  Use these methods before constructing rich input:
656
714
 
657
- - `skills.list` returns stable `skill.path` values
715
+ - `skills.list` returns stable skill `name`/`scope` values; local skill paths are intentionally not exposed
658
716
  - `apps.list` returns stable `mention_path=app://<id>` values
659
717
  - `plugins.list` and `plugins.read` return stable `mention_path=plugin://<plugin>@<marketplace>` values
660
718
 
661
719
  Result-shape guidance:
662
720
 
663
721
  - use the normalized stable fields declared in Agent Card / OpenAPI first
664
- - inspect `codex.raw` only when you need upstream-specific fields outside the declared minimum contract
665
- - `plugin/list` and `plugin/read` remain upstream experimental; this service exposes a stable minimum subset plus passthrough raw payloads
722
+ - discovery responses are whitelisted summaries; raw upstream records and local paths are intentionally excluded
723
+ - `plugin/list` and `plugin/read` remain upstream experimental; this service exposes a stable minimum subset without raw passthrough
666
724
 
667
725
  ### Skills List (`codex.discovery.skills.list`)
668
726
 
@@ -0,0 +1,17 @@
1
+ # Inspection Deliverables
2
+
3
+ This directory records security-audit decisions and deliverables produced by
4
+ the tracked sub-items of the system security audit ([#331]).
5
+
6
+ Current contents:
7
+
8
+ - [Error redaction policy](./error_redaction_policy.md) — client-facing error
9
+ text masking of absolute local paths (#346).
10
+ - [Release integrity policy](./release_integrity_policy.md) — SHA256 checksum
11
+ manifest for published GitHub release assets (#346).
12
+
13
+ Remaining audit deliverables (full security report, network surface map,
14
+ capability graph, test plan, and architecture invariants) will be added as the
15
+ remaining sub-items are merged.
16
+
17
+ [#331]: https://github.com/liujuanjuan1984/codex-a2a/issues/331
@@ -0,0 +1,56 @@
1
+ # Error Redaction Policy
2
+
3
+ ## Decision
4
+
5
+ Client-visible error text must never expose absolute local filesystem paths.
6
+ All error responses that can leave the process are passed through a single
7
+ deterministic masker (`codex_a2a.redact.redact_absolute_paths`) before
8
+ serialization.
9
+
10
+ Masked output uses the fixed placeholder `<redacted-path>`.
11
+
12
+ ## Boundaries covered
13
+
14
+ - JSON-RPC error responses — `codex_a2a.jsonrpc.errors.adapt_jsonrpc_error`
15
+ (message and error metadata, including nested values).
16
+ - REST/HTTP error bodies — `codex_a2a.jsonrpc.errors.build_http_error_body`.
17
+ - Raw exception fallback in
18
+ `codex_a2a.jsonrpc.application._generate_error_response`.
19
+ - Streaming task error messages — `codex_a2a.execution.executor._emit_error`.
20
+ - Interactive exec session failure messages and metadata —
21
+ `codex_a2a.execution.exec_runtime._run_exec_session`.
22
+ - A2A tool-call error results —
23
+ `codex_a2a.execution.executor._handle_a2a_call_tool`; the error text is
24
+ embedded in follow-up turns within the same upstream session, which session
25
+ queries can later expose.
26
+
27
+ ## Masking rules
28
+
29
+ Masked:
30
+
31
+ - POSIX absolute paths (`/a/b/c`).
32
+ - Windows drive paths (`C:\a\b`, `C:/a/b`) and UNC paths (`\\server\share`).
33
+ - `file://` URIs that embed a local path (`file:///tmp/x`, `file://C:/tmp/x`).
34
+ - Any other slash-prefixed token (for example API route strings such as
35
+ `/tasks/123`) — masking is deliberately conservative; a slash-prefixed
36
+ token is treated as a potential path rather than risk leaking a real one.
37
+
38
+ Preserved:
39
+
40
+ - Remote URLs (`https://host/path`).
41
+ - Relative paths (`a/b`, `./x`, `../y`).
42
+ - Ordinary prose without absolute paths.
43
+
44
+ ## Logging policy
45
+
46
+ Server-side logs intentionally retain full exception context (including
47
+ absolute paths) for diagnosability. Logs are produced on the host that owns
48
+ the filesystem and are not sent to clients. Any pipeline that ships logs off
49
+ the host must apply the same redaction or restrict access before export.
50
+
51
+ ## Verification
52
+
53
+ Unit coverage lives in `tests/test_redact.py`, with boundary tests in
54
+ `tests/execution/test_error_redaction.py` and
55
+ `tests/jsonrpc/test_error_redaction.py`. The masker is deterministic and
56
+ idempotent.
@@ -0,0 +1,33 @@
1
+ # Release Integrity Policy
2
+
3
+ ## Decision
4
+
5
+ Every GitHub Release must ship a `SHA256SUMS` checksum manifest alongside the
6
+ wheel and sdist artifacts so consumers can verify artifact integrity
7
+ independently of the registry.
8
+
9
+ ## Generation
10
+
11
+ `scripts/publish_github_release.sh` regenerates `dist/SHA256SUMS` from the
12
+ release assets at publish time, before upload:
13
+
14
+ - One line per asset in `sha256sum` format: `<sha256-hex> <basename>`.
15
+ - Lines are sorted by basename so the manifest is deterministic.
16
+ - The manifest itself is uploaded as a release asset and is skipped
17
+ idempotently when it is already present on the release.
18
+
19
+ ## Verification
20
+
21
+ Consumers verify a downloaded release with:
22
+
23
+ ```bash
24
+ sha256sum -c SHA256SUMS
25
+ ```
26
+
27
+ ## Scope notes
28
+
29
+ Supply-chain dependency auditing is already covered separately by Dependabot
30
+ and `pip-audit` (part of the baseline validation); this policy covers only the
31
+ published artifact integrity gap from [#346].
32
+
33
+ [#346]: https://github.com/liujuanjuan1984/codex-a2a/issues/346
@@ -43,6 +43,29 @@ if [[ "${#release_assets[@]}" -eq 0 ]]; then
43
43
  exit 1
44
44
  fi
45
45
 
46
+ checksum_manifest_name="SHA256SUMS"
47
+ checksum_path="${dist_dir}/${checksum_manifest_name}"
48
+
49
+ # Regenerate the checksum manifest for the current release assets so every
50
+ # published artifact can be verified against the release (sha256sum -c).
51
+ "${python_bin}" - "${checksum_path}" "${release_assets[@]}" <<'PY'
52
+ from __future__ import annotations
53
+
54
+ import hashlib
55
+ import sys
56
+ from pathlib import Path
57
+
58
+ manifest_path = Path(sys.argv[1])
59
+ assets = [Path(arg) for arg in sys.argv[2:]]
60
+ lines = []
61
+ for asset in sorted(assets, key=lambda item: item.name):
62
+ digest = hashlib.sha256(asset.read_bytes()).hexdigest()
63
+ lines.append(f"{digest} {asset.name}")
64
+ manifest_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
65
+ PY
66
+
67
+ release_assets+=("${checksum_path}")
68
+
46
69
  retry() {
47
70
  local attempt=1
48
71
  local exit_code=0
@@ -3,14 +3,18 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import logging
6
7
 
7
8
  from codex_a2a.config import Settings
8
9
  from codex_a2a.contracts import extensions as extension_contracts
9
10
 
10
11
  from .client import A2AClient
11
12
  from .config import A2AClientConfig
13
+ from .network_policy import validate_agent_url
12
14
  from .request_context import build_default_headers
13
15
 
16
+ logger = logging.getLogger(__name__)
17
+
14
18
 
15
19
  class A2AClientManager:
16
20
  """Cache `A2AClient` instances by normalized agent URL."""
@@ -30,6 +34,23 @@ class A2AClientManager:
30
34
  normalized = agent_url.rstrip("/")
31
35
  if not normalized:
32
36
  raise ValueError("agent_url is required")
37
+ policy = await validate_agent_url(
38
+ normalized,
39
+ allowed_hosts=self._settings.a2a_client_allowed_hosts,
40
+ allow_private_hosts=self._settings.a2a_client_allow_private_hosts,
41
+ )
42
+ default_headers: dict[str, str] = {}
43
+ if policy.credentials_allowed:
44
+ default_headers = build_default_headers(
45
+ self._settings.a2a_client_bearer_token,
46
+ self._settings.a2a_client_basic_auth,
47
+ )
48
+ elif self._settings.a2a_client_bearer_token or self._settings.a2a_client_basic_auth:
49
+ logger.warning(
50
+ "Outbound A2A credentials are configured but host=%s is not in "
51
+ "A2A_CLIENT_ALLOWED_HOSTS; credentials will not be sent",
52
+ policy.host,
53
+ )
33
54
  async with self._lock:
34
55
  client = self._clients.get(normalized)
35
56
  if client is not None:
@@ -40,10 +61,7 @@ class A2AClientManager:
40
61
  request_timeout_seconds=self._settings.a2a_client_timeout_seconds,
41
62
  card_fetch_timeout_seconds=self._settings.a2a_client_card_fetch_timeout_seconds,
42
63
  use_client_preference=self._settings.a2a_client_use_client_preference,
43
- default_headers=build_default_headers(
44
- self._settings.a2a_client_bearer_token,
45
- self._settings.a2a_client_basic_auth,
46
- ),
64
+ default_headers=default_headers,
47
65
  supported_transports=list(self._settings.a2a_client_supported_transports),
48
66
  accepted_output_modes=["text/plain"],
49
67
  extensions=[
@@ -0,0 +1,156 @@
1
+ """Outbound A2A client network policy (SSRF guard and credential binding).
2
+
3
+ The A2A client is used by the ``a2a_call`` tool, where the target URL is
4
+ produced by the upstream model. Without an explicit policy this creates a
5
+ server-side request forgery surface: the adapter would happily connect to
6
+ private/loopback/link-local addresses (cloud metadata, internal services) and
7
+ would attach globally configured outbound credentials to any endpoint.
8
+
9
+ This module centralizes the checks:
10
+
11
+ - only ``http``/``https`` schemes are accepted;
12
+ - userinfo credentials in the URL are rejected;
13
+ - when ``A2A_CLIENT_ALLOWED_HOSTS`` is configured, the host must match the
14
+ allowlist (exact or ``*.example.com`` wildcard);
15
+ - unless ``A2A_CLIENT_ALLOW_PRIVATE_HOSTS`` is set, DNS resolution results are
16
+ validated and private/loopback/link-local/reserved addresses are rejected
17
+ (this also defends against DNS rebinding at connect time);
18
+ - outbound credentials may only be attached to hosts that match the allowlist.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import asyncio
24
+ import ipaddress
25
+ import logging
26
+ import socket
27
+ from collections.abc import Sequence
28
+ from dataclasses import dataclass
29
+ from urllib.parse import urlsplit
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ _ALLOWED_SCHEMES = frozenset({"http", "https"})
34
+
35
+ _Address = ipaddress.IPv4Address | ipaddress.IPv6Address
36
+
37
+
38
+ class A2ANetworkPolicyError(ValueError):
39
+ """Raised when an outbound agent URL violates the network policy."""
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class NetworkPolicyDecision:
44
+ """Outcome of validating an outbound agent URL."""
45
+
46
+ host: str
47
+ allowed_host: bool
48
+ credentials_allowed: bool
49
+
50
+
51
+ def matches_allowed_host(host: str, allowed_hosts: Sequence[str]) -> bool:
52
+ """Return whether ``host`` matches an allowlist entry.
53
+
54
+ Entries may be exact hostnames or ``*.example.com`` wildcards. A wildcard
55
+ matches any number of subdomain labels but never the bare apex domain.
56
+ """
57
+
58
+ normalized = (host or "").strip().lower().rstrip(".")
59
+ if not normalized:
60
+ return False
61
+ for raw_entry in allowed_hosts or ():
62
+ entry = (raw_entry or "").strip().lower().rstrip(".")
63
+ if not entry:
64
+ continue
65
+ if entry.startswith("*."):
66
+ suffix = entry[1:] # ".example.com"
67
+ apex = suffix[1:] # "example.com"
68
+ if normalized != apex and normalized.endswith(suffix):
69
+ return True
70
+ elif normalized == entry:
71
+ return True
72
+ return False
73
+
74
+
75
+ def _is_private_address(address: _Address) -> bool:
76
+ return bool(
77
+ address.is_private
78
+ or address.is_loopback
79
+ or address.is_link_local
80
+ or address.is_reserved
81
+ or address.is_multicast
82
+ or address.is_unspecified
83
+ )
84
+
85
+
86
+ async def resolve_host_addresses(host: str) -> tuple[str, ...]:
87
+ """Resolve ``host`` to a de-duplicated tuple of IP address strings."""
88
+
89
+ loop = asyncio.get_running_loop()
90
+ try:
91
+ infos = await loop.getaddrinfo(host, None, type=socket.SOCK_STREAM)
92
+ except socket.gaierror as exc:
93
+ raise A2ANetworkPolicyError(f"Agent URL host could not be resolved: {host!r}") from exc
94
+ addresses: list[str] = []
95
+ for info in infos:
96
+ ip = str(info[4][0])
97
+ if ip not in addresses:
98
+ addresses.append(ip)
99
+ return tuple(addresses)
100
+
101
+
102
+ async def validate_agent_url(
103
+ agent_url: str,
104
+ *,
105
+ allowed_hosts: Sequence[str] | None = None,
106
+ allow_private_hosts: bool = False,
107
+ ) -> NetworkPolicyDecision:
108
+ """Validate an outbound agent URL against the configured network policy."""
109
+
110
+ normalized = (agent_url or "").strip()
111
+ if not normalized:
112
+ raise A2ANetworkPolicyError("agent_url is required")
113
+
114
+ parsed = urlsplit(normalized)
115
+ scheme = (parsed.scheme or "").lower()
116
+ if scheme not in _ALLOWED_SCHEMES:
117
+ raise A2ANetworkPolicyError(
118
+ f"Agent URL scheme must be http or https, got {scheme or 'empty'}"
119
+ )
120
+ host = (parsed.hostname or "").strip().lower()
121
+ if not host:
122
+ raise A2ANetworkPolicyError("Agent URL must include a host")
123
+ if parsed.username or parsed.password:
124
+ raise A2ANetworkPolicyError("Agent URL must not include userinfo credentials")
125
+
126
+ allowlist = tuple(
127
+ (item or "").strip().lower().rstrip(".")
128
+ for item in allowed_hosts or ()
129
+ if (item or "").strip()
130
+ )
131
+ matched = matches_allowed_host(host, allowlist)
132
+ if allowlist and not matched:
133
+ raise A2ANetworkPolicyError(
134
+ f"Agent URL host {host!r} is not allowed by A2A_CLIENT_ALLOWED_HOSTS"
135
+ )
136
+
137
+ if not allow_private_hosts:
138
+ try:
139
+ addresses = await resolve_host_addresses(host)
140
+ except A2ANetworkPolicyError:
141
+ raise
142
+ except OSError as exc:
143
+ raise A2ANetworkPolicyError(f"Agent URL host could not be resolved: {host!r}") from exc
144
+ private = [
145
+ address for address in addresses if _is_private_address(ipaddress.ip_address(address))
146
+ ]
147
+ if private:
148
+ raise A2ANetworkPolicyError(
149
+ f"Agent URL host {host!r} resolves to a private/loopback address: {private[0]}"
150
+ )
151
+
152
+ return NetworkPolicyDecision(
153
+ host=host,
154
+ allowed_host=matched,
155
+ credentials_allowed=bool(allowlist) and matched,
156
+ )
@@ -320,6 +320,14 @@ class Settings(BaseSettings):
320
320
 
321
321
  # A2A settings
322
322
  a2a_public_url: str = Field(default="http://127.0.0.1:8000", alias="A2A_PUBLIC_URL")
323
+ a2a_allowed_origins: Annotated[list[str], NoDecode] = Field(
324
+ default_factory=list,
325
+ alias="A2A_ALLOWED_ORIGINS",
326
+ )
327
+ a2a_allowed_hosts: Annotated[list[str], NoDecode] = Field(
328
+ default_factory=list,
329
+ alias="A2A_ALLOWED_HOSTS",
330
+ )
323
331
  a2a_project: str | None = Field(default=None, alias="A2A_PROJECT")
324
332
  a2a_title: str = Field(default="Codex A2A", alias="A2A_TITLE")
325
333
  a2a_description: str = Field(default="A2A wrapper service for Codex", alias="A2A_DESCRIPTION")
@@ -330,6 +338,10 @@ class Settings(BaseSettings):
330
338
  a2a_enable_turn_control: bool = Field(default=True, alias="A2A_ENABLE_TURN_CONTROL")
331
339
  a2a_enable_review_control: bool = Field(default=False, alias="A2A_ENABLE_REVIEW_CONTROL")
332
340
  a2a_enable_exec_control: bool = Field(default=False, alias="A2A_ENABLE_EXEC_CONTROL")
341
+ a2a_expose_workspace_root_in_card: bool = Field(
342
+ default=False,
343
+ alias="A2A_EXPOSE_WORKSPACE_ROOT_IN_CARD",
344
+ )
333
345
  a2a_log_level: str = Field(default="WARNING", alias="A2A_LOG_LEVEL")
334
346
  a2a_log_payloads: bool = Field(default=False, alias="A2A_LOG_PAYLOADS")
335
347
  a2a_log_body_limit: int = Field(default=0, alias="A2A_LOG_BODY_LIMIT")
@@ -341,6 +353,27 @@ class Settings(BaseSettings):
341
353
  default=32,
342
354
  alias="A2A_MAX_CONCURRENT_OPERATIONS",
343
355
  )
356
+ a2a_rate_limit_enabled: bool = Field(default=True, alias="A2A_RATE_LIMIT_ENABLED")
357
+ a2a_rate_limit_window_seconds: float = Field(
358
+ default=60.0,
359
+ alias="A2A_RATE_LIMIT_WINDOW_SECONDS",
360
+ )
361
+ a2a_rate_limit_max_requests: int = Field(
362
+ default=120,
363
+ alias="A2A_RATE_LIMIT_MAX_REQUESTS",
364
+ )
365
+ a2a_stream_max_bytes: int = Field(
366
+ default=64 * 1024 * 1024,
367
+ alias="A2A_STREAM_MAX_BYTES",
368
+ )
369
+ a2a_stream_max_duration_seconds: float = Field(
370
+ default=3600.0,
371
+ alias="A2A_STREAM_MAX_DURATION_SECONDS",
372
+ )
373
+ a2a_stream_idle_timeout_seconds: float = Field(
374
+ default=120.0,
375
+ alias="A2A_STREAM_IDLE_TIMEOUT_SECONDS",
376
+ )
344
377
  a2a_documentation_url: str | None = Field(default=None, alias="A2A_DOCUMENTATION_URL")
345
378
  a2a_allow_directory_override: bool = Field(default=True, alias="A2A_ALLOW_DIRECTORY_OVERRIDE")
346
379
  a2a_host: str = Field(default="127.0.0.1", alias="A2A_HOST")
@@ -386,6 +419,14 @@ class Settings(BaseSettings):
386
419
  default_factory=lambda: ["JSONRPC", "HTTP+JSON"],
387
420
  alias="A2A_CLIENT_SUPPORTED_TRANSPORTS",
388
421
  )
422
+ a2a_client_allowed_hosts: Annotated[list[str], NoDecode] = Field(
423
+ default_factory=list,
424
+ alias="A2A_CLIENT_ALLOWED_HOSTS",
425
+ )
426
+ a2a_client_allow_private_hosts: bool = Field(
427
+ default=False,
428
+ alias="A2A_CLIENT_ALLOW_PRIVATE_HOSTS",
429
+ )
389
430
  a2a_interrupt_request_ttl_seconds: int = Field(
390
431
  default=3600,
391
432
  alias="A2A_INTERRUPT_REQUEST_TTL_SECONDS",
@@ -448,6 +489,31 @@ class Settings(BaseSettings):
448
489
  raise ValueError("A2A_STREAM_IDLE_DIAGNOSTIC_SECONDS must be > 0")
449
490
  return value
450
491
 
492
+ @field_validator("a2a_rate_limit_window_seconds")
493
+ @classmethod
494
+ def validate_rate_limit_window_seconds(cls, value: float) -> float:
495
+ if value <= 0:
496
+ raise ValueError("A2A_RATE_LIMIT_WINDOW_SECONDS must be > 0")
497
+ return value
498
+
499
+ @field_validator("a2a_rate_limit_max_requests")
500
+ @classmethod
501
+ def validate_rate_limit_max_requests(cls, value: int) -> int:
502
+ if value <= 0:
503
+ raise ValueError("A2A_RATE_LIMIT_MAX_REQUESTS must be > 0")
504
+ return value
505
+
506
+ @field_validator(
507
+ "a2a_stream_max_bytes",
508
+ "a2a_stream_max_duration_seconds",
509
+ "a2a_stream_idle_timeout_seconds",
510
+ )
511
+ @classmethod
512
+ def validate_stream_budget_values(cls, value: int | float) -> int | float:
513
+ if value < 0:
514
+ raise ValueError("A2A stream budgets must be >= 0")
515
+ return value
516
+
451
517
  @field_validator("a2a_interrupt_request_ttl_seconds")
452
518
  @classmethod
453
519
  def validate_interrupt_request_ttl_seconds(cls, value: int) -> int:
@@ -459,6 +525,9 @@ class Settings(BaseSettings):
459
525
  "codex_sandbox_workspace_write_writable_roots",
460
526
  "a2a_execution_sandbox_writable_roots",
461
527
  "a2a_execution_network_allowed_domains",
528
+ "a2a_client_allowed_hosts",
529
+ "a2a_allowed_origins",
530
+ "a2a_allowed_hosts",
462
531
  mode="before",
463
532
  )
464
533
  @classmethod