praetorian-cli 2.4.4__tar.gz → 2.4.7__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 (154) hide show
  1. {praetorian_cli-2.4.4/praetorian_cli.egg-info → praetorian_cli-2.4.7}/PKG-INFO +3 -2
  2. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/README.md +2 -1
  3. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/add.py +90 -2
  4. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/delete.py +0 -17
  5. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/get.py +147 -6
  6. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/list.py +39 -5
  7. praetorian_cli-2.4.7/praetorian_cli/handlers/query.py +48 -0
  8. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/search.py +4 -2
  9. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/main.py +1 -0
  10. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/chariot.py +50 -29
  11. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/ad.py +6 -6
  12. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/assets.py +3 -3
  13. praetorian_cli-2.4.7/praetorian_cli/sdk/entities/conversations.py +107 -0
  14. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/credentials.py +7 -1
  15. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/risks.py +143 -58
  16. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/search.py +124 -9
  17. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/webpage.py +1 -1
  18. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/keychain.py +10 -2
  19. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/model/globals.py +18 -0
  20. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/model/query.py +99 -31
  21. praetorian_cli-2.4.7/praetorian_cli/sdk/test/test_my_by_raw_query.py +132 -0
  22. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_z_cli.py +0 -1
  23. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/commands/reporting.py +1 -1
  24. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/console.py +3 -0
  25. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/renderer.py +8 -0
  26. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7/praetorian_cli.egg-info}/PKG-INFO +3 -2
  27. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli.egg-info/SOURCES.txt +3 -0
  28. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/setup.cfg +1 -1
  29. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/LICENSE +0 -0
  30. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/MANIFEST.in +0 -0
  31. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/__init__.py +0 -0
  32. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/__init__.py +0 -0
  33. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/access.py +0 -0
  34. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/aegis.py +0 -0
  35. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/agent.py +0 -0
  36. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/chariot.py +0 -0
  37. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/cli_decorators.py +0 -0
  38. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/configure.py +0 -0
  39. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/critfinder.py +0 -0
  40. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/engagement.py +0 -0
  41. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/enrich.py +0 -0
  42. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/export.py +0 -0
  43. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/find.py +0 -0
  44. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/imports.py +0 -0
  45. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/link.py +0 -0
  46. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/report.py +0 -0
  47. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/run.py +0 -0
  48. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/script.py +0 -0
  49. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/ssh_utils.py +0 -0
  50. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/test.py +0 -0
  51. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/unlink.py +0 -0
  52. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/update.py +0 -0
  53. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/handlers/utils.py +0 -0
  54. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/runners/__init__.py +0 -0
  55. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/runners/local.py +0 -0
  56. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/scripts/__init__.py +0 -0
  57. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/scripts/commands/__init__.py +0 -0
  58. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/scripts/commands/nmap-example.py +0 -0
  59. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/scripts/utils.py +0 -0
  60. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/__init__.py +0 -0
  61. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/__init__.py +0 -0
  62. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/account_discovery.py +0 -0
  63. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/accounts.py +0 -0
  64. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/aegis.py +0 -0
  65. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/agents.py +0 -0
  66. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/attributes.py +0 -0
  67. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/capabilities.py +0 -0
  68. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/configurations.py +0 -0
  69. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/definitions.py +0 -0
  70. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/files.py +0 -0
  71. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/integrations.py +0 -0
  72. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/jobs.py +0 -0
  73. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/keys.py +0 -0
  74. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/preseeds.py +0 -0
  75. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/reports.py +0 -0
  76. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/scanners.py +0 -0
  77. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/schedules.py +0 -0
  78. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/schema.py +0 -0
  79. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/seeds.py +0 -0
  80. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/settings.py +0 -0
  81. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/statistics.py +0 -0
  82. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/entities/webhook.py +0 -0
  83. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/guard.py +0 -0
  84. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/mcp_server.py +0 -0
  85. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/model/__init__.py +0 -0
  86. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/model/aegis.py +0 -0
  87. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/model/utils.py +0 -0
  88. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/__init__.py +0 -0
  89. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/pytest.ini +0 -0
  90. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_access_aws.py +0 -0
  91. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_account.py +0 -0
  92. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_agent.py +0 -0
  93. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_asset.py +0 -0
  94. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_attribute.py +0 -0
  95. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_bulk.py +0 -0
  96. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_capabilities.py +0 -0
  97. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_configuration.py +0 -0
  98. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_conversation.py +0 -0
  99. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_credential_process.py +0 -0
  100. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_credentials.py +0 -0
  101. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_definition.py +0 -0
  102. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_export_cli.py +0 -0
  103. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_extend.py +0 -0
  104. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_file.py +0 -0
  105. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_job.py +0 -0
  106. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_key.py +0 -0
  107. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_local_runner.py +0 -0
  108. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_mcp.py +0 -0
  109. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_preseed.py +0 -0
  110. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_report.py +0 -0
  111. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_risk.py +0 -0
  112. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_run_cli.py +0 -0
  113. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_search.py +0 -0
  114. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_seed.py +0 -0
  115. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_setting.py +0 -0
  116. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_webhook.py +0 -0
  117. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/test_webpage.py +0 -0
  118. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/ui_mocks.py +0 -0
  119. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/sdk/test/utils.py +0 -0
  120. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/__init__.py +0 -0
  121. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/__init__.py +0 -0
  122. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/account_selector.py +0 -0
  123. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/__init__.py +0 -0
  124. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/cp.py +0 -0
  125. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/help.py +0 -0
  126. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/info.py +0 -0
  127. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/job.py +0 -0
  128. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/job_helpers.py +0 -0
  129. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/list.py +0 -0
  130. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/proxy.py +0 -0
  131. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/schedule.py +0 -0
  132. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/schedule_helpers.py +0 -0
  133. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/set.py +0 -0
  134. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/commands/ssh.py +0 -0
  135. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/constants.py +0 -0
  136. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/menu.py +0 -0
  137. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/theme.py +0 -0
  138. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/aegis/utils.py +0 -0
  139. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/__init__.py +0 -0
  140. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/commands/__init__.py +0 -0
  141. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/commands/accounts.py +0 -0
  142. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/commands/context.py +0 -0
  143. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/commands/data.py +0 -0
  144. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/commands/marcus.py +0 -0
  145. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/commands/search.py +0 -0
  146. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/commands/tools.py +0 -0
  147. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/console/context.py +0 -0
  148. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/conversation/__init__.py +0 -0
  149. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli/ui/conversation/textual_chat.py +0 -0
  150. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli.egg-info/dependency_links.txt +0 -0
  151. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli.egg-info/entry_points.txt +0 -0
  152. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli.egg-info/requires.txt +0 -0
  153. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/praetorian_cli.egg-info/top_level.txt +0 -0
  154. {praetorian_cli-2.4.4 → praetorian_cli-2.4.7}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praetorian-cli
