devops-bot-sdk 1.6.42__tar.gz → 1.6.44__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.44}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/devops_bot_sdk.egg-info/SOURCES.txt +4 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/collectors/__init__.py +13 -4
- devops_bot_sdk-1.6.44/sdk/activity/collectors/macos_eslogger.py +356 -0
- devops_bot_sdk-1.6.44/sdk/activity/os_setup.py +428 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/cli.py +10 -1
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/server.py +9 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/updater.py +29 -2
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_activity_collectors.py +5 -3
- devops_bot_sdk-1.6.44/tests/test_activity_macos.py +262 -0
- devops_bot_sdk-1.6.44/tests/test_activity_os_setup.py +151 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/README.md +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/pyproject.toml +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/client.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/api.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.44}/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.44
|
|
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.44" # 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
|
+
)
|