devops-bot-sdk 1.6.42__tar.gz → 1.6.46__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.
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/SOURCES.txt +4 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/collectors/__init__.py +13 -4
- devops_bot_sdk-1.6.46/sdk/activity/collectors/macos_eslogger.py +356 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/collectors/windows_eventlog.py +28 -1
- devops_bot_sdk-1.6.46/sdk/activity/os_setup.py +428 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/cli.py +10 -1
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/server.py +9 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/service.py +87 -3
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/updater.py +29 -2
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_activity_collectors.py +35 -3
- devops_bot_sdk-1.6.46/tests/test_activity_macos.py +262 -0
- devops_bot_sdk-1.6.46/tests/test_activity_os_setup.py +151 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_update_timer.py +94 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/README.md +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/pyproject.toml +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/client.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/api.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_write_hook.py +0 -0
|
@@ -54,6 +54,7 @@ sdk/write_hook.py
|
|
|
54
54
|
sdk/activity/__init__.py
|
|
55
55
|
sdk/activity/classifier.py
|
|
56
56
|
sdk/activity/models.py
|
|
57
|
+
sdk/activity/os_setup.py
|
|
57
58
|
sdk/activity/recorder.py
|
|
58
59
|
sdk/activity/redact.py
|
|
59
60
|
sdk/activity/settings.py
|
|
@@ -62,6 +63,7 @@ sdk/activity/spool.py
|
|
|
62
63
|
sdk/activity/collectors/__init__.py
|
|
63
64
|
sdk/activity/collectors/common.py
|
|
64
65
|
sdk/activity/collectors/linux_auditd.py
|
|
66
|
+
sdk/activity/collectors/macos_eslogger.py
|
|
65
67
|
sdk/activity/collectors/windows_eventlog.py
|
|
66
68
|
sdk/agents/__init__.py
|
|
67
69
|
sdk/agents/activity.py
|
|
@@ -137,6 +139,8 @@ sdk/ui/static/styles.css
|
|
|
137
139
|
sdk/ui/static/tickets.html
|
|
138
140
|
tests/test_activity_collectors.py
|
|
139
141
|
tests/test_activity_core.py
|
|
142
|
+
tests/test_activity_macos.py
|
|
143
|
+
tests/test_activity_os_setup.py
|
|
140
144
|
tests/test_activity_pipeline.py
|
|
141
145
|
tests/test_agent_activity.py
|
|
142
146
|
tests/test_agent_activity_tracking.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
|
|
2
2
|
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.46
|
|
4
4
|
|
|
5
5
|
Public surface:
|
|
6
6
|
BackendClient.from_config() — create client from ~/.agentos/config.toml
|
|
@@ -34,7 +34,7 @@ Rules:
|
|
|
34
34
|
- All data egress through submit_webhook only
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
__version__ = "1.6.
|
|
37
|
+
__version__ = "1.6.46" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
|
|
38
38
|
__author__ = "AgentOS" # sdk.client.SDK_VERSION and the UI server version all read this.
|
|
39
39
|
|
|
40
40
|
from sdk.client import BackendClient
|
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Linux auditd sdk.activity.collectors.linux_auditd
|
|
4
4
|
Windows Security log sdk.activity.collectors.windows_eventlog
|
|
5
|
-
macOS
|
|
6
|
-
|
|
7
|
-
docs/ACTIVITY_AUDIT.md. (Agent runs are still recorded on macOS: that
|
|
8
|
-
part is the SDK's own and needs no OS support.)
|
|
5
|
+
macOS Endpoint Security, through Apple's own ``eslogger`` (macOS 13+)
|
|
6
|
+
sdk.activity.collectors.macos_eslogger
|
|
9
7
|
|
|
10
8
|
``start()`` has the same contract as the other crons the UI lifespan starts
|
|
11
9
|
(``sdk.vps_health.start`` …): it returns an asyncio task, or ``None`` when disabled
|
|
@@ -47,6 +45,17 @@ def make_collector(os_name: Os | None = None) -> Any | None:
|
|
|
47
45
|
)
|
|
48
46
|
|
|
49
47
|
return WindowsEventLogCollector(WindowsEventProcessor(agent_user=AGENT_OS_USER))
|
|
48
|
+
if os_name is Os.MACOS:
|
|
49
|
+
from sdk.activity.collectors.macos_eslogger import EsloggerCollector, EsloggerParser
|
|
50
|
+
|
|
51
|
+
mac_uid: int | None = None
|
|
52
|
+
try:
|
|
53
|
+
import pwd
|
|
54
|
+
|
|
55
|
+
mac_uid = pwd.getpwnam(AGENT_OS_USER).pw_uid
|
|
56
|
+
except Exception: # noqa: BLE001 — no such user (yet)
|
|
57
|
+
mac_uid = None
|
|
58
|
+
return EsloggerCollector(parser=EsloggerParser(agent_uid=mac_uid))
|
|
50
59
|
return None
|
|
51
60
|
|
|
52
61
|
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""macOS collector: turns Endpoint Security events (via Apple's ``eslogger``) into
|
|
2
|
+
sessions and events.
|
|
3
|
+
|
|
4
|
+
A faithful macOS collector needs an Endpoint Security client, and an ES client must
|
|
5
|
+
be a signed, entitled binary — which this Python SDK cannot be. Apple ships one:
|
|
6
|
+
``/usr/bin/eslogger`` (macOS 13+) subscribes to ES events and prints each as a line
|
|
7
|
+
of JSON. So macOS gets the same shape as Linux:
|
|
8
|
+
|
|
9
|
+
eslogger (root LaunchDaemon, set up by sdk.activity.os_setup)
|
|
10
|
+
→ /var/log/agentos/eslogger.ndjson (like auditd → audit.log)
|
|
11
|
+
→ EsloggerCollector tails it in the panel (like AuditdCollector)
|
|
12
|
+
|
|
13
|
+
Events used:
|
|
14
|
+
|
|
15
|
+
* ``exec`` → a COMMAND event, one per exec
|
|
16
|
+
* ``openssh_login`` / ``openssh_logout`` → SSH LOGIN / LOGOUT and the login session
|
|
17
|
+
* ``login_login`` / ``login_logout``,
|
|
18
|
+
``lw_session_login`` / ``lw_session_logout`` → CONSOLE LOGIN / LOGOUT
|
|
19
|
+
* ``sudo`` / ``su`` → a PRIVILEGE event
|
|
20
|
+
|
|
21
|
+
eslogger's JSON mirrors ``es_message_t``: ``event_type``, ``time``, ``process`` (with
|
|
22
|
+
``audit_token`` → pid/euid/ruid/auid/asid, ``ppid``, ``executable.path``, ``tty``) and
|
|
23
|
+
``event`` holding exactly one key named after the event. Field access here is
|
|
24
|
+
defensive (``.get`` everywhere): the format is Apple's and has grown between macOS
|
|
25
|
+
releases.
|
|
26
|
+
|
|
27
|
+
The parser is pure and stateful — feed it lines, it records through
|
|
28
|
+
:mod:`sdk.activity.recorder` — so it is testable without a Mac. Attribution follows
|
|
29
|
+
the Linux collector: agentos provenance (registry, or ``AGENTOS_RUN_ID`` in the exec
|
|
30
|
+
environment when eslogger includes it) → the ``agentos`` OS user → an interactive
|
|
31
|
+
session (a tty, or an audit session opened by a login) → launchd with no terminal =
|
|
32
|
+
SYSTEM. SYSTEM execs are skipped by default, as on Windows: a Mac runs a constant
|
|
33
|
+
stream of ``xpcproxy`` / ``mdworker`` housekeeping that is nobody's action.
|
|
34
|
+
"""
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import json
|
|
38
|
+
import logging
|
|
39
|
+
from collections import OrderedDict
|
|
40
|
+
from collections.abc import Callable
|
|
41
|
+
from datetime import datetime
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
from sdk.activity import recorder
|
|
45
|
+
from sdk.activity.classifier import AgentProcessRegistry, ProcessFacts, classify
|
|
46
|
+
from sdk.activity.collectors.common import SessionBook
|
|
47
|
+
from sdk.activity.collectors.linux_auditd import AuditdCollector
|
|
48
|
+
from sdk.activity.models import (
|
|
49
|
+
ActivityEvent,
|
|
50
|
+
ActorType,
|
|
51
|
+
Category,
|
|
52
|
+
Os,
|
|
53
|
+
SessionKind,
|
|
54
|
+
Severity,
|
|
55
|
+
Source,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
logger = logging.getLogger("sdk.activity.collectors.macos")
|
|
59
|
+
|
|
60
|
+
DEFAULT_ES_LOG = "/var/log/agentos/eslogger.ndjson"
|
|
61
|
+
|
|
62
|
+
#: The ES events the LaunchDaemon subscribes to — kept here so os_setup and the
|
|
63
|
+
#: parser cannot disagree about them.
|
|
64
|
+
ES_EVENTS = (
|
|
65
|
+
"exec",
|
|
66
|
+
"openssh_login", "openssh_logout",
|
|
67
|
+
"login_login", "login_logout",
|
|
68
|
+
"lw_session_login", "lw_session_logout",
|
|
69
|
+
"sudo", "su",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
#: ``auid`` when no login owns the process (AU_DEFAUDITID).
|
|
73
|
+
_UNSET_AUID = {4294967295, -1}
|
|
74
|
+
_MAX_PID_CACHE = 20_000
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _ts(msg: dict) -> float:
|
|
78
|
+
raw = msg.get("time")
|
|
79
|
+
if isinstance(raw, str):
|
|
80
|
+
try:
|
|
81
|
+
return datetime.fromisoformat(raw.replace("Z", "+00:00")).timestamp()
|
|
82
|
+
except ValueError:
|
|
83
|
+
pass
|
|
84
|
+
import time
|
|
85
|
+
|
|
86
|
+
return time.time()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _token(proc: dict | None) -> dict:
|
|
90
|
+
return (proc or {}).get("audit_token") or {}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _path(obj) -> str | None:
|
|
94
|
+
"""``{"path": "/bin/ls", ...}`` → ``/bin/ls``; tolerates a bare string or null."""
|
|
95
|
+
if isinstance(obj, dict):
|
|
96
|
+
return obj.get("path") or None
|
|
97
|
+
return obj or None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _uid(obj) -> int | None:
|
|
101
|
+
"""eslogger nests uids as ``{"uid": 501}``; accept a bare int too."""
|
|
102
|
+
if isinstance(obj, dict):
|
|
103
|
+
obj = obj.get("uid")
|
|
104
|
+
try:
|
|
105
|
+
return int(obj) if obj is not None else None
|
|
106
|
+
except (TypeError, ValueError):
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _default_user_name(uid: int | None) -> str | None:
|
|
111
|
+
if uid is None or uid in _UNSET_AUID:
|
|
112
|
+
return None
|
|
113
|
+
try:
|
|
114
|
+
import pwd
|
|
115
|
+
|
|
116
|
+
return pwd.getpwuid(uid).pw_name
|
|
117
|
+
except Exception: # noqa: BLE001 — unknown uid, not POSIX
|
|
118
|
+
return str(uid)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _env_run_id(env) -> str | None:
|
|
122
|
+
"""``AGENTOS_RUN_ID`` from an exec's environment, when eslogger included it."""
|
|
123
|
+
for item in env or []:
|
|
124
|
+
if isinstance(item, str) and item.startswith("AGENTOS_RUN_ID="):
|
|
125
|
+
return item.split("=", 1)[1] or None
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class EsloggerParser:
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
*,
|
|
133
|
+
agent_uid: int | None = None,
|
|
134
|
+
registry: AgentProcessRegistry | None = None,
|
|
135
|
+
user_name: Callable[[int | None], str | None] = _default_user_name,
|
|
136
|
+
book: SessionBook | None = None,
|
|
137
|
+
ignore_system: bool = True,
|
|
138
|
+
) -> None:
|
|
139
|
+
self._agent_uid = agent_uid
|
|
140
|
+
self._registry = registry or AgentProcessRegistry()
|
|
141
|
+
self._user_name = user_name
|
|
142
|
+
self._book = book or SessionBook()
|
|
143
|
+
self._ignore_system = ignore_system
|
|
144
|
+
self._ppid: OrderedDict[int, int] = OrderedDict()
|
|
145
|
+
self._asid_key: dict[int, str] = {} # audit session id → our session key
|
|
146
|
+
self._asid_kind: dict[int, SessionKind] = {}
|
|
147
|
+
self._ssh: dict[tuple[str, str], str] = {} # (user, source) → session key
|
|
148
|
+
|
|
149
|
+
# ── input ────────────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
def feed(self, line: str) -> None:
|
|
152
|
+
line = line.strip()
|
|
153
|
+
if not line.startswith("{"):
|
|
154
|
+
return
|
|
155
|
+
try:
|
|
156
|
+
msg = json.loads(line)
|
|
157
|
+
event = msg.get("event") or {}
|
|
158
|
+
if not isinstance(event, dict) or not event:
|
|
159
|
+
return
|
|
160
|
+
name, body = next(iter(event.items()))
|
|
161
|
+
handler = getattr(self, f"_on_{name}", None)
|
|
162
|
+
if handler is not None:
|
|
163
|
+
handler(msg, body or {})
|
|
164
|
+
except Exception as exc: # noqa: BLE001 — one bad line must not stop the tail
|
|
165
|
+
logger.warning("eslogger: could not process line: %s", exc)
|
|
166
|
+
|
|
167
|
+
def flush(self) -> None:
|
|
168
|
+
"""Nothing is buffered — every eslogger line is a whole event."""
|
|
169
|
+
|
|
170
|
+
# ── sessions ─────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
def _asid_session(self, asid: int, ts: float) -> str:
|
|
173
|
+
key = self._asid_key.get(asid)
|
|
174
|
+
if key is None:
|
|
175
|
+
# asids are reused after a reboot; the first-seen time keeps keys unique.
|
|
176
|
+
key = f"es-asid:{asid}:{int(ts)}"
|
|
177
|
+
self._asid_key[asid] = key
|
|
178
|
+
return key
|
|
179
|
+
|
|
180
|
+
def _login(self, msg: dict, *, kind: SessionKind, user: str | None, uid: int | None,
|
|
181
|
+
remote: str | None, ok: bool, action: str = "login") -> str | None:
|
|
182
|
+
ts = _ts(msg)
|
|
183
|
+
if not ok:
|
|
184
|
+
recorder.record_event(ActivityEvent(
|
|
185
|
+
os=Os.MACOS, source=Source.ENDPOINT_SECURITY, category=Category.LOGIN,
|
|
186
|
+
action="login.failed", occurredAt=ts, osUser=user, target=remote,
|
|
187
|
+
severity=Severity.WARNING, actorType=ActorType.UNKNOWN, actorConfidence=0,
|
|
188
|
+
))
|
|
189
|
+
return None
|
|
190
|
+
verdict = classify(ProcessFacts(
|
|
191
|
+
login_kind=kind, has_tty=True, is_agent_os_user=self._is_agent(uid),
|
|
192
|
+
))
|
|
193
|
+
asid = _token(msg.get("process")).get("asid")
|
|
194
|
+
key = (self._asid_session(int(asid), ts) if isinstance(asid, int) and asid > 0
|
|
195
|
+
else f"es-login:{user}:{remote or 'local'}:{int(ts)}")
|
|
196
|
+
if isinstance(asid, int) and asid > 0:
|
|
197
|
+
self._asid_kind[int(asid)] = kind
|
|
198
|
+
info = self._book.ensure(key, started=ts, verdict=verdict, kind=kind,
|
|
199
|
+
os_user=user, remote_ip=remote)
|
|
200
|
+
recorder.record_event(ActivityEvent(
|
|
201
|
+
os=Os.MACOS, source=Source.ENDPOINT_SECURITY, category=Category.LOGIN,
|
|
202
|
+
action=action, occurredAt=ts, sessionKey=key, osUser=user, target=remote,
|
|
203
|
+
agentRunId=info.agent_run_id,
|
|
204
|
+
))
|
|
205
|
+
return key
|
|
206
|
+
|
|
207
|
+
def _logout(self, msg: dict, key: str | None, user: str | None) -> None:
|
|
208
|
+
if not key:
|
|
209
|
+
return
|
|
210
|
+
ts = _ts(msg)
|
|
211
|
+
recorder.record_event(ActivityEvent(
|
|
212
|
+
os=Os.MACOS, source=Source.ENDPOINT_SECURITY, category=Category.LOGOUT,
|
|
213
|
+
action="logout", occurredAt=ts, sessionKey=key, osUser=user,
|
|
214
|
+
))
|
|
215
|
+
self._book.end(key, ts)
|
|
216
|
+
for asid, k in list(self._asid_key.items()):
|
|
217
|
+
if k == key:
|
|
218
|
+
self._asid_key.pop(asid, None)
|
|
219
|
+
self._asid_kind.pop(asid, None)
|
|
220
|
+
|
|
221
|
+
# ── handlers (named _on_<eslogger event>) ───────────────────────────────
|
|
222
|
+
|
|
223
|
+
def _on_openssh_login(self, msg: dict, ev: dict) -> None:
|
|
224
|
+
user = ev.get("username") or None
|
|
225
|
+
remote = ev.get("source_address") or None
|
|
226
|
+
key = self._login(msg, kind=SessionKind.SSH, user=user, uid=_uid(ev.get("uid")),
|
|
227
|
+
remote=remote, ok=bool(ev.get("success", True)))
|
|
228
|
+
if key:
|
|
229
|
+
self._ssh[(str(user), str(remote))] = key
|
|
230
|
+
|
|
231
|
+
def _on_openssh_logout(self, msg: dict, ev: dict) -> None:
|
|
232
|
+
user = ev.get("username") or None
|
|
233
|
+
key = self._ssh.pop((str(user), str(ev.get("source_address") or None)), None)
|
|
234
|
+
self._logout(msg, key, user)
|
|
235
|
+
|
|
236
|
+
def _on_login_login(self, msg: dict, ev: dict) -> None:
|
|
237
|
+
self._login(msg, kind=SessionKind.CONSOLE, user=ev.get("username") or None,
|
|
238
|
+
uid=_uid(ev.get("uid")), remote=None, ok=bool(ev.get("success", True)))
|
|
239
|
+
|
|
240
|
+
def _on_lw_session_login(self, msg: dict, ev: dict) -> None:
|
|
241
|
+
self._login(msg, kind=SessionKind.CONSOLE, user=ev.get("username") or None,
|
|
242
|
+
uid=None, remote=None, ok=True)
|
|
243
|
+
|
|
244
|
+
def _console_logout(self, msg: dict, ev: dict) -> None:
|
|
245
|
+
asid = _token(msg.get("process")).get("asid")
|
|
246
|
+
key = self._asid_key.get(int(asid)) if isinstance(asid, int) else None
|
|
247
|
+
self._logout(msg, key, ev.get("username") or None)
|
|
248
|
+
|
|
249
|
+
_on_login_logout = _console_logout
|
|
250
|
+
_on_lw_session_logout = _console_logout
|
|
251
|
+
|
|
252
|
+
def _privilege(self, msg: dict, ev: dict, action: str) -> None:
|
|
253
|
+
ts = _ts(msg)
|
|
254
|
+
tok = _token(msg.get("process"))
|
|
255
|
+
asid = tok.get("asid")
|
|
256
|
+
ok = bool(ev.get("success", True))
|
|
257
|
+
recorder.record_event(ActivityEvent(
|
|
258
|
+
os=Os.MACOS, source=Source.ENDPOINT_SECURITY, category=Category.PRIVILEGE,
|
|
259
|
+
action=action, occurredAt=ts,
|
|
260
|
+
sessionKey=self._asid_key.get(int(asid)) if isinstance(asid, int) else None,
|
|
261
|
+
osUser=ev.get("from_username") or self._user_name(_uid(ev.get("from_uid"))),
|
|
262
|
+
command=ev.get("command") or None,
|
|
263
|
+
target=ev.get("to_username") or None,
|
|
264
|
+
pid=tok.get("pid"),
|
|
265
|
+
severity=Severity.NOTICE if ok else Severity.WARNING,
|
|
266
|
+
exitCode=0 if ok else 1,
|
|
267
|
+
))
|
|
268
|
+
|
|
269
|
+
def _on_sudo(self, msg: dict, ev: dict) -> None:
|
|
270
|
+
self._privilege(msg, ev, "sudo")
|
|
271
|
+
|
|
272
|
+
def _on_su(self, msg: dict, ev: dict) -> None:
|
|
273
|
+
self._privilege(msg, ev, "su")
|
|
274
|
+
|
|
275
|
+
def _on_exec(self, msg: dict, ev: dict) -> None:
|
|
276
|
+
ts = _ts(msg)
|
|
277
|
+
target = ev.get("target") or {}
|
|
278
|
+
tok = _token(target) or _token(msg.get("process"))
|
|
279
|
+
pid = int(tok.get("pid") or 0)
|
|
280
|
+
ppid = int(target.get("ppid") or (msg.get("process") or {}).get("ppid") or 0)
|
|
281
|
+
self._remember_ppid(pid, ppid)
|
|
282
|
+
euid, auid, asid = tok.get("euid"), tok.get("auid"), tok.get("asid")
|
|
283
|
+
login = isinstance(auid, int) and auid not in _UNSET_AUID
|
|
284
|
+
tty = _path(target.get("tty"))
|
|
285
|
+
has_tty = bool(tty)
|
|
286
|
+
|
|
287
|
+
root = self._registry.lookup(pid, self._ppid.get)
|
|
288
|
+
run_id = root.run_id if root else _env_run_id(ev.get("env"))
|
|
289
|
+
known_asid = isinstance(asid, int) and asid in self._asid_key
|
|
290
|
+
|
|
291
|
+
verdict = classify(ProcessFacts(
|
|
292
|
+
descends_from_agent=bool(run_id),
|
|
293
|
+
agent_run_id=run_id,
|
|
294
|
+
is_agent_os_user=self._is_agent(euid) or self._is_agent(auid),
|
|
295
|
+
has_tty=has_tty,
|
|
296
|
+
login_kind=self._asid_kind.get(asid) if isinstance(asid, int) else None,
|
|
297
|
+
spawned_by_service_manager=not login and not has_tty and not known_asid,
|
|
298
|
+
))
|
|
299
|
+
if verdict.actor_type is ActorType.SYSTEM and self._ignore_system:
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
if run_id:
|
|
303
|
+
session_key, agent_run_id = recorder.agent_session_key(run_id), run_id
|
|
304
|
+
elif isinstance(asid, int) and asid > 0 and (login or has_tty or known_asid):
|
|
305
|
+
session_key = self._asid_session(asid, ts)
|
|
306
|
+
info = self._book.ensure(session_key, started=ts, verdict=verdict,
|
|
307
|
+
os_user=self._user_name(auid if login else euid))
|
|
308
|
+
agent_run_id = info.agent_run_id
|
|
309
|
+
elif verdict.actor_type is ActorType.AGENT:
|
|
310
|
+
session_key = f"os-user:{euid}:{int(ts) // 86400}"
|
|
311
|
+
info = self._book.ensure(session_key, started=ts, verdict=verdict,
|
|
312
|
+
os_user=self._user_name(euid))
|
|
313
|
+
agent_run_id = info.agent_run_id
|
|
314
|
+
else:
|
|
315
|
+
session_key, agent_run_id = None, None
|
|
316
|
+
|
|
317
|
+
args = ev.get("args")
|
|
318
|
+
command = " ".join(str(a) for a in args) if isinstance(args, list) else None
|
|
319
|
+
exe = _path(target.get("executable"))
|
|
320
|
+
recorder.record_event(ActivityEvent(
|
|
321
|
+
os=Os.MACOS, source=Source.ENDPOINT_SECURITY, category=Category.COMMAND,
|
|
322
|
+
action="exec", occurredAt=ts, sessionKey=session_key,
|
|
323
|
+
actorType=verdict.actor_type, actorConfidence=verdict.confidence,
|
|
324
|
+
agentRunId=agent_run_id,
|
|
325
|
+
osUser=self._user_name(auid if login else euid),
|
|
326
|
+
pid=pid or None, ppid=ppid or None,
|
|
327
|
+
processName=Path(exe).name if exe else None,
|
|
328
|
+
command=command or None, target=exe, exitCode=0,
|
|
329
|
+
))
|
|
330
|
+
|
|
331
|
+
# ── helpers ──────────────────────────────────────────────────────────────
|
|
332
|
+
|
|
333
|
+
def _is_agent(self, uid) -> bool:
|
|
334
|
+
return self._agent_uid is not None and uid == self._agent_uid
|
|
335
|
+
|
|
336
|
+
def _remember_ppid(self, pid: int, ppid: int) -> None:
|
|
337
|
+
if not pid:
|
|
338
|
+
return
|
|
339
|
+
self._ppid[pid] = ppid
|
|
340
|
+
self._ppid.move_to_end(pid)
|
|
341
|
+
while len(self._ppid) > _MAX_PID_CACHE:
|
|
342
|
+
self._ppid.popitem(last=False)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class EsloggerCollector(AuditdCollector):
|
|
346
|
+
"""Tails the eslogger log. The file-following (resume offset, partial lines,
|
|
347
|
+
rotation/truncation) is identical to the auditd tail, so it is reused as is."""
|
|
348
|
+
|
|
349
|
+
def __init__(self, path: str = DEFAULT_ES_LOG, *, parser: EsloggerParser | None = None,
|
|
350
|
+
state_file: Path | None = None, poll_s: float = 1.0) -> None:
|
|
351
|
+
super().__init__(
|
|
352
|
+
path,
|
|
353
|
+
parser=parser or EsloggerParser(), # type: ignore[arg-type] — same feed() contract
|
|
354
|
+
state_file=state_file or (Path.home() / ".agentos" / "activity_eslogger_state.json"),
|
|
355
|
+
poll_s=poll_s,
|
|
356
|
+
)
|
|
@@ -14,6 +14,11 @@ The event parser and the processor are pure; only :class:`WindowsEventLogCollect
|
|
|
14
14
|
touches the OS. Processes created by SYSTEM / LOCAL SERVICE / NETWORK SERVICE are
|
|
15
15
|
skipped by default (``ignore_system``): they are thousands of events an hour of
|
|
16
16
|
Windows housekeeping that no human or agent did.
|
|
17
|
+
|
|
18
|
+
The collector's own polling is skipped too: every poll runs ``wevtutil.exe`` from the
|
|
19
|
+
panel process (as the panel's user, not SYSTEM), and each of those is itself a 4688.
|
|
20
|
+
Unfiltered, that is ~8,600 "Administrator ran wevtutil" events a day per VPS, each
|
|
21
|
+
poll recording the previous one.
|
|
17
22
|
"""
|
|
18
23
|
from __future__ import annotations
|
|
19
24
|
|
|
@@ -47,6 +52,9 @@ logger = logging.getLogger("sdk.activity.collectors.windows")
|
|
|
47
52
|
_NS = "{http://schemas.microsoft.com/win/2004/08/events/event}"
|
|
48
53
|
EVENT_IDS = (4688, 4624, 4634, 4647, 4625)
|
|
49
54
|
_MAX_PID_CACHE = 20_000
|
|
55
|
+
#: Executables the collector itself spawns to poll the log.
|
|
56
|
+
_OWN_TOOLS = ("wevtutil.exe",)
|
|
57
|
+
_MAX_OWN_PIDS = 1_000
|
|
50
58
|
|
|
51
59
|
#: Logon types that are a PERSON at a keyboard / remote desktop.
|
|
52
60
|
_INTERACTIVE = {2: SessionKind.CONSOLE, 11: SessionKind.CONSOLE, 10: SessionKind.RDP}
|
|
@@ -123,11 +131,14 @@ class WindowsEventProcessor:
|
|
|
123
131
|
registry: AgentProcessRegistry | None = None,
|
|
124
132
|
book: SessionBook | None = None,
|
|
125
133
|
ignore_system: bool = True,
|
|
134
|
+
self_pid: int | None = None,
|
|
126
135
|
) -> None:
|
|
127
136
|
self._agent_user = agent_user.lower() if agent_user else None
|
|
128
137
|
self._registry = registry or AgentProcessRegistry()
|
|
129
138
|
self._book = book or SessionBook()
|
|
130
139
|
self._ignore_system = ignore_system
|
|
140
|
+
self._self_pid = os.getpid() if self_pid is None else self_pid
|
|
141
|
+
self._own: OrderedDict[int, None] = OrderedDict() # pids of our own polls + children
|
|
131
142
|
self._ppid: OrderedDict[int, int] = OrderedDict()
|
|
132
143
|
self._logons: dict[str, str] = {} # logon id → session key
|
|
133
144
|
self._kinds: dict[str, SessionKind] = {} # logon id → login kind
|
|
@@ -202,6 +213,9 @@ class WindowsEventProcessor:
|
|
|
202
213
|
d = ev.data
|
|
203
214
|
pid, ppid = _hex(d.get("NewProcessId")), _hex(d.get("ProcessId"))
|
|
204
215
|
self._remember_ppid(pid, ppid)
|
|
216
|
+
image = d.get("NewProcessName", "")
|
|
217
|
+
if self._is_own_poll(pid, ppid, image):
|
|
218
|
+
return
|
|
205
219
|
logon_id = d.get("SubjectLogonId", "").lower()
|
|
206
220
|
if self._ignore_system and logon_id in _SYSTEM_LOGON_IDS:
|
|
207
221
|
return
|
|
@@ -232,7 +246,6 @@ class WindowsEventProcessor:
|
|
|
232
246
|
session_key, started=ev.time, verdict=verdict, os_user=user,
|
|
233
247
|
).agent_run_id
|
|
234
248
|
|
|
235
|
-
image = d.get("NewProcessName", "")
|
|
236
249
|
recorder.record_event(ActivityEvent(
|
|
237
250
|
os=Os.WINDOWS, source=Source.WIN_EVENTLOG, category=Category.COMMAND, action="exec",
|
|
238
251
|
occurredAt=ev.time, sessionKey=session_key,
|
|
@@ -243,6 +256,20 @@ class WindowsEventProcessor:
|
|
|
243
256
|
metadata={"parent": d.get("ParentProcessName") or None},
|
|
244
257
|
))
|
|
245
258
|
|
|
259
|
+
def _is_own_poll(self, pid: int, ppid: int, image: str) -> bool:
|
|
260
|
+
"""A ``wevtutil`` this process spawned to poll, or a child of one (its ``conhost``).
|
|
261
|
+
|
|
262
|
+
Matched by parent pid AND image name, so anything else the panel spawns —
|
|
263
|
+
an agent run, ``git``, ``claude`` — is still recorded.
|
|
264
|
+
"""
|
|
265
|
+
name = image.rsplit("\\", 1)[-1].lower()
|
|
266
|
+
if not ((ppid == self._self_pid and name in _OWN_TOOLS) or ppid in self._own):
|
|
267
|
+
return False
|
|
268
|
+
self._own[pid] = None
|
|
269
|
+
while len(self._own) > _MAX_OWN_PIDS:
|
|
270
|
+
self._own.popitem(last=False)
|
|
271
|
+
return True
|
|
272
|
+
|
|
246
273
|
def _remember_ppid(self, pid: int, ppid: int) -> None:
|
|
247
274
|
self._ppid[pid] = ppid
|
|
248
275
|
self._ppid.move_to_end(pid)
|