praetorian-cli 2.2.3__tar.gz → 2.2.5__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.2.3/praetorian_cli.egg-info → praetorian_cli-2.2.5}/PKG-INFO +1 -1
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/add.py +42 -27
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/agent.py +29 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/cli_decorators.py +1 -1
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/delete.py +12 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/get.py +21 -2
- praetorian_cli-2.2.5/praetorian_cli/handlers/link.py +60 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/list.py +22 -0
- praetorian_cli-2.2.5/praetorian_cli/handlers/unlink.py +55 -0
- praetorian_cli-2.2.5/praetorian_cli/handlers/utils.py +97 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/chariot.py +2 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/configurations.py +1 -1
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/definitions.py +11 -2
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/files.py +10 -5
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/search.py +6 -4
- praetorian_cli-2.2.5/praetorian_cli/sdk/entities/webpage.py +180 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/model/globals.py +3 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/model/query.py +7 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_asset.py +36 -0
- praetorian_cli-2.2.5/praetorian_cli/sdk/test/test_conversation.py +195 -0
- praetorian_cli-2.2.5/praetorian_cli/sdk/test/test_webpage.py +46 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_z_cli.py +55 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/utils.py +5 -0
- praetorian_cli-2.2.5/praetorian_cli/ui/conversation/__init__.py +3 -0
- praetorian_cli-2.2.5/praetorian_cli/ui/conversation/textual_chat.py +622 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5/praetorian_cli.egg-info}/PKG-INFO +1 -1
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli.egg-info/SOURCES.txt +6 -1
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/setup.cfg +1 -1
- praetorian_cli-2.2.3/praetorian_cli/handlers/link.py +0 -32
- praetorian_cli-2.2.3/praetorian_cli/handlers/unlink.py +0 -27
- praetorian_cli-2.2.3/praetorian_cli/handlers/utils.py +0 -38
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/LICENSE +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/MANIFEST.in +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/README.md +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/aegis.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/chariot.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/configure.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/enrich.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/imports.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/script.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/search.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/ssh_utils.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/test.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/handlers/update.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/main.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/scripts/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/scripts/commands/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/scripts/commands/nmap-example.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/scripts/utils.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/accounts.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/aegis.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/agents.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/assets.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/attributes.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/capabilities.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/credentials.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/integrations.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/jobs.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/keys.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/preseeds.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/risks.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/scanners.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/schema.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/seeds.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/settings.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/statistics.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/entities/webhook.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/keychain.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/mcp_server.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/model/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/model/aegis.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/model/utils.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/pytest.ini +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_account.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_agent.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_attribute.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_capabilities.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_configuration.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_definition.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_extend.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_file.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_job.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_key.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_mcp.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_preseed.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_risk.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_search.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_seed.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_setting.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/test_webhook.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/sdk/test/ui_mocks.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/commands/__init__.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/commands/help.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/commands/info.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/commands/job.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/commands/list.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/commands/set.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/commands/ssh.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/constants.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/menu.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli/ui/aegis/utils.py +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli.egg-info/dependency_links.txt +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli.egg-info/entry_points.txt +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli.egg-info/requires.txt +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/praetorian_cli.egg-info/top_level.txt +0 -0
- {praetorian_cli-2.2.3 → praetorian_cli-2.2.5}/pyproject.toml +0 -0
|
@@ -5,7 +5,7 @@ import click
|
|
|
5
5
|
|
|
6
6
|
from praetorian_cli.handlers.chariot import chariot
|
|
7
7
|
from praetorian_cli.handlers.cli_decorators import cli_handler, praetorian_only
|
|
8
|
-
from praetorian_cli.handlers.utils import error
|
|
8
|
+
from praetorian_cli.handlers.utils import error, parse_configuration_value
|
|
9
9
|
from praetorian_cli.sdk.model.globals import AddRisk, Asset, Seed, Kind
|
|
10
10
|
|
|
11
11
|
|
|
@@ -29,6 +29,9 @@ def asset(sdk, name, dns, asset_type, status, surface):
|
|
|
29
29
|
Add an asset to the Chariot database. This command requires a DNS name for the asset.
|
|
30
30
|
Optionally, a name can be provided to give the asset more specific information,
|
|
31
31
|
such as IP address. If no name is provided, the DNS name will be used as the name.
|
|
32
|
+
The DNS is the group and the name is the specific identifier. This is for legacy reasons.
|
|
33
|
+
|
|
34
|
+
The type can be one of the following: asset, addomain, repository, webapplication.
|
|
32
35
|
|
|
33
36
|
\b
|
|
34
37
|
Example assets:
|
|
@@ -41,6 +44,7 @@ def asset(sdk, name, dns, asset_type, status, surface):
|
|
|
41
44
|
- praetorian chariot add asset --dns example.com
|
|
42
45
|
- praetorian chariot add asset --dns example.com --name 1.2.3.4
|
|
43
46
|
- praetorian chariot add asset --dns internal.example.com --name 10.2.3.4 --surface internal
|
|
47
|
+
- praetorian chariot add asset --dns https://example.com --name 'Example Web Application' --type webapplication
|
|
44
48
|
"""
|
|
45
49
|
if not name:
|
|
46
50
|
name = dns
|
|
@@ -270,40 +274,33 @@ def setting(sdk, name, value):
|
|
|
270
274
|
@add.command()
|
|
271
275
|
@cli_handler
|
|
272
276
|
@click.option('-n', '--name', required=True, help='Name of the configuration')
|
|
273
|
-
@click.option('-e', '--entry', required=
|
|
277
|
+
@click.option('-e', '--entry', required=False, multiple=True,
|
|
278
|
+
help='Key-value pair in format key=value. Can be specified multiple times to set multiple values.')
|
|
279
|
+
@click.option('--string', 'string_value', required=False,
|
|
280
|
+
help='Set the configuration value to a string')
|
|
281
|
+
@click.option('--integer', 'integer_value', required=False,
|
|
282
|
+
help='Set the configuration value to an integer')
|
|
283
|
+
@click.option('--float', 'float_value', required=False,
|
|
284
|
+
help='Set the configuration value to a floating point number')
|
|
274
285
|
@praetorian_only
|
|
275
|
-
def configuration(sdk, name, entry):
|
|
286
|
+
def configuration(sdk, name, entry, string_value, integer_value, float_value):
|
|
276
287
|
""" Add a configuration
|
|
277
288
|
|
|
278
|
-
This command adds, or overwrites if exists, a
|
|
289
|
+
This command adds, or overwrites if exists, a configuration value.
|
|
290
|
+
|
|
291
|
+
Configuration values can be provided as a mapping of key-value pairs using
|
|
292
|
+
``--entry`` (the previous behavior), or as primitive values using
|
|
293
|
+
``--string``, ``--integer``, or ``--float``.
|
|
279
294
|
|
|
280
295
|
\b
|
|
281
296
|
Example usages:
|
|
282
297
|
- praetorian chariot add configuration --name "nuclei" --entry extra-tags=http,sql --entry something=else
|
|
298
|
+
- praetorian chariot add configuration --name "billing-status" --string PAID_MS
|
|
299
|
+
- praetorian chariot add configuration --name "request-timeout" --integer 60
|
|
300
|
+
- praetorian chariot add configuration --name "scoring-threshold" --float 0.85
|
|
283
301
|
"""
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if '=' not in item:
|
|
287
|
-
click.echo(f"Error: Entry '{item}' is not in the format key=value")
|
|
288
|
-
return
|
|
289
|
-
|
|
290
|
-
if item.count('=') > 1:
|
|
291
|
-
click.echo(f"Error: Entry '{item}' contains multiple '=' characters. Format should be key=value")
|
|
292
|
-
return
|
|
293
|
-
|
|
294
|
-
key, value = item.split('=', 1)
|
|
295
|
-
|
|
296
|
-
if not key:
|
|
297
|
-
click.echo("Error: Key cannot be empty")
|
|
298
|
-
return
|
|
299
|
-
|
|
300
|
-
if not value:
|
|
301
|
-
click.echo("Error: Value cannot be empty")
|
|
302
|
-
return
|
|
303
|
-
|
|
304
|
-
config_dict[key] = value
|
|
305
|
-
|
|
306
|
-
sdk.configurations.add(name, config_dict)
|
|
302
|
+
config_value = parse_configuration_value(entry, string_value, integer_value, float_value)
|
|
303
|
+
sdk.configurations.add(name, config_value)
|
|
307
304
|
|
|
308
305
|
|
|
309
306
|
@add.command()
|
|
@@ -328,3 +325,21 @@ def key(sdk, name, expires):
|
|
|
328
325
|
return
|
|
329
326
|
click.echo(f'API key created: {result.get("key", "N/A")}')
|
|
330
327
|
click.echo(f'Secret (save this, it will not be shown again): {result["secret"]}')
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@add.command()
|
|
331
|
+
@cli_handler
|
|
332
|
+
@click.option('-u', '--url', required=True, help='The full URL of the page')
|
|
333
|
+
@click.option('-p', '--parent', required=False, help='Optional key of the parent WebApplication')
|
|
334
|
+
def webpage(sdk, url, parent):
|
|
335
|
+
""" Add a Webpage
|
|
336
|
+
|
|
337
|
+
Add a web page to the Chariot database. Webpages can optionally be associated
|
|
338
|
+
with a parent WebApplication or exist independently.
|
|
339
|
+
|
|
340
|
+
\b
|
|
341
|
+
Example usages:
|
|
342
|
+
- praetorian chariot add webpage --url https://app.example.com/login
|
|
343
|
+
- praetorian chariot add webpage --url https://app.example.com/admin --parent "#webapplication#https://app.example.com"
|
|
344
|
+
"""
|
|
345
|
+
sdk.webpage.add(url, parent)
|
|
@@ -69,3 +69,32 @@ def tools(sdk, allowed):
|
|
|
69
69
|
"""
|
|
70
70
|
for tool in dict.keys(sdk.agents.list_mcp_tools(allowed)):
|
|
71
71
|
click.echo(tool)
|
|
72
|
+
|
|
73
|
+
@agent.command()
|
|
74
|
+
@cli_handler
|
|
75
|
+
def conversation(sdk):
|
|
76
|
+
""" Interactive conversation with Chariot AI assistant
|
|
77
|
+
|
|
78
|
+
Start an interactive chat session with the Chariot AI assistant.
|
|
79
|
+
The AI can help you query security data, understand findings,
|
|
80
|
+
and provide insights about your attack surface.
|
|
81
|
+
|
|
82
|
+
\b
|
|
83
|
+
Commands within conversation:
|
|
84
|
+
- help Show available commands and query examples
|
|
85
|
+
- clear Clear the screen
|
|
86
|
+
- new Start a new conversation
|
|
87
|
+
- quit Exit the conversation
|
|
88
|
+
|
|
89
|
+
\b
|
|
90
|
+
Example queries:
|
|
91
|
+
- "Find all active assets"
|
|
92
|
+
- "Show me critical risks"
|
|
93
|
+
- "What assets do we have for example.com?"
|
|
94
|
+
|
|
95
|
+
\b
|
|
96
|
+
Usage:
|
|
97
|
+
praetorian chariot agent conversation
|
|
98
|
+
"""
|
|
99
|
+
from praetorian_cli.ui.conversation import run_textual_conversation
|
|
100
|
+
run_textual_conversation(sdk)
|
|
@@ -68,7 +68,7 @@ def list_params(filter_by, has_details=True, has_filter=True, has_type=False):
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def pagination(func):
|
|
71
|
-
func = click.option('-o', '--offset', default=
|
|
71
|
+
func = click.option('-o', '--offset', default=0, help='List results from an offset')(func)
|
|
72
72
|
func = click.option('-p', '--page', type=click.Choice(('first', 'all')), default='first',
|
|
73
73
|
help='Pagination mode. "all" pages up to 10,000 pages.', show_default=True)(func)
|
|
74
74
|
return func
|
|
@@ -182,3 +182,15 @@ def key(chariot, key):
|
|
|
182
182
|
- praetorian chariot delete key "#key#550e8400-e29b-41d4-a716-446655440000"
|
|
183
183
|
"""
|
|
184
184
|
chariot.keys.delete(key)
|
|
185
|
+
|
|
186
|
+
@delete.command()
|
|
187
|
+
@cli_handler
|
|
188
|
+
@click.argument('key', required=True)
|
|
189
|
+
def webpage(chariot, key):
|
|
190
|
+
""" Delete a webpage
|
|
191
|
+
|
|
192
|
+
\b
|
|
193
|
+
Arguments:
|
|
194
|
+
- KEY: the key of an existing webpage
|
|
195
|
+
"""
|
|
196
|
+
chariot.webpage.delete(key)
|
|
@@ -147,7 +147,8 @@ def file(chariot, name, path):
|
|
|
147
147
|
@cli_handler
|
|
148
148
|
@click.argument('name')
|
|
149
149
|
@click.option('-path', '--path', default=os.getcwd(), help='Download path. Default: save to current directory')
|
|
150
|
-
|
|
150
|
+
@click.option('--global', 'global_', is_flag=True, help='Fetch from global definitions instead of user-specific')
|
|
151
|
+
def definition(chariot, name, path, global_):
|
|
151
152
|
""" Download a definition using the risk name
|
|
152
153
|
|
|
153
154
|
\b
|
|
@@ -158,8 +159,9 @@ def definition(chariot, name, path):
|
|
|
158
159
|
Example usage:
|
|
159
160
|
- praetorian chariot get definition jira-unauthenticated-user-picker
|
|
160
161
|
- praetorian chariot get definition CVE-2024-23049
|
|
162
|
+
- praetorian chariot get definition CVE-2024-23049 --global
|
|
161
163
|
"""
|
|
162
|
-
downloaded_path = chariot.definitions.get(name, path)
|
|
164
|
+
downloaded_path = chariot.definitions.get(name, path, global_=global_)
|
|
163
165
|
click.echo(f'Saved definition at {downloaded_path}')
|
|
164
166
|
|
|
165
167
|
|
|
@@ -300,6 +302,23 @@ def scanner(chariot, key):
|
|
|
300
302
|
|
|
301
303
|
@get.command()
|
|
302
304
|
@cli_handler
|
|
305
|
+
@click.argument('key', required=True)
|
|
306
|
+
def webpage(chariot, key):
|
|
307
|
+
""" Get Webpage details
|
|
308
|
+
|
|
309
|
+
Retrieve detailed information about a specific web page, including
|
|
310
|
+
its URL, method, authentication requirements, and other metadata.
|
|
311
|
+
|
|
312
|
+
\b
|
|
313
|
+
Argument:
|
|
314
|
+
- KEY: the key of an existing Webpage
|
|
315
|
+
|
|
316
|
+
\b
|
|
317
|
+
Example usages:
|
|
318
|
+
- praetorian chariot get webpage "#webpage#https://app.example.com/dashboard"
|
|
319
|
+
"""
|
|
320
|
+
print_json(chariot.webpage.get(key))
|
|
321
|
+
|
|
303
322
|
@click.option('-t', '--type', help='Optional specific entity type (e.g., asset, risk, attribute)')
|
|
304
323
|
@click.option('-d', '--details', is_flag=True, help='Further retrieve the details of the schema')
|
|
305
324
|
def schema(chariot, type, details):
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from praetorian_cli.handlers.chariot import chariot
|
|
4
|
+
from praetorian_cli.handlers.cli_decorators import cli_handler
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@chariot.group()
|
|
8
|
+
def link():
|
|
9
|
+
""" Link resources to other entities """
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@link.command()
|
|
14
|
+
@cli_handler
|
|
15
|
+
@click.argument('username')
|
|
16
|
+
def account(chariot, username):
|
|
17
|
+
""" Add a collaborator account to your account
|
|
18
|
+
|
|
19
|
+
This allows them to assume access into your account
|
|
20
|
+
and perform actions on your behalf.
|
|
21
|
+
|
|
22
|
+
\b
|
|
23
|
+
Arguments:
|
|
24
|
+
- NAME: their email address
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
\b
|
|
29
|
+
Example usages:
|
|
30
|
+
- praetorian chariot link account john@praetorian.com
|
|
31
|
+
"""
|
|
32
|
+
chariot.accounts.add_collaborator(username)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@link.command('webpage-source')
|
|
36
|
+
@cli_handler
|
|
37
|
+
@click.argument('webpage_key')
|
|
38
|
+
@click.argument('entity_key')
|
|
39
|
+
def webpage_source(chariot, webpage_key, entity_key):
|
|
40
|
+
""" Link a file or repository to a webpage as source code
|
|
41
|
+
|
|
42
|
+
This associates source code files or repositories with webpages
|
|
43
|
+
to track where webpage content originates from.
|
|
44
|
+
|
|
45
|
+
\b
|
|
46
|
+
Arguments:
|
|
47
|
+
- WEBPAGE_KEY: The webpage key in format #webpage#{url}
|
|
48
|
+
- ENTITY_KEY: The file or repository key to link
|
|
49
|
+
Format: #file#{path} or #repository#{url}#{name}
|
|
50
|
+
|
|
51
|
+
\b
|
|
52
|
+
Example usages:
|
|
53
|
+
- praetorian chariot link webpage-source "#webpage#https://example.com" "#file#proofs/scan.txt"
|
|
54
|
+
- praetorian chariot link webpage-source "#webpage#https://example.com/login" "#repository#https://github.com/org/repo.git#repo.git"
|
|
55
|
+
"""
|
|
56
|
+
result = chariot.webpage.link_source(webpage_key, entity_key)
|
|
57
|
+
if result:
|
|
58
|
+
click.echo(f"Successfully linked {entity_key} to {webpage_key}")
|
|
59
|
+
if 'artifacts' in result:
|
|
60
|
+
click.echo(f"Webpage now has {len(result['artifacts'])} linked artifacts")
|
|
@@ -368,3 +368,25 @@ def scanners(chariot, filter, details, offset, page):
|
|
|
368
368
|
- praetorian chariot list scanners --page all
|
|
369
369
|
"""
|
|
370
370
|
render_list_results(chariot.scanners.list(filter, offset, pagination_size(page)), details)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
@list.command()
|
|
374
|
+
@click.option('--parent', required=False, help='Optional WebApp key to filter pages')
|
|
375
|
+
@click.option('-f', '--filter', required=False, help='Optional URL to filter pages')
|
|
376
|
+
@click.option('-d', '--details', is_flag=True, default=False, help='Show detailed information')
|
|
377
|
+
@pagination
|
|
378
|
+
@cli_handler
|
|
379
|
+
def webpages(chariot, parent, filter, details, offset, page):
|
|
380
|
+
""" List WebPages
|
|
381
|
+
|
|
382
|
+
Retrieve and display a list of pages/URLs. Can optionally filter by specific WebApplication.
|
|
383
|
+
|
|
384
|
+
\b
|
|
385
|
+
Example usages:
|
|
386
|
+
- praetorian chariot list webpages
|
|
387
|
+
- praetorian chariot list webpages --parent "#webapplication#https://app.example.com"
|
|
388
|
+
- praetorian chariot list webpages --filter /login
|
|
389
|
+
- praetorian chariot list webpages --parent "#webapplication#https://app.example.com" --details
|
|
390
|
+
- praetorian chariot list webpages --page all
|
|
391
|
+
"""
|
|
392
|
+
render_list_results(chariot.webpage.list(parent, filter, offset, pagination_size(page)), details)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from praetorian_cli.handlers.chariot import chariot
|
|
4
|
+
from praetorian_cli.handlers.cli_decorators import cli_handler
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@chariot.group()
|
|
8
|
+
def unlink():
|
|
9
|
+
""" Remove links between resources """
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@unlink.command()
|
|
14
|
+
@cli_handler
|
|
15
|
+
@click.argument('username')
|
|
16
|
+
def account(chariot, username):
|
|
17
|
+
""" Remove a collaborator account from your account. This will
|
|
18
|
+
revoke their access to your account.
|
|
19
|
+
|
|
20
|
+
Arguments:
|
|
21
|
+
- NAME: Their email address.
|
|
22
|
+
|
|
23
|
+
\b
|
|
24
|
+
Example usages:
|
|
25
|
+
- praetorian chariot unlink account john@praetorian.com
|
|
26
|
+
"""
|
|
27
|
+
chariot.accounts.delete_collaborator(username)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@unlink.command('webpage-source')
|
|
31
|
+
@cli_handler
|
|
32
|
+
@click.argument('webpage_key')
|
|
33
|
+
@click.argument('entity_key')
|
|
34
|
+
def webpage_source(chariot, webpage_key, entity_key):
|
|
35
|
+
""" Unlink a file or repository from a webpage's source code
|
|
36
|
+
|
|
37
|
+
This removes the association between source code files or
|
|
38
|
+
repositories and webpages.
|
|
39
|
+
|
|
40
|
+
\b
|
|
41
|
+
Arguments:
|
|
42
|
+
- WEBPAGE_KEY: The webpage key in format #webpage#{url}
|
|
43
|
+
- ENTITY_KEY: The file or repository key to unlink
|
|
44
|
+
Format: #file#{path} or #repository#{url}#{name}
|
|
45
|
+
|
|
46
|
+
\b
|
|
47
|
+
Example usages:
|
|
48
|
+
- praetorian chariot unlink webpage-source "#webpage#https://example.com" "#file#proofs/scan.txt"
|
|
49
|
+
- praetorian chariot unlink webpage-source "#webpage#https://example.com/login" "#repository#https://github.com/org/repo.git#repo.git"
|
|
50
|
+
"""
|
|
51
|
+
result = chariot.webpage.unlink_source(webpage_key, entity_key)
|
|
52
|
+
if result:
|
|
53
|
+
click.echo(f"Successfully unlinked {entity_key} from {webpage_key}")
|
|
54
|
+
if 'artifacts' in result:
|
|
55
|
+
click.echo(f"Webpage now has {len(result['artifacts'])} linked artifacts")
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse_configuration_value(
|
|
7
|
+
entries = [], s_val=None, i_val=None, f_val=None):
|
|
8
|
+
"""Return a configuration value derived from CLI inputs."""
|
|
9
|
+
has_entries = len(entries) > 0
|
|
10
|
+
typed_values = {
|
|
11
|
+
'string': s_val,
|
|
12
|
+
'integer': i_val,
|
|
13
|
+
'float': f_val,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if has_entries and any(value is not None for value in typed_values.values()):
|
|
17
|
+
error('--entry cannot be combined with --string, --integer, or --float')
|
|
18
|
+
|
|
19
|
+
if has_entries:
|
|
20
|
+
return _parse_entry_dict(entries)
|
|
21
|
+
|
|
22
|
+
provided = [(name, value) for name, value in typed_values.items() if value is not None]
|
|
23
|
+
|
|
24
|
+
if not provided:
|
|
25
|
+
error('Provide configuration data via --entry, --string, --integer, or --float')
|
|
26
|
+
if len(provided) > 1:
|
|
27
|
+
error('Specify only one of --string, --integer, or --float')
|
|
28
|
+
|
|
29
|
+
value_type, raw_value = provided[0]
|
|
30
|
+
return _cast_typed_value(value_type, raw_value)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _parse_entry_dict(entries):
|
|
34
|
+
parsed = {}
|
|
35
|
+
|
|
36
|
+
for item in entries:
|
|
37
|
+
key, value = _split_entry(item)
|
|
38
|
+
if not key:
|
|
39
|
+
error(f'Key cannot be empty: {item}')
|
|
40
|
+
if not value:
|
|
41
|
+
error(f'Value cannot be empty: {item}')
|
|
42
|
+
parsed[key] = value
|
|
43
|
+
return parsed
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _split_entry(item):
|
|
47
|
+
if '=' not in item:
|
|
48
|
+
error(f"Entry '{item}' is not in the format key=value")
|
|
49
|
+
if item.count('=') > 1:
|
|
50
|
+
error(f"Entry '{item}' contains multiple '=' characters. Format should be key=value")
|
|
51
|
+
return item.split('=', 1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _cast_typed_value(value_type, raw_value):
|
|
55
|
+
try:
|
|
56
|
+
if value_type == 'integer':
|
|
57
|
+
return int(raw_value)
|
|
58
|
+
if value_type == 'float':
|
|
59
|
+
return float(raw_value)
|
|
60
|
+
except ValueError:
|
|
61
|
+
error(f'{value_type} must be a valid {value_type}')
|
|
62
|
+
return raw_value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def render_list_results(list_results, details):
|
|
66
|
+
list_data, offset = list_results
|
|
67
|
+
if details:
|
|
68
|
+
output = dict(data=list_data)
|
|
69
|
+
if offset:
|
|
70
|
+
output['offset'] = offset
|
|
71
|
+
print_json(output)
|
|
72
|
+
else:
|
|
73
|
+
for hit in list_data:
|
|
74
|
+
click.echo(hit['key'])
|
|
75
|
+
render_offset(offset)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def render_offset(offset):
|
|
79
|
+
if offset:
|
|
80
|
+
click.echo('There are more results. Add the following argument to the command to view them:')
|
|
81
|
+
click.echo(f'--offset {json.dumps(offset)}')
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def pagination_size(page):
|
|
85
|
+
return 10000 if page == 'all' else 1
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def print_json(data):
|
|
89
|
+
if data:
|
|
90
|
+
click.echo(json.dumps(data, indent=2))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def error(message, quit=True):
|
|
94
|
+
click.secho('ERROR: ', fg='red', nl=False, err=True)
|
|
95
|
+
click.echo(message, err=True)
|
|
96
|
+
if quit:
|
|
97
|
+
exit(1)
|
|
@@ -21,6 +21,7 @@ from praetorian_cli.sdk.entities.search import Search
|
|
|
21
21
|
from praetorian_cli.sdk.entities.seeds import Seeds
|
|
22
22
|
from praetorian_cli.sdk.entities.settings import Settings
|
|
23
23
|
from praetorian_cli.sdk.entities.statistics import Statistics
|
|
24
|
+
from praetorian_cli.sdk.entities.webpage import Webpage
|
|
24
25
|
from praetorian_cli.sdk.entities.webhook import Webhook
|
|
25
26
|
from praetorian_cli.sdk.keychain import Keychain
|
|
26
27
|
from praetorian_cli.sdk.model.globals import GLOBAL_FLAG
|
|
@@ -52,6 +53,7 @@ class Chariot:
|
|
|
52
53
|
self.keys = Keys(self)
|
|
53
54
|
self.capabilities = Capabilities(self)
|
|
54
55
|
self.credentials = Credentials(self)
|
|
56
|
+
self.webpage = Webpage(self)
|
|
55
57
|
self.schema = Schema(self)
|
|
56
58
|
self.proxy = proxy
|
|
57
59
|
|
|
@@ -29,7 +29,7 @@ class Definitions:
|
|
|
29
29
|
definition_name = os.path.basename(local_filepath)
|
|
30
30
|
return self.api.files.add(local_filepath, f'definitions/{definition_name}')
|
|
31
31
|
|
|
32
|
-
def get(self, definition_name, download_directory=os.getcwd()):
|
|
32
|
+
def get(self, definition_name, download_directory=os.getcwd(), global_=False):
|
|
33
33
|
"""
|
|
34
34
|
Download a risk definition file from the definitions folder.
|
|
35
35
|
|
|
@@ -37,15 +37,24 @@ class Definitions:
|
|
|
37
37
|
:type definition_name: str
|
|
38
38
|
:param download_directory: The directory to save the downloaded file (defaults to current working directory)
|
|
39
39
|
:type download_directory: str
|
|
40
|
+
:param global_: If True, fetch from global definitions instead of user-specific
|
|
41
|
+
:type global_: bool
|
|
40
42
|
:return: The local file path where the definition was saved
|
|
41
43
|
:rtype: str
|
|
42
44
|
"""
|
|
43
|
-
|
|
45
|
+
try:
|
|
46
|
+
content = self.api.files.get_utf8(f'definitions/{definition_name}', _global=global_)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
if global_:
|
|
49
|
+
raise Exception(f'Global definition {definition_name} not found or inaccessible.')
|
|
50
|
+
else:
|
|
51
|
+
raise
|
|
44
52
|
download_path = os.path.join(download_directory, definition_name)
|
|
45
53
|
with open(download_path, 'w') as file:
|
|
46
54
|
file.write(content)
|
|
47
55
|
return download_path
|
|
48
56
|
|
|
57
|
+
|
|
49
58
|
def list(self, name_filter='', offset=None, pages=100000) -> tuple:
|
|
50
59
|
"""
|
|
51
60
|
List the definition names, optionally prefix-filtered by a definition name.
|
|
@@ -46,30 +46,35 @@ class Files:
|
|
|
46
46
|
|
|
47
47
|
return download_path
|
|
48
48
|
|
|
49
|
-
def get(self, chariot_filepath) -> bytes:
|
|
49
|
+
def get(self, chariot_filepath, _global=False) -> bytes:
|
|
50
50
|
"""
|
|
51
51
|
Download a file from Chariot storage into memory as bytes.
|
|
52
52
|
|
|
53
53
|
:param chariot_filepath: Path of the file in Chariot storage to download
|
|
54
54
|
:type chariot_filepath: str
|
|
55
|
+
:param _global: If True, fetch from global storage instead of user-specific
|
|
56
|
+
:type _global: bool
|
|
55
57
|
:return: The file content as bytes
|
|
56
58
|
:rtype: bytes
|
|
57
59
|
:raises Exception: If the file does not exist in Chariot storage
|
|
58
60
|
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
if not _global:
|
|
62
|
+
self.raise_if_missing(chariot_filepath)
|
|
63
|
+
return self.api.download(chariot_filepath, global_=_global)
|
|
61
64
|
|
|
62
|
-
def get_utf8(self, chariot_filepath) -> str:
|
|
65
|
+
def get_utf8(self, chariot_filepath, _global=False) -> str:
|
|
63
66
|
"""
|
|
64
67
|
Download a file from Chariot storage into memory as a UTF-8 string.
|
|
65
68
|
|
|
66
69
|
:param chariot_filepath: Path of the file in Chariot storage to download
|
|
67
70
|
:type chariot_filepath: str
|
|
71
|
+
:param _global: If True, fetch from global storage instead of user-specific
|
|
72
|
+
:type _global: bool
|
|
68
73
|
:return: The file content as a UTF-8 decoded string
|
|
69
74
|
:rtype: str
|
|
70
75
|
:raises Exception: If the file does not exist in Chariot storage
|
|
71
76
|
"""
|
|
72
|
-
return self.get(chariot_filepath).decode('utf-8')
|
|
77
|
+
return self.get(chariot_filepath, _global=_global).decode('utf-8')
|
|
73
78
|
|
|
74
79
|
def list(self, prefix_filter='', offset=None, pages=100000) -> tuple:
|
|
75
80
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from praetorian_cli.sdk.model.query import Query
|
|
3
|
-
from praetorian_cli.sdk.model.globals import EXACT_FLAG, DESCENDING_FLAG, GLOBAL_FLAG, Kind
|
|
3
|
+
from praetorian_cli.sdk.model.globals import EXACT_FLAG, DESCENDING_FLAG, GLOBAL_FLAG, USER_FLAG, Kind
|
|
4
4
|
class Search:
|
|
5
5
|
|
|
6
6
|
def __init__(self, api):
|
|
@@ -17,7 +17,7 @@ class Search:
|
|
|
17
17
|
"""
|
|
18
18
|
return self.api.count(dict(key=search_term))
|
|
19
19
|
|
|
20
|
-
def by_key_prefix(self, key_prefix, offset=None, pages=100000) -> tuple:
|
|
20
|
+
def by_key_prefix(self, key_prefix, offset=None, pages=100000, user=False) -> tuple:
|
|
21
21
|
"""
|
|
22
22
|
Search for entities by key prefix. <mcp>If the response is too large, make your query more specific.<mcp>
|
|
23
23
|
|
|
@@ -30,7 +30,7 @@ class Search:
|
|
|
30
30
|
:return: A tuple containing (list of matching entities, next page offset)
|
|
31
31
|
:rtype: tuple
|
|
32
32
|
"""
|
|
33
|
-
return self.by_term(key_prefix, None, offset, pages)
|
|
33
|
+
return self.by_term(key_prefix, None, offset, pages, user=user)
|
|
34
34
|
|
|
35
35
|
def by_exact_key(self, key, get_attributes=False) -> {}:
|
|
36
36
|
"""
|
|
@@ -119,7 +119,7 @@ class Search:
|
|
|
119
119
|
return self.by_term(f'dns:{dns_prefix}', kind, offset, pages)
|
|
120
120
|
|
|
121
121
|
def by_term(self, search_term, kind=None, offset=None, pages=100000, exact=False, descending=False,
|
|
122
|
-
global_=False) -> tuple:
|
|
122
|
+
global_=False, user=False) -> tuple:
|
|
123
123
|
"""
|
|
124
124
|
Search for a given kind by term.
|
|
125
125
|
|
|
@@ -151,6 +151,8 @@ class Search:
|
|
|
151
151
|
params |= DESCENDING_FLAG
|
|
152
152
|
if global_:
|
|
153
153
|
params |= GLOBAL_FLAG
|
|
154
|
+
if user:
|
|
155
|
+
params |= USER_FLAG
|
|
154
156
|
|
|
155
157
|
results = self.api.my(params, pages)
|
|
156
158
|
|