agentmetry 0.9.4__tar.gz → 0.9.5__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 (287) hide show
  1. {agentmetry-0.9.4 → agentmetry-0.9.5}/.gitignore +8 -0
  2. {agentmetry-0.9.4 → agentmetry-0.9.5}/PKG-INFO +2 -2
  3. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/atlas.py +1 -1
  4. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/canonical.py +1 -1
  5. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/version.py +1 -1
  6. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/hooks/ingest.py +47 -1
  7. {agentmetry-0.9.4 → agentmetry-0.9.5}/pyproject.toml +1 -1
  8. agentmetry-0.9.5/tests/fixtures/codex_hooks/README.md +10 -0
  9. agentmetry-0.9.5/tests/fixtures/codex_hooks/permission-request.command.output.schema.json +91 -0
  10. agentmetry-0.9.5/tests/fixtures/codex_hooks/pre-tool-use.command.output.schema.json +93 -0
  11. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_atlas_detection_enrichment.py +1 -1
  12. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_hook_decision_format.py +95 -0
  13. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_readme_claims.py +40 -0
  14. {agentmetry-0.9.4 → agentmetry-0.9.5}/tools/generate_sigma_pack.py +3 -3
  15. {agentmetry-0.9.4 → agentmetry-0.9.5}/.dockerignore +0 -0
  16. {agentmetry-0.9.4 → agentmetry-0.9.5}/.env.agentmetry-demo +0 -0
  17. {agentmetry-0.9.4 → agentmetry-0.9.5}/.env.example +0 -0
  18. {agentmetry-0.9.4 → agentmetry-0.9.5}/Dockerfile +0 -0
  19. {agentmetry-0.9.4 → agentmetry-0.9.5}/README.md +0 -0
  20. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/__init__.py +0 -0
  21. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/api/__init__.py +0 -0
  22. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/api/main.py +0 -0
  23. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/api/routes/__init__.py +0 -0
  24. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/api/routes/audit.py +0 -0
  25. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/api/routes/auth.py +0 -0
  26. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/api/trusted_host.py +0 -0
  27. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/api/websocket.py +0 -0
  28. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/api/ws_bridge.py +0 -0
  29. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/cli/__init__.py +0 -0
  30. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/cli/__main__.py +0 -0
  31. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/__init__.py +0 -0
  32. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/api_token.py +0 -0
  33. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/__init__.py +0 -0
  34. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/adapters/__init__.py +0 -0
  35. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/adapters/agt.py +0 -0
  36. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/adapters/chronicle.py +0 -0
  37. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/adapters/cloudevents.py +0 -0
  38. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/adapters/ecs.py +0 -0
  39. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/adapters/sentinel.py +0 -0
  40. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/adapters/splunk.py +0 -0
  41. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/alerts.py +0 -0
  42. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/compliance_digest.py +0 -0
  43. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/__init__.py +0 -0
  44. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/benchmark.py +0 -0
  45. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
  46. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
  47. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
  48. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
  49. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
  50. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
  51. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
  52. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
  53. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
  54. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
  55. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
  56. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
  57. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
  58. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
  59. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
  60. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
  61. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +0 -0
  62. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
  63. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
  64. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +0 -0
  65. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
  66. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
  67. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
  68. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
  69. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
  70. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
  71. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
  72. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
  73. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +0 -0
  74. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
  75. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
  76. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
  77. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_env_example_read.jsonl +0 -0
  78. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
  79. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
  80. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
  81. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_fetch_piped_into_own_script.jsonl +0 -0
  82. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
  83. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_github_raw_docs_then_script.jsonl +0 -0
  84. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
  85. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
  86. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
  87. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
  88. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
  89. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_mcp_text_mentions_credential_path.jsonl +0 -0
  90. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
  91. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
  92. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
  93. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
  94. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
  95. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
  96. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +0 -0
  97. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
  98. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
  99. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/corpus/corpus.yaml +0 -0
  100. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/disposition.py +0 -0
  101. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/engine.py +0 -0
  102. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/live.py +0 -0
  103. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/live_store.py +0 -0
  104. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/models.py +0 -0
  105. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/rules.py +0 -0
  106. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/traits.py +0 -0
  107. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/yaml_config.py +0 -0
  108. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/detection/yaml_rules.py +0 -0
  109. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/dlp/__init__.py +0 -0
  110. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/dlp/loader.py +0 -0
  111. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/dlp/models.py +0 -0
  112. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/dlp/scanner.py +0 -0
  113. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/dogfood.py +0 -0
  114. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/evidence_pack.py +0 -0
  115. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/external.py +0 -0
  116. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/forwarder.py +0 -0
  117. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/hashing.py +0 -0
  118. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/heartbeat.py +0 -0
  119. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/hook_bootstrap.py +0 -0
  120. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/identity.py +0 -0
  121. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/ingest.py +0 -0
  122. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/managed_hooks.py +0 -0
  123. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/mcp_inventory_event.py +0 -0
  124. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/migrate.py +0 -0
  125. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/mitre.py +0 -0
  126. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/otel_ingest.py +0 -0
  127. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/policy.py +0 -0
  128. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/policy_settings.py +0 -0
  129. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/redaction.py +0 -0
  130. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/replay.py +0 -0
  131. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/run_context.py +0 -0
  132. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/signing.py +0 -0
  133. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/sinks.py +0 -0
  134. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/spool.py +0 -0
  135. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
  136. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/tool_policy/capability.py +0 -0
  137. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/tool_policy/evaluator.py +0 -0
  138. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/tool_policy/loader.py +0 -0
  139. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/tool_policy/models.py +0 -0
  140. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/trail_anchor.py +0 -0
  141. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/trail_chain.py +0 -0
  142. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/trail_db.py +0 -0
  143. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/trail_merkle.py +0 -0
  144. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/audit/trail_rotation.py +0 -0
  145. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/auth.py +0 -0
  146. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/bus/__init__.py +0 -0
  147. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/bus/audit_exporter.py +0 -0
  148. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/bus/bridges.py +0 -0
  149. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/bus/bus.py +0 -0
  150. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/bus/events.py +0 -0
  151. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/bus/outbox.py +0 -0
  152. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/config.py +0 -0
  153. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/dashboard_session.py +0 -0
  154. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/diagnostics/__init__.py +0 -0
  155. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/diagnostics/autostart.py +0 -0
  156. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/diagnostics/doctor.py +0 -0
  157. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/diagnostics/driver_paths.py +0 -0
  158. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/diagnostics/env_file.py +0 -0
  159. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/diagnostics/hook_coverage.py +0 -0
  160. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/diagnostics/mcp_inventory.py +0 -0
  161. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/diagnostics/mcp_schema.py +0 -0
  162. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/drivers/__init__.py +0 -0
  163. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/drivers/host.py +0 -0
  164. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/drivers/permissions.py +0 -0
  165. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/drivers/spec.py +0 -0
  166. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/extensions.py +0 -0
  167. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/health.py +0 -0
  168. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/operator_identity.py +0 -0
  169. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/core/paths.py +0 -0
  170. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/demo.py +0 -0
  171. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/hooks/__init__.py +0 -0
  172. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/hooks/mcp_proxy.py +0 -0
  173. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/policies/detection/manifest.yaml +0 -0
  174. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/policies/dlp/manifest.yaml +0 -0
  175. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/policies/opa/agent_rules.rego +0 -0
  176. {agentmetry-0.9.4 → agentmetry-0.9.5}/agentmetry/policies/tool/manifest.yaml +0 -0
  177. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/conftest.py +0 -0
  178. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
  179. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
  180. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/fixtures/fake_mcp_server.py +0 -0
  181. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/fixtures/otel_claude_code_session.json +0 -0
  182. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_agentmetry_audit.py +0 -0
  183. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_agentmetry_ingest_client.py +0 -0
  184. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_agt_adapter.py +0 -0
  185. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_ask_gate_phase_a.py +0 -0
  186. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_atlas_mapping.py +0 -0
  187. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_audit_sinks.py +0 -0
  188. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_audit_stats.py +0 -0
  189. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_audit_tail.py +0 -0
  190. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_auth.py +0 -0
  191. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_auth_default.py +0 -0
  192. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_autostart.py +0 -0
  193. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_boot_sequence.py +0 -0
  194. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_burst_windows.py +0 -0
  195. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_chinese_agent_hooks.py +0 -0
  196. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_chinese_agent_sprint_b.py +0 -0
  197. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_chinese_agent_sprint_c.py +0 -0
  198. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_chronicle_adapter.py +0 -0
  199. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_cli_backup.py +0 -0
  200. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_cli_commands.py +0 -0
  201. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_cli_env_var_names.py +0 -0
  202. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_cloudevents_adapter.py +0 -0
  203. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_compliance_digest.py +0 -0
  204. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_container_and_legacy_runtime.py +0 -0
  205. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_crewai_adapter.py +0 -0
  206. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_demo.py +0 -0
  207. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_adi.py +0 -0
  208. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_benchmark.py +0 -0
  209. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_default_config.py +0 -0
  210. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_disposition.py +0 -0
  211. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_disposition_api.py +0 -0
  212. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_duplicate_emission.py +0 -0
  213. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_emit_durability.py +0 -0
  214. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_engine.py +0 -0
  215. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_evasion.py +0 -0
  216. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_hf_incident.py +0 -0
  217. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_quoted_command_words.py +0 -0
  218. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_rule_identity.py +0 -0
  219. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_detection_rules_v2.py +0 -0
  220. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_dlp_agent_env_override.py +0 -0
  221. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_dlp_invisible_unicode.py +0 -0
  222. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_dlp_markdown_exfil.py +0 -0
  223. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_dlp_scanner.py +0 -0
  224. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_doctor.py +0 -0
  225. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_dogfood.py +0 -0
  226. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_dogfood_freeze.py +0 -0
  227. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_driver_paths.py +0 -0
  228. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_ecs_threat.py +0 -0
  229. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_event_bus.py +0 -0
  230. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_event_signing.py +0 -0
  231. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_evidence_pack.py +0 -0
  232. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_extensions.py +0 -0
  233. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_external_ingest.py +0 -0
  234. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_forwarder.py +0 -0
  235. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_forwarding_shape.py +0 -0
  236. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_funding_manifest.py +0 -0
  237. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_hash_key.py +0 -0
  238. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_heartbeat.py +0 -0
  239. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_hook_bootstrap.py +0 -0
  240. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_hook_coverage.py +0 -0
  241. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_hook_enforcement_timing.py +0 -0
  242. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_hook_import_cost.py +0 -0
  243. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_hook_real_process.py +0 -0
  244. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_hook_spool.py +0 -0
  245. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_identity_enrich.py +0 -0
  246. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_identity_provider.py +0 -0
  247. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_ingest_roundtrip.py +0 -0
  248. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_isolation.py +0 -0
  249. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_launch_targets.py +0 -0
  250. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_live_detection.py +0 -0
  251. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_live_detection_store.py +0 -0
  252. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_managed_hooks.py +0 -0
  253. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_mcp_audit_proxy.py +0 -0
  254. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_mcp_concealed_text.py +0 -0
  255. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_mcp_inventory.py +0 -0
  256. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_mcp_inventory_event.py +0 -0
  257. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_mcp_meta_fingerprint.py +0 -0
  258. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_mcp_proxy_packaged.py +0 -0
  259. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_mcp_schema.py +0 -0
  260. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_mitre_dlp.py +0 -0
  261. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_opensre_adapter.py +0 -0
  262. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_operator_attribution.py +0 -0
  263. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_otel_ingest.py +0 -0
  264. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_paths.py +0 -0
  265. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_powershell_encoding.py +0 -0
  266. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_redaction.py +0 -0
  267. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_replay.py +0 -0
  268. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_sentinel.py +0 -0
  269. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_serve_script_binds_loopback.py +0 -0
  270. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_shared_install.py +0 -0
  271. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_sigma_pack.py +0 -0
  272. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_static_serving.py +0 -0
  273. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_subagent_lifecycle.py +0 -0
  274. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_suite_leaves_real_home_alone.py +0 -0
  275. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_tool_policy.py +0 -0
  276. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_tool_policy_agent_cli.py +0 -0
  277. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_tool_policy_git_hooks.py +0 -0
  278. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_trail_anchor.py +0 -0
  279. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_trail_chain.py +0 -0
  280. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_trail_concurrency.py +0 -0
  281. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_trail_merkle.py +0 -0
  282. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_trusted_host.py +0 -0
  283. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_unattended_agent_policy.py +0 -0
  284. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_version.py +0 -0
  285. {agentmetry-0.9.4 → agentmetry-0.9.5}/tests/test_yaml_detection_rules.py +0 -0
  286. {agentmetry-0.9.4 → agentmetry-0.9.5}/tools/mcp_audit_proxy.py +0 -0
  287. {agentmetry-0.9.4 → agentmetry-0.9.5}/tools/vault_fs_server.py +0 -0
