sandbox-cli 0.2.49__tar.gz → 0.3.0__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.
- {sandbox_cli-0.2.49 → sandbox_cli-0.3.0}/PKG-INFO +8 -9
- {sandbox_cli-0.2.49 → sandbox_cli-0.3.0}/pyproject.toml +44 -27
- sandbox_cli-0.3.0/sandbox_cli/__main__.py +41 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/__init__.py +135 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/_converters.py +131 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/browser.py +47 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/download/__init__.py +6 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/download/artifacts.py +376 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/download/email.py +66 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/images.py +86 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/report.py +202 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/rules/__init__.py +17 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/rules/compile.py +56 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/rules/test.py +59 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/scanner/__init__.py +19 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/scanner/re_scan.py +116 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/scanner/scan.py +272 -0
- sandbox_cli-0.3.0/sandbox_cli/cli/scanner/scan_new.py +422 -0
- {sandbox_cli-0.2.49 → sandbox_cli-0.3.0}/sandbox_cli/cli/unpack.py +2 -2
- sandbox_cli-0.3.0/sandbox_cli/console.py +74 -0
- sandbox_cli-0.3.0/sandbox_cli/core/browser.py +26 -0
- sandbox_cli-0.3.0/sandbox_cli/core/config.py +222 -0
- sandbox_cli-0.3.0/sandbox_cli/core/exceptions.py +53 -0
- sandbox_cli-0.3.0/sandbox_cli/core/progress.py +95 -0
- sandbox_cli-0.3.0/sandbox_cli/core/sandbox.py +75 -0
- sandbox_cli-0.3.0/sandbox_cli/core/scan.py +72 -0
- sandbox_cli-0.3.0/sandbox_cli/services/__init__.py +1 -0
- sandbox_cli-0.3.0/sandbox_cli/services/compiler/__init__.py +31 -0
- sandbox_cli-0.3.0/sandbox_cli/services/compiler/abc.py +98 -0
- sandbox_cli-0.3.0/sandbox_cli/services/compiler/docker.py +215 -0
- sandbox_cli-0.3.0/sandbox_cli/services/compiler/ssh.py +237 -0
- sandbox_cli-0.3.0/sandbox_cli/services/downloader.py +374 -0
- sandbox_cli-0.3.0/sandbox_cli/services/report.py +66 -0
- sandbox_cli-0.3.0/sandbox_cli/services/scanner/__init__.py +3 -0
- sandbox_cli-0.3.0/sandbox_cli/services/scanner/advanced.py +374 -0
- sandbox_cli-0.3.0/sandbox_cli/services/scanner/basic.py +220 -0
- sandbox_cli-0.3.0/sandbox_cli/services/scanner/compile.py +76 -0
- sandbox_cli-0.2.49/sandbox_cli/utils/merge_dll_hooks.py → sandbox_cli-0.3.0/sandbox_cli/services/scanner/hooks.py +17 -14
- sandbox_cli-0.3.0/sandbox_cli/services/scanner/images.py +72 -0
- sandbox_cli-0.3.0/sandbox_cli/services/scanner/rescan.py +248 -0
- sandbox_cli-0.3.0/sandbox_cli/services/scanner/tasks.py +59 -0
- sandbox_cli-0.3.0/sandbox_cli/services/scanner/uploads.py +84 -0
- sandbox_cli-0.3.0/sandbox_cli/services/scanner/utils.py +53 -0
- {sandbox_cli-0.2.49/sandbox_cli/utils → sandbox_cli-0.3.0/sandbox_cli/services}/unpack/__init__.py +44 -26
- sandbox_cli-0.3.0/sandbox_cli/services/unpack/plugins/abc.py +58 -0
- sandbox_cli-0.3.0/sandbox_cli/services/unpack/plugins/correlation.py +35 -0
- sandbox_cli-0.3.0/sandbox_cli/services/unpack/plugins/sort_by_plugins.py +11 -0
- sandbox_cli-0.2.49/sandbox_cli/__main__.py +0 -28
- sandbox_cli-0.2.49/sandbox_cli/cli/__init__.py +0 -52
- sandbox_cli-0.2.49/sandbox_cli/cli/downloader.py +0 -384
- sandbox_cli-0.2.49/sandbox_cli/cli/images.py +0 -45
- sandbox_cli-0.2.49/sandbox_cli/cli/reporter.py +0 -210
- sandbox_cli-0.2.49/sandbox_cli/cli/rules/__init__.py +0 -83
- sandbox_cli-0.2.49/sandbox_cli/cli/scanner/__init__.py +0 -842
- sandbox_cli-0.2.49/sandbox_cli/console.py +0 -23
- sandbox_cli-0.2.49/sandbox_cli/internal/config.py +0 -157
- sandbox_cli-0.2.49/sandbox_cli/internal/helpers.py +0 -89
- sandbox_cli-0.2.49/sandbox_cli/models/detections.py +0 -91
- sandbox_cli-0.2.49/sandbox_cli/models/sandbox_arguments.py +0 -22
- sandbox_cli-0.2.49/sandbox_cli/utils/__init__.py +0 -0
- sandbox_cli-0.2.49/sandbox_cli/utils/compiler/__init__.py +0 -60
- sandbox_cli-0.2.49/sandbox_cli/utils/compiler/abc.py +0 -51
- sandbox_cli-0.2.49/sandbox_cli/utils/compiler/docker.py +0 -136
- sandbox_cli-0.2.49/sandbox_cli/utils/compiler/ssh.py +0 -203
- sandbox_cli-0.2.49/sandbox_cli/utils/downloader/__init__.py +0 -244
- sandbox_cli-0.2.49/sandbox_cli/utils/extractors.py +0 -64
- sandbox_cli-0.2.49/sandbox_cli/utils/scanner/__init__.py +0 -291
- sandbox_cli-0.2.49/sandbox_cli/utils/scanner/advanced.py +0 -459
- sandbox_cli-0.2.49/sandbox_cli/utils/scanner/rescan.py +0 -279
- sandbox_cli-0.2.49/sandbox_cli/utils/unpack/plugins/__init__.py +0 -0
- sandbox_cli-0.2.49/sandbox_cli/utils/unpack/plugins/abc.py +0 -18
- sandbox_cli-0.2.49/sandbox_cli/utils/unpack/plugins/correlation.py +0 -50
- sandbox_cli-0.2.49/sandbox_cli/utils/unpack/plugins/sort_by_plugins.py +0 -28
- {sandbox_cli-0.2.49 → sandbox_cli-0.3.0}/.gitignore +0 -0
- {sandbox_cli-0.2.49 → sandbox_cli-0.3.0}/LICENSE +0 -0
- {sandbox_cli-0.2.49 → sandbox_cli-0.3.0}/NOTICE +0 -0
- {sandbox_cli-0.2.49 → sandbox_cli-0.3.0}/README.md +0 -0
- {sandbox_cli-0.2.49/sandbox_cli/internal → sandbox_cli-0.3.0/sandbox_cli/core}/__init__.py +0 -0
- {sandbox_cli-0.2.49/sandbox_cli/models → sandbox_cli-0.3.0/sandbox_cli/services/unpack/plugins}/__init__.py +0 -0
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sandbox-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Command line tool for interaction with sandboxes
|
|
5
5
|
Project-URL: Homepage, https://github.com/Security-Experts-Community/sandbox-cli
|
|
6
6
|
Project-URL: Documentation, https://security-experts-community.github.io/sandbox-cli
|
|
7
7
|
Project-URL: Repository, https://github.com/Security-Experts-Community/sandbox-cli
|
|
8
8
|
Project-URL: Issues, https://github.com/Security-Experts-Community/sandbox-cli/issues
|
|
9
|
-
Author: Alexey Kolesnikov
|
|
9
|
+
Author-email: Alexey Kolesnikov <delvinru@gmail.com>
|
|
10
10
|
License-Expression: MIT
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
License-File: NOTICE
|
|
13
13
|
Requires-Python: >=3.11
|
|
14
14
|
Requires-Dist: aiofiles>=25.1.0
|
|
15
|
-
Requires-Dist: asyncssh>=2.
|
|
15
|
+
Requires-Dist: asyncssh>=2.24.0
|
|
16
16
|
Requires-Dist: colorama>=0.4.6
|
|
17
|
-
Requires-Dist: cryptography>=
|
|
18
|
-
Requires-Dist: cyclopts>=4.
|
|
19
|
-
Requires-Dist: docker>=7.
|
|
20
|
-
Requires-Dist: ptsandbox>=5.0
|
|
21
|
-
Requires-Dist:
|
|
22
|
-
Requires-Dist: rich>=14.3.3
|
|
17
|
+
Requires-Dist: cryptography>=49.0.0
|
|
18
|
+
Requires-Dist: cyclopts>=4.20.0
|
|
19
|
+
Requires-Dist: docker>=7.2.0
|
|
20
|
+
Requires-Dist: ptsandbox>=5.1.0
|
|
21
|
+
Requires-Dist: rich>=15.0.0
|
|
23
22
|
Requires-Dist: zstandard>=0.25.0
|
|
24
23
|
Description-Content-Type: text/markdown
|
|
25
24
|
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sandbox-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Command line tool for interaction with sandboxes"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
7
|
license = "MIT"
|
|
8
8
|
license-files = ["LICENSE", "NOTICE"]
|
|
9
|
-
authors = [{ name = "Alexey Kolesnikov" }]
|
|
9
|
+
authors = [{ name = "Alexey Kolesnikov", email = "delvinru@gmail.com" }]
|
|
10
10
|
dependencies = [
|
|
11
11
|
"aiofiles>=25.1.0",
|
|
12
|
-
"asyncssh>=2.
|
|
12
|
+
"asyncssh>=2.24.0",
|
|
13
13
|
"colorama>=0.4.6",
|
|
14
|
-
"cryptography>=
|
|
15
|
-
"cyclopts>=4.
|
|
16
|
-
"docker>=7.
|
|
17
|
-
"ptsandbox>=5.0
|
|
18
|
-
"
|
|
19
|
-
"rich>=14.3.3",
|
|
14
|
+
"cryptography>=49.0.0",
|
|
15
|
+
"cyclopts>=4.20.0",
|
|
16
|
+
"docker>=7.2.0",
|
|
17
|
+
"ptsandbox>=5.1.0",
|
|
18
|
+
"rich>=15.0.0",
|
|
20
19
|
"zstandard>=0.25.0",
|
|
21
20
|
]
|
|
22
21
|
|
|
@@ -28,13 +27,15 @@ Issues = "https://github.com/Security-Experts-Community/sandbox-cli/issues"
|
|
|
28
27
|
|
|
29
28
|
[dependency-groups]
|
|
30
29
|
dev = [
|
|
31
|
-
"black
|
|
32
|
-
"isort
|
|
33
|
-
"mypy
|
|
34
|
-
"
|
|
35
|
-
"types-
|
|
30
|
+
"black==26.5.1",
|
|
31
|
+
"isort==8.0.1",
|
|
32
|
+
"mypy==2.3.0",
|
|
33
|
+
"ruff==0.14.0",
|
|
34
|
+
"types-aiofiles==25.1.0.20260518",
|
|
35
|
+
"types-docker==7.2.0.20260728",
|
|
36
|
+
"types-colorama==0.4.15.20260508",
|
|
36
37
|
]
|
|
37
|
-
docs = ["mkdocs
|
|
38
|
+
docs = ["mkdocs==1.6.1", "mkdocs-material==9.7.7", "termynal==0.14.0"]
|
|
38
39
|
|
|
39
40
|
[project.scripts]
|
|
40
41
|
sandbox-cli = "sandbox_cli.__main__:main"
|
|
@@ -82,24 +83,40 @@ exclude = '''
|
|
|
82
83
|
|
|
83
84
|
[[tool.mypy.overrides]]
|
|
84
85
|
strict = true
|
|
85
|
-
module = ["ptsandbox.*", "docker.*"
|
|
86
|
+
module = ["ptsandbox.*", "docker.*"]
|
|
86
87
|
ignore_missing_imports = true
|
|
87
88
|
|
|
89
|
+
[tool.mypy]
|
|
90
|
+
python_version = "3.11"
|
|
91
|
+
warn_return_any = true
|
|
92
|
+
warn_unused_configs = true
|
|
93
|
+
disallow_untyped_defs = false
|
|
94
|
+
check_untyped_defs = true
|
|
95
|
+
warn_redundant_casts = true
|
|
96
|
+
warn_unused_ignores = true
|
|
97
|
+
no_implicit_optional = true
|
|
98
|
+
show_error_codes = true
|
|
99
|
+
namespace_packages = true
|
|
100
|
+
explicit_package_bases = true
|
|
101
|
+
exclude = ["\\.venv/", "\\.mypy_cache/", "build/", "dist/"]
|
|
102
|
+
|
|
88
103
|
[tool.ruff]
|
|
89
104
|
lint.select = [
|
|
90
|
-
"E",
|
|
91
|
-
"W",
|
|
92
|
-
"F",
|
|
93
|
-
"I",
|
|
94
|
-
"B",
|
|
95
|
-
"C4",
|
|
96
|
-
"UP",
|
|
97
|
-
"
|
|
105
|
+
"E", # pycodestyle errors
|
|
106
|
+
"W", # pycodestyle warnings
|
|
107
|
+
"F", # pyflakes
|
|
108
|
+
"I", # isort
|
|
109
|
+
"B", # flake8-bugbear
|
|
110
|
+
"C4", # flake8-comprehensions
|
|
111
|
+
"UP", # pyupgrade
|
|
112
|
+
"SIM", # flake8-simplify
|
|
113
|
+
"RUF", # ruff-specific
|
|
98
114
|
]
|
|
99
115
|
lint.ignore = [
|
|
100
|
-
"E501",
|
|
101
|
-
"B008",
|
|
102
|
-
"W191",
|
|
116
|
+
"E501", # line too long, handled by black
|
|
117
|
+
"B008", # do not perform function calls in argument defaults
|
|
118
|
+
"W191", # indentation contains tabs
|
|
119
|
+
"SIM117", # nested with (readability)
|
|
103
120
|
]
|
|
104
121
|
line-length = 120
|
|
105
122
|
target-version = "py311"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from http import HTTPStatus
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
import aiohttp.client_exceptions
|
|
7
|
+
|
|
8
|
+
from sandbox_cli.cli import app
|
|
9
|
+
from sandbox_cli.console import console
|
|
10
|
+
from sandbox_cli.core.exceptions import SandboxCliError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> None:
|
|
14
|
+
if sys.platform == "win32":
|
|
15
|
+
import asyncio
|
|
16
|
+
|
|
17
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
app()
|
|
21
|
+
except aiohttp.client_exceptions.ClientResponseError as e:
|
|
22
|
+
# global handler for 401 error
|
|
23
|
+
if e.status == HTTPStatus.UNAUTHORIZED:
|
|
24
|
+
console.error(f"The specified token is not valid. {e}")
|
|
25
|
+
except SandboxCliError as e:
|
|
26
|
+
console.error(str(e))
|
|
27
|
+
except KeyboardInterrupt:
|
|
28
|
+
console.warning("Operation cancelled")
|
|
29
|
+
sys.exit(130)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
# Show a concise error by default; full traceback only with DEBUG env var
|
|
32
|
+
# (see https://no-color.org/ style conventions for CLI tools).
|
|
33
|
+
if os.environ.get("DEBUG"):
|
|
34
|
+
console.print_exception()
|
|
35
|
+
else:
|
|
36
|
+
console.error(f"{type(e).__name__}: {e}")
|
|
37
|
+
console.info("Set DEBUG=1 for a full traceback.")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
main()
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
from typing import Annotated, Literal
|
|
5
|
+
|
|
6
|
+
from colorama import init
|
|
7
|
+
from cyclopts import App, Parameter
|
|
8
|
+
|
|
9
|
+
from sandbox_cli.console import console
|
|
10
|
+
from sandbox_cli.core.config import configpath, settings
|
|
11
|
+
|
|
12
|
+
init() # enable ANSI colors on Windows
|
|
13
|
+
|
|
14
|
+
__all__ = ["app"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_version() -> str:
|
|
18
|
+
version = importlib.metadata.version("sandbox-cli")
|
|
19
|
+
return f"sandbox-cli {version}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
app = App(
|
|
23
|
+
name="sandbox-cli",
|
|
24
|
+
help="Work with sandbox like a pro"
|
|
25
|
+
+ (
|
|
26
|
+
f"\n\nTo access other commands, specify at least one sandbox in the config at **{configpath}**"
|
|
27
|
+
if len(settings.sandbox_keys) == 0
|
|
28
|
+
else ""
|
|
29
|
+
),
|
|
30
|
+
help_format="markdown",
|
|
31
|
+
version=get_version,
|
|
32
|
+
console=console,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def completion(
|
|
37
|
+
shell: Annotated[
|
|
38
|
+
Literal["bash", "zsh", "fish"],
|
|
39
|
+
Parameter(
|
|
40
|
+
help="Shell to generate completion for",
|
|
41
|
+
),
|
|
42
|
+
],
|
|
43
|
+
/,
|
|
44
|
+
*,
|
|
45
|
+
install: Annotated[
|
|
46
|
+
bool,
|
|
47
|
+
Parameter(
|
|
48
|
+
name=["--install", "-i"],
|
|
49
|
+
help="Install the completion script to the default location for the current shell and add it to the RC file",
|
|
50
|
+
negative="",
|
|
51
|
+
),
|
|
52
|
+
] = False,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Generate or install shell completion.
|
|
56
|
+
"""
|
|
57
|
+
if install:
|
|
58
|
+
path = app.install_completion(shell=shell)
|
|
59
|
+
console.info(f"Completion installed to {path}")
|
|
60
|
+
else:
|
|
61
|
+
console.print(app.generate_completion(shell=shell), end="")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
app.command(name="completion")(completion)
|
|
65
|
+
|
|
66
|
+
# Lazy-loaded commands — modules are imported only when the command is invoked,
|
|
67
|
+
# keeping ``--help`` and ``--version`` fast by avoiding heavy imports
|
|
68
|
+
app.command(
|
|
69
|
+
"sandbox_cli.cli.unpack:unpack_logs",
|
|
70
|
+
name=["conv", "unpack"],
|
|
71
|
+
help="Convert sandbox logs into an analysis-friendly format.",
|
|
72
|
+
)
|
|
73
|
+
app.command(
|
|
74
|
+
"sandbox_cli.cli.report:generate_report",
|
|
75
|
+
name="report",
|
|
76
|
+
help="Generate short report from sandbox scans.",
|
|
77
|
+
)
|
|
78
|
+
app.command(
|
|
79
|
+
"sandbox_cli.cli.browser:open_browser",
|
|
80
|
+
name="browser",
|
|
81
|
+
help="Open sandbox link in the default browser.",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if len(settings.sandbox_keys) > 0:
|
|
85
|
+
scanner_app = App(name="scanner", help="Scan with the sandbox.", help_format="markdown", console=console)
|
|
86
|
+
scanner_app.command(
|
|
87
|
+
"sandbox_cli.cli.scanner.scan:scan",
|
|
88
|
+
name="scan",
|
|
89
|
+
help="Send files to scan with the sandbox.",
|
|
90
|
+
)
|
|
91
|
+
scanner_app.command(
|
|
92
|
+
"sandbox_cli.cli.scanner.scan_new:scan_new",
|
|
93
|
+
name="scan-new",
|
|
94
|
+
help="Send files to scan with the sandbox (advanced scan).",
|
|
95
|
+
)
|
|
96
|
+
scanner_app.command(
|
|
97
|
+
"sandbox_cli.cli.scanner.re_scan:re_scan",
|
|
98
|
+
name="re-scan",
|
|
99
|
+
help="Send traces to re-scan.",
|
|
100
|
+
)
|
|
101
|
+
app.command(scanner_app)
|
|
102
|
+
|
|
103
|
+
rules_app = App(
|
|
104
|
+
name="rules",
|
|
105
|
+
help="Working with raw sandbox rules.",
|
|
106
|
+
help_format="markdown",
|
|
107
|
+
console=console,
|
|
108
|
+
)
|
|
109
|
+
rules_app.command(
|
|
110
|
+
"sandbox_cli.cli.rules.compile:compile_rules",
|
|
111
|
+
name="compile",
|
|
112
|
+
help="Get compiled rules for working with third-party services.",
|
|
113
|
+
)
|
|
114
|
+
rules_app.command(
|
|
115
|
+
"sandbox_cli.cli.rules.test:test_rules",
|
|
116
|
+
name="test",
|
|
117
|
+
help="Testing written rules.",
|
|
118
|
+
)
|
|
119
|
+
app.command(rules_app)
|
|
120
|
+
|
|
121
|
+
app.command(
|
|
122
|
+
"sandbox_cli.cli.images:get_images",
|
|
123
|
+
name="images",
|
|
124
|
+
help="Get available images in the sandbox.",
|
|
125
|
+
)
|
|
126
|
+
app.command(
|
|
127
|
+
"sandbox_cli.cli.download:download_command",
|
|
128
|
+
name="download",
|
|
129
|
+
help="Download any artifact from the sandbox.",
|
|
130
|
+
)
|
|
131
|
+
app.command(
|
|
132
|
+
"sandbox_cli.cli.download:download_email",
|
|
133
|
+
name="email",
|
|
134
|
+
help="Upload an email and get its headers.",
|
|
135
|
+
)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from collections import deque
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from cyclopts import validators
|
|
10
|
+
|
|
11
|
+
from sandbox_cli.console import console
|
|
12
|
+
from sandbox_cli.core.config import Platform, VMImage, parse_image, settings
|
|
13
|
+
from sandbox_cli.core.exceptions import ConfigError
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from cyclopts import Token
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"files_path_resolver",
|
|
20
|
+
"image_converter",
|
|
21
|
+
"out_dir_converter",
|
|
22
|
+
"out_dir_validator",
|
|
23
|
+
"rules_path_resolver",
|
|
24
|
+
"trace_converter",
|
|
25
|
+
"trace_validator",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def image_converter(_: Any, tokens: Sequence[Token]) -> set[VMImage | str]:
|
|
30
|
+
"""
|
|
31
|
+
Convert ``--image`` tokens into a set of ``VMImage`` or custom image ids.
|
|
32
|
+
"""
|
|
33
|
+
return {parse_image(token.value) for token in tokens}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def out_dir_converter(_: Any, tokens: Sequence[Token]) -> Path:
|
|
37
|
+
"""
|
|
38
|
+
Resolve ``--out`` value: expand ``~``, create the directory, return absolute path.
|
|
39
|
+
"""
|
|
40
|
+
path = Path(tokens[0].value).expanduser()
|
|
41
|
+
path.mkdir(exist_ok=True, parents=True)
|
|
42
|
+
return path.resolve()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def out_dir_validator(_: Any, value: Any) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Ensure the output directory exists (also covers default values).
|
|
48
|
+
"""
|
|
49
|
+
if isinstance(value, Path):
|
|
50
|
+
value.mkdir(exist_ok=True, parents=True)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def trace_converter(_: Any, tokens: Sequence[Token]) -> list[Path]:
|
|
54
|
+
"""
|
|
55
|
+
Resolve trace paths: expand ``~`` and return absolute paths.
|
|
56
|
+
"""
|
|
57
|
+
return [Path(t.value).expanduser().resolve() for t in tokens]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def trace_validator(_: Any, value: Any) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Validate that all trace paths exist.
|
|
63
|
+
"""
|
|
64
|
+
path_validator = validators.Path(exists=True)
|
|
65
|
+
if isinstance(value, list):
|
|
66
|
+
for v in value:
|
|
67
|
+
path_validator(_, v)
|
|
68
|
+
elif isinstance(value, Path):
|
|
69
|
+
path_validator(_, value)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def rules_path_resolver(_: Any, tokens: Sequence[Token]) -> Path | None:
|
|
73
|
+
"""
|
|
74
|
+
Resolve ``--rules`` tokens into a rules directory path.
|
|
75
|
+
|
|
76
|
+
Accepts either a platform alias (``windows``/``linux``) resolved
|
|
77
|
+
against ``settings.rules_path``, or an explicit path.
|
|
78
|
+
"""
|
|
79
|
+
path: Path | None = None
|
|
80
|
+
for token in tokens:
|
|
81
|
+
if token.value in {Platform.LINUX, Platform.WINDOWS}:
|
|
82
|
+
if not settings.rules_path:
|
|
83
|
+
raise ConfigError("You can't use aliases without specifying the path in the config")
|
|
84
|
+
|
|
85
|
+
path = settings.rules_path / token.value
|
|
86
|
+
break
|
|
87
|
+
else:
|
|
88
|
+
# no platform alias — use the specified value as a path
|
|
89
|
+
path = Path(token.value)
|
|
90
|
+
|
|
91
|
+
return path
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def files_path_resolver(files: list[Path]) -> list[Path] | None:
|
|
95
|
+
"""
|
|
96
|
+
Expand a list of file/folder paths into a flat list of files.
|
|
97
|
+
|
|
98
|
+
Handles wildcards on Windows and recurses into directories. Returns
|
|
99
|
+
``None`` when one or more inputs are invalid so the caller can bail out.
|
|
100
|
+
"""
|
|
101
|
+
files_queue: deque[Path] = deque(files)
|
|
102
|
+
files_for_analysis: set[Path] = set()
|
|
103
|
+
|
|
104
|
+
is_ok = True
|
|
105
|
+
while files_queue:
|
|
106
|
+
file = files_queue.popleft()
|
|
107
|
+
|
|
108
|
+
# process wildcards
|
|
109
|
+
if sys.platform == "win32" and "*" in str(file):
|
|
110
|
+
for f in Path.cwd().glob(str(file)):
|
|
111
|
+
if f.is_file():
|
|
112
|
+
files_queue.append(f)
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
file = file.expanduser().resolve()
|
|
116
|
+
|
|
117
|
+
if not file.exists():
|
|
118
|
+
console.error(f"{file!s} doesn't exist")
|
|
119
|
+
is_ok = False
|
|
120
|
+
|
|
121
|
+
if file.is_dir():
|
|
122
|
+
files_for_analysis.update(file.glob("**/*"))
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
files_for_analysis.add(file)
|
|
126
|
+
|
|
127
|
+
if len(files_for_analysis) == 0:
|
|
128
|
+
console.error("Nothing to scan")
|
|
129
|
+
is_ok = False
|
|
130
|
+
|
|
131
|
+
return list(files_for_analysis) if is_ok else None
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
from cyclopts import Parameter
|
|
7
|
+
|
|
8
|
+
from sandbox_cli.console import console
|
|
9
|
+
from sandbox_cli.core.browser import open_link
|
|
10
|
+
from sandbox_cli.core.sandbox import (
|
|
11
|
+
format_link,
|
|
12
|
+
get_key_by_name,
|
|
13
|
+
)
|
|
14
|
+
from sandbox_cli.core.scan import load_report, load_scan_arguments
|
|
15
|
+
|
|
16
|
+
__all__ = ["open_browser"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def open_browser(
|
|
20
|
+
path: Annotated[
|
|
21
|
+
Path,
|
|
22
|
+
Parameter(
|
|
23
|
+
help="Folder with sandbox report (report.json and scan_config.json)",
|
|
24
|
+
),
|
|
25
|
+
] = Path(),
|
|
26
|
+
) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Open sandbox link in the default browser.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
report_file = path / "report.json"
|
|
32
|
+
if not report_file.exists():
|
|
33
|
+
console.error(f"Can't find report.json: {path}")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
scan_config_file = path / "scan_config.json"
|
|
37
|
+
if not scan_config_file.exists():
|
|
38
|
+
console.error(f"Can't find scan_config.json: {path}")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
report = load_report(report_file)
|
|
42
|
+
scan_config = load_scan_arguments(scan_config_file)
|
|
43
|
+
|
|
44
|
+
key = get_key_by_name(scan_config.sandbox_key_name)
|
|
45
|
+
link = format_link(report, key=key)
|
|
46
|
+
|
|
47
|
+
open_link(link)
|