praetorian-cli 2.4.7__tar.gz → 2.4.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {praetorian_cli-2.4.7/praetorian_cli.egg-info → praetorian_cli-2.4.9}/PKG-INFO +117 -1
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/README.md +115 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/access.py +125 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/add.py +28 -6
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/agent.py +21 -3
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/chariot.py +8 -1
- praetorian_cli-2.4.9/praetorian_cli/handlers/cli_decorators.py +658 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/configure.py +3 -1
- praetorian_cli-2.4.9/praetorian_cli/handlers/hunt.py +156 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/list.py +2 -1
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/run.py +34 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/ssh_utils.py +1 -28
- praetorian_cli-2.4.9/praetorian_cli/handlers/test.py +202 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/main.py +53 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/chariot.py +21 -14
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/accounts.py +19 -1
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/aegis.py +1 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/assets.py +0 -1
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/files.py +4 -2
- praetorian_cli-2.4.9/praetorian_cli/sdk/entities/hunts.py +99 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/preseeds.py +3 -3
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/seeds.py +9 -7
- praetorian_cli-2.4.9/praetorian_cli/sdk/exceptions.py +63 -0
- praetorian_cli-2.4.9/praetorian_cli/sdk/keychain.py +357 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/mcp_server.py +30 -8
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/model/aegis.py +28 -1
- praetorian_cli-2.4.9/praetorian_cli/sdk/test/test_access_github.py +201 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_account.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_agent.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_asset.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_attribute.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_capabilities.py +3 -2
- praetorian_cli-2.4.9/praetorian_cli/sdk/test/test_cli_errors.py +802 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_configuration.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_conversation.py +4 -3
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_definition.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_file.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_job.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_key.py +3 -2
- praetorian_cli-2.4.9/praetorian_cli/sdk/test/test_keychain.py +321 -0
- praetorian_cli-2.4.9/praetorian_cli/sdk/test/test_keychain_cache.py +50 -0
- praetorian_cli-2.4.9/praetorian_cli/sdk/test/test_mcp.py +243 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_my_by_raw_query.py +7 -7
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_preseed.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_risk.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_run_cli.py +33 -0
- praetorian_cli-2.4.9/praetorian_cli/sdk/test/test_sdk_exceptions.py +564 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_search.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_seed.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_setting.py +3 -2
- praetorian_cli-2.4.9/praetorian_cli/sdk/test/test_test_handler.py +560 -0
- praetorian_cli-2.4.9/praetorian_cli/sdk/test/test_test_target.py +369 -0
- praetorian_cli-2.4.9/praetorian_cli/sdk/test/test_update_check.py +2063 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_webhook.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_webpage.py +3 -2
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_z_cli.py +20 -6
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/utils.py +33 -3
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9/praetorian_cli.egg-info}/PKG-INFO +117 -1
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli.egg-info/SOURCES.txt +11 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli.egg-info/requires.txt +1 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/setup.cfg +2 -1
- praetorian_cli-2.4.7/praetorian_cli/handlers/cli_decorators.py +0 -83
- praetorian_cli-2.4.7/praetorian_cli/handlers/test.py +0 -29
- praetorian_cli-2.4.7/praetorian_cli/sdk/keychain.py +0 -200
- praetorian_cli-2.4.7/praetorian_cli/sdk/test/test_mcp.py +0 -28
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/LICENSE +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/MANIFEST.in +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/aegis.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/critfinder.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/delete.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/engagement.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/enrich.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/export.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/find.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/get.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/imports.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/link.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/query.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/report.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/script.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/search.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/unlink.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/update.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/handlers/utils.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/runners/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/runners/local.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/scripts/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/scripts/commands/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/scripts/commands/nmap-example.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/scripts/utils.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/account_discovery.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/ad.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/agents.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/attributes.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/capabilities.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/configurations.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/conversations.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/credentials.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/definitions.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/integrations.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/jobs.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/keys.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/reports.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/risks.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/scanners.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/schedules.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/schema.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/search.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/settings.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/statistics.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/webhook.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/entities/webpage.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/guard.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/model/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/model/globals.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/model/query.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/model/utils.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/pytest.ini +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_access_aws.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_bulk.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_credential_process.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_credentials.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_export_cli.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_extend.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_local_runner.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/test_report.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/sdk/test/ui_mocks.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/account_selector.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/cp.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/help.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/info.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/job.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/job_helpers.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/list.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/proxy.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/schedule.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/schedule_helpers.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/set.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/commands/ssh.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/constants.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/menu.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/theme.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/aegis/utils.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/commands/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/commands/accounts.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/commands/context.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/commands/data.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/commands/marcus.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/commands/reporting.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/commands/search.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/commands/tools.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/console.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/context.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/console/renderer.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/conversation/__init__.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli/ui/conversation/textual_chat.py +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli.egg-info/dependency_links.txt +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli.egg-info/entry_points.txt +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/praetorian_cli.egg-info/top_level.txt +0 -0
- {praetorian_cli-2.4.7 → praetorian_cli-2.4.9}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: praetorian-cli
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.9
|
|
4
4
|
Summary: For interacting with the Guard API
|
|
5
5
|
Home-page: https://github.com/praetorian-inc/praetorian-cli
|
|
6
6
|
Author: Praetorian
|
|
@@ -14,6 +14,7 @@ License-File: LICENSE
|
|
|
14
14
|
Requires-Dist: click>=8.1.7
|
|
15
15
|
Requires-Dist: boto3>=1.34.0
|
|
16
16
|
Requires-Dist: requests>=2.31.0
|
|
17
|
+
Requires-Dist: truststore>=0.10.0
|
|
17
18
|
Requires-Dist: pytest>=8.0.2
|
|
18
19
|
Requires-Dist: mcp>=1.12.2
|
|
19
20
|
Requires-Dist: anyio>=3.0.0
|
|
@@ -44,6 +45,7 @@ Dynamic: license-file
|
|
|
44
45
|
- [Signing up](#signing-up)
|
|
45
46
|
- [Authentication](#authentication)
|
|
46
47
|
- [Using the CLI](#using-the-cli)
|
|
48
|
+
- [Update checks](#update-checks)
|
|
47
49
|
- [Operators](#operators)
|
|
48
50
|
- [Interactive Console](#interactive-console)
|
|
49
51
|
- [Install local security tools (optional)](#install-local-security-tools-optional)
|
|
@@ -52,6 +54,7 @@ Dynamic: license-file
|
|
|
52
54
|
- [Developers](#developers)
|
|
53
55
|
- [SDK](#sdk)
|
|
54
56
|
- [Developing external scripts](#developing-external-scripts)
|
|
57
|
+
- [Running the test suite](#running-the-test-suite)
|
|
55
58
|
- [Contributing](#contributing)
|
|
56
59
|
- [Support](#support)
|
|
57
60
|
- [License](#license)
|
|
@@ -146,6 +149,10 @@ export PRAETORIAN_CLI_API_KEY_ID=your-api-key-id-here
|
|
|
146
149
|
export PRAETORIAN_CLI_API_KEY_SECRET=your-api-key-here
|
|
147
150
|
```
|
|
148
151
|
|
|
152
|
+
The backend API URL and the Cognito emulator endpoint (`aws_endpoint_url`)
|
|
153
|
+
must be HTTPS; the CLI refuses to send credentials over plaintext HTTP. For local development against a loopback endpoint (`localhost`,
|
|
154
|
+
`127.0.0.1`, `::1`), set `PRAETORIAN_CLI_ALLOW_HTTP_LOOPBACK=1`.
|
|
155
|
+
|
|
149
156
|
For more advanced configuration options or managing access in SSO organizations see
|
|
150
157
|
[the documentation on configuration](https://github.com/praetorian-inc/praetorian-cli/blob/main/docs/configure.md).
|
|
151
158
|
|
|
@@ -174,6 +181,72 @@ To get detailed information about a specific asset, run:
|
|
|
174
181
|
guard --account guard+example@praetorian.com get asset <ASSET_KEY>
|
|
175
182
|
```
|
|
176
183
|
|
|
184
|
+
## Update checks
|
|
185
|
+
|
|
186
|
+
After a command finishes, the CLI may check PyPI for a newer release of
|
|
187
|
+
`praetorian-cli` and print a three-line upgrade advisory to stderr. The check is
|
|
188
|
+
deliberately quiet and infrequent:
|
|
189
|
+
|
|
190
|
+
- **At most once every 24 hours.** Every attempt is recorded at
|
|
191
|
+
`${XDG_CACHE_HOME:-~/.cache}/praetorian-cli/update-check.json` *before* the
|
|
192
|
+
request is made, so a refresh that fails — offline, DNS, timeout, a malformed
|
|
193
|
+
response — is rate-limited exactly like one that succeeds, and keeps
|
|
194
|
+
advertising the last version it did learn. Between refreshes the check reads
|
|
195
|
+
that file and makes no network request at all — as long as the record is a
|
|
196
|
+
plain file that belongs to you and is no larger than 64 KiB. Anything else at
|
|
197
|
+
that path is ignored as though it were absent, so the next invocation refreshes
|
|
198
|
+
and replaces it rather than trusting what it found. Commands that start at the
|
|
199
|
+
same instant are excluded by a marker file created beside that record, so eight
|
|
200
|
+
concurrent invocations perform one refresh rather than eight — including when
|
|
201
|
+
the request fails instantly, since the attempt is recorded before it is made.
|
|
202
|
+
A process killed mid-refresh leaves the marker behind, which defers the next
|
|
203
|
+
refresh that is actually due rather than permitting an extra one — by up to a
|
|
204
|
+
minute, or up to two if the clock steps backwards in between, since a marker
|
|
205
|
+
counts as held while its age is within a minute in *either* direction. A
|
|
206
|
+
relative `XDG_CACHE_HOME` is
|
|
207
|
+
ignored in favour of `~/.cache`, as the base-directory spec requires —
|
|
208
|
+
honouring it would put a separate cache in every working directory and so
|
|
209
|
+
defeat the limit outright. If the cache directory cannot be written at all (a
|
|
210
|
+
read-only home, one owned by another user, or one reached through a symlink),
|
|
211
|
+
the check does not run: a probe whose rate we cannot limit is one we do not
|
|
212
|
+
send.
|
|
213
|
+
- **Only when a human is watching.** It is skipped unless **both** stdout and
|
|
214
|
+
stderr are a terminal, and skipped when `CI` or `GITHUB_ACTIONS` is set or
|
|
215
|
+
`TERM=dumb`. Piping either stream — `guard list assets | jq` — turns it off.
|
|
216
|
+
A script you launch by hand from your own terminal inherits your terminal, so
|
|
217
|
+
treat the opt-out below, not this gate, as the way to guarantee silence.
|
|
218
|
+
- **Never in place of your command's work.** A command that raises skips the
|
|
219
|
+
check, and a group that is only delegating to a subcommand leaves the check to
|
|
220
|
+
the subcommand that does the work. A command that reports an error but still
|
|
221
|
+
exits `0` is the one case where an advisory can follow a visible error.
|
|
222
|
+
- **Not on the path you run day to day.** The daily refresh passes a 2-second
|
|
223
|
+
timeout to `requests`, which bounds how long the server may go without
|
|
224
|
+
sending data — not total wall-clock, so DNS, TLS, redirects and a slow trickle
|
|
225
|
+
are outside it. Ordinary failures are swallowed and cannot change your exit
|
|
226
|
+
code; `Ctrl-C` and process-control exceptions raised during the check still
|
|
227
|
+
propagate, by design, so that interrupting the CLI always works.
|
|
228
|
+
|
|
229
|
+
### Disabling it
|
|
230
|
+
|
|
231
|
+
```zsh
|
|
232
|
+
export PRAETORIAN_CLI_DISABLE_UPDATE_CHECK=1
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`1`, `true`, `yes` or `on` (any case). Nothing is read or written — no request,
|
|
236
|
+
no cache file.
|
|
237
|
+
|
|
238
|
+
### Privacy
|
|
239
|
+
|
|
240
|
+
A refresh is an unauthenticated `GET` to `https://pypi.org/pypi/praetorian-cli/json`.
|
|
241
|
+
It sends no account, profile, command, or argument — only what any HTTPS request
|
|
242
|
+
inherently reveals to the server: your IP address and the fact that some
|
|
243
|
+
`praetorian-cli` install asked for the package index at that moment. Because
|
|
244
|
+
every attempt is recorded before it is made and concurrent attempts are
|
|
245
|
+
excluded, that happens at most once per day per user account on the machine —
|
|
246
|
+
several users each get their own cache — so your per-command usage cadence is
|
|
247
|
+
not exposed. If contacting
|
|
248
|
+
pypi.org at all is unacceptable in your environment, set the variable above.
|
|
249
|
+
|
|
177
250
|
# Operators
|
|
178
251
|
|
|
179
252
|
## Interactive Console
|
|
@@ -370,6 +443,49 @@ For developing scripts, you can refer to
|
|
|
370
443
|
this [readme file](https://github.com/praetorian-inc/praetorian-cli/blob/main/docs/script-development.md).
|
|
371
444
|
|
|
372
445
|
|
|
446
|
+
## Running the test suite
|
|
447
|
+
|
|
448
|
+
`guard test` runs the integration suites that ship with the package. **Some of
|
|
449
|
+
those suites create, modify and delete real entities in whatever account the
|
|
450
|
+
selected profile points at**, so the command is gated.
|
|
451
|
+
|
|
452
|
+
Suites are selected with `-s`/`--suite`:
|
|
453
|
+
|
|
454
|
+
| Suite | What it runs | Touches a live account? |
|
|
455
|
+
| --- | --- | --- |
|
|
456
|
+
| `safe` *(default)* | Local, offline unit tests | No |
|
|
457
|
+
| `coherence` | End-to-end entity lifecycle tests | **Yes — creates and deletes assets, risks, settings, configurations** |
|
|
458
|
+
| `cli` | CLI-level integration tests | **Yes — mutating** |
|
|
459
|
+
| `tui` | Terminal UI tests | No |
|
|
460
|
+
|
|
461
|
+
A bare `guard test` runs the `safe` suite only. It executes with a temporary
|
|
462
|
+
`HOME` and with credential environment variables stripped, so it cannot reach
|
|
463
|
+
your real credentials.
|
|
464
|
+
|
|
465
|
+
The mutating suites additionally require:
|
|
466
|
+
|
|
467
|
+
1. **An explicit profile and account.** Both must be given and non-empty —
|
|
468
|
+
`guard --profile <profile> --account <account> test --suite coherence`.
|
|
469
|
+
There is no fallback: an unset or blank value is an error, so the suite can
|
|
470
|
+
never quietly run against a default profile.
|
|
471
|
+
2. **Interactive confirmation.** The command prints the suite, profile, account,
|
|
472
|
+
and whether the suite is live and mutating, then asks you to confirm.
|
|
473
|
+
Declining — or a non-interactive invocation, which cannot confirm — exits
|
|
474
|
+
non-zero without running any test.
|
|
475
|
+
|
|
476
|
+
```zsh
|
|
477
|
+
# Safe by default — no account is touched.
|
|
478
|
+
guard test
|
|
479
|
+
|
|
480
|
+
# Mutating suite: target must be explicit, and you must confirm.
|
|
481
|
+
guard --profile "United States" --account guard+scratch@praetorian.com \
|
|
482
|
+
test --suite coherence
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Never point a mutating suite at a production account. Use a dedicated scratch
|
|
486
|
+
account whose contents you are willing to lose.
|
|
487
|
+
|
|
488
|
+
|
|
373
489
|
## Contributing
|
|
374
490
|
|
|
375
491
|
We welcome contributions from the community, from scripts, to the core CLI and SDK. To contribute, fork this
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- [Signing up](#signing-up)
|
|
21
21
|
- [Authentication](#authentication)
|
|
22
22
|
- [Using the CLI](#using-the-cli)
|
|
23
|
+
- [Update checks](#update-checks)
|
|
23
24
|
- [Operators](#operators)
|
|
24
25
|
- [Interactive Console](#interactive-console)
|
|
25
26
|
- [Install local security tools (optional)](#install-local-security-tools-optional)
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
- [Developers](#developers)
|
|
29
30
|
- [SDK](#sdk)
|
|
30
31
|
- [Developing external scripts](#developing-external-scripts)
|
|
32
|
+
- [Running the test suite](#running-the-test-suite)
|
|
31
33
|
- [Contributing](#contributing)
|
|
32
34
|
- [Support](#support)
|
|
33
35
|
- [License](#license)
|
|
@@ -122,6 +124,10 @@ export PRAETORIAN_CLI_API_KEY_ID=your-api-key-id-here
|
|
|
122
124
|
export PRAETORIAN_CLI_API_KEY_SECRET=your-api-key-here
|
|
123
125
|
```
|
|
124
126
|
|
|
127
|
+
The backend API URL and the Cognito emulator endpoint (`aws_endpoint_url`)
|
|
128
|
+
must be HTTPS; the CLI refuses to send credentials over plaintext HTTP. For local development against a loopback endpoint (`localhost`,
|
|
129
|
+
`127.0.0.1`, `::1`), set `PRAETORIAN_CLI_ALLOW_HTTP_LOOPBACK=1`.
|
|
130
|
+
|
|
125
131
|
For more advanced configuration options or managing access in SSO organizations see
|
|
126
132
|
[the documentation on configuration](https://github.com/praetorian-inc/praetorian-cli/blob/main/docs/configure.md).
|
|
127
133
|
|
|
@@ -150,6 +156,72 @@ To get detailed information about a specific asset, run:
|
|
|
150
156
|
guard --account guard+example@praetorian.com get asset <ASSET_KEY>
|
|
151
157
|
```
|
|
152
158
|
|
|
159
|
+
## Update checks
|
|
160
|
+
|
|
161
|
+
After a command finishes, the CLI may check PyPI for a newer release of
|
|
162
|
+
`praetorian-cli` and print a three-line upgrade advisory to stderr. The check is
|
|
163
|
+
deliberately quiet and infrequent:
|
|
164
|
+
|
|
165
|
+
- **At most once every 24 hours.** Every attempt is recorded at
|
|
166
|
+
`${XDG_CACHE_HOME:-~/.cache}/praetorian-cli/update-check.json` *before* the
|
|
167
|
+
request is made, so a refresh that fails — offline, DNS, timeout, a malformed
|
|
168
|
+
response — is rate-limited exactly like one that succeeds, and keeps
|
|
169
|
+
advertising the last version it did learn. Between refreshes the check reads
|
|
170
|
+
that file and makes no network request at all — as long as the record is a
|
|
171
|
+
plain file that belongs to you and is no larger than 64 KiB. Anything else at
|
|
172
|
+
that path is ignored as though it were absent, so the next invocation refreshes
|
|
173
|
+
and replaces it rather than trusting what it found. Commands that start at the
|
|
174
|
+
same instant are excluded by a marker file created beside that record, so eight
|
|
175
|
+
concurrent invocations perform one refresh rather than eight — including when
|
|
176
|
+
the request fails instantly, since the attempt is recorded before it is made.
|
|
177
|
+
A process killed mid-refresh leaves the marker behind, which defers the next
|
|
178
|
+
refresh that is actually due rather than permitting an extra one — by up to a
|
|
179
|
+
minute, or up to two if the clock steps backwards in between, since a marker
|
|
180
|
+
counts as held while its age is within a minute in *either* direction. A
|
|
181
|
+
relative `XDG_CACHE_HOME` is
|
|
182
|
+
ignored in favour of `~/.cache`, as the base-directory spec requires —
|
|
183
|
+
honouring it would put a separate cache in every working directory and so
|
|
184
|
+
defeat the limit outright. If the cache directory cannot be written at all (a
|
|
185
|
+
read-only home, one owned by another user, or one reached through a symlink),
|
|
186
|
+
the check does not run: a probe whose rate we cannot limit is one we do not
|
|
187
|
+
send.
|
|
188
|
+
- **Only when a human is watching.** It is skipped unless **both** stdout and
|
|
189
|
+
stderr are a terminal, and skipped when `CI` or `GITHUB_ACTIONS` is set or
|
|
190
|
+
`TERM=dumb`. Piping either stream — `guard list assets | jq` — turns it off.
|
|
191
|
+
A script you launch by hand from your own terminal inherits your terminal, so
|
|
192
|
+
treat the opt-out below, not this gate, as the way to guarantee silence.
|
|
193
|
+
- **Never in place of your command's work.** A command that raises skips the
|
|
194
|
+
check, and a group that is only delegating to a subcommand leaves the check to
|
|
195
|
+
the subcommand that does the work. A command that reports an error but still
|
|
196
|
+
exits `0` is the one case where an advisory can follow a visible error.
|
|
197
|
+
- **Not on the path you run day to day.** The daily refresh passes a 2-second
|
|
198
|
+
timeout to `requests`, which bounds how long the server may go without
|
|
199
|
+
sending data — not total wall-clock, so DNS, TLS, redirects and a slow trickle
|
|
200
|
+
are outside it. Ordinary failures are swallowed and cannot change your exit
|
|
201
|
+
code; `Ctrl-C` and process-control exceptions raised during the check still
|
|
202
|
+
propagate, by design, so that interrupting the CLI always works.
|
|
203
|
+
|
|
204
|
+
### Disabling it
|
|
205
|
+
|
|
206
|
+
```zsh
|
|
207
|
+
export PRAETORIAN_CLI_DISABLE_UPDATE_CHECK=1
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`1`, `true`, `yes` or `on` (any case). Nothing is read or written — no request,
|
|
211
|
+
no cache file.
|
|
212
|
+
|
|
213
|
+
### Privacy
|
|
214
|
+
|
|
215
|
+
A refresh is an unauthenticated `GET` to `https://pypi.org/pypi/praetorian-cli/json`.
|
|
216
|
+
It sends no account, profile, command, or argument — only what any HTTPS request
|
|
217
|
+
inherently reveals to the server: your IP address and the fact that some
|
|
218
|
+
`praetorian-cli` install asked for the package index at that moment. Because
|
|
219
|
+
every attempt is recorded before it is made and concurrent attempts are
|
|
220
|
+
excluded, that happens at most once per day per user account on the machine —
|
|
221
|
+
several users each get their own cache — so your per-command usage cadence is
|
|
222
|
+
not exposed. If contacting
|
|
223
|
+
pypi.org at all is unacceptable in your environment, set the variable above.
|
|
224
|
+
|
|
153
225
|
# Operators
|
|
154
226
|
|
|
155
227
|
## Interactive Console
|
|
@@ -346,6 +418,49 @@ For developing scripts, you can refer to
|
|
|
346
418
|
this [readme file](https://github.com/praetorian-inc/praetorian-cli/blob/main/docs/script-development.md).
|
|
347
419
|
|
|
348
420
|
|
|
421
|
+
## Running the test suite
|
|
422
|
+
|
|
423
|
+
`guard test` runs the integration suites that ship with the package. **Some of
|
|
424
|
+
those suites create, modify and delete real entities in whatever account the
|
|
425
|
+
selected profile points at**, so the command is gated.
|
|
426
|
+
|
|
427
|
+
Suites are selected with `-s`/`--suite`:
|
|
428
|
+
|
|
429
|
+
| Suite | What it runs | Touches a live account? |
|
|
430
|
+
| --- | --- | --- |
|
|
431
|
+
| `safe` *(default)* | Local, offline unit tests | No |
|
|
432
|
+
| `coherence` | End-to-end entity lifecycle tests | **Yes — creates and deletes assets, risks, settings, configurations** |
|
|
433
|
+
| `cli` | CLI-level integration tests | **Yes — mutating** |
|
|
434
|
+
| `tui` | Terminal UI tests | No |
|
|
435
|
+
|
|
436
|
+
A bare `guard test` runs the `safe` suite only. It executes with a temporary
|
|
437
|
+
`HOME` and with credential environment variables stripped, so it cannot reach
|
|
438
|
+
your real credentials.
|
|
439
|
+
|
|
440
|
+
The mutating suites additionally require:
|
|
441
|
+
|
|
442
|
+
1. **An explicit profile and account.** Both must be given and non-empty —
|
|
443
|
+
`guard --profile <profile> --account <account> test --suite coherence`.
|
|
444
|
+
There is no fallback: an unset or blank value is an error, so the suite can
|
|
445
|
+
never quietly run against a default profile.
|
|
446
|
+
2. **Interactive confirmation.** The command prints the suite, profile, account,
|
|
447
|
+
and whether the suite is live and mutating, then asks you to confirm.
|
|
448
|
+
Declining — or a non-interactive invocation, which cannot confirm — exits
|
|
449
|
+
non-zero without running any test.
|
|
450
|
+
|
|
451
|
+
```zsh
|
|
452
|
+
# Safe by default — no account is touched.
|
|
453
|
+
guard test
|
|
454
|
+
|
|
455
|
+
# Mutating suite: target must be explicit, and you must confirm.
|
|
456
|
+
guard --profile "United States" --account guard+scratch@praetorian.com \
|
|
457
|
+
test --suite coherence
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Never point a mutating suite at a production account. Use a dedicated scratch
|
|
461
|
+
account whose contents you are willing to lose.
|
|
462
|
+
|
|
463
|
+
|
|
349
464
|
## Contributing
|
|
350
465
|
|
|
351
466
|
We welcome contributions from the community, from scripts, to the core CLI and SDK. To contribute, fork this
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import shlex
|
|
2
3
|
|
|
3
4
|
import click
|
|
4
5
|
|
|
5
6
|
from praetorian_cli.handlers.chariot import chariot
|
|
6
7
|
from praetorian_cli.handlers.cli_decorators import cli_handler
|
|
8
|
+
from praetorian_cli.handlers.utils import error
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
def extract_prefix(email):
|
|
@@ -207,3 +209,126 @@ def aws(sdk, account, prefix):
|
|
|
207
209
|
click.echo(f'Wrote {len(all_profiles)} AWS profile(s) to ~/.aws/config:')
|
|
208
210
|
for profile_name, _ in all_profiles:
|
|
209
211
|
click.echo(f' {profile_name}')
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@access.command()
|
|
215
|
+
@cli_handler
|
|
216
|
+
@click.option('--org', default=None,
|
|
217
|
+
help='GitHub organization to retrieve a token for. Required when the '
|
|
218
|
+
'account has more than one GitHub integration.')
|
|
219
|
+
@click.option('--format', 'output_format', default='token',
|
|
220
|
+
type=click.Choice(['token', 'env']), show_default=True,
|
|
221
|
+
help='Output shape. token: bare token on stdout. '
|
|
222
|
+
'env: export GITHUB_TOKEN / GH_TOKEN lines for `eval`.')
|
|
223
|
+
def github(sdk, org, output_format):
|
|
224
|
+
"""Retrieve a temporary GitHub App installation token.
|
|
225
|
+
|
|
226
|
+
Looks up github integrations for the active account, asks the broker to
|
|
227
|
+
mint a 1-hour App installation token, and prints it. Guard only brokers
|
|
228
|
+
temporary App installation tokens; static PATs are refused server-side.
|
|
229
|
+
|
|
230
|
+
The account is inherited from the top-level guard --account flag.
|
|
231
|
+
|
|
232
|
+
\b
|
|
233
|
+
Example usages:
|
|
234
|
+
- guard access github
|
|
235
|
+
- guard --account chariot+client@praetorian.com access github --org acme-inc
|
|
236
|
+
- eval "$(guard access github --format env)" && gh auth status
|
|
237
|
+
"""
|
|
238
|
+
account = sdk.keychain.account
|
|
239
|
+
|
|
240
|
+
integrations, _ = sdk.integrations.list(name_filter='github')
|
|
241
|
+
if not integrations:
|
|
242
|
+
error(f'No GitHub integrations found for account {account}')
|
|
243
|
+
|
|
244
|
+
integration = _select_github_integration(integrations, org)
|
|
245
|
+
target = integration.get('value') or integration.get('key', '')
|
|
246
|
+
|
|
247
|
+
token = _fetch_github_app_token(sdk, integration.get('key', ''), target)
|
|
248
|
+
if not token:
|
|
249
|
+
error(f'No GitHub App installation token was retrieved for {target}.')
|
|
250
|
+
|
|
251
|
+
_print_github_token(token, output_format)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _select_github_integration(integrations, org):
|
|
255
|
+
"""Pick the one integration to mint a token for.
|
|
256
|
+
|
|
257
|
+
A single integration is used as-is. With several, --org disambiguates by
|
|
258
|
+
matching the integration value (a repo/org URL) or its trailing path
|
|
259
|
+
segment; ambiguity without --org is an error rather than a guess.
|
|
260
|
+
"""
|
|
261
|
+
if org:
|
|
262
|
+
matches = [i for i in integrations if _matches_org(i, org)]
|
|
263
|
+
if not matches:
|
|
264
|
+
error(f'No GitHub integration matching --org {org!r}. Available:\n'
|
|
265
|
+
+ _integration_list(integrations))
|
|
266
|
+
if len(matches) > 1:
|
|
267
|
+
error(f'--org {org!r} matches more than one GitHub integration:\n'
|
|
268
|
+
+ _integration_list(matches))
|
|
269
|
+
return matches[0]
|
|
270
|
+
|
|
271
|
+
if len(integrations) > 1:
|
|
272
|
+
error('This account has more than one GitHub integration. '
|
|
273
|
+
'Select one with --org:\n' + _integration_list(integrations))
|
|
274
|
+
|
|
275
|
+
return integrations[0]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _matches_org(integration, org):
|
|
279
|
+
value = (integration.get('value') or '').rstrip('/')
|
|
280
|
+
org = org.strip().rstrip('/').lower()
|
|
281
|
+
return value.lower() == org or value.rsplit('/', 1)[-1].lower() == org
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _integration_list(integrations):
|
|
285
|
+
return '\n'.join(f' {i.get("value") or i.get("key", "")}' for i in integrations)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _fetch_github_app_token(sdk, integration_key, target):
|
|
289
|
+
"""Resolve a github credential via the broker.
|
|
290
|
+
|
|
291
|
+
Github integrations don't use the standard "Asset + Credential record"
|
|
292
|
+
pattern that from-parent walks: the OAuth installation flow stores the
|
|
293
|
+
binding (installation_id, value) directly on the Account record, and the
|
|
294
|
+
broker reads it from SSM keyed by the integration's own key
|
|
295
|
+
(backend/pkg/services/account/integration_utils.go writes
|
|
296
|
+
aws.Secrets.Set(account.Key, account.Secret); the github handler reads
|
|
297
|
+
aws.Secrets.Get(request.CredentialID)). So for github, CredentialID *is*
|
|
298
|
+
the integration key, and the right resolution is by-target.
|
|
299
|
+
|
|
300
|
+
Returns the token string on success, None after printing a stderr message
|
|
301
|
+
on failure.
|
|
302
|
+
"""
|
|
303
|
+
try:
|
|
304
|
+
result = sdk.credentials.get(
|
|
305
|
+
integration_key, 'env-integration', 'github', ['token'],
|
|
306
|
+
resolution='by-target',
|
|
307
|
+
)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
msg = str(e)
|
|
310
|
+
if '[403]' in msg or 'unauthorized' in msg.lower():
|
|
311
|
+
click.echo(
|
|
312
|
+
f'{target}: Guard denied the request. End-user retrieval of '
|
|
313
|
+
'GitHub App installation tokens may not be enabled on this '
|
|
314
|
+
'deployment yet.',
|
|
315
|
+
err=True,
|
|
316
|
+
)
|
|
317
|
+
else:
|
|
318
|
+
click.echo(f'{target}: {msg.splitlines()[0]}', err=True)
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
token = (result or {}).get('credentialValue', {}).get('github')
|
|
322
|
+
if not isinstance(token, str) or not token:
|
|
323
|
+
click.echo(f'{target}: broker returned no token', err=True)
|
|
324
|
+
return None
|
|
325
|
+
return token
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _print_github_token(token, output_format):
|
|
329
|
+
if output_format == 'env':
|
|
330
|
+
quoted = shlex.quote(token)
|
|
331
|
+
click.echo(f'export GITHUB_TOKEN={quoted}')
|
|
332
|
+
click.echo(f'export GH_TOKEN={quoted}')
|
|
333
|
+
else:
|
|
334
|
+
click.echo(token)
|
|
@@ -61,16 +61,16 @@ def asset(sdk, identifier, group, asset_type, status, surface, resource_type):
|
|
|
61
61
|
@add.command()
|
|
62
62
|
@cli_handler
|
|
63
63
|
@click.argument('path')
|
|
64
|
-
@click.option('-n', '--name', help='
|
|
65
|
-
|
|
64
|
+
@click.option('-n', '--name', help='Destination path in Guard storage. Without this flag, the file is placed automatically')
|
|
65
|
+
@click.option('--public', 'is_public', is_flag=True, default=False, help='Share file with the customer (Praetorian users only)')
|
|
66
|
+
def file(sdk, path, name, is_public):
|
|
66
67
|
""" Upload a file
|
|
67
68
|
|
|
68
69
|
This commands takes the path to a local file and uploads it to the
|
|
69
70
|
Guard file system. The Guard file system is where the platform
|
|
70
71
|
stores proofs of exploit, risk definitions, and other supporting data.
|
|
71
72
|
|
|
72
|
-
User files
|
|
73
|
-
at https://guard.praetorian.com/app/files
|
|
73
|
+
User files appear in the app at https://guard.praetorian.com/app/files
|
|
74
74
|
|
|
75
75
|
\b
|
|
76
76
|
Arguments:
|
|
@@ -79,10 +79,32 @@ def file(sdk, path, name):
|
|
|
79
79
|
\b
|
|
80
80
|
Example usages:
|
|
81
81
|
- guard add file ./file.txt
|
|
82
|
-
- guard add file ./file.txt --
|
|
82
|
+
- guard add file ./file.txt --public
|
|
83
|
+
- guard add file ./file.txt --name "custom/path/file.txt"
|
|
83
84
|
"""
|
|
84
85
|
try:
|
|
85
|
-
|
|
86
|
+
expanded = os.path.expanduser(path)
|
|
87
|
+
filename = os.path.basename(expanded)
|
|
88
|
+
praetorian = False
|
|
89
|
+
|
|
90
|
+
if name:
|
|
91
|
+
# if name is provided, use it as the destination path regardless of whether
|
|
92
|
+
# the path follows the Guard file system filepath conventions. If it does not
|
|
93
|
+
# follow the conventions, it will still be uploaded but it will not be
|
|
94
|
+
# displayed in the Guard app UI.
|
|
95
|
+
dest_path = name
|
|
96
|
+
elif sdk.is_praetorian_user():
|
|
97
|
+
if is_public:
|
|
98
|
+
dest_path = f'home/shared-with/{filename}'
|
|
99
|
+
else:
|
|
100
|
+
dest_path = f'home/internal/{filename}'
|
|
101
|
+
praetorian = True
|
|
102
|
+
else:
|
|
103
|
+
dest_path = f'home/shared-by/{filename}'
|
|
104
|
+
|
|
105
|
+
sdk.files.add(expanded, dest_path, praetorian=praetorian)
|
|
106
|
+
size = os.path.getsize(expanded)
|
|
107
|
+
click.echo(f'Uploaded {expanded} -> {dest_path} ({size} bytes)')
|
|
86
108
|
except Exception as e:
|
|
87
109
|
error(f'Unable to upload file {path}. Error: {e}')
|
|
88
110
|
|
|
@@ -7,6 +7,11 @@ from praetorian_cli.handlers.chariot import chariot
|
|
|
7
7
|
from praetorian_cli.handlers.cli_decorators import cli_handler
|
|
8
8
|
from praetorian_cli.handlers.utils import error
|
|
9
9
|
|
|
10
|
+
# Default MCP tool allow profile: read-only query/list/get tools. Sensitive
|
|
11
|
+
# tools (see praetorian_cli.sdk.mcp_server.SENSITIVE_TOOL_PATTERNS) are never
|
|
12
|
+
# matched by these wildcards — they require an exact-name -a entry.
|
|
13
|
+
DEFAULT_MCP_TOOLS = ['search_by_query', '*_list', '*_get']
|
|
14
|
+
|
|
10
15
|
|
|
11
16
|
@chariot.group()
|
|
12
17
|
def agent():
|
|
@@ -38,19 +43,28 @@ def mcp():
|
|
|
38
43
|
|
|
39
44
|
@mcp.command()
|
|
40
45
|
@cli_handler
|
|
41
|
-
@click.option('--allowed', '-a', type=str, multiple=True, default=
|
|
46
|
+
@click.option('--allowed', '-a', type=str, multiple=True, default=DEFAULT_MCP_TOOLS)
|
|
42
47
|
def start(sdk, allowed):
|
|
43
48
|
""" Starts the Guard MCP server
|
|
44
49
|
|
|
50
|
+
The default profile exposes read-only query/list/get tools. Tools that
|
|
51
|
+
return or manage secrets (accounts_*, credentials_*, integrations_*,
|
|
52
|
+
keys_*, webhook_*) are never matched by wildcard patterns — the default
|
|
53
|
+
profile included — and are only exposed by passing the exact tool name
|
|
54
|
+
with -a (e.g. -a credentials_get). Opting in accounts_assume_role is
|
|
55
|
+
higher-consequence than a read tool: it switches the session's tenant for
|
|
56
|
+
all subsequent tool calls.
|
|
57
|
+
|
|
45
58
|
\b
|
|
46
59
|
Example usages:
|
|
47
60
|
- guard agent mcp start
|
|
48
61
|
- guard agent mcp start -a search_by_term -a risk_add
|
|
49
62
|
- guard agent mcp start -a search_* -a risk_add
|
|
63
|
+
- guard agent mcp start -a credentials_get # exact name required for sensitive tools
|
|
50
64
|
|
|
51
65
|
\b
|
|
52
66
|
Claude code configuration/usage:
|
|
53
|
-
- claude mcp add chariot -- guard agent mcp start # read-only
|
|
67
|
+
- claude mcp add chariot -- guard agent mcp start # default read-only, non-sensitive tools
|
|
54
68
|
- claude mcp add chariot -- guard agent mcp start -a search_by_query -a risk_add -a asset_add # select write tools
|
|
55
69
|
- claude "show me my chariot assets from the example.com domain"
|
|
56
70
|
- claude "show me my chariot assets with port 22 open"
|
|
@@ -61,11 +75,15 @@ def start(sdk, allowed):
|
|
|
61
75
|
sdk.agents.start_mcp_server(allowed)
|
|
62
76
|
|
|
63
77
|
@mcp.command()
|
|
64
|
-
@click.option('--allowed', '-a', type=str, multiple=True, default=
|
|
78
|
+
@click.option('--allowed', '-a', type=str, multiple=True, default=DEFAULT_MCP_TOOLS)
|
|
65
79
|
@cli_handler
|
|
66
80
|
def tools(sdk, allowed):
|
|
67
81
|
""" Lists available mcp tools
|
|
68
82
|
|
|
83
|
+
Sensitive tools (accounts_*, credentials_*, integrations_*, keys_*,
|
|
84
|
+
webhook_*) never match wildcard patterns and appear only when named
|
|
85
|
+
exactly with -a.
|
|
86
|
+
|
|
69
87
|
\b
|
|
70
88
|
Example usages:
|
|
71
89
|
- guard agent mcp tools
|
|
@@ -4,7 +4,14 @@ import click
|
|
|
4
4
|
@click.group()
|
|
5
5
|
@click.pass_context
|
|
6
6
|
def chariot(click_context):
|
|
7
|
-
|
|
7
|
+
from praetorian_cli.handlers.test import TestTarget
|
|
8
|
+
|
|
9
|
+
if isinstance(click_context.obj, TestTarget):
|
|
10
|
+
return
|
|
11
|
+
|
|
12
|
+
# Deferred import: keeps the cost of importing the whole SDK off the
|
|
13
|
+
# module-import path, so `guard --help` and unrelated commands do not pay
|
|
14
|
+
# for it. ENG-6585 owns making this lazy loading systematic.
|
|
8
15
|
from praetorian_cli.sdk.chariot import Chariot
|
|
9
16
|
|
|
10
17
|
""" Command group for interacting with the Guard product """
|