devops-bot-sdk 1.7.43__tar.gz → 1.7.45__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/devops_bot_sdk.egg-info/SOURCES.txt +1 -0
  4. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/firewall.py +178 -39
  6. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/help_data/areas.json +4 -4
  7. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/help_data/releases.json +10 -0
  8. devops_bot_sdk-1.7.45/sdk/help_data/snapshot.json +17 -0
  9. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/api.py +27 -2
  10. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/ip_allowlist.py +182 -51
  11. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/security.py +22 -2
  12. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/server.py +15 -1
  13. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/updater.py +13 -5
  14. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_firewall_open_on_update.py +131 -15
  15. devops_bot_sdk-1.7.45/tests/test_ui_bruteforce_guard.py +170 -0
  16. devops_bot_sdk-1.7.43/sdk/help_data/snapshot.json +0 -17
  17. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/README.md +0 -0
  18. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  19. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  20. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/devops_bot_sdk.egg-info/requires.txt +0 -0
  21. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  22. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/pyproject.toml +0 -0
  23. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/__init__.py +0 -0
  24. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/classifier.py +0 -0
  25. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/collectors/__init__.py +0 -0
  26. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/collectors/common.py +0 -0
  27. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/collectors/linux_auditd.py +0 -0
  28. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/collectors/macos_eslogger.py +0 -0
  29. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/collectors/windows_eventlog.py +0 -0
  30. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/models.py +0 -0
  31. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/os_setup.py +0 -0
  32. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/recorder.py +0 -0
  33. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/redact.py +0 -0
  34. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/settings.py +0 -0
  35. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/shipper.py +0 -0
  36. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/activity/spool.py +0 -0
  37. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agent_sharing.py +0 -0
  38. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agentd.py +0 -0
  39. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/__init__.py +0 -0
  40. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/activity.py +0 -0
  41. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/builder.py +0 -0
  42. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/chat.py +0 -0
  43. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/connectors.py +0 -0
  44. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/engines/__init__.py +0 -0
  45. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/engines/claude_code.py +0 -0
  46. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/engines/http.py +0 -0
  47. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/engines/mcp.py +0 -0
  48. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/engines/n8n.py +0 -0
  49. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/engines/shell.py +0 -0
  50. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/integrations.py +0 -0
  51. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/inventory.py +0 -0
  52. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/library/__init__.py +0 -0
  53. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/manifest.py +0 -0
  54. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/prompt_studio.py +0 -0
  55. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/prompt_template.py +0 -0
  56. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/prompt_vars.py +0 -0
  57. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/run_log.py +0 -0
  58. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/schedules.py +0 -0
  59. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/secrets.py +0 -0
  60. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/ticket_run.py +0 -0
  61. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/usage.py +0 -0
  62. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/agents/varstore.py +0 -0
  63. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/browser_profile.py +0 -0
  64. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/claude_usage.py +0 -0
  65. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/cli.py +0 -0
  66. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/client.py +0 -0
  67. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/collectors/__init__.py +0 -0
  68. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/collectors/files.py +0 -0
  69. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/collectors/process.py +0 -0
  70. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/collectors/screenshot.py +0 -0
  71. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/config.py +0 -0
  72. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/crucial.py +0 -0
  73. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/custom_agents.py +0 -0
  74. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/deferred_tickets.py +0 -0
  75. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/departments.py +0 -0
  76. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/design_verify/__init__.py +0 -0
  77. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/design_verify/assets.py +0 -0
  78. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/design_verify/bootstrap.py +0 -0
  79. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/design_verify/browser.py +0 -0
  80. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/design_verify/compare.py +0 -0
  81. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/design_verify/loop.py +0 -0
  82. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/estimator.py +0 -0
  83. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/exceptions.py +0 -0
  84. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/fallback_handoff.py +0 -0
  85. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/faqs.py +0 -0
  86. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/formats.py +0 -0
  87. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/git_ops.py +0 -0
  88. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/github_fetch.py +0 -0
  89. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/github_ingest.py +0 -0
  90. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/graph_recon.py +0 -0
  91. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/graphify.py +0 -0
  92. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/help_actions.py +0 -0
  93. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/help_data/commands.json +0 -0
  94. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/help_data/help.json +0 -0
  95. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/help_data/pages.json +0 -0
  96. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/helpdesk.py +0 -0
  97. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/helpdesk_kb.md +0 -0
  98. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/hooks/__init__.py +0 -0
  99. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/hooks/crucial_guard.py +0 -0
  100. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/instructions_template.py +0 -0
  101. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/intake.py +0 -0
  102. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ipc/__init__.py +0 -0
  103. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ipc/electron_bridge.py +0 -0
  104. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ipc/handlers.py +0 -0
  105. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/journal.py +0 -0
  106. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/local_exec.py +0 -0
  107. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/log_colors.py +0 -0
  108. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/memory_guard.py +0 -0
  109. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/model_policy.py +0 -0
  110. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/models/__init__.py +0 -0
  111. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/models/envelope.py +0 -0
  112. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/models/requests.py +0 -0
  113. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/models/responses.py +0 -0
  114. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/models/snapshots.py +0 -0
  115. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/phase_timer.py +0 -0
  116. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/plan_run.py +0 -0
  117. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/platform_compat.py +0 -0
  118. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/prd_progress.py +0 -0
  119. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/proof_readers.py +0 -0
  120. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/providers.py +0 -0
  121. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/py.typed +0 -0
  122. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/resume.py +0 -0
  123. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/router.py +0 -0
  124. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/run_auto.py +0 -0
  125. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/schedule_tickets.py +0 -0
  126. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/schedule_timeline.py +0 -0
  127. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/scheduler.py +0 -0
  128. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/scope_plan.py +0 -0
  129. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/self_report.py +0 -0
  130. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/self_report_local.py +0 -0
  131. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/sse.py +0 -0
  132. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/swagger.py +0 -0
  133. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/task_file.py +0 -0
  134. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/task_types.py +0 -0
  135. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/test.py +0 -0
  136. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/test_env.py +0 -0
  137. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/test_pipeline.py +0 -0
  138. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ticket_board.py +0 -0
  139. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ticket_bulk.py +0 -0
  140. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ticket_fields.py +0 -0
  141. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ticket_plan.py +0 -0
  142. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ticket_pools.py +0 -0
  143. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/tickets.py +0 -0
  144. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/tiers/__init__.py +0 -0
  145. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/tiers/mini_agent.py +0 -0
  146. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/__init__.py +0 -0
  147. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/dashboard.py +0 -0
  148. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/health.py +0 -0
  149. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/jobs.py +0 -0
  150. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/models.py +0 -0
  151. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/runner.py +0 -0
  152. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/service.py +0 -0
  153. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/session.py +0 -0
  154. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/setup_manager.py +0 -0
  155. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/agents.html +0 -0
  156. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/app.js +0 -0
  157. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/chat.html +0 -0
  158. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/custom-agents.html +0 -0
  159. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/default-agents.html +0 -0
  160. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/faqs.html +0 -0
  161. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/help.html +0 -0
  162. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/index.html +0 -0
  163. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/report.html +0 -0
  164. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/runs.html +0 -0
  165. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/schedules.html +0 -0
  166. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/setup.html +0 -0
  167. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/styles.css +0 -0
  168. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/static/tickets.html +0 -0
  169. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/ui/swagger.py +0 -0
  170. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/url_access.py +0 -0
  171. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/vps_health.py +0 -0
  172. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/vps_identity.py +0 -0
  173. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/sdk/write_hook.py +0 -0
  174. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/setup.cfg +0 -0
  175. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_activity_claude_runs.py +0 -0
  176. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_activity_collectors.py +0 -0
  177. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_activity_core.py +0 -0
  178. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_activity_live_tracking.py +0 -0
  179. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_activity_macos.py +0 -0
  180. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_activity_os_setup.py +0 -0
  181. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_activity_pipeline.py +0 -0
  182. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_activity.py +0 -0
  183. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_activity_tracking.py +0 -0
  184. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_chat_features.py +0 -0
  185. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_crud_cli.py +0 -0
  186. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_listing.py +0 -0
  187. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_prompt_async.py +0 -0
  188. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_prompt_models.py +0 -0
  189. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_run_ticket.py +0 -0
  190. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_schedules.py +0 -0
  191. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_sharing.py +0 -0
  192. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agent_workspace_settings.py +0 -0
  193. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_agents_connectors_builder.py +0 -0
  194. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_allowed_tools_config.py +0 -0
  195. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_approval_park.py +0 -0
  196. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_auto_agent_dispatch.py +0 -0
  197. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_auto_ticket_queue.py +0 -0
  198. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_browser_profile.py +0 -0
  199. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_cheap_tier.py +0 -0
  200. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_claude_usage_gate.py +0 -0
  201. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_client_jira_projects.py +0 -0
  202. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_config_token_diagnostics.py +0 -0
  203. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_configure_token_owner.py +0 -0
  204. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_console_encoding.py +0 -0
  205. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_crash_recovery.py +0 -0
  206. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_create_safe_retry.py +0 -0
  207. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_deferred_tickets.py +0 -0
  208. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_departments.py +0 -0
  209. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_done_transition.py +0 -0
  210. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_epic_decompose_run.py +0 -0
  211. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_epic_inherited_agent_id.py +0 -0
  212. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_epic_session_isolation.py +0 -0
  213. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_estimator.py +0 -0
  214. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_fallback_cli.py +0 -0
  215. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_fallback_cli_upgrade.py +0 -0
  216. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_fallback_rate_limit.py +0 -0
  217. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_faqs.py +0 -0
  218. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_formats.py +0 -0
  219. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_git_action.py +0 -0
  220. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_github_fetch.py +0 -0
  221. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_github_ingest.py +0 -0
  222. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_graph_off_by_default.py +0 -0
  223. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_graph_recon.py +0 -0
  224. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_graphify_build.py +0 -0
  225. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_graphify_hook_install.py +0 -0
  226. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_health.py +0 -0
  227. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_help_actions.py +0 -0
  228. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_help_attachments.py +0 -0
  229. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_help_questions.py +0 -0
  230. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_helpdesk.py +0 -0
  231. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_infographic_data.py +0 -0
  232. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_instructions_template.py +0 -0
  233. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_intake_job_outcome.py +0 -0
  234. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_intake_plan_review.py +0 -0
  235. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_journal.py +0 -0
  236. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_limit_fallback_handoff.py +0 -0
  237. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_local_exec_provider.py +0 -0
  238. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_log_colors_backlog.py +0 -0
  239. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_mcp_route_contract.py +0 -0
  240. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_memory_guard.py +0 -0
  241. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_model_policy.py +0 -0
  242. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_panel_help.py +0 -0
  243. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_panel_ticket_schedules.py +0 -0
  244. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_parallel_tickets.py +0 -0
  245. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_phase_timer.py +0 -0
  246. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_plan_ahead.py +0 -0
  247. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_plan_new_command.py +0 -0
  248. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_plan_run_activity.py +0 -0
  249. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_pool_pr_modes.py +0 -0
  250. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_pr_identity.py +0 -0
  251. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_prd_artifact.py +0 -0
  252. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_project_path_resolution.py +0 -0
  253. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_proof_readers.py +0 -0
  254. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_providers.py +0 -0
  255. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_quiet_run_log.py +0 -0
  256. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_recon_session_isolation.py +0 -0
  257. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_resume.py +0 -0
  258. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_router.py +0 -0
  259. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_run_auto_agent_only.py +0 -0
  260. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_run_outputs.py +0 -0
  261. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_run_verdict_is_honest.py +0 -0
  262. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_schedule_api.py +0 -0
  263. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_schedule_outputs_and_plans.py +0 -0
  264. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_schedule_tickets.py +0 -0
  265. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_schedule_timeline.py +0 -0
  266. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_scheduler.py +0 -0
  267. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_scheduler_transient_retry.py +0 -0
  268. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_self_report.py +0 -0
  269. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_service_token_warning.py +0 -0
  270. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_stale_panel_restart.py +0 -0
  271. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_swagger_command.py +0 -0
  272. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_task_file_and_plan_run.py +0 -0
  273. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_task_file_format_doc.py +0 -0
  274. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_theme_switch.py +0 -0
  275. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ticket_board.py +0 -0
  276. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ticket_bulk.py +0 -0
  277. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ticket_cli.py +0 -0
  278. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ticket_detail_view.py +0 -0
  279. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ticket_fields.py +0 -0
  280. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ticket_plan.py +0 -0
  281. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ticket_pools.py +0 -0
  282. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ticket_run_resolve_by_id.py +0 -0
  283. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ticket_schedule_and_agent.py +0 -0
  284. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ticket_speed_fixes.py +0 -0
  285. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_tier_flag_parity.py +0 -0
  286. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ui_gate.py +0 -0
  287. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ui_lifespan.py +0 -0
  288. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_ui_no_native_dialogs.py +0 -0
  289. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_update_timer.py +0 -0
  290. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_updater_extras.py +0 -0
  291. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_updater_ignores_cwd.py +0 -0
  292. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_updater_pep668.py +0 -0
  293. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_updater_reports_the_truth.py +0 -0
  294. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_url_access.py +0 -0
  295. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_version_flag.py +0 -0
  296. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_vps_health.py +0 -0
  297. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_vps_only_reporting.py +0 -0
  298. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_windows_loop_identity.py +0 -0
  299. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_windows_panel_task.py +0 -0
  300. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_windows_stop_and_pidfile.py +0 -0
  301. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_windows_task_paths.py +0 -0
  302. {devops_bot_sdk-1.7.43 → devops_bot_sdk-1.7.45}/tests/test_write_hook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.7.43