3
- Version: 2.4.4
3
+ Version: 2.4.7
4
4
  Summary: For interacting with the Guard API
5
5
  Home-page: https://github.com/praetorian-inc/praetorian-cli
6
6
  Author: Praetorian
@@ -298,7 +298,8 @@ guard find "example.com"
298
298
  guard find "CVE-2024" --type risk
299
299
 
300
300
  # Evidence hydration
301
- guard get risk "#risk#example.com#CVE-2024-1234" --evidence
301
+ guard get risk "#risk#example.com#CVE-2024-1234" --evidence basic
302
+ guard get risk "#risk#example.com#CVE-2024-1234" --evidence full
302
303
 
303
304
  # Reports
304
305
  guard report generate --title "Q1 Pentest" --client "Acme Corp" --risks "status:OH"
@@ -274,7 +274,8 @@ guard find "example.com"
274
274
  guard find "CVE-2024" --type risk
275
275
 
276
276
  # Evidence hydration
277
- guard get risk "#risk#example.com#CVE-2024-1234" --evidence
277
+ guard get risk "#risk#example.com#CVE-2024-1234" --evidence basic
278
+ guard get risk "#risk#example.com#CVE-2024-1234" --evidence full
278
279
 
279
280
  # Reports
280
281
  guard report generate --title "Q1 Pentest" --client "Acme Corp" --risks "status:OH"