@@ -100,3 +100,11 @@ docs/*-audit-*.md
100
100
  # pytest-cov data file
101
101
  .coverage
102
102
  .coverage.*
103
+
104
+ # Demo video renders and their working files (scripts/make_demo_video.py).
105
+ # The site serves its own copy from ai-audit-watch/public/video/.
106
+ docs/assets/*.mp4
107
+ docs/assets/agentmetry-demo-poster.png
108
+ docs/assets/agentmetry-linkedin-*-poster.png
109
+ docs/assets/voice/
110
+ docs/assets/demo-stills/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentmetry
3
- Version: 0.9.4
3
+ Version: 0.9.5
4
4
  Summary: Local-first flight recorder for AI coding agents: hash-chained audit trail, MITRE-mapped sequence detection, DLP
5
5
  Project-URL: Homepage, https://agentmetry.ai
6
6
  Project-URL: Source, https://github.com/blitzcrieg1/agentmetry
@@ -37,7 +37,7 @@ Provides-Extra: dev
37
37
  Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
38
38
  Requires-Dist: pytest-cov>=6.0; extra == 'dev'
39
39
  Requires-Dist: pytest>=8.0; extra == 'dev'
40
- Requires-Dist: ruff==0.16.9; extra == 'dev'
40
+ Requires-Dist: ruff==0.16.10; extra == 'dev'
41
41
  Description-Content-Type: text/markdown
42
42
 
43
43
  # Agentmetry
@@ -170,7 +170,7 @@ def attach_atlas(tool: Any) -> None:
170
170
  # adversary technique a whole sequence is evidence of, which is a claim only a
171
171
  # rule can make and never a single call.
172
172
  #
173
- # Deliberately small. Of the fifteen built-in rules, one describes an ATLAS
173
+ # Deliberately small. Of the fourteen published rules, one describes an ATLAS
174
174
  # technique; the rest are host and operations behaviour that ATT&CK already
175
175
  # covers, and tagging them would put an AI-threat label on a shell command.
176
176
  # The empty entries are the design, not a backlog.
@@ -1,4 +1,4 @@
1
- """Map durable outbox rows to Agentmetry canonical events (schema v1.1.0)."""
1
+ """Map durable outbox rows to Agentmetry canonical events (schema v1.2.0)."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -10,4 +10,4 @@ Bump this here and add the matching CHANGELOG section in the same commit.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
- __version__ = "0.9.4"
13
+ __version__ = "0.9.5"
@@ -1489,7 +1489,7 @@ def map_hook(hook_name: str, data: dict[str, Any]) -> dict[str, Any] | None:
1489
1489
 
1490
1490
 
1491
1491
  def _emit_hook_stdout(hook_name: str) -> None:
1492
- """Antigravity requires JSON on stdout; Cursor/Codex use permission when enforcing."""
1492
+ """Antigravity requires JSON on stdout; the rest print a decision only when enforcing."""
1493
1493
  source = _source_app()
1494
1494
  enforce = os.environ.get("AGENTMETRY_ENFORCE", "").strip().lower()
1495
1495
 
@@ -1546,6 +1546,49 @@ def _is_blocking_hook(hook_name: str, data: dict[str, Any]) -> bool:
1546
1546
  #: the vendor's published schema; the rest inherit it.
1547
1547
  _CLAUDE_PROTOCOL = frozenset({"claude", "qwen", "qoder", "codebuddy", "kimi"})
1548
1548
 
1549
+ #: Codex's deny reason must be non-empty, or Codex rejects the whole output.
1550
+ _CODEX_DEFAULT_REASON = "Blocked by Agentmetry policy"
1551
+
1552
+
1553
+ def _codex_decision_output(hook_name: str, decision: str, reason: str) -> dict[str, Any]:
1554
+ """Codex's hook schema, which is Claude-shaped and strict where Claude is not.
1555
+
1556
+ Checked against Codex's parser (openai/codex `codex-rs/hooks`, 806d2d1,
1557
+ 2026-10-08), not only its docs, because every way this goes wrong fails
1558
+ open. Codex parses hook stdout with `deny_unknown_fields`. Output it cannot
1559
+ parse, or parses and rejects, marks the hook run `Failed` and the tool runs.
1560
+ Until this fix Codex was sent Cursor's `{"permission": "deny"}`, which is an
1561
+ unknown field, so every Agentmetry deny on Codex ran the tool while the
1562
+ trail recorded `outcome: denied`.
1563
+
1564
+ The other rejections, each also a silent allow:
1565
+
1566
+ * `permissionDecision: "deny"` without a non-empty reason.
1567
+ * `permissionDecision: "ask"`, "parsed but not supported yet".
1568
+ * `permissionDecision: "allow"` without `updatedInput`.
1569
+
1570
+ So on PreToolUse a deny always carries a reason, an ask becomes a deny, and
1571
+ an allow is an empty object, which Codex reads as no objection. On
1572
+ PermissionRequest the behaviour is allow|deny, and an ask becomes a deny.
1573
+ """
1574
+ reason = reason.strip() or _CODEX_DEFAULT_REASON
1575
+ if decision != "allow":
1576
+ decision = "deny"
1577
+ if hook_name == "PermissionRequest":
1578
+ verdict: dict[str, Any] = {"behavior": decision}
1579
+ if decision == "deny":
1580
+ verdict["message"] = reason
1581
+ return {"hookSpecificOutput": {"hookEventName": hook_name, "decision": verdict}}
1582
+ if decision == "allow":
1583
+ return {}
1584
+ return {
1585
+ "hookSpecificOutput": {
1586
+ "hookEventName": hook_name,
1587
+ "permissionDecision": "deny",
1588
+ "permissionDecisionReason": reason,
1589
+ }
1590
+ }
1591
+
1549
1592
 
1550
1593
  def _decision_output(hook_name: str, decision: str, reason: str = "") -> dict[str, Any]:
1551
1594
  """A hook decision in the exact shape the calling agent honours.
@@ -1568,10 +1611,13 @@ def _decision_output(hook_name: str, decision: str, reason: str = "") -> dict[st
1568
1611
  The safe direction, never the other way.
1569
1612
  * Cursor: top-level `permission`. Unchanged, because it already worked.
1570
1613
  * Antigravity: top-level `decision`.
1614
+ * Codex: Claude-shaped, but strict. See `_codex_decision_output`.
1571
1615
  """
1572
1616
  source = _source_app()
1573
1617
  if source == "antigravity":
1574
1618
  return {"decision": decision}
1619
+ if source == "codex":
1620
+ return _codex_decision_output(hook_name, decision, reason)
1575
1621
  if source in _CLAUDE_PROTOCOL:
1576
1622
  if hook_name == "PermissionRequest":
1577
1623
  behaviour = "allow" if decision == "allow" else "deny"
@@ -52,7 +52,7 @@ dependencies = [
52
52
  # so a linter release could turn a green branch red with no change to this repo.
53
53
  # It did: 0.16.0 widened the default rule set and CI reported 132 errors against
54
54
  # code that passed locally on 0.15.x. Bump this deliberately, not by accident.
55
- dev = ["pytest>=8.0", "pytest-asyncio>=0.24", "pytest-cov>=6.0", "ruff==0.16.9"]
55
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.24", "pytest-cov>=6.0", "ruff==0.16.10"]
56
56
 
57
57
  [project.urls]
58
58
  Homepage = "https://agentmetry.ai"
@@ -0,0 +1,10 @@
1
+ # Codex hook output schemas
2
+
3
+ Copied unchanged from [openai/codex](https://github.com/openai/codex) (Apache-2.0),
4
+ `codex-rs/hooks/schema/generated/`, at commit
5
+ `806d2d1cc194548170a606946aa6c68e99927e58` (2026-10-08).
6
+
7
+ `tests/test_hook_decision_format.py` validates every decision the hook prints
8
+ for Codex against these, because Codex parses hook output with
9
+ `deny_unknown_fields` and runs the tool when it cannot. When Codex changes its
10
+ schema, refresh both files from the same path and rerun that test.
@@ -0,0 +1,91 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "additionalProperties": false,
4
+ "definitions": {
5
+ "PermissionRequestBehaviorWire": {
6
+ "enum": [
7
+ "allow",
8
+ "deny"
9
+ ],
10
+ "type": "string"
11
+ },
12
+ "PermissionRequestDecisionWire": {
13
+ "additionalProperties": false,
14
+ "properties": {
15
+ "behavior": {
16
+ "$ref": "#/definitions/PermissionRequestBehaviorWire"
17
+ },
18
+ "interrupt": {
19
+ "default": false,
20
+ "description": "Reserved for future short-circuiting semantics.\n\nPermissionRequest hooks currently fail closed if this field is `true`.",
21
+ "type": "boolean"
22
+ },
23
+ "message": {
24
+ "default": null,
25
+ "type": "string"
26
+ },
27
+ "updatedInput": {
28
+ "default": null,
29
+ "description": "Reserved for a future input-rewrite capability.\n\nPermissionRequest hooks currently fail closed if this field is present."
30
+ },
31
+ "updatedPermissions": {
32
+ "default": null,
33
+ "description": "Reserved for a future permission-rewrite capability.\n\nPermissionRequest hooks currently fail closed if this field is present."
34
+ }
35
+ },
36
+ "required": [
37
+ "behavior"
38
+ ],
39
+ "type": "object"
40
+ },
41
+ "PermissionRequestHookSpecificOutputWire": {
42
+ "additionalProperties": false,
43
+ "properties": {
44
+ "decision": {
45
+ "allOf": [
46
+ {
47
+ "$ref": "#/definitions/PermissionRequestDecisionWire"
48
+ }
49
+ ],
50
+ "default": null
51
+ },
52
+ "hookEventName": {
53
+ "const": "PermissionRequest",
54
+ "type": "string"
55
+ }
56
+ },
57
+ "required": [
58
+ "hookEventName"
59
+ ],
60
+ "type": "object"
61
+ }
62
+ },
63
+ "properties": {
64
+ "continue": {
65
+ "default": true,
66
+ "type": "boolean"
67
+ },
68
+ "hookSpecificOutput": {
69
+ "allOf": [
70
+ {
71
+ "$ref": "#/definitions/PermissionRequestHookSpecificOutputWire"
72
+ }
73
+ ],
74
+ "default": null
75
+ },
76
+ "stopReason": {
77
+ "default": null,
78
+ "type": "string"
79
+ },
80
+ "suppressOutput": {
81
+ "default": false,
82
+ "type": "boolean"
83
+ },
84
+ "systemMessage": {
85
+ "default": null,
86
+ "type": "string"
87
+ }
88
+ },
89
+ "title": "permission-request.command.output",
90
+ "type": "object"
91
+ }
@@ -0,0 +1,93 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "additionalProperties": false,
4
+ "definitions": {
5
+ "PreToolUseDecisionWire": {
6
+ "enum": [
7
+ "approve",
8
+ "block"
9
+ ],
10
+ "type": "string"
11
+ },
12
+ "PreToolUseHookSpecificOutputWire": {
13
+ "additionalProperties": false,
14
+ "properties": {
15
+ "additionalContext": {
16
+ "default": null,
17
+ "type": "string"
18
+ },
19
+ "hookEventName": {
20
+ "const": "PreToolUse",
21
+ "type": "string"
22
+ },
23
+ "permissionDecision": {
24
+ "allOf": [
25
+ {
26
+ "$ref": "#/definitions/PreToolUsePermissionDecisionWire"
27
+ }
28
+ ],
29
+ "default": null
30
+ },
31
+ "permissionDecisionReason": {
32
+ "default": null,
33
+ "type": "string"
34
+ },
35
+ "updatedInput": {
36
+ "default": null
37
+ }
38
+ },
39
+ "required": [
40
+ "hookEventName"
41
+ ],
42
+ "type": "object"
43
+ },
44
+ "PreToolUsePermissionDecisionWire": {
45
+ "enum": [
46
+ "allow",
47
+ "deny",
48
+ "ask"
49
+ ],
50
+ "type": "string"
51
+ }
52
+ },
53
+ "properties": {
54
+ "continue": {
55
+ "default": true,
56
+ "type": "boolean"
57
+ },
58
+ "decision": {
59
+ "allOf": [
60
+ {
61
+ "$ref": "#/definitions/PreToolUseDecisionWire"
62
+ }
63
+ ],
64
+ "default": null
65
+ },
66
+ "hookSpecificOutput": {
67
+ "allOf": [
68
+ {
69
+ "$ref": "#/definitions/PreToolUseHookSpecificOutputWire"
70
+ }
71
+ ],
72
+ "default": null
73
+ },
74
+ "reason": {
75
+ "default": null,
76
+ "type": "string"
77
+ },
78
+ "stopReason": {
79
+ "default": null,
80
+ "type": "string"
81
+ },
82
+ "suppressOutput": {
83
+ "default": false,
84
+ "type": "boolean"
85
+ },
86
+ "systemMessage": {
87
+ "default": null,
88
+ "type": "string"
89
+ }
90
+ },
91
+ "title": "pre-tool-use.command.output",
92
+ "type": "object"
93
+ }
@@ -4,7 +4,7 @@ ATT&CK describes what one tool call did. ATLAS at the detection level describes
4
4
  the adversary technique the sequence is evidence of, which is a claim only a
5
5
  rule is in a position to make.
6
6
 
7
- The mapping is deliberately one rule wide. Of the fifteen built-in rules, one
7
+ The mapping is deliberately one rule wide. Of the fourteen published rules, one
8
8
  describes an ATLAS technique; the rest are host and operations behaviour that
9
9
  ATT&CK already covers, and labelling those would put an AI-threat technique on
10
10
  a shell command. Most of this file exists to hold that line, because the
@@ -19,6 +19,10 @@ Schemas, from each vendor's hook documentation:
19
19
  Claude Code PermissionRequest hookSpecificOutput.decision.behavior allow|deny, no ask
20
20
  Cursor top-level permission allow|deny|ask
21
21
  Antigravity top-level decision
22
+ Codex Claude-shaped, strict; see the Codex section
23
+
24
+ Codex was the one agent the 0.9.0 fix missed. It kept receiving Cursor's key,
25
+ and its parser rejects unknown fields, so every deny ran the tool.
22
26
  """
23
27
 
24
28
  from __future__ import annotations
@@ -131,6 +135,97 @@ def test_antigravity_output_is_unchanged(monkeypatch):
131
135
  assert _decision(monkeypatch, "antigravity", "PreToolUse", "deny") == {"decision": "deny"}
132
136
 
133
137
 
138
+ # --- Codex: checked against its own schema and parser rules ------------------
139
+ #
140
+ # Codex parses hook stdout with `deny_unknown_fields` (openai/codex
141
+ # codex-rs/hooks, 806d2d1). Anything it cannot parse, or parses and rejects,
142
+ # marks the hook `Failed` and the tool runs. So a shape check is not enough:
143
+ # these validate against Codex's published schema, then apply the parser's
144
+ # rejections that the schema cannot express.
145
+
146
+ _CODEX_SCHEMAS = Path(__file__).parent / "fixtures" / "codex_hooks"
147
+
148
+
149
+ def _codex_blocks(hook: str, out: dict) -> bool:
150
+ """What Codex does with this output: True blocks, False runs the tool.
151
+
152
+ Raises if Codex would mark the hook `Failed`, which also runs the tool, but
153
+ is a malformed decision rather than a deliberate allow.
154
+ """
155
+ import jsonschema
156
+
157
+ name = {"PreToolUse": "pre-tool-use", "PermissionRequest": "permission-request"}[hook]
158
+ schema = json.loads((_CODEX_SCHEMAS / f"{name}.command.output.schema.json").read_text())
159
+ jsonschema.validate(out, schema)
160
+ specific = out.get("hookSpecificOutput") or {}
161
+ if hook == "PermissionRequest":
162
+ return (specific.get("decision") or {}).get("behavior") == "deny"
163
+ decision = specific.get("permissionDecision")
164
+ if decision == "ask":
165
+ raise AssertionError("Codex rejects permissionDecision:ask")
166
+ if decision == "allow" and "updatedInput" not in specific:
167
+ raise AssertionError("Codex rejects permissionDecision:allow without updatedInput")
168
+ if decision == "deny":
169
+ if not str(specific.get("permissionDecisionReason") or "").strip():
170
+ raise AssertionError("Codex rejects a deny without a non-empty reason")
171
+ return True
172
+ return False
173
+
174
+
175
+ def test_codex_is_never_sent_the_cursor_key(monkeypatch):
176
+ """The defect: Codex rejected `permission` as an unknown field and ran the tool."""
177
+ import jsonschema
178
+
179
+ old = {"permission": "deny"}
180
+ schema = json.loads((_CODEX_SCHEMAS / "pre-tool-use.command.output.schema.json").read_text())
181
+ with pytest.raises(jsonschema.ValidationError):
182
+ jsonschema.validate(old, schema)
183
+ for hook in ("PreToolUse", "PermissionRequest"):
184
+ for decision in ("allow", "deny", "ask"):
185
+ assert "permission" not in _decision(monkeypatch, "codex", hook, decision), (hook, decision)
186
+
187
+
188
+ @pytest.mark.parametrize("hook", ["PreToolUse", "PermissionRequest"])
189
+ @pytest.mark.parametrize("reason", ["dlp:aws_key", ""])
190
+ def test_codex_deny_blocks(monkeypatch, hook, reason):
191
+ """Including an empty reason, which Codex would otherwise reject as a fail-open."""
192
+ assert _codex_blocks(hook, _decision(monkeypatch, "codex", hook, "deny", reason))
193
+
194
+
195
+ @pytest.mark.parametrize("hook", ["PreToolUse", "PermissionRequest"])
196
+ def test_codex_ask_becomes_deny_never_allow(monkeypatch, hook):
197
+ """Codex marks an ask `Failed` and runs the tool, so an ask has to be a deny."""
198
+ assert _codex_blocks(hook, _decision(monkeypatch, "codex", hook, "ask", "tool_policy:x"))
199
+
200
+
201
+ @pytest.mark.parametrize("hook", ["PreToolUse", "PermissionRequest"])
202
+ def test_codex_allow_is_a_clean_allow(monkeypatch, hook):
203
+ assert not _codex_blocks(hook, _decision(monkeypatch, "codex", hook, "allow"))
204
+
205
+
206
+ def test_codex_deny_keeps_the_rule_in_the_reason(monkeypatch):
207
+ out = _decision(monkeypatch, "codex", "PreToolUse", "deny", "tool_policy:block_shell_rm")
208
+ assert out["hookSpecificOutput"]["permissionDecisionReason"] == "tool_policy:block_shell_rm"
209
+ out = _decision(monkeypatch, "codex", "PermissionRequest", "deny", "dlp:aws_key")
210
+ assert out["hookSpecificOutput"]["decision"]["message"] == "dlp:aws_key"
211
+
212
+
213
+ def test_a_real_policy_block_on_codex_blocks(monkeypatch, capsys):
214
+ """End to end through hook_main, with Codex's own Bash tool input."""
215
+ monkeypatch.setenv("AGENTMETRY_SOURCE_APP", "codex")
216
+ _block(monkeypatch, "block_shell_rm")
217
+ monkeypatch.setattr(
218
+ ingest,
219
+ "read_hook_stdin",
220
+ lambda: ({"session_id": "s1", "hook_event_name": "PreToolUse", "tool_name": "Bash",
221
+ "tool_input": {"command": "rm -rf /"}}, False),
222
+ )
223
+ ingest.hook_main("PreToolUse")
224
+ out = _last_json(capsys)
225
+ assert _codex_blocks("PreToolUse", out)
226
+ assert out["hookSpecificOutput"]["permissionDecisionReason"] == "tool_policy:block_shell_rm"
227
+
228
+
134
229
  # --- through the real hook path ---------------------------------------------
135
230
 
136
231
 
@@ -241,3 +241,43 @@ def test_readme_sigma_count_matches_the_pack():
241
241
  assert int(match.group(1)) == shipped, (
242
242
  f"README claims {match.group(1)} Sigma rules, pack ships {shipped}"
243
243
  )
244
+
245
+
246
+ def test_readme_collected_count_matches_pytest():
247
+ """The README quotes the suite size. Collection is the check.
248
+
249
+ A full `pytest -q` is too slow to pin the pass count here. The collected
250
+ total moves on the same commits, and it is what a reader can reproduce
251
+ without waiting on the suite.
252
+ """
253
+ import subprocess
254
+ import sys
255
+
256
+ match = re.search(r"(\d+) tests collected", _readme())
257
+ assert match, "README no longer states a collected test count"
258
+
259
+ root = Path(__file__).resolve().parents[1]
260
+ proc = subprocess.run(
261
+ [sys.executable, "-m", "pytest", "--collect-only", "-q", "-p", "no:cacheprovider"],
262
+ cwd=root,
263
+ capture_output=True,
264
+ text=True,
265
+ check=False,
266
+ )
267
+ combined = proc.stdout + "\n" + proc.stderr
268
+ assert proc.returncode == 0, combined
269
+ collected = re.search(r"(\d+) tests collected", combined)
270
+ assert collected, combined
271
+ assert int(match.group(1)) == int(collected.group(1)), (
272
+ f"README says {match.group(1)} tests collected; pytest collected {collected.group(1)}"
273
+ )
274
+
275
+
276
+ def test_readme_coverage_floor_matches_ci():
277
+ """The durable coverage claim is the CI floor, not last Tuesday's percentage."""
278
+ match = re.search(r"CI fails the coverage job under (\d+)%", _readme())
279
+ assert match, "README no longer states the CI coverage floor"
280
+ ci = Path(__file__).resolve().parents[3] / ".github" / "workflows" / "ci.yml"
281
+ floor = re.search(r"cov-fail-under=(\d+)", ci.read_text(encoding="utf-8"))
282
+ assert floor, "ci.yml lost cov-fail-under"
283
+ assert match.group(1) == floor.group(1)
@@ -8,12 +8,12 @@ Run from the orchestrator:
8
8
 
9
9
  The Sigma pack that already existed covers the recorder's own health: heartbeat
10
10
  silence, degraded coverage, MCP schema drift, denial bursts. Useful, and none of
11
- it is the product. The fifteen sequence detections, which are what a SOC would
11
+ it is the product. The published sequence detections, which are what a SOC would
12
12
  actually route on, had no Sigma representation at all, so a Splunk or Sentinel
13
13
  team wanting to alert on `credential-exfil` had to write the search themselves
14
14
  from a schema document.
15
15
 
16
- Hand-writing fifteen would put rule ids, severities and MITRE ids in a second
16
+ Hand-writing them would put rule ids, severities and MITRE ids in a second
17
17
  place that drifts from the first. So the metadata is harvested by replaying the
18
18
  benchmark corpus through the real engine and reading the `Detection` objects it
19
19
  emits. A severity that changes in `rules.py` changes here on the next run, and
@@ -107,7 +107,7 @@ def _stable_uuid(rule_id: str, severity: str = "") -> str:
107
107
 
108
108
  Sigma requires a UUID and consumers key on it, so running this twice must
109
109
  produce no diff. Severity joins the seed only for a split rule, which keeps
110
- the fourteen unsplit ids exactly where they are.
110
+ the unsplit ids exactly where they are.
111
111
  """
112
112
  seed = f"agentmetry.sigma.{rule_id}" + (f".{severity}" if severity else "")
113
113
  return str(uuid.UUID(bytes=hashlib.sha256(seed.encode()).digest()[:16], version=5))
File without changes
File without changes
File without changes
File without changes