3
+ Version: 1.7.45
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.7.43
3
+ Version: 1.7.45
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -279,6 +279,7 @@ tests/test_ticket_run_resolve_by_id.py
279
279
  tests/test_ticket_schedule_and_agent.py
280
280
  tests/test_ticket_speed_fixes.py
281
281
  tests/test_tier_flag_parity.py
282
+ tests/test_ui_bruteforce_guard.py
282
283
  tests/test_ui_gate.py
283
284
  tests/test_ui_lifespan.py
284
285
  tests/test_ui_no_native_dialogs.py
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.7.43
3
+ Version: 1.7.45
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.7.43" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.7.45" # 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
@@ -19,10 +19,13 @@ from the *same* list the middleware enforces, so the two layers cannot drift.
19
19
  agentos firewall --any --apply# open the port, leaving the allowlist as the gate
20
20
  agentos firewall --revoke IP # print the matching delete rule
21
21
 
22
- ``--any`` is also what runs automatically after a self-update
23
- (``updater._ensure_firewall``), because a pip upgrade replaces code and
24
- nothing else — a host whose kernel drops the SYNs stays broken across every
25
- update while reporting a healthy version.
22
+ After a self-update ``updater._ensure_firewall`` runs :func:`ensure_restricted`:
23
+ it adds a per-IP rule for every allowlisted address and REMOVES the open-to-any
24
+ rule. It used to run ``--any``, and that left every VPS with 8765 open to the
25
+ internet: each brute-force/scanner request then reached Python before the
26
+ allowlist refused it, and a flood pinned the panel at 100% CPU. Open-to-any is
27
+ now only kept when the operator chose it — ``AGENTOS_UI_PUBLIC=1`` (public mode
28
+ needs the port reachable) or ``AGENTOS_FIREWALL_OPEN_ANY=1``.
26
29
 