@@ -419,9 +419,97 @@ def credential_webauth(sdk, resource_key, label, headers):
419
419
  - guard add credential webauth -k "#webapplication#https://app.example.com" -l "Prod token" -H "Authorization=Bearer abc123" -H "X-Tenant=acme"
420
420
  """
421
421
  headers_dict = parse_kv_entries(headers, label='Header')
422
- parameters = {'method': 'static-token', 'headers': headers_dict}
422
+ return _add_webauth(sdk, resource_key, label, {'method': 'static-token', 'headers': headers_dict})
423
+
424
+
425
+ def _add_webauth(sdk, resource_key, label, parameters):
426
+ """Send a web-auth credential to the broker and echo the response. Returns the result."""
423
427
  try:
424
428
  result = sdk.credentials.add(resource_key, 'env-integration', 'web-auth', label, parameters)
425
- click.echo(json.dumps(result, indent=2))
426
429
  except Exception as e:
427
430
  error(f'Unable to add web-auth credential. Error: {e}')
431
+ click.echo(json.dumps(result, indent=2))
432
+ return result
433
+
434
+
435
+ @credential.command('webauth-discover')
436
+ @cli_handler
437
+ @click.option('-k', '--resource-key', required=True,
438
+ help='Web-application key (e.g., #webapplication#https://app.example.com)')
439
+ @click.option('-l', '--label', required=True, help='A human-readable label for the credential')
440
+ @click.option('--login-url', required=True, help='URL of the login page for the recorder agent to drive')
441
+ @click.option('-i', '--input', 'inputs', multiple=True, required=True,
442
+ help='Login input as key=value (e.g. username=alice, password=s3cret). Repeatable.')
443
+ @click.option('--hint', default=None,
444
+ help='Free-text guidance for the recorder agent (e.g. "MFA via authenticator app")')
445
+ def credential_webauth_discover(sdk, resource_key, label, login_url, inputs, hint):
446
+ """ Add a discover-login web-auth credential (AI records the login flow).
447
+
448
+ A recorder agent drives a browser through the login at --login-url using
449
+ the supplied inputs, then commits a replayable recipe for you. This is the
450
+ recommended path for browser-based logins — far more reliable than
451
+ hand-authoring browser steps in a recipe. Follow the recorder run with
452
+ `guard get conversation <id> --watch` using the returned conversation_id.
453
+
454
+ \b
455
+ Example usage:
456
+ - guard add credential webauth-discover -k "#webapplication#https://app.example.com" -l "Admin" --login-url https://app.example.com/login -i username=alice -i password=s3cret
457
+ """
458
+ inputs_dict = parse_kv_entries(inputs, label='Input')
459
+ parameters = {'method': 'discover-login', 'login_url': login_url, 'inputs': inputs_dict}
460
+ if hint:
461
+ parameters['hint'] = hint
462
+ _add_webauth(sdk, resource_key, label, parameters)
463
+
464
+
465
+ @credential.command('webauth-recipe')
466
+ @cli_handler
467
+ @click.option('-k', '--resource-key', required=True,
468
+ help='Web-application key (e.g., #webapplication#https://app.example.com)')
469
+ @click.option('-l', '--label', required=True, help='A human-readable label for the credential')
470
+ @click.option('--steps', 'steps_file', required=True, type=click.File('r'),
471
+ help='Path to a JSON file containing the recipe steps array (see step reference below)')
472
+ @click.option('-i', '--input', 'inputs', multiple=True,
473
+ help='Recipe input as key=value, referenced as {{key}} in steps (e.g. username=alice). Repeatable.')
474
+ def credential_webauth_recipe(sdk, resource_key, label, steps_file, inputs):
475
+ """ Add a dynamic-login web-auth credential from a recipe.
476
+
477
+ A recipe is a JSON array of steps that logs in and captures the resulting
478
+ auth header, replayed before each scan. PREFER HTTP-BASED STEPS
479
+ (http/extract/capture) — they replay reliably. For browser-driven logins
480
+ (SPAs, MFA, JS-set cookies) use `webauth-discover` instead; hand-authored
481
+ browser steps are brittle.
482
+
483
+ \b
484
+ HTTP-based step reference:
485
+ - http {method, url, headers?, body?} send a request; fields take {{vars}}
486
+ - extract {from, save_as, jsonpath?|regex?} bind a var from the last response
487
+ from: "response_body" (needs jsonpath OR one-group regex),
488
+ "response_header:<Name>", "response_cookie:<Name>",
489
+ "request_header:<Name>"
490
+ - capture {name, value} write an output header (value takes {{vars}}) [required]
491
+ Browser steps (discouraged — use webauth-discover): navigate, click, type,
492
+ totp, cookie, load_request.
493
+
494
+ \b
495
+ Example recipe.json (POST login, pull token from JSON, set Authorization):
496
+ [
497
+ {"kind": "http", "method": "POST", "url": "https://app.example.com/api/login",
498
+ "headers": {"Content-Type": "application/json"},
499
+ "body": "{\\"user\\":\\"{{username}}\\",\\"pass\\":\\"{{password}}\\"}"},
500
+ {"kind": "extract", "from": "response_body", "jsonpath": "$.token", "save_as": "token"},
501
+ {"kind": "capture", "name": "Authorization", "value": "Bearer {{token}}"}
502
+ ]
503
+
504
+ \b
505
+ Example usage:
506
+ - guard add credential webauth-recipe -k "#webapplication#https://app.example.com" -l "API login" --steps recipe.json -i username=alice -i password=s3cret
507
+ """
508
+ inputs_dict = parse_kv_entries(inputs, label='Input') if inputs else {}
509
+ try:
510
+ steps = json.load(steps_file)
511
+ except json.JSONDecodeError as e:
512
+ error(f'--steps is not valid JSON: {e}')
513
+
514
+ # The backend validates the recipe schema; we only ensure it is valid JSON.
515
+ _add_webauth(sdk, resource_key, label, {'method': 'dynamic-login', 'steps': steps, 'inputs': inputs_dict})
@@ -115,23 +115,6 @@ def file(chariot, filepath):
115
115
  chariot.files.delete(filepath)
116
116
 
117
117
 
118
- # Special command for deleting your account and all related information.
119
- @chariot.command()
120
- @cli_handler
121
- def purge(controller):
122
- """ Delete account and all related information
123
-
124
- Example usage:
125
- - guard purge
126
- """
127
- if click.confirm('This will delete all your data and revoke access, are you sure?', default=False):
128
- controller.purge()
129
- else:
130
- click.echo('Purge cancelled')
131
- return
132
- click.echo('Account deleted successfully')
133
-
134
-
135
118
  @delete.command()
136
119
  @cli_handler
137
120
  @click.argument('name', required=True)
@@ -1,4 +1,6 @@
1
+ import json
1
2
  import os
3
+ import time
2
4
 
3
5
  import click
4
6
 
@@ -36,7 +38,7 @@ def asset(chariot, key, details):
36
38
  @cli_handler
37
39
  @click.argument('key', required=True)
38
40
  @click.option('-d', '--details', is_flag=True, help='Further retrieve the attributes and affected assets of the risk')
39
- @click.option('-e', '--evidence', is_flag=True, help='Retrieve all evidence from all sources (attributes, webpages, files, definitions)')
41
+ @click.option('-e', '--evidence', type=click.Choice(['off', 'basic', 'full']), default='off', show_default=True, help='Evidence hydration mode. "basic" inlines one proof file; "full" inlines all proof files.')
40
42
  def risk(chariot, key, details, evidence):
41
43
  """ Get risk details
