agentmetry 0.9.7__tar.gz → 0.9.9__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.
- {agentmetry-0.9.7 → agentmetry-0.9.9}/PKG-INFO +4 -4
- {agentmetry-0.9.7 → agentmetry-0.9.9}/README.md +1 -1
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/main.py +33 -8
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/routes/audit.py +25 -2
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/cli/__init__.py +104 -6
- agentmetry-0.9.9/agentmetry/core/audit/antigravity_trajectory.py +81 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/cloud_bridge.py +24 -0
- agentmetry-0.9.9/agentmetry/core/audit/detection/corpus/benign_container_registry_publish.jsonl +3 -0
- agentmetry-0.9.9/agentmetry/core/audit/detection/corpus/benign_dependency_audit_network_burst.jsonl +4 -0
- agentmetry-0.9.9/agentmetry/core/audit/detection/corpus/benign_terraform_apply_with_profile.jsonl +4 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/corpus.yaml +28 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/external.py +20 -2
- agentmetry-0.9.9/agentmetry/core/audit/grok_bot_transcript.py +58 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/heartbeat.py +30 -2
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/hook_bootstrap.py +40 -8
- agentmetry-0.9.9/agentmetry/core/audit/session_tree.py +190 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/trail_db.py +76 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/config.py +11 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/doctor.py +161 -6
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/hook_coverage.py +21 -3
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/machine_view.py +2 -2
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/mcp_inventory.py +3 -2
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/posture.py +17 -6
- agentmetry-0.9.9/agentmetry/core/managed_policy.py +818 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/paths.py +18 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/version.py +1 -1
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/hooks/ingest.py +10 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/pyproject.toml +2 -2
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/conftest.py +11 -0
- agentmetry-0.9.9/tests/test_antigravity_trajectory.py +35 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_cloud_bridge.py +14 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_doctor.py +78 -0
- agentmetry-0.9.9/tests/test_doctor_json_and_remediation.py +154 -0
- agentmetry-0.9.9/tests/test_grok_bot_transcript.py +44 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_heartbeat.py +2 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_bootstrap.py +59 -0
- agentmetry-0.9.9/tests/test_managed_policy.py +518 -0
- agentmetry-0.9.9/tests/test_session_tree.py +188 -0
- agentmetry-0.9.9/tests/test_transcript_watcher.py +162 -0
- agentmetry-0.9.9/tests/test_user_profile_dir.py +138 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/.dockerignore +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/.env.agentmetry-demo +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/.env.example +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/.gitignore +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/Dockerfile +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/routes/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/routes/auth.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/trusted_host.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/websocket.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/ws_bridge.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/cli/__main__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/api_token.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/agt.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/chronicle.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/cloudevents.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/ecs.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/sentinel.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/splunk.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/alerts.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/atlas.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/canonical.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/compliance_digest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/benchmark.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_env_example_read.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_fetch_piped_into_own_script.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_github_raw_docs_then_script.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_mcp_text_mentions_credential_path.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/disposition.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/engine.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/live.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/live_store.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/models.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/rules.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/traits.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/yaml_config.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/yaml_rules.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/dlp/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/dlp/loader.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/dlp/models.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/dlp/scanner.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/dogfood.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/evidence_pack.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/forwarder.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/hashing.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/identity.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/ingest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/managed_hooks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/mcp_inventory_event.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/migrate.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/mitre.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/otel_ingest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/policy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/policy_settings.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/posture_event.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/redaction.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/replay.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/run_context.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/signing.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/sinks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/spool.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/tool_policy/capability.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/tool_policy/evaluator.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/tool_policy/loader.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/tool_policy/models.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/trail_anchor.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/trail_chain.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/trail_merkle.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/trail_rotation.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/auth.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/audit_exporter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/bridges.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/bus.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/events.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/outbox.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/dashboard_session.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/autostart.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/driver_paths.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/env_file.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/mcp_schema.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/drivers/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/drivers/host.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/drivers/permissions.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/drivers/spec.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/extensions.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/health.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/operator_identity.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/demo.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/hooks/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/hooks/mcp_proxy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/policies/detection/manifest.yaml +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/policies/dlp/manifest.yaml +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/policies/opa/agent_rules.rego +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/policies/tool/manifest.yaml +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/codex_hooks/README.md +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/codex_hooks/permission-request.command.output.schema.json +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/codex_hooks/pre-tool-use.command.output.schema.json +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/fake_mcp_server.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/otel_claude_code_session.json +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_agentmetry_audit.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_agentmetry_ingest_client.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_agt_adapter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_ask_gate_phase_a.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_atlas_detection_enrichment.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_atlas_mapping.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_audit_sinks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_audit_stats.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_audit_tail.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_auth.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_auth_default.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_autostart.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_boot_sequence.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_burst_windows.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_chinese_agent_hooks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_chinese_agent_sprint_b.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_chinese_agent_sprint_c.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_chronicle_adapter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_cli_backup.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_cli_commands.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_cli_env_var_names.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_cloudevents_adapter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_compliance_digest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_container_and_legacy_runtime.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_copilot_hooks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_crewai_adapter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_demo.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_adi.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_benchmark.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_default_config.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_disposition.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_disposition_api.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_duplicate_emission.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_emit_durability.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_engine.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_evasion.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_hf_incident.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_quoted_command_words.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_rule_identity.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_rules_v2.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dlp_agent_env_override.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dlp_invisible_unicode.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dlp_markdown_exfil.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dlp_scanner.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dogfood.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dogfood_freeze.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_driver_paths.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_ecs_threat.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_event_bus.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_event_signing.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_evidence_pack.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_extensions.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_external_ingest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_forwarder.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_forwarding_shape.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_funding_manifest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hash_key.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_coverage.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_decision_format.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_enforcement_timing.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_import_cost.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_real_process.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_spool.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_identity_enrich.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_identity_provider.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_ingest_roundtrip.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_isolation.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_launch_targets.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_live_detection.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_live_detection_store.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_machine_view.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_managed_hooks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_audit_proxy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_concealed_text.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_inventory.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_inventory_event.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_meta_fingerprint.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_proxy_packaged.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_schema.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mitre_dlp.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_opensre_adapter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_operator_attribution.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_otel_ingest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_paths.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_posture.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_powershell_encoding.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_readme_claims.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_redaction.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_replay.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_sentinel.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_serve_script_binds_loopback.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_shared_install.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_sigma_pack.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_static_serving.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_subagent_lifecycle.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_suite_leaves_real_home_alone.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_tool_policy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_tool_policy_agent_cli.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_tool_policy_git_hooks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_trail_anchor.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_trail_chain.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_trail_concurrency.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_trail_merkle.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_trusted_host.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_unattended_agent_policy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_version.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_yaml_detection_rules.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tools/generate_sigma_pack.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tools/mcp_audit_proxy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.9}/tools/vault_fs_server.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agentmetry
|
|
3
|
-
Version: 0.9.
|
|
4
|
-
Summary: Local-first
|
|
3
|
+
Version: 0.9.9
|
|
4
|
+
Summary: Local-first endpoint sensor 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
|
|
7
7
|
Project-URL: Issues, https://github.com/blitzcrieg1/agentmetry/issues
|
|
@@ -37,12 +37,12 @@ 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.
|
|
40
|
+
Requires-Dist: ruff==0.17.1; extra == 'dev'
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
42
42
|
|
|
43
43
|
# Agentmetry
|
|
44
44
|
|
|
45
|
-
A local-first
|
|
45
|
+
A local-first endpoint sensor for AI coding agents.
|
|
46
46
|
|
|
47
47
|
Agentmetry hooks the tool lifecycle of Claude Code, Cursor, Codex and Antigravity,
|
|
48
48
|
writes every tool call, approval and denial to a hash-chained JSONL trail on your
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agentmetry
|
|
2
2
|
|
|
3
|
-
A local-first
|
|
3
|
+
A local-first endpoint sensor for AI coding agents.
|
|
4
4
|
|
|
5
5
|
Agentmetry hooks the tool lifecycle of Claude Code, Cursor, Codex and Antigravity,
|
|
6
6
|
writes every tool call, approval and denial to a hash-chained JSONL trail on your
|
|
@@ -2,6 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
5
8
|
from contextlib import asynccontextmanager
|
|
6
9
|
from logging.handlers import RotatingFileHandler
|
|
7
10
|
from pathlib import Path
|
|
@@ -76,6 +79,34 @@ def _start_forwarders() -> list[asyncio.Task]:
|
|
|
76
79
|
return tasks
|
|
77
80
|
|
|
78
81
|
|
|
82
|
+
def start_transcript_watcher(watcher_path: Path) -> subprocess.Popen:
|
|
83
|
+
"""Start the Antigravity transcript watcher, tied to this process's life.
|
|
84
|
+
|
|
85
|
+
Its stdin is a pipe only this process holds, and the lifeline variable
|
|
86
|
+
tells it to exit at end-of-file. When this process dies, however it dies,
|
|
87
|
+
the pipe closes and the watcher follows. Without it each restart left a
|
|
88
|
+
watcher behind, and every one of them recorded every Antigravity call.
|
|
89
|
+
"""
|
|
90
|
+
return subprocess.Popen(
|
|
91
|
+
[sys.executable, str(watcher_path)],
|
|
92
|
+
stdin=subprocess.PIPE,
|
|
93
|
+
stdout=subprocess.DEVNULL,
|
|
94
|
+
stderr=subprocess.DEVNULL,
|
|
95
|
+
env={**os.environ, "AGENTMETRY_WATCHER_LIFELINE": "1"},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def stop_transcript_watcher(process: subprocess.Popen) -> None:
|
|
100
|
+
"""Close the lifeline, then terminate. Closing alone is enough; terminate is
|
|
101
|
+
for a watcher that predates the lifeline."""
|
|
102
|
+
try:
|
|
103
|
+
if process.stdin:
|
|
104
|
+
process.stdin.close()
|
|
105
|
+
except OSError:
|
|
106
|
+
pass
|
|
107
|
+
process.terminate()
|
|
108
|
+
|
|
109
|
+
|
|
79
110
|
@asynccontextmanager
|
|
80
111
|
async def lifespan(app: FastAPI):
|
|
81
112
|
from agentmetry.core.audit.migrate import backfill_db_from_jsonl
|
|
@@ -177,17 +208,11 @@ async def lifespan(app: FastAPI):
|
|
|
177
208
|
)
|
|
178
209
|
|
|
179
210
|
# Launch transcript watcher for Antigravity in the background
|
|
180
|
-
import subprocess
|
|
181
|
-
import sys
|
|
182
211
|
watcher_process = None
|
|
183
212
|
try:
|
|
184
213
|
watcher_path = Path(__file__).resolve().parents[4] / "scripts" / "antigravity_transcript_watcher.py"
|
|
185
214
|
if watcher_path.exists():
|
|
186
|
-
watcher_process =
|
|
187
|
-
[sys.executable, str(watcher_path)],
|
|
188
|
-
stdout=subprocess.DEVNULL,
|
|
189
|
-
stderr=subprocess.DEVNULL,
|
|
190
|
-
)
|
|
215
|
+
watcher_process = start_transcript_watcher(watcher_path)
|
|
191
216
|
logger.info("Started Antigravity transcript watcher (PID: %s)", watcher_process.pid)
|
|
192
217
|
except Exception as exc:
|
|
193
218
|
logger.warning("Failed to start Antigravity transcript watcher: %s", exc)
|
|
@@ -201,7 +226,7 @@ async def lifespan(app: FastAPI):
|
|
|
201
226
|
for task in bridge_tasks:
|
|
202
227
|
task.cancel()
|
|
203
228
|
if watcher_process:
|
|
204
|
-
watcher_process
|
|
229
|
+
stop_transcript_watcher(watcher_process)
|
|
205
230
|
|
|
206
231
|
|
|
207
232
|
app = FastAPI(
|
|
@@ -101,11 +101,11 @@ class ExternalIngestBody(BaseModel):
|
|
|
101
101
|
|
|
102
102
|
source_app: str = Field(
|
|
103
103
|
...,
|
|
104
|
-
description="cursor | claude | antigravity | codex | copilot | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre",
|
|
104
|
+
description="cursor | claude | antigravity | codex | copilot | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre | zcode | grok_bot",
|
|
105
105
|
)
|
|
106
106
|
event_type: str = Field(
|
|
107
107
|
...,
|
|
108
|
-
description="session_start | session_end | tool_called | tool_denied | tool_failed | approval_request | approval_response | mcp_schema",
|
|
108
|
+
description="session_start | session_end | tool_called | tool_denied | tool_failed | approval_request | approval_response | mcp_schema | hook_remediation",
|
|
109
109
|
)
|
|
110
110
|
correlation_id: str = ""
|
|
111
111
|
session_id: str = ""
|
|
@@ -309,6 +309,29 @@ async def audit_session(
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
|
|
312
|
+
@router.get("/session/{correlation_id}/tree", dependencies=[Depends(require_api_key)])
|
|
313
|
+
async def audit_session_tree(
|
|
314
|
+
correlation_id: str,
|
|
315
|
+
limit: int = Query(20000, ge=1, le=50000),
|
|
316
|
+
around_utc: str = Query("", max_length=40),
|
|
317
|
+
):
|
|
318
|
+
"""One session folded into investigation-tree nodes.
|
|
319
|
+
|
|
320
|
+
Each ask, its answer and the call it led to become one node, so a detection
|
|
321
|
+
that cites the ask and a policy verdict recorded on it land on the call
|
|
322
|
+
they belong to. A session longer than `limit` is windowed on `around_utc`
|
|
323
|
+
(a detection's first_seen_utc) when given. See `core/audit/session_tree.py`.
|
|
324
|
+
"""
|
|
325
|
+
from agentmetry.core.audit.session_tree import session_tree
|
|
326
|
+
|
|
327
|
+
if not settings.audit_export_enabled:
|
|
328
|
+
return {"correlation_id": correlation_id, "enabled": False, "nodes": [], "summary": {}, "truncated": False}
|
|
329
|
+
try:
|
|
330
|
+
return await asyncio.to_thread(session_tree, correlation_id, limit, around_utc)
|
|
331
|
+
except Exception as exc:
|
|
332
|
+
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
333
|
+
|
|
334
|
+
|
|
312
335
|
@router.get("/detections/{correlation_id}", dependencies=[Depends(require_api_key)])
|
|
313
336
|
async def audit_detections(correlation_id: str):
|
|
314
337
|
"""Run correlated behavioral rules over one session and return detections.
|
|
@@ -48,6 +48,70 @@ def _api_base_url(port: int) -> str:
|
|
|
48
48
|
return os.environ.get("AGENTMETRY_URL", "").strip().rstrip("/") or _base_url(port)
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _add_dir_flags(parser: argparse.ArgumentParser) -> None:
|
|
52
|
+
"""The path split. Omit the flags and nothing moves.
|
|
53
|
+
|
|
54
|
+
`SUPPRESS`, so the same flag on `agentmetry` and on the subcommand do not
|
|
55
|
+
overwrite each other with a default of None.
|
|
56
|
+
"""
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--data-dir", default=argparse.SUPPRESS,
|
|
59
|
+
help="trail, spool, token and logs (env AGENTMETRY_DATA_DIR)",
|
|
60
|
+
)
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
"--user-profile-dir", default=argparse.SUPPRESS,
|
|
63
|
+
help="the developer home whose IDE configs to install into and check "
|
|
64
|
+
"(env AGENTMETRY_USER_PROFILE_DIR); for a recorder run as root or LocalSystem",
|
|
65
|
+
)
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--policy-dir", default=argparse.SUPPRESS,
|
|
68
|
+
help="an extra managed policy location, checked like the platform one "
|
|
69
|
+
"(env AGENTMETRY_POLICY_DIR)",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _apply_dir_flags(args: argparse.Namespace) -> None:
|
|
74
|
+
"""Export the path flags before a command reads data_dir() or the profile.
|
|
75
|
+
|
|
76
|
+
Exported, not passed: `start` spawns `serve`, and the hook commands it
|
|
77
|
+
writes run in other processes, all of which read the environment.
|
|
78
|
+
"""
|
|
79
|
+
global _DATA_DIR, _PID_FILE
|
|
80
|
+
if getattr(args, "user_profile_dir", None):
|
|
81
|
+
os.environ["AGENTMETRY_USER_PROFILE_DIR"] = args.user_profile_dir
|
|
82
|
+
if getattr(args, "policy_dir", None):
|
|
83
|
+
os.environ["AGENTMETRY_POLICY_DIR"] = args.policy_dir
|
|
84
|
+
if getattr(args, "data_dir", None):
|
|
85
|
+
os.environ["AGENTMETRY_DATA_DIR"] = args.data_dir
|
|
86
|
+
# Only when the flag was given: a caller that already pointed these at
|
|
87
|
+
# a temp tree must not have them recomputed under it.
|
|
88
|
+
_DATA_DIR = data_dir()
|
|
89
|
+
_PID_FILE = _DATA_DIR / "agentmetry.pid"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _apply_managed_policy(args: argparse.Namespace) -> None:
|
|
93
|
+
"""Locks over flags and environment, for this process and everything it starts.
|
|
94
|
+
|
|
95
|
+
After `_apply_dir_flags`, so `--data-dir` cannot move a trail a fleet
|
|
96
|
+
locked. Says so when it overrides a flag: a silently ignored flag reads as
|
|
97
|
+
a bug.
|
|
98
|
+
"""
|
|
99
|
+
global _DATA_DIR, _PID_FILE
|
|
100
|
+
from agentmetry.core import managed_policy
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
policy = managed_policy.apply_locks()
|
|
104
|
+
except Exception as exc:
|
|
105
|
+
print(f"Managed policy was not applied: {exc}", file=sys.stderr)
|
|
106
|
+
return
|
|
107
|
+
if "data_dir" not in policy.locked:
|
|
108
|
+
return
|
|
109
|
+
if getattr(args, "data_dir", None):
|
|
110
|
+
print("--data-dir ignored: managed policy locks data_dir.", file=sys.stderr)
|
|
111
|
+
_DATA_DIR = data_dir()
|
|
112
|
+
_PID_FILE = _DATA_DIR / "agentmetry.pid"
|
|
113
|
+
|
|
114
|
+
|
|
51
115
|
def _api_headers() -> dict[str, str]:
|
|
52
116
|
from agentmetry.core.api_token import client_token
|
|
53
117
|
|
|
@@ -551,6 +615,7 @@ def cmd_cloud(args: argparse.Namespace) -> int:
|
|
|
551
615
|
map_claude_messages,
|
|
552
616
|
map_claude_session,
|
|
553
617
|
map_cursor_agent,
|
|
618
|
+
map_outbound_poll,
|
|
554
619
|
)
|
|
555
620
|
|
|
556
621
|
base = (args.url or "").strip() or None
|
|
@@ -576,7 +641,9 @@ def cmd_cloud(args: argparse.Namespace) -> int:
|
|
|
576
641
|
if not api_key:
|
|
577
642
|
print("CURSOR_API_KEY is not set.")
|
|
578
643
|
return 2
|
|
579
|
-
|
|
644
|
+
cursor_base = base or __import__("agentmetry.core.audit.cloud_bridge", fromlist=["CURSOR_DEFAULT_BASE"]).CURSOR_DEFAULT_BASE
|
|
645
|
+
post(map_outbound_poll("cursor_cloud", "GET", f"{cursor_base.rstrip('/')}/v1/agents"))
|
|
646
|
+
for agent in fetch_cursor_agents(api_key, cursor_base):
|
|
580
647
|
runs = []
|
|
581
648
|
if not args.skip_runs:
|
|
582
649
|
try:
|
|
@@ -594,7 +661,9 @@ def cmd_cloud(args: argparse.Namespace) -> int:
|
|
|
594
661
|
print("ANTHROPIC_COMPLIANCE_KEY is not set (a Compliance Access Key with read:compliance_user_data, not an admin key).")
|
|
595
662
|
return 2
|
|
596
663
|
kind = "remote" if args.remote else "local"
|
|
597
|
-
|
|
664
|
+
claude_base = base or "https://api.anthropic.com"
|
|
665
|
+
post(map_outbound_poll("claude_cloud", "GET", f"{claude_base.rstrip('/')}/v1/compliance/apps/sessions/{kind}"))
|
|
666
|
+
sessions = fetch_claude_sessions(api_key, kind=kind, base=claude_base, limit=args.limit)
|
|
598
667
|
for session in sessions[: args.limit]:
|
|
599
668
|
post(map_claude_session(session))
|
|
600
669
|
try:
|
|
@@ -631,11 +700,12 @@ def _hooks_status() -> int:
|
|
|
631
700
|
"""
|
|
632
701
|
from agentmetry.core.diagnostics import hook_coverage
|
|
633
702
|
|
|
634
|
-
if hook_coverage.
|
|
703
|
+
if hook_coverage.profile_blind():
|
|
635
704
|
print(
|
|
636
705
|
"UNDETERMINABLE: running under a service profile. Hook configuration "
|
|
637
706
|
"lives in each developer's profile and none is visible from here. "
|
|
638
|
-
"Run this in the user's context
|
|
707
|
+
"Run this in the user's context, or name the developer's home with "
|
|
708
|
+
"--user-profile-dir.",
|
|
639
709
|
file=sys.stderr,
|
|
640
710
|
)
|
|
641
711
|
return 2
|
|
@@ -663,6 +733,21 @@ def cmd_hooks(args: argparse.Namespace) -> int:
|
|
|
663
733
|
from agentmetry.core.audit import hook_bootstrap
|
|
664
734
|
from agentmetry.core.diagnostics import hook_coverage
|
|
665
735
|
|
|
736
|
+
if args.action == "install":
|
|
737
|
+
# Before --managed too: a managed install would put the temporary path
|
|
738
|
+
# into machine-wide policy files.
|
|
739
|
+
reason = hook_bootstrap.ephemeral_environment()
|
|
740
|
+
if reason:
|
|
741
|
+
print(
|
|
742
|
+
f"Not installing hooks: {reason}. That cache can be deleted at any "
|
|
743
|
+
"time, and every hook would then fail (most agents silently stop "
|
|
744
|
+
"recording; Copilot CLI blocks every tool call). Install Agentmetry "
|
|
745
|
+
"somewhere that stays, then run this again:\n"
|
|
746
|
+
" uv tool install agentmetry (or: pipx install agentmetry, or: pip install agentmetry)\n"
|
|
747
|
+
" agentmetry hooks install",
|
|
748
|
+
file=sys.stderr,
|
|
749
|
+
)
|
|
750
|
+
return 1
|
|
666
751
|
if getattr(args, "managed", False):
|
|
667
752
|
return _hooks_managed(args)
|
|
668
753
|
if args.action == "status":
|
|
@@ -1394,10 +1479,10 @@ def cmd_demo(args: argparse.Namespace) -> int:
|
|
|
1394
1479
|
def cmd_doctor(args: argparse.Namespace) -> int:
|
|
1395
1480
|
"""SIEM preflight: manifests, trail chain, orchestrator health, hooks."""
|
|
1396
1481
|
sys.path.insert(0, str(_ORCH_ROOT))
|
|
1397
|
-
from agentmetry.core.diagnostics.doctor import
|
|
1482
|
+
from agentmetry.core.diagnostics.doctor import print_report, run_doctor
|
|
1398
1483
|
|
|
1399
1484
|
report = run_doctor(fix_drivers=getattr(args, "fix", False))
|
|
1400
|
-
|
|
1485
|
+
print_report(report, as_json=getattr(args, "json", False))
|
|
1401
1486
|
return report.exit_code
|
|
1402
1487
|
|
|
1403
1488
|
|
|
@@ -1431,6 +1516,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1431
1516
|
prog="agentmetry",
|
|
1432
1517
|
description="Agentmetry local ops. New here? Start with `agentmetry demo`.",
|
|
1433
1518
|
)
|
|
1519
|
+
_add_dir_flags(parser)
|
|
1434
1520
|
parser.add_argument("--port", type=int, default=8000)
|
|
1435
1521
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
1436
1522
|
# First, so it heads the help: it is the one command that needs nothing set up.
|
|
@@ -1445,6 +1531,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1445
1531
|
|
|
1446
1532
|
sub.add_parser("stop", help="stop the orchestrator")
|
|
1447
1533
|
start = sub.add_parser("start", help="start the orchestrator (detached)")
|
|
1534
|
+
_add_dir_flags(start)
|
|
1448
1535
|
start.add_argument(
|
|
1449
1536
|
"--host",
|
|
1450
1537
|
default="127.0.0.1",
|
|
@@ -1454,12 +1541,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1454
1541
|
"serve",
|
|
1455
1542
|
help="run the orchestrator in the foreground (what autostart registers)",
|
|
1456
1543
|
)
|
|
1544
|
+
_add_dir_flags(serve)
|
|
1457
1545
|
serve.add_argument("--host", default="127.0.0.1")
|
|
1458
1546
|
serve.add_argument("--port", type=int, default=8000)
|
|
1459
1547
|
hooks = sub.add_parser(
|
|
1460
1548
|
"hooks",
|
|
1461
1549
|
help="install IDE hook configs for the agents on this machine",
|
|
1462
1550
|
)
|
|
1551
|
+
_add_dir_flags(hooks)
|
|
1463
1552
|
hooks.add_argument(
|
|
1464
1553
|
"action",
|
|
1465
1554
|
choices=("install", "status"),
|
|
@@ -1661,6 +1750,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1661
1750
|
doctor = sub.add_parser(
|
|
1662
1751
|
"doctor", help="SIEM preflight (manifests, trail chain, health, hooks)"
|
|
1663
1752
|
)
|
|
1753
|
+
_add_dir_flags(doctor)
|
|
1754
|
+
doctor.add_argument(
|
|
1755
|
+
"--json",
|
|
1756
|
+
action="store_true",
|
|
1757
|
+
help="the report as JSON, for a deployment script: exit_code, "
|
|
1758
|
+
"orchestrator_reachable, hooks, policy and findings",
|
|
1759
|
+
)
|
|
1664
1760
|
doctor.add_argument(
|
|
1665
1761
|
"--fix",
|
|
1666
1762
|
action="store_true",
|
|
@@ -1688,6 +1784,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1688
1784
|
replay.add_argument("--trail", default="", help="trail to read (default: the configured trail)")
|
|
1689
1785
|
|
|
1690
1786
|
args = parser.parse_args(argv)
|
|
1787
|
+
_apply_dir_flags(args)
|
|
1788
|
+
_apply_managed_policy(args)
|
|
1691
1789
|
handlers = {
|
|
1692
1790
|
"demo": cmd_demo,
|
|
1693
1791
|
"start": cmd_start,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Read tool calls out of an Antigravity conversation database.
|
|
2
|
+
|
|
3
|
+
The installed hook only matches `run_command`, so file reads, writes, search
|
|
4
|
+
and the other tools never reach the trail. The conversation database stores
|
|
5
|
+
every step. The payload is a protobuf blob. The tool name is a length-prefixed
|
|
6
|
+
ASCII field and the arguments are the JSON object that follows it. That framing
|
|
7
|
+
was read off a real database on 2026-10-16. A blob that does not match it
|
|
8
|
+
yields nothing.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sqlite3
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Iterator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _varint(buf: bytes, index: int) -> tuple[int, int]:
|
|
20
|
+
value = 0
|
|
21
|
+
shift = 0
|
|
22
|
+
while index < len(buf) and shift <= 28:
|
|
23
|
+
byte = buf[index]
|
|
24
|
+
index += 1
|
|
25
|
+
value |= (byte & 0x7F) << shift
|
|
26
|
+
if not byte & 0x80:
|
|
27
|
+
return value, index
|
|
28
|
+
shift += 7
|
|
29
|
+
raise ValueError("truncated varint")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def iter_tool_calls(payload: bytes) -> Iterator[tuple[str, dict[str, Any]]]:
|
|
33
|
+
"""Yield (tool name, arguments) from one step payload."""
|
|
34
|
+
index = 0
|
|
35
|
+
while True:
|
|
36
|
+
marker = payload.find(b"\x12", index)
|
|
37
|
+
if marker < 0 or marker + 2 >= len(payload):
|
|
38
|
+
return
|
|
39
|
+
name_len = payload[marker + 1]
|
|
40
|
+
if name_len == 0 or name_len > 64 or name_len & 0x80:
|
|
41
|
+
index = marker + 1
|
|
42
|
+
continue
|
|
43
|
+
name_at = marker + 2
|
|
44
|
+
name_end = name_at + name_len
|
|
45
|
+
if name_end >= len(payload) or payload[name_end] != 0x1A:
|
|
46
|
+
index = marker + 1
|
|
47
|
+
continue
|
|
48
|
+
name = payload[name_at:name_end]
|
|
49
|
+
if not name.isascii() or not all(chr(c).isalnum() or c == ord("_") for c in name):
|
|
50
|
+
index = marker + 1
|
|
51
|
+
continue
|
|
52
|
+
try:
|
|
53
|
+
json_len, json_at = _varint(payload, name_end + 1)
|
|
54
|
+
except ValueError:
|
|
55
|
+
index = marker + 1
|
|
56
|
+
continue
|
|
57
|
+
blob = payload[json_at : json_at + json_len]
|
|
58
|
+
if not blob.startswith(b"{"):
|
|
59
|
+
index = marker + 1
|
|
60
|
+
continue
|
|
61
|
+
try:
|
|
62
|
+
args = json.loads(blob)
|
|
63
|
+
except json.JSONDecodeError:
|
|
64
|
+
index = marker + 1
|
|
65
|
+
continue
|
|
66
|
+
if isinstance(args, dict):
|
|
67
|
+
yield name.decode("ascii"), args
|
|
68
|
+
index = json_at + max(json_len, 1)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def tool_names(db_path: Path) -> dict[str, int]:
|
|
72
|
+
"""Count tool names in one conversation database. Arguments are not returned."""
|
|
73
|
+
counts: dict[str, int] = {}
|
|
74
|
+
uri = f"file:{Path(db_path).as_posix()}?mode=ro"
|
|
75
|
+
with sqlite3.connect(uri, uri=True) as con:
|
|
76
|
+
for (payload,) in con.execute("SELECT step_payload FROM steps"):
|
|
77
|
+
if not isinstance(payload, (bytes, bytearray)):
|
|
78
|
+
continue
|
|
79
|
+
for name, _args in iter_tool_calls(payload):
|
|
80
|
+
counts[name] = counts.get(name, 0) + 1
|
|
81
|
+
return counts
|
|
@@ -26,6 +26,7 @@ from __future__ import annotations
|
|
|
26
26
|
import hashlib
|
|
27
27
|
import json
|
|
28
28
|
from typing import Any
|
|
29
|
+
from urllib.parse import urlsplit
|
|
29
30
|
|
|
30
31
|
REDACT_KEYS = frozenset(
|
|
31
32
|
{"authorization", "api_key", "apikey", "password", "secret", "token", "session_key"}
|
|
@@ -110,6 +111,29 @@ def tool_summary(args: Any) -> dict[str, Any]:
|
|
|
110
111
|
return summary
|
|
111
112
|
|
|
112
113
|
|
|
114
|
+
def map_outbound_poll(source_app: str, method: str, url: str) -> dict[str, Any]:
|
|
115
|
+
"""The request this machine sends. The key is not in the payload.
|
|
116
|
+
|
|
117
|
+
Query strings are dropped: a token does not belong in the trail. The path
|
|
118
|
+
and host are the trace of the send.
|
|
119
|
+
"""
|
|
120
|
+
parts = urlsplit(url)
|
|
121
|
+
target = f"{method} {parts.netloc}{parts.path}"
|
|
122
|
+
return {
|
|
123
|
+
"source_app": source_app,
|
|
124
|
+
"adapter": "cloud_bridge",
|
|
125
|
+
"event_type": "tool_called",
|
|
126
|
+
"outcome": "success",
|
|
127
|
+
"correlation_id": f"{source_app}-outbound",
|
|
128
|
+
"reason": "outbound poll; key sent and not stored",
|
|
129
|
+
"tool": {
|
|
130
|
+
"qualified": f"{source_app}.poll",
|
|
131
|
+
"server": source_app,
|
|
132
|
+
"command": target,
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
113
137
|
# --- Cursor Cloud Agents API -------------------------------------------------
|
|
114
138
|
|
|
115
139
|
CURSOR_DEFAULT_BASE = "https://api.cursor.com"
|
agentmetry-0.9.9/agentmetry/core/audit/detection/corpus/benign_container_registry_publish.jsonl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:00:00+00:00","tool":{"arguments":{"command":"docker login registry.example.com --username ci-bot --password-stdin"},"command":"docker login registry.example.com --username ci-bot --password-stdin","input_hash":"1f3b6a8a28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d01","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
|
|
2
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:00:30+00:00","tool":{"arguments":{"command":"docker build -t registry.example.com/team/service:build-42 ."},"command":"docker build -t registry.example.com/team/service:build-42 .","input_hash":"2a4c6d8e28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d02","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
3
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:01:20+00:00","tool":{"arguments":{"command":"docker push registry.example.com/team/service:build-42"},"command":"docker push registry.example.com/team/service:build-42","input_hash":"3b5d7e9f28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d03","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
|
agentmetry-0.9.9/agentmetry/core/audit/detection/corpus/benign_dependency_audit_network_burst.jsonl
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:00+00:00","tool":{"arguments":{"command":"npm audit --json > reports/npm-audit.json"},"command":"npm audit --json > reports/npm-audit.json","input_hash":"8091234428dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d08","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
2
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:20+00:00","tool":{"arguments":{"command":"pip-audit -r requirements.txt -f json -o reports/pip-audit.json"},"command":"pip-audit -r requirements.txt -f json -o reports/pip-audit.json","input_hash":"91a2345528dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d09","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
3
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:40+00:00","tool":{"arguments":{"command":"curl -s https://registry.npmjs.org/-/npm/v1/security/advisories?limit=20 > reports/npm-advisories.json"},"command":"curl -s https://registry.npmjs.org/-/npm/v1/security/advisories?limit=20 > reports/npm-advisories.json","input_hash":"a2b3456628dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d10","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
|
|
4
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e004","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:01:20+00:00","tool":{"arguments":{"command":"python scripts/summarise_audit.py reports/npm-audit.json reports/pip-audit.json reports/npm-advisories.json"},"command":"python scripts/summarise_audit.py reports/npm-audit.json reports/pip-audit.json reports/npm-advisories.json","input_hash":"b3c4567728dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d11","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
agentmetry-0.9.9/agentmetry/core/audit/detection/corpus/benign_terraform_apply_with_profile.jsonl
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:00:00+00:00","tool":{"arguments":{"command":"terraform init -backend-config=env/staging.backend"},"command":"terraform init -backend-config=env/staging.backend","input_hash":"4c6e8f1028dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d04","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
2
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:02:00+00:00","tool":{"arguments":{"command":"terraform plan -var-file env/staging.tfvars -out tfplan"},"command":"terraform plan -var-file env/staging.tfvars -out tfplan","input_hash":"5d7f901128dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d05","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
3
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:04:00+00:00","tool":{"arguments":{"command":"AWS_PROFILE=staging terraform apply -auto-approve tfplan"},"command":"AWS_PROFILE=staging terraform apply -auto-approve tfplan","input_hash":"6e80912228dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d06","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
4
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d004","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:06:00+00:00","tool":{"arguments":{"command":"aws sts get-caller-identity --profile staging"},"command":"aws sts get-caller-identity --profile staging","input_hash":"7f90123328dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d07","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["cloud_api"]}}
|
|
@@ -419,6 +419,34 @@ cases:
|
|
|
419
419
|
Thirty read-only calls spread over half an hour. Burst rules measure a
|
|
420
420
|
time window; a long session must not trip them by length alone.
|
|
421
421
|
|
|
422
|
+
- name: container registry publish
|
|
423
|
+
session: benign_container_registry_publish.jsonl
|
|
424
|
+
expect: []
|
|
425
|
+
benign: true
|
|
426
|
+
note: >
|
|
427
|
+
`docker login`, `docker build` and `docker push` against a registry. This
|
|
428
|
+
looks like authenticated egress, but no credential is read from disk or
|
|
429
|
+
the environment; the password arrives through stdin and never belongs in
|
|
430
|
+
the trail.
|
|
431
|
+
|
|
432
|
+
- name: terraform apply with a named profile
|
|
433
|
+
session: benign_terraform_apply_with_profile.jsonl
|
|
434
|
+
expect: []
|
|
435
|
+
benign: true
|
|
436
|
+
note: >
|
|
437
|
+
Infrastructure automation touches cloud state and later calls `aws sts`,
|
|
438
|
+
but it uses a named profile rather than reading credentials. Cloud API use
|
|
439
|
+
by itself is not lateral movement after credential harvest.
|
|
440
|
+
|
|
441
|
+
- name: dependency audit touches remote advisories
|
|
442
|
+
session: benign_dependency_audit_network_burst.jsonl
|
|
443
|
+
expect: []
|
|
444
|
+
benign: true
|
|
445
|
+
note: >
|
|
446
|
+
Package audits query registries and advisory feeds in quick succession,
|
|
447
|
+
then run a local summariser. A short network-heavy audit is not a session
|
|
448
|
+
tool burst, a download cradle or credential exfiltration.
|
|
449
|
+
|
|
422
450
|
- name: human-driven deletes currently fire
|
|
423
451
|
session: benign_human_driven_deletes.jsonl
|
|
424
452
|
expect: [destructive-delete-burst]
|
|
@@ -21,13 +21,13 @@ from agentmetry.core.config import settings
|
|
|
21
21
|
ExternalApp = Literal[
|
|
22
22
|
"cursor", "claude", "antigravity", "codex", "mcp_proxy",
|
|
23
23
|
"qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
|
|
24
|
-
"cursor_cloud", "claude_cloud", "grok_build", "agentmetry",
|
|
24
|
+
"cursor_cloud", "claude_cloud", "grok_build", "grok_bot", "agentmetry",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
KNOWN_EXTERNAL_APPS = frozenset({
|
|
28
28
|
"cursor", "claude", "antigravity", "codex", "mcp_proxy",
|
|
29
29
|
"qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
|
|
30
|
-
"cursor_cloud", "claude_cloud", "grok_build",
|
|
30
|
+
"cursor_cloud", "claude_cloud", "grok_build", "grok_bot",
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
_ACTION_MAP = {
|
|
@@ -38,8 +38,20 @@ _ACTION_MAP = {
|
|
|
38
38
|
"tool_failed": ("tool_called", "error"),
|
|
39
39
|
"approval_request": ("approval_request", "pending"),
|
|
40
40
|
"approval_response": ("approval_response", "success"),
|
|
41
|
+
# A deployment tool (an Intune remediation, a LaunchAgent) reinstalled
|
|
42
|
+
# hooks. That changes this machine's configuration; it is not a tool call.
|
|
43
|
+
# Unmapped it was stored as `tool_called`, which tool-sequence rules count.
|
|
44
|
+
"hook_remediation": ("config_change", "success"),
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
#: The only payload fields a remediation notice keeps. Anything session- or
|
|
48
|
+
#: tool-shaped a client attached is dropped: it would join the notice to an
|
|
49
|
+
#: agent's session, or give a detection a tool to read.
|
|
50
|
+
_REMEDIATION_FIELDS = frozenset({
|
|
51
|
+
"event_type", "source_app", "outcome", "reason", "timestamp_utc", "adapter",
|
|
52
|
+
"triggered_by", "initiator", "operator",
|
|
53
|
+
})
|
|
54
|
+
|
|
43
55
|
|
|
44
56
|
def _utc_now() -> str:
|
|
45
57
|
return datetime.now(timezone.utc).isoformat()
|
|
@@ -82,8 +94,14 @@ def build_external_canonical(payload: dict[str, Any]) -> dict[str, Any]:
|
|
|
82
94
|
source_app, claimed_app = _resolve_source_app(payload.get("source_app"))
|
|
83
95
|
|
|
84
96
|
event_type = str(payload.get("event_type") or "tool_called")
|
|
97
|
+
if event_type == "hook_remediation":
|
|
98
|
+
payload = {k: v for k, v in payload.items() if k in _REMEDIATION_FIELDS}
|
|
85
99
|
action_type, default_outcome = _ACTION_MAP.get(event_type, ("tool_called", "success"))
|
|
86
100
|
outcome = str(payload.get("outcome") or default_outcome)
|
|
101
|
+
if event_type == "hook_remediation" and outcome not in ("success", "error"):
|
|
102
|
+
# Anything else is not a confirmed success, and a fleet alerting on
|
|
103
|
+
# failed remediations must not have one hidden by a spelling.
|
|
104
|
+
outcome = "error"
|
|
87
105
|
reason = str(payload.get("reason") or "")
|
|
88
106
|
|
|
89
107
|
correlation_id = str(payload.get("correlation_id") or payload.get("thread_id") or "")
|