27
30
  Applying is deliberately opt-in: editing a live firewall is exactly the operation
28
31
  that can lock an operator out of their own box, so the default is to print and let
@@ -30,6 +33,7 @@ a human read it first.
30
33
  """
31
34
  from __future__ import annotations
32
35
 
36
+ import ipaddress
33
37
  import os
34
38
  import platform
35
39
  import shutil
@@ -111,10 +115,13 @@ def allow_any_rules(port: int, system: str | None = None) -> list[str]:
111
115
  corrected in seconds without root on the box, which is exactly what a
112
116
  kernel rule cannot offer.
113
117
 
114
- So: open the port, keep the decision in the allowlist. Use
115
- :func:`allow_rules` instead when the host must not accept a connection from
116
- an unlisted address at all (a shared or hostile network), and accept that a
117
- backend IP change then needs a root session on every host.
118
+ That reasoning held for *access* but not for *load*: every request from the
119
+ internet still costs a Python round-trip before the allowlist refuses it, and
120
+ a brute-force flood took panels to 100% CPU. So this is no longer the default
121
+ — :func:`ensure_restricted` is — and is kept for public mode and for an
122
+ operator who opts in with ``AGENTOS_FIREWALL_OPEN_ANY=1``. A backend IP move
123
+ is still self-healing: the new IP goes in ``_DEFAULT_ALLOWED_IPS`` and the
124
+ next self-update adds its rule.
118
125
  """