42
44
 
@@ -48,12 +50,10 @@ def risk(chariot, key, details, evidence):
48
50
  Example usages:
49
51
  - guard get risk "#risk#api.example.com#CVE-2024-23049"
50
52
  - guard get risk "#risk#api.example.com#CVE-2024-23049" --details
51
- - guard get risk "#risk#api.example.com#CVE-2024-23049" --evidence
53
+ - guard get risk "#risk#api.example.com#CVE-2024-23049" --evidence basic
54
+ - guard get risk "#risk#api.example.com#CVE-2024-23049" --evidence full
52
55
  """
53
- if evidence:
54
- print_json(chariot.risks.get(key, evidence=True))
55
- else:
56
- print_json(chariot.risks.get(key, details))
56
+ print_json(chariot.risks.get(key, details, evidence=evidence))
57
57
 
58
58
 
59
59
  @get.command()
@@ -389,3 +389,144 @@ def schedule(chariot, schedule_id):
389
389
  print_json(result)
390
390
  else:
391
391
  click.echo(f'Schedule not found: {schedule_id}')
392
+
393
+
394
+ @get.command('ttl-status')
395
+ @cli_handler
396
+ @click.argument('key', required=True)
397
+ @click.option('--format', 'fmt', type=click.Choice(['text', 'json']), default='text', show_default=True)
398
+ def ttl_status(chariot, key, fmt):
399
+ """ Explain why the TTL cron would (or would not) collect an entity
400
+
401
+ \b
402
+ A :TTL node is deleted only when its ttl has expired AND it has no
403
+ outgoing HAS_VULNERABILITY / HAS_PORT / HAS_WEBPAGE / HAS_MEMBER edge.
404
+ This shows the node's ttl plus the exact edges pinning it alive, so you
405
+ can chase the chain (e.g. WebApplication -HAS_WEBPAGE-> Webpage
406
+ -HAS_VULNERABILITY-> Risk) by re-running against a blocker's key.
407
+
408
+ \b
409
+ Example usages:
410
+ - guard get ttl-status "#webapplication#https://example.com:443/"
411
+ - guard get ttl-status "#webpage#https://example.com/login" --format json
412
+ """
413
+ status = chariot.search.ttl_status(key)
414
+
415
+ if fmt == 'json':
416
+ print_json(status)
417
+ return
418
+
419
+ ttl, blockers = status['ttl'], status['blockers']
420
+ click.echo(f"key: {status['key']}")
421
+ click.echo(f'ttl: {ttl}' + (' (expired)' if status['ttl_expired']
422
+ else ' (not expired)' if ttl else ' (none)'))
423
+ if blockers:
424
+ click.echo(f'blocked by: {len(blockers)} edge(s)')
425
+ for b in blockers:
426
+ click.echo(f" {b['edge']} -> {b['target']} (edge visited {b['visited']})")
427
+ else:
428
+ click.echo('blocked by: none')
429
+ click.echo(f"would delete: {status['would_delete']}")
430
+
431
+
432
+ @get.command()
433
+ @cli_handler
434
+ @click.argument('conversation_id', required=True)
435
+ @click.option('--format', 'fmt', type=click.Choice(['text', 'json', 'live']), default='text', show_default=True,
436
+ help='text/json render the current transcript; live polls and streams new messages until the conversation is no longer active')
437
+ @click.option('--full', is_flag=True, help='Do not truncate long tool inputs/responses in text mode')
438
+ def conversation(chariot, conversation_id, fmt, full):
439
+ """ Get a full conversation transcript, including every tool call
440
+
441
+ \b
442
+ Argument:
443
+ - CONVERSATION_ID: the uuid of a conversation (see 'guard list conversations')
444
+
445
+ \b
446
+ Example usages:
447
+ - guard get conversation e5e8db7d-9116-4d7a-a16a-e36680a78c14
448
+ - guard get conversation e5e8db7d-9116-4d7a-a16a-e36680a78c14 --full
449
+ - guard get conversation e5e8db7d-9116-4d7a-a16a-e36680a78c14 --format json
450
+ - guard get conversation e5e8db7d-9116-4d7a-a16a-e36680a78c14 --format live
451
+ """
452
+ if fmt == 'live':
453
+ watch_conversation(chariot, conversation_id, full)
454
+ return
455
+ convo = chariot.conversations.get(conversation_id)
456
+ if fmt == 'json':
457
+ print_json(convo)
458
+ return
459
+ _render_conversation(convo, full)
460
+
461
+
462
+ def watch_conversation(chariot, conversation_id, full=False, interval=3, timeout=600):
463
+ """Stream a conversation, printing new messages as they arrive. Stops when the
464
+ conversation leaves 'active' status or after `timeout` seconds."""
465
+ seen = 0
466
+ deadline = time.time() + timeout
467
+ while True:
468
+ convo = chariot.conversations.get(conversation_id)
469
+ ended = bool(convo.get('status')) and convo['status'] != 'active'
470
+ messages = convo['messages']
471
+ while seen < len(messages):
472
+ m = messages[seen]
473
+ # A tool call's response is folded into the same message and may
474
+ # arrive on a later poll; hold the message until it lands (unless
475
+ # the conversation has ended and nothing more is coming).
476
+ if not ended and m.get('tool') and m['tool'].get('response') is None:
477
+ break
478
+ _echo_message(m, full)
479
+ seen += 1
480
+ if ended:
481
+ click.secho(f"— conversation {convo['status']} —", fg='blue')
482
+ return
483
+ if time.time() >= deadline:
484
+ click.secho('— watch timed out (conversation still active) —', fg='yellow')
485
+ return
486
+ time.sleep(interval)
487
+
488
+
489
+ def _render_conversation(convo, full):
490
+ click.echo(click.style(f"Conversation {convo['uuid']}", bold=True) + f" ({convo['status']})")
491
+ if convo['topic']:
492
+ click.echo(f"Topic: {convo['topic']}")
493
+ if convo['created']:
494
+ click.echo(f"Created: {convo['created']}")
495
+ click.echo('─' * 70)
496
+ for m in convo['messages']:
497
+ _echo_message(m, full)
498
+
499
+
500
+ _ROLE_COLOR = {'user': 'cyan', 'chariot': 'green', 'system': 'magenta'}
501
+
502
+
503
+ def _echo_message(m, full):
504
+ if m.get('tool'):
505
+ _echo_tool(m['tool'], full)
506
+ else:
507
+ click.secho(f"[{m['role']}]", fg=_ROLE_COLOR.get(m['role'], 'white'))
508
+ click.echo(_indent(m['content']))
509
+ click.echo()
510
+
511
+
512
+ def _echo_tool(tool, full):
513
+ args = _as_text(tool['input'])
514
+ header = click.style(f"[tool call] {tool['name']}", fg='yellow')
515
+ click.echo(header + (f' {_truncate(args, 200, full)}' if args else ''))
516
+ if tool['response'] is not None:
517
+ click.echo(_indent('→ ' + _truncate(_as_text(tool['response']), 800, full)))
518
+
519
+
520
+ def _as_text(value):
521
+ if value is None or value == '':
522
+ return ''
523
+ return value if isinstance(value, str) else json.dumps(value, separators=(',', ':'))
524
+
525
+
526
+ def _truncate(s, limit, full):
527
+ return s if full or len(s) <= limit else f'{s[:limit]}… ({len(s)} chars, --full for all)'
528
+
529
+
530
+ def _indent(text):
531
+ return '\n'.join(' ' + line for line in str(text).splitlines())
532
+
@@ -31,7 +31,9 @@ def assets(chariot, filter, model_type, details, offset, page):
31
31
 
32
32
  @list.command()
33
33
  @list_params('group of the associated assets')
34
- def risks(chariot, filter, details, offset, page):
34
+ @click.option('-s', '--sort', 'sort_by', type=click.Choice(['visited', 'created', 'updated', 'name', 'status', 'source', 'title']), default=None, help='Sort risks by a field')
35
+ @click.option('-desc', '--desc', is_flag=True, default=False, help='Return data in descending order')
36
+ def risks(chariot, filter, details, offset, page, sort_by, desc):
35
37
  """ List risks