119
126
  sysname = (system or _system()).lower()
120
127
 
@@ -201,6 +208,17 @@ def ensure_open(port: int | None = None, *, apply: bool = True) -> tuple[bool, s
201
208
  who = "Administrator" if sysname == "windows" else "root (sudo)"
202
209
  return (False, f"TCP {port} is not open and opening it needs {who}:\n{printable}")
203
210
 
211
+ failed = _apply_rules(rules)
212
+ if failed:
213
+ return (False, "could not open TCP %d: %s" % (port, "; ".join(failed)))
214
+ return (True, f"opened TCP {port} inbound")
215
+
216
+
217
+ def _apply_rules(rules: list[str]) -> list[str]:
218
+ """Run each rule (already privileged, so a printed ``sudo `` is stripped).
219
+
220
+ Returns one ``"<cmd>: <reason>"`` per failure — empty means all applied.
221
+ """
204
222
  failed: list[str] = []
205
223
  for rule in rules:
206
224
  cmd = rule[len("sudo "):] if rule.startswith("sudo ") else rule
@@ -214,9 +232,129 @@ def ensure_open(port: int | None = None, *, apply: bool = True) -> tuple[bool, s
214
232
  if proc.returncode != 0:
215
233
  tail = (proc.stderr or proc.stdout or "").strip().splitlines()
216
234
  failed.append(f"{cmd}: {tail[-1][:120] if tail else f'exit {proc.returncode}'}")
235
+ return failed
236
+
237
+
238
+ def _net(value: str):
239
+ """``value`` as an ip_network, or None — so ``1.2.3.4`` and ``1.2.3.4/32`` match."""
240
+ try:
241
+ return ipaddress.ip_network(value.strip(), strict=False)
242
+ except ValueError:
243
+ return None
244
+
245
+
246
+ def open_any_opted_in() -> bool:
247
+ """Has the operator chosen to keep the port open to any source?
248
+
249
+ True for ``AGENTOS_UI_PUBLIC=1`` (public mode is unreachable otherwise) or an
250
+ explicit ``AGENTOS_FIREWALL_OPEN_ANY=1``. Read from the process env or
251
+ ``~/.agentos/.env``, like every other ``AGENTOS_UI_*`` toggle.
252
+ """
253
+ try:
254
+ from sdk.ui.ip_allowlist import _TRUE, _setting, public_mode
255
+ except Exception: # noqa: BLE001 — never fail open on an import hiccup
256
+ return False
257
+ return public_mode() or _setting("AGENTOS_FIREWALL_OPEN_ANY").lower() in _TRUE
258
+
259
+
260
+ def ensure_restricted(port: int | None = None, *, apply: bool = True,
261
+ ips: list[str] | None = None) -> tuple[bool, str]:
262
+ """Limit ``port`` to the allowlisted IPs. Returns ``(changed, message)``. Never raises.
263
+
264
+ Adds a per-IP rule for each allowlisted address that lacks one, THEN removes
265
+ the open-to-any rule — in that order, and the removal is skipped if any add
266
+ failed, so the backend never loses its route mid-change. Same contract as
267
+ :func:`ensure_open` otherwise: unreadable state is never treated as closed,
268
+ an unprivileged run only returns the commands, and macOS never applies.
269
+
270
+ A message that needs the operator to act is multi-line (it carries commands),
271
+ which is what ``updater._ensure_firewall`` keys on to surface it.
272
+ """
273
+ port = ui_port() if port is None else port
274
+ sysname = _system()
275
+ if ips is None:
276
+ try:
277
+ from sdk.config import get_allowed_ips
278
+ ips = get_allowed_ips()
279
+ except Exception: # noqa: BLE001
280
+ ips = []
281
+
282
+ try:
283
+ sources = port_sources(port, sysname, collapse=False)
284
+ except Exception: # noqa: BLE001 — a status probe must never break an update
285
+ sources = None
286
+
287
+ inactive_note = ""
288
+ if sysname == "linux" and _ufw_inactive():
289
+ inactive_note = ("\nNOTE: ufw is INACTIVE, so no rule is enforced and the port is open "
290
+ "to everyone.\nEnable it once you have allowed SSH:\n"
291
+ " sudo ufw allow OpenSSH && sudo ufw enable")
292
+
293
+ if sources is None:
294
+ rules = allow_rules(ips, port, sysname) + [revoke_any_rule(port, sysname)]
295
+ printable = " " + "\n ".join(rules)
296
+ ok, tool = _tool_present()
297
+ why = tool if not ok else "needs root to read the firewall state"
298
+ return (False, f"could not read the firewall state ({why}) — to restrict TCP {port} "
299
+ f"to the allowlist yourself:\n{printable}")
300
+
301
+ have = {n for n in (_net(x) for x in sources) if n is not None}
302
+ missing = [ip for ip in ips if _net(ip) is not None and _net(ip) not in have]
303
+ open_any = "Anywhere" in sources
304
+
305
+ if not missing and not open_any:
306
+ return (False, f"TCP {port} is restricted to the allowlist" + inactive_note)
307
+
308
+ rules = allow_rules(missing, port, sysname)
309
+ if open_any:
310
+ rules.append(revoke_any_rule(port, sysname))
311
+ printable = " " + "\n ".join(rules)
312
+ what = (f"TCP {port} is open to ANY source" if open_any
313
+ else f"TCP {port} is missing a rule for: {', '.join(missing)}")
314
+
315
+ if not apply or sysname == "darwin":
316
+ return (False, f"{what}. To restrict it to the allowlist:\n{printable}" + inactive_note)
317
+ if not _is_privileged():
318
+ who = "Administrator" if sysname == "windows" else "root (sudo)"
319
+ return (False, f"{what} and restricting it needs {who}:\n{printable}" + inactive_note)
320
+
321
+ failed = _apply_rules(allow_rules(missing, port, sysname))
217
322
  if failed:
218
- return (False, "could not open TCP %d: %s" % (port, "; ".join(failed)))
219
- return (True, f"opened TCP {port} inbound")
323
+ # The any-rule stays: removing it now could cut the backend off.
324
+ return (False, f"could not add allowlist rules for TCP {port}: {'; '.join(failed)}"
325
+ f"\nThe open-to-any rule was left in place.")
326
+ if open_any:
327
+ failed = _apply_rules([revoke_any_rule(port, sysname)])
328
+ still = port_sources(port, sysname, collapse=False) or set()
329
+ if failed or "Anywhere" in still:
330
+ return (bool(missing), f"TCP {port} is still open to any source — a rule the SDK "
331
+ f"did not create allows it. Find and delete it:\n "
332
+ + ("sudo ufw status numbered" if sysname == "linux" else
333
+ "netsh advfirewall firewall show rule name=all dir=in")
334
+ + inactive_note)
335
+ return (True, f"restricted TCP {port} to the allowlist ({len(ips)} IP(s))" + inactive_note)
336
+ return (True, f"added allowlist rule(s) for TCP {port}: {', '.join(missing)}" + inactive_note)
337
+
338
+
339
+ def apply_ip(ip: str, *, add: bool = True, port: int | None = None) -> tuple[bool, str]:
340
+ """Add (or remove) the kernel rule for one runtime-allowlisted ``ip``. Never raises.
341
+
342
+ Called by the ``/api/ui/allowlist`` routes so a newly whitelisted IP is not
343
+ dropped by the host firewall until the next self-update. Applies only when
344
+ the panel is privileged and the port is not deliberately open to any source;
345
+ otherwise returns the command for the operator to run.
346
+ """
347
+ port = ui_port() if port is None else port
348
+ sysname = _system()
349
+ rule = (allow_rules([ip], port, sysname)[0] if add else revoke_rule(ip, port, sysname))
350
+ if open_any_opted_in():
351
+ return (False, "port is open to any source by choice — no per-IP rule needed")
352
+ if sysname not in ("linux", "windows") or not _is_privileged():
353
+ return (False, f"run on the host to update its firewall:\n {rule}")
354
+ failed = _apply_rules([rule])
355
+ if failed:
356
+ return (False, f"firewall rule failed: {failed[0]}")
357
+ return (True, f"firewall rule {'added' if add else 'removed'} for {ip}")
220
358
 
221
359
 
222
360
  def revoke_rule(ip: str, port: int, system: str | None = None) -> str:
@@ -310,7 +448,8 @@ def _parse_netsh(text: str, port: int) -> set[str]:
310
448
  return out
311
449
 
312
450
 
313
- def port_sources(port: int, system: str | None = None) -> set[str] | None:
451
+ def port_sources(port: int, system: str | None = None, *,
452
+ collapse: bool = True) -> set[str] | None:
314
453
  """Which sources the HOST firewall currently allows to ``port``.
315
454
 
316
455
  ``{"Anywhere"}`` means open to the world. **``None`` means the state could not
@@ -322,6 +461,9 @@ def port_sources(port: int, system: str | None = None) -> set[str] | None:
322
461
  (`sdk.config.get_allowed_ips`) and the host firewall are separate layers, and
323
462
  the interesting question is where they DISAGREE — an IP the app permits but the
324
463
  kernel drops looks exactly like a broken backend.
464
+
465
+ ``collapse=False`` keeps the per-IP sources alongside ``"Anywhere"`` — what
466
+ :func:`ensure_restricted` needs to know which IP rules already exist.
325
467
  """
326
468
  sysname = (system or _system()).lower()
327
469
  if sysname == "linux":
@@ -329,17 +471,27 @@ def port_sources(port: int, system: str | None = None) -> set[str] | None:
329
471
  if text is None:
330
472
  return None
331
473
  got = _parse_ufw(text, port)
332
- return {"Anywhere"} if "Anywhere" in got else got
474
+ return {"Anywhere"} if collapse and "Anywhere" in got else got
333
475
  if sysname == "windows":
334
476
  text = _run(["netsh", "advfirewall", "firewall", "show", "rule",
335
477
  "name=all", "dir=in"], timeout=25.0)
336
478
  if text is None:
337
479
  return None
338
480
  got = _parse_netsh(text, port)
339
- return {"Anywhere"} if "Anywhere" in got else got
481
+ return {"Anywhere"} if collapse and "Anywhere" in got else got
340
482
  return None # macOS pf and anything else: not readable this way
341
483
 
342
484
 
485
+ def _ufw_inactive() -> bool:
486
+ """True only when ufw positively reports ``Status: inactive``.
487
+
488
+ Then every ufw rule is decoration and the port is open regardless — the
489
+ "per-IP rules are applied but the flood still arrives" case.
490
+ """
491
+ text = _run(["ufw", "status"])
492
+ return bool(text) and "status: inactive" in text.lower()
493
+
494
+
343
495
  def _is_privileged() -> bool:
344
496
  if _system() == "windows":
345
497
  try:
@@ -382,10 +534,9 @@ def cli(argv: list[str]) -> int:
382
534
  print(f"Allowed : {', '.join(ips) if ips else '(none — loopback only)'}")
383
535
 
384
536
  # `--any` opens the port to every source and leaves the decision to
385
- # IPAllowlistMiddleware. This is what the post-update hook uses; see
386
- # `allow_any_rules` for why a per-IP kernel rule is the more fragile of the
387
- # two. It does NOT need an allowlist to be populated first, so it is checked
388
- # before the "nothing to open" bail below.
537
+ # IPAllowlistMiddleware. NOT the default any more (see `allow_any_rules`):
538
+ # every internet scanner then reaches Python. It does NOT need an allowlist to
539
+ # be populated first, so it is checked before the "nothing to open" bail below.
389
540
  if "--any" in argv or "--open" in argv:
390
541
  current = port_sources(port, sysname)
391
542
  if current:
@@ -403,7 +554,10 @@ def cli(argv: list[str]) -> int:
403
554
  for r in rules:
404
555
  print(f" {r}")
405
556
  print("\nAccess is still gated by the app allowlist above — an unlisted IP is\n"
406
- "refused by IPAllowlistMiddleware even with the port open.")
557
+ "refused by IPAllowlistMiddleware even with the port open.\n"
558
+ "WARNING: every scanner on the internet can then reach the panel process;\n"
559
+ "a brute-force flood costs CPU even though it is refused. The next\n"
560
+ "self-update narrows it back unless AGENTOS_FIREWALL_OPEN_ANY=1 is set.")
407
561
  if "--apply" not in argv:
408
562
  print("\n(dry run — nothing changed. Re-run with --apply to execute.)")
409
563
  return 0
@@ -417,6 +571,9 @@ def cli(argv: list[str]) -> int:
417
571
  return 0
418
572
 
419
573
  rules = allow_rules(ips, port, sysname)
574
+ if "Anywhere" in (port_sources(port, sysname) or set()):
575
+ rules.append(revoke_any_rule(port, sysname))
576
+ print(f"\nTCP {port} is currently open to ANY source — the last rule removes that.")
420
577
  print("\nFirewall rules for this host:\n")
421
578
  for r in rules:
422
579
  print(f" {r}")
@@ -445,27 +602,9 @@ def cli(argv: list[str]) -> int:
445
602
  print(f"\nerror: --apply needs {who}.")
446
603
  return 1
447
604
 
448
- failed = 0
449
- print()
450
- for r in rules:
451
- # The printed form carries `sudo` for humans; we are already privileged.
452
- cmd = r[len("sudo "):] if r.startswith("sudo ") else r
453
- try:
454
- proc = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
455
- except Exception as exc: # noqa: BLE001 — never leave the firewall half-known
456
- print(f" ✗ {cmd}\n {exc}")
457
- failed += 1
458
- continue
459
- tail = (proc.stdout or proc.stderr or "").strip().splitlines()
460
- note = tail[-1][:100] if tail else ""
461
- if proc.returncode == 0:
462
- print(f" ✓ {cmd}\n {note}" if note else f" ✓ {cmd}")
463
- else:
464
- print(f" ✗ {cmd} (exit {proc.returncode})\n {note}")
465
- failed += 1
466
-
467
- print(f"\n{len(rules) - failed}/{len(rules)} applied.")
468
- return 1 if failed else 0
605
+ changed, msg = ensure_restricted(port, apply=True, ips=ips)
606
+ print(f"\n{msg}")
607
+ return 0 if changed or "restricted to the allowlist" in msg else 1
469
608
 
470
609
 
471
610
  if __name__ == "__main__": # pragma: no cover
@@ -1,12 +1,12 @@
1
1
  [
2
2
  {
3
3
  "area": "Control panel",
4
- "changes_30d": 6,
4
+ "changes_30d": 7,
5
5
  "features_30d": 2,
6
6
  "fixes_30d": 1,
7
- "latest": "security: stop tracking .env (PyPI token) + FAQ: panel/SDK from another machine, Linux & Windows IP commands",
8
- "latest_pr": 187,
9
- "latest_date": "2026-10-28"
7
+ "latest": "security: restrict panel port to the allowlist, cheap refusals, auth-failure bans, connection cap",
8
+ "latest_pr": 188,
9
+ "latest_date": "2026-10-29"
10
10
  },
11
11
  {
12
12
  "area": "Help",
@@ -1,4 +1,14 @@
1
1
  [
2
+ {
3
+ "pr": 188,
4
+ "date": "2026-10-29",
5
+ "merged_at": "2026-10-29T17:17:14+05:00",
6
+ "type": "Other",
7
+ "area": "Control panel",
8
+ "title": "security: restrict panel port to the allowlist, cheap refusals, auth-failure bans, connection cap",
9
+ "version": "1.7.45",
10
+ "url": "https://github.com/consultancy-outfit/DevOpsBot-SDK/pull/188"
11
+ },
2
12
  {
3
13
  "pr": 187,
4
14
  "date": "2026-10-28",
@@ -0,0 +1,17 @@
1
+ [
2
+ {
3
+ "version": "1.7.45",
4
+ "commit": "1f30499",
5
+ "pushed_at": "2026-10-29T17:17:14+05:00",
6
+ "generated_at": "2026-10-29T12:18:17+00:00",
7
+ "commands": 58,
8
+ "commands_invalid": 0,
9
+ "commands_undocumented": 0,
10
+ "panel_pages": 11,
11
+ "tests": 2591,
12
+ "prs_7d": 29,
13
+ "prs_30d": 40,
14
+ "features_30d": 12,
15
+ "fixes_30d": 18
16
+ }
17
+ ]
@@ -230,7 +230,9 @@ async def add_to_allowlist(body: AllowlistEntry) -> dict:
230
230
  """
231
231
  from sdk.config import add_allowed_ip
232
232
 
233
- return {"added": body.ip, "allowed_ips": add_allowed_ip(body.ip)}
233
+ allowed = add_allowed_ip(body.ip)
234
+ return {"added": body.ip, "allowed_ips": allowed,
235
+ "firewall": await _sync_allowlist(body.ip, add=True)}
234
236
 
235
237
 
236
238
  @router.delete("/allowlist", dependencies=[_auth])
@@ -239,9 +241,32 @@ async def remove_from_allowlist(ip: str = Query(...)) -> dict:
239
241
  from sdk.config import remove_allowed_ip, PermanentAllowlistEntry
240
242
 
241
243
  try:
242
- return {"removed": ip, "allowed_ips": remove_allowed_ip(ip)}
244
+ allowed = remove_allowed_ip(ip)
243
245
  except PermanentAllowlistEntry as exc:
244
246
  raise HTTPException(status_code=400, detail=str(exc))
247
+ return {"removed": ip, "allowed_ips": allowed,
248
+ "firewall": await _sync_allowlist(ip, add=False)}
249
+
250
+
251
+ async def _sync_allowlist(ip: str, *, add: bool) -> str:
252
+ """Make an allowlist edit take effect at both layers; return the firewall note.
253
+
254
+ The middleware caches the allowlist, so it is told to re-read. The host
255
+ firewall now only admits allowlisted IPs (`sdk.firewall.ensure_restricted`),
256
+ so a new IP also needs its kernel rule — applied when the panel is privileged,
257
+ otherwise the note carries the command. Off the event loop: it shells out.
258
+ """
259
+ import asyncio
260
+
261
+ from sdk import firewall
262
+ from sdk.ui.ip_allowlist import invalidate
263
+
264
+ invalidate()
265
+ try:
266
+ _ok, msg = await asyncio.to_thread(firewall.apply_ip, ip, add=add)
267
+ except Exception as exc: # noqa: BLE001 — the allowlist change already stands
268
+ msg = f"firewall not updated: {exc}"
269
+ return msg
245
270
 
246
271
 
247
272
  # ── GitHub trigger (PR-only / push-only; never merges) ───────────────────────