36
38
 
37
39
  Retrieve and display a list of risks.
@@ -42,8 +44,9 @@ def risks(chariot, filter, details, offset, page):
42
44
  - guard list risks --filter api.example.com
43
45
  - guard list risks --details
44
46
  - guard list risks --page all
47
+ - guard list risks --sort visited --desc
45
48
  """
46
- render_list_results(chariot.risks.list(filter, offset, pagination_size(page)), details)
49
+ render_list_results(chariot.risks.list(filter, offset, pagination_size(page), sort_by=sort_by, descending=desc), details)
47
50
 
48
51
 
49
52
  @list.command()
@@ -344,18 +347,22 @@ def keys(chariot, details, offset, page):
344
347
  render_list_results(chariot.keys.list(offset, pagination_size(page)), details)
345
348
 
346
349
  @list.command()
350
+ @click.option('-k', '--resource-key', 'resource_key', default=None,
351
+ help='Scope to credentials attached to this WebApplication (via HAS_CREDENTIAL)')
347
352
  @list_params('credential ID', has_details=False, has_filter=False)
348
- def credentials(chariot, offset, page):
353
+ def credentials(chariot, resource_key, offset, page):
349
354
  """ List credentials
350
355
 
351
- Retrieve and display a list of credentials.
356
+ Retrieve and display a list of credentials, optionally scoped to one
357
+ WebApplication.
352
358
 
353
359
  \b
354
360
  Example usages:
355
361
  - guard list credentials
356
362
  - guard list credentials --page all
363
+ - guard list credentials -k "#webapplication#https://app.example.com"
357
364
  """
358
- print_json(chariot.credentials.list(offset, pagination_size(page)))
365
+ print_json(chariot.credentials.list(offset, pagination_size(page), resource_key=resource_key))
359
366
 
360
367
 
361
368
  @list.command()
@@ -427,3 +434,30 @@ def schedules(chariot, details, offset, page):
427
434
  - guard list schedules --page all
428
435
  """
429
436
  render_list_results(chariot.schedules.list(pagination_size(page)), details)
437
+
438
+
439
+ @list.command()
440
+ @pagination
441
+ @cli_handler
442
+ @click.option('--scope', type=click.Choice(['user', 'tenant', 'all']), default='all', show_default=True,
443
+ help='user: your private conversations; tenant: shared public + hunt-owned; all: both')
444
+ @click.option('--format', 'fmt', type=click.Choice(['text', 'json']), default='text', show_default=True)
445
+ def conversations(chariot, offset, page, scope, fmt):
446
+ """ List Guard AI conversations, most recent first
447
+
448
+ \b
449
+ Example usages:
450
+ - guard list conversations
451
+ - guard list conversations --scope tenant
452
+ - guard list conversations --scope all --page all
453
+ - guard list conversations --format json
454
+ """
455
+ convos, offset = chariot.conversations.list(scope, offset or None, pagination_size(page))
456
+ if fmt == 'json':
457
+ print_json(dict(data=convos, offset=offset))
458
+ else:
459
+ for c in convos:
460
+ topic = ' '.join((c.get('topic') or '').split())
461
+ click.echo(f"{(c.get('uuid') or ''):38} {(c.get('created') or '')[:16]:16} "
462
+ f"{(c.get('status') or ''):8} {topic}")
463
+ render_offset(offset)
@@ -0,0 +1,48 @@
1
+ import click
2
+
3
+ from praetorian_cli.handlers.chariot import chariot
4
+ from praetorian_cli.handlers.cli_decorators import cli_handler
5
+ from praetorian_cli.handlers.utils import render_list_results, print_json, error
6
+ from praetorian_cli.sdk.model.globals import Kind
7
+
8
+
9
+ @chariot.command()
10
+ @cli_handler
11
+ @click.option('--json', 'raw_json', help='Raw graph query as a JSON string (see sdk search.by_query)')
12
+ @click.option('--anchor', help='Exact key of the node to traverse from (shorthand mode)')
13
+ @click.option('--rel', 'rels', multiple=True,
14
+ help='Relationship label(s) to follow, e.g. HAS_WEBPAGE. Omit to follow the common core edges.')
15
+ @click.option('--neighbor', type=click.Choice([k.value for k in Kind]), help='Constrain the neighbor kind')
16
+ @click.option('-d', '--details', is_flag=True, default=False, help='Show full records instead of just keys')
17
+ def query(chariot, raw_json, anchor, rels, neighbor, details):
18
+ """ Run an arbitrary graph query, including relationships.
19
+
20
+ \b
21
+ Two modes:
22
+
23
+ \b
24
+ - Raw: pass a full graph query as JSON (same shape the MCP tool uses).
25
+ guard query --json '{"node": {"labels": ["Asset"], "relationships": [
26
+ {"label": "HAS_VULNERABILITY", "target": {"labels": ["Risk"],
27
+ "filters": [{"field": "priority", "operator": ">=", "value": 70}]}}]}}'
28
+
29
+ \b
30
+ - Shorthand: one hop from an anchor node to its neighbors.
31
+ guard query --anchor "#webapplication#https://example.com" # every edge
32
+ guard query --anchor "#asset#example.com#1.2.3.4" --rel HAS_WEBPAGE
33
+ guard query --anchor "#webapplication#https://example.com" --rel HAS_WEBPAGE --neighbor webpage
34
+ guard query --anchor "#asset#example.com#1.2.3.4" --rel HAS_VULNERABILITY --neighbor risk --details
35
+ """
36
+ if raw_json:
37
+ render_list_results(chariot.search.by_query(raw_json), details)
38
+ return
39
+
40
+ if not anchor:
41
+ error('Provide either --json, or --anchor (with optional --rel).')
42
+
43
+ edges = chariot.search.relationships(anchor, list(rels), neighbor)
44
+ if details:
45
+ print_json(dict(data=edges))
46
+ else:
47
+ for e in edges:
48
+ click.echo(f"{e['label']:20} {e['target'].get('key', '')}")
@@ -15,7 +15,8 @@ from praetorian_cli.sdk.model.globals import Kind
15
15
  @click.option('-d', '--details', is_flag=True, default=False, help='Show detailed information')
16
16
  @click.option('-desc', '--desc', is_flag=True, default=False, help='Return data in descending order')
17
17
  @click.option('-g', '--global', 'global_', is_flag=True, default=False, help='Use the global data set')
18
- def search(chariot, term, count, kind, details, offset, page, desc, global_):
18
+ @click.option('-a', '--all-tenants', 'all_tenants', is_flag=True, default=False, help='Search across all tenants you are authorized to access')
19
+ def search(chariot, term, count, kind, details, offset, page, desc, global_, all_tenants):
19
20
  """ Query Guard for matches or counts using the search syntax
20
21
 
21
22
  \b
@@ -67,5 +68,6 @@ def search(chariot, term, count, kind, details, offset, page, desc, global_):
67
68
  error(f'Search term "{term}" requires the entity kind (--kind) argument.')
68
69
 
69
70
  render_list_results(
70
- chariot.search.by_term(term, kind, offset, pagination_size(page), descending=desc, global_=global_),
71
+ chariot.search.by_term(term, kind, offset, pagination_size(page), descending=desc, global_=global_,
72
+ all_tenants=all_tenants),
71
73
  details)
@@ -15,6 +15,7 @@ import praetorian_cli.handlers.run
15
15
  import praetorian_cli.handlers.imports
16
16
  import praetorian_cli.handlers.link
17
17
  import praetorian_cli.handlers.list
18
+ import praetorian_cli.handlers.query
18
19
  import praetorian_cli.handlers.report
19
20
  import praetorian_cli.handlers.script
20
21
  import praetorian_cli.handlers.search