sandbox-cli 0.2.29__tar.gz → 0.2.30__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/PKG-INFO +5 -5
  2. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/pyproject.toml +5 -5
  3. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/cli/__init__.py +2 -1
  4. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/cli/reporter.py +55 -2
  5. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/cli/scanner/__init__.py +27 -0
  6. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/internal/config.py +7 -0
  7. sandbox_cli-0.2.30/sandbox_cli/internal/helpers.py +88 -0
  8. sandbox_cli-0.2.30/sandbox_cli/models/sandbox_arguments.py +22 -0
  9. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/downloader/__init__.py +8 -2
  10. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/scanner/__init__.py +21 -44
  11. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/scanner/advanced.py +18 -2
  12. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/scanner/rescan.py +18 -2
  13. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/unpack/plugins/sort_by_plugins.py +1 -1
  14. sandbox_cli-0.2.29/sandbox_cli/internal/helpers.py +0 -32
  15. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/.gitignore +0 -0
  16. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/LICENSE +0 -0
  17. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/NOTICE +0 -0
  18. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/README.md +0 -0
  19. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/__main__.py +0 -0
  20. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/cli/downloader.py +0 -0
  21. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/cli/images.py +0 -0
  22. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/cli/rules/__init__.py +0 -0
  23. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/cli/unpack.py +0 -0
  24. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/console.py +0 -0
  25. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/internal/__init__.py +0 -0
  26. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/models/__init__.py +0 -0
  27. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/models/detections.py +0 -0
  28. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/__init__.py +0 -0
  29. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/compiler/__init__.py +0 -0
  30. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/compiler/abc.py +0 -0
  31. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/compiler/docker.py +0 -0
  32. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/compiler/ssh.py +0 -0
  33. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/extractors.py +0 -0
  34. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/merge_dll_hooks.py +0 -0
  35. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/unpack/__init__.py +0 -0
  36. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/unpack/plugins/__init__.py +0 -0
  37. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/unpack/plugins/abc.py +0 -0
  38. {sandbox_cli-0.2.29 → sandbox_cli-0.2.30}/sandbox_cli/utils/unpack/plugins/correlation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sandbox-cli
3
- Version: 0.2.29
3
+ Version: 0.2.30
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
@@ -12,13 +12,13 @@ License-File: LICENSE
12
12
  License-File: NOTICE
13
13
  Requires-Python: >=3.11
14
14
  Requires-Dist: aiofiles>=24.1.0
15
- Requires-Dist: asyncssh>=2.19.0
16
- Requires-Dist: cryptography>=44.0.0
17
- Requires-Dist: cyclopts>=3.9.0
15
+ Requires-Dist: asyncssh>=2.20.0
16
+ Requires-Dist: cryptography>=44.0.2
17
+ Requires-Dist: cyclopts>=3.12.0
18
18
  Requires-Dist: docker>=7.1.0
19
19
  Requires-Dist: ptsandbox>=5.0.0
20
20
  Requires-Dist: pyzipper>=0.3.6
21
- Requires-Dist: rich>=13.9.4
21
+ Requires-Dist: rich>=14.0.0
22
22
  Requires-Dist: zstandard>=0.23.0
23
23
  Description-Content-Type: text/markdown
24
24
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sandbox-cli"
3
- version = "0.2.29"
3
+ version = "0.2.30"
4
4
  description = "Command line tool for interaction with sandboxes"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -9,13 +9,13 @@ license-files = ["LICENSE", "NOTICE"]
9
9
  authors = [{ name = "Alexey Kolesnikov" }]
10
10
  dependencies = [
11
11
  "aiofiles>=24.1.0",
12
- "asyncssh>=2.19.0",
13
- "cryptography>=44.0.0",
14
- "cyclopts>=3.9.0",
12
+ "asyncssh>=2.20.0",
13
+ "cryptography>=44.0.2",
14
+ "cyclopts>=3.12.0",
15
15
  "docker>=7.1.0",
16
16
  "ptsandbox>=5.0.0",
17
17
  "pyzipper>=0.3.6",
18
- "rich>=13.9.4",
18
+ "rich>=14.0.0",
19
19
  "zstandard>=0.23.0",
20
20
  ]
21
21
 
@@ -4,7 +4,7 @@ Register all commands here
4
4
 
5
5
  from cyclopts import App
6
6
 
7
- from sandbox_cli.cli.reporter import generate_report
7
+ from sandbox_cli.cli.reporter import generate_report, open_browser
8
8
  from sandbox_cli.cli.unpack import unpack_logs
9
9
  from sandbox_cli.console import console
10
10
  from sandbox_cli.internal.config import configpath, settings
@@ -32,6 +32,7 @@ app = App(
32
32
 
33
33
  app.command(name=["conv", "unpack"])(unpack_logs)
34
34
  app.command(name="report")(generate_report)
35
+ app.command(name="browser")(open_browser)
35
36
 
36
37
  if len(settings.sandbox_keys) > 0:
37
38
  from sandbox_cli.cli.downloader import download_command, download_email
@@ -9,6 +9,8 @@ from ptsandbox.models import SandboxBaseTaskResponse
9
9
  from rich.table import Table
10
10
 
11
11
  from sandbox_cli.console import console
12
+ from sandbox_cli.internal.helpers import format_link, get_key_by_name, open_link
13
+ from sandbox_cli.models.sandbox_arguments import SandboxArguments
12
14
  from sandbox_cli.utils.extractors import (
13
15
  extract_memory,
14
16
  extract_network_from_trace,
@@ -72,6 +74,10 @@ def generate_report(
72
74
  if not report_file.exists():
73
75
  continue
74
76
 
77
+ scan_config_file = root / "scan_config.json"
78
+ if not scan_config_file.exists():
79
+ continue
80
+
75
81
  if latest and (
76
82
  datetime.datetime.now() - datetime.datetime.fromtimestamp(report_file.stat().st_mtime)
77
83
  ) > datetime.timedelta(hours=2):
@@ -81,6 +87,10 @@ def generate_report(
81
87
  report_data = fd.read()
82
88
  scan_data = SandboxBaseTaskResponse.model_validate_json(report_data)
83
89
 
90
+ with open(scan_config_file, encoding="utf-8") as fd:
91
+ scan_config_data = fd.read()
92
+ scan_config = SandboxArguments.model_validate_json(scan_config_data)
93
+
84
94
  if (report := scan_data.get_long_report()) is None:
85
95
  console.warning(f"A report without behavioral analysis: {root}")
86
96
  continue
@@ -101,6 +111,9 @@ def generate_report(
101
111
  if image is None:
102
112
  image = root.name
103
113
 
114
+ key = get_key_by_name(scan_config.sandbox_key_name)
115
+ link = format_link(scan_data, key=key)
116
+
104
117
  data.append(
105
118
  {
106
119
  "sample": report.artifacts[0].file_info.file_path, # type: ignore
@@ -109,6 +122,7 @@ def generate_report(
109
122
  "static": delimeter.join(extract_static(report)),
110
123
  "memory": delimeter.join(extract_memory(report)),
111
124
  "network": delimeter.join(extract_network_from_trace(corr_trace)),
125
+ "sandbox": link,
112
126
  }
113
127
  )
114
128
 
@@ -116,8 +130,8 @@ def generate_report(
116
130
  case "md":
117
131
  md_report_head = textwrap.dedent(
118
132
  """
119
- | Sample | Image | Verdict | Static | Memory | Network |
120
- | --- | --- | --- | --- | --- | --- |"""
133
+ | Sample | Image | Verdict | Static | Memory | Network | Sandbox |
134
+ | --- | --- | --- | --- | --- | --- | --- |"""
121
135
  ).strip()
122
136
 
123
137
  md_report_base = "|{sample}|{image}|{verdict}|{static}|{memory}|{network}|"
@@ -132,6 +146,8 @@ def generate_report(
132
146
  table.add_column("Static", overflow="fold", style="bold")
133
147
  table.add_column("Memory", overflow="fold")
134
148
  table.add_column("Network", overflow="fold")
149
+ table.add_column("Sandbox", overflow="fold")
150
+
135
151
  for d in data:
136
152
  table.add_row(
137
153
  d["sample"],
@@ -140,7 +156,44 @@ def generate_report(
140
156
  d["static"],
141
157
  d["memory"],
142
158
  d["network"],
159
+ d["sandbox"],
143
160
  )
144
161
  console.print(table)
145
162
  case _:
146
163
  pass
164
+
165
+
166
+ def open_browser(
167
+ path: Annotated[
168
+ Path,
169
+ Parameter(
170
+ help="Folder with sandbox report (report.json and scan_config.json)",
171
+ ),
172
+ ] = Path(),
173
+ ) -> None:
174
+ """
175
+ Open sandbox link in the default browser.
176
+ """
177
+
178
+ report_file = path / "report.json"
179
+ if not report_file.exists():
180
+ console.error(f"Can't find report.json: {path}")
181
+ return
182
+
183
+ scan_config_file = path / "scan_config.json"
184
+ if not scan_config_file.exists():
185
+ console.error(f"Can't find scan_config.json: {path}")
186
+ return
187
+
188
+ with open(report_file, encoding="utf-8") as fd:
189
+ report_data = fd.read()
190
+ report = SandboxBaseTaskResponse.model_validate_json(report_data)
191
+
192
+ with open(scan_config_file, encoding="utf-8") as fd:
193
+ scan_config_data = fd.read()
194
+ scan_config = SandboxArguments.model_validate_json(scan_config_data)
195
+
196
+ key = get_key_by_name(scan_config.sandbox_key_name)
197
+ link = format_link(report, key=key)
198
+
199
+ open_link(link)
@@ -110,6 +110,14 @@ async def re_scan(
110
110
  group="Download options",
111
111
  ),
112
112
  ] = False,
113
+ open_browser: Annotated[
114
+ bool,
115
+ Parameter(
116
+ name=["--open-browser", "-ob"],
117
+ help="Open analysis link in the default browser",
118
+ negative="",
119
+ ),
120
+ ] = False,
113
121
  ) -> None:
114
122
  """
115
123
  Send traces to re-scan.
@@ -137,6 +145,7 @@ async def re_scan(
137
145
  is_local=is_local,
138
146
  unpack=unpack,
139
147
  debug=debug,
148
+ open_browser=open_browser,
140
149
  )
141
150
 
142
151
 
@@ -312,6 +321,14 @@ async def scan(
312
321
  negative="",
313
322
  ),
314
323
  ] = False,
324
+ open_browser: Annotated[
325
+ bool,
326
+ Parameter(
327
+ name=["--open-browser", "-ob"],
328
+ help="Open analysis link in the default browser",
329
+ negative="",
330
+ ),
331
+ ] = False,
315
332
  ) -> None:
316
333
  """
317
334
  Send files to scan with the sandbox.
@@ -374,6 +391,7 @@ async def scan(
374
391
  crashdumps=crashdumps,
375
392
  procdumps=procdumps,
376
393
  decompress=decompress,
394
+ open_browser=open_browser,
377
395
  )
378
396
 
379
397
 
@@ -630,6 +648,14 @@ async def scan_new(
630
648
  negative="",
631
649
  ),
632
650
  ] = False,
651
+ open_browser: Annotated[
652
+ bool,
653
+ Parameter(
654
+ name=["--open-browser", "-ob"],
655
+ help="Open analysis link in the default browser",
656
+ negative="",
657
+ ),
658
+ ] = False,
633
659
  ) -> None:
634
660
  """
635
661
  Send files to scan with the sandbox (advanced scan).
@@ -698,4 +724,5 @@ async def scan_new(
698
724
  crashdumps=crashdumps,
699
725
  procdumps=procdumps,
700
726
  decompress=decompress,
727
+ open_browser=open_browser,
701
728
  )
@@ -84,6 +84,10 @@ class Settings(BaseModel):
84
84
  description=self.description,
85
85
  )
86
86
 
87
+ class Browser(BaseModel):
88
+ path: Path
89
+ args: list[str]
90
+
87
91
  # default settings (not changable)
88
92
  linux_images: set[VMImage] = {
89
93
  VMImage.ALTWORKSTATION_X64,
@@ -119,10 +123,13 @@ class Settings(BaseModel):
119
123
  docker: Docker = Docker()
120
124
  sandbox: list[Sandbox] = []
121
125
  rules_path: Path | None = Field(default=None, alias="rules-path")
126
+ browser: Browser | None = Field(default=None)
122
127
 
123
128
  def model_post_init(self, __context: Any) -> None:
124
129
  self.sandbox_keys = [x.sandbox_key for x in self.sandbox]
125
130
  self.rules_path = Path(self.rules_path) if self.rules_path else None
131
+ if self.browser:
132
+ self.browser.args.append("%s")
126
133
 
127
134
 
128
135
  def load_config(path: Path) -> Settings:
@@ -0,0 +1,88 @@
1
+ import sys
2
+ import webbrowser
3
+ from pathlib import Path
4
+ from typing import Any, overload
5
+
6
+ from ptsandbox import Sandbox, SandboxKey
7
+ from ptsandbox.models import SandboxBaseTaskResponse
8
+
9
+ from sandbox_cli.console import console
10
+ from sandbox_cli.internal.config import settings
11
+ from sandbox_cli.models.sandbox_arguments import SandboxArguments
12
+
13
+
14
+ def get_key_by_name(key_name: str) -> SandboxKey:
15
+ for sandbox_key in settings.sandbox_keys:
16
+ if sandbox_key.name == key_name:
17
+ return sandbox_key
18
+ raise KeyError()
19
+
20
+
21
+ def get_sandbox_key_by_host(task_host: str) -> SandboxKey:
22
+ for sandbox_key in settings.sandbox_keys:
23
+ if sandbox_key.host == task_host:
24
+ return sandbox_key
25
+
26
+ raise KeyError()
27
+
28
+
29
+ def validate_key(_: Any, value: Any) -> None:
30
+ try:
31
+ get_key_by_name(value)
32
+ except KeyError:
33
+ console.error(
34
+ f'Key "{value}" doesn\'t exists in config. Available keys: "{'","'.join(x.name for x in settings.sandbox_keys)}"'
35
+ )
36
+ sys.exit(1)
37
+
38
+
39
+ @overload
40
+ def format_link(
41
+ report: SandboxBaseTaskResponse,
42
+ *,
43
+ sandbox: Sandbox,
44
+ key: SandboxKey | None = None,
45
+ ) -> str: ...
46
+
47
+
48
+ @overload
49
+ def format_link(
50
+ report: SandboxBaseTaskResponse,
51
+ *,
52
+ sandbox: Sandbox | None = None,
53
+ key: SandboxKey,
54
+ ) -> str: ...
55
+
56
+
57
+ def format_link(
58
+ report: SandboxBaseTaskResponse,
59
+ *,
60
+ sandbox: Sandbox | None = None,
61
+ key: SandboxKey | None = None,
62
+ ) -> str:
63
+ key = key or (sandbox.api.key if sandbox else None)
64
+
65
+ if not key:
66
+ console.error("Key not provided")
67
+ sys.exit(1)
68
+
69
+ if not (short_report := report.get_short_report()):
70
+ return "Unknown"
71
+
72
+ return f"https://{key.host}/tasks/{short_report.scan_id}"
73
+
74
+
75
+ def save_scan_arguments(out_dir: Path, scan_args: SandboxArguments) -> None:
76
+ scan_config_path = out_dir / "scan_config.json"
77
+ scan_config_path.write_text(scan_args.model_dump_json(exclude="debug_options", indent=4), encoding="utf-8")
78
+
79
+
80
+ def open_link(link: str) -> None:
81
+ if settings.browser != None:
82
+ webbrowser.register("new_default_browser", None, webbrowser.GenericBrowser([settings.browser.path, *settings.browser.args]), preferred=True)
83
+ if not webbrowser.open(link):
84
+ console.error("Can't open link in the specified browser. Please check browser path and args.")
85
+ return
86
+
87
+ if not webbrowser.open_new_tab(link):
88
+ console.error("Can't open link in the default browser. Try adding path and args for your browser to the config file.")
@@ -0,0 +1,22 @@
1
+ from enum import Enum
2
+
3
+ from ptsandbox.models.api import SandboxOptions, SandboxOptionsAdvanced
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class ScanType(str, Enum):
8
+ SCAN = "scan"
9
+ RE_SCAN = "re-scan"
10
+ SCAN_NEW = "scan-new"
11
+
12
+ def __str__(self) -> str:
13
+ return str(self.value)
14
+
15
+ def __repr__(self) -> str:
16
+ return str(self.value)
17
+
18
+
19
+ class SandboxArguments(BaseModel):
20
+ type: ScanType
21
+ sandbox_key_name: str
22
+ sandbox_options: SandboxOptions | SandboxOptionsAdvanced
@@ -40,6 +40,14 @@ async def _save_artifact(
40
40
 
41
41
  # sanitize path
42
42
  path = out_dir / Path(str(path).replace(" ", "_"))
43
+ n = 1
44
+ while path.exists():
45
+ if not path.with_name(path.name + f"_{n}").exists():
46
+ path = path.with_name(path.name + f"_{n}")
47
+ break
48
+ n += 1
49
+ path.parent.mkdir(exist_ok=True, parents=True)
50
+ path.touch()
43
51
  task_id: TaskID = None # type: ignore
44
52
 
45
53
  async with semaphore:
@@ -53,8 +61,6 @@ async def _save_artifact(
53
61
  )
54
62
  else:
55
63
  task_id = progress.add_task(rf"\[[green1]{scan_id}[/]] {escape(path.name)}")
56
-
57
- path.parent.mkdir(exist_ok=True, parents=True)
58
64
  try:
59
65
  downloaded_data = await sandbox.get_file(uri)
60
66
  except SandboxFileNotFoundException:
@@ -2,62 +2,27 @@ import asyncio
2
2
  import sys
3
3
  from collections.abc import Coroutine
4
4
  from pathlib import Path
5
- from typing import Any, overload
5
+ from typing import Any
6
6
 
7
7
  import aiofiles
8
8
  from ptsandbox import Sandbox
9
- from ptsandbox.models import (
10
- SandboxBaseScanTaskRequest,
11
- SandboxBaseTaskResponse,
12
- SandboxKey,
13
- SandboxOptions,
14
- )
9
+ from ptsandbox.models import SandboxBaseScanTaskRequest, SandboxKey, SandboxOptions
15
10
 
16
11
  from sandbox_cli.console import console
17
12
  from sandbox_cli.internal.config import VMImage, settings
18
- from sandbox_cli.internal.helpers import get_key_by_name
13
+ from sandbox_cli.internal.helpers import (
14
+ format_link,
15
+ get_key_by_name,
16
+ open_link,
17
+ save_scan_arguments,
18
+ )
19
+ from sandbox_cli.models.sandbox_arguments import SandboxArguments, ScanType
19
20
  from sandbox_cli.utils.compiler import compile_rules_internal
20
21
  from sandbox_cli.utils.downloader import download
21
22
  from sandbox_cli.utils.merge_dll_hooks import merge_dll_hooks
22
23
  from sandbox_cli.utils.unpack import Unpack
23
24
 
24
25
 
25
- @overload
26
- def format_link(
27
- report: SandboxBaseTaskResponse,
28
- *,
29
- sandbox: Sandbox,
30
- key: SandboxKey | None = None,
31
- ) -> str: ...
32
-
33
-
34
- @overload
35
- def format_link(
36
- report: SandboxBaseTaskResponse,
37
- *,
38
- sandbox: Sandbox | None = None,
39
- key: SandboxKey,
40
- ) -> str: ...
41
-
42
-
43
- def format_link(
44
- report: SandboxBaseTaskResponse,
45
- *,
46
- sandbox: Sandbox | None = None,
47
- key: SandboxKey | None = None,
48
- ) -> str:
49
- key = key or (sandbox.api.key if sandbox else None)
50
-
51
- if not key:
52
- console.error("Key not provided")
53
- sys.exit(1)
54
-
55
- if not (short_report := report.get_short_report()):
56
- return "Unknown"
57
-
58
- return f"https://{key.host}/tasks/{short_report.scan_id}"
59
-
60
-
61
26
  async def _get_compiled_rules(rules_dir: Path | None, is_local: bool) -> bytes | None:
62
27
  if not rules_dir:
63
28
  return None
@@ -188,6 +153,7 @@ async def scan_internal(
188
153
  crashdumps: bool,
189
154
  procdumps: bool,
190
155
  decompress: bool,
156
+ open_browser: bool,
191
157
  ) -> None:
192
158
  key = get_key_by_name(key_name)
193
159
  sandbox_sem = asyncio.Semaphore(value=key.max_workers)
@@ -222,6 +188,10 @@ async def scan_internal(
222
188
  console.info(
223
189
  rf"{idx} [magenta]\[{sandbox_options.sandbox.image_id}][/magenta] Waiting [yellow]{file_path.name}[/]: {format_link(scan_result, key=key)}"
224
190
  )
191
+
192
+ if open_browser:
193
+ open_link(format_link(scan_result, key=key))
194
+
225
195
  awaited_report = await sandbox.wait_for_report(scan_result, wait_time)
226
196
  if not awaited_report:
227
197
  console.error(f"{idx} Scan [yellow]{file_path.name}[/] failed: {scan_result=}")
@@ -263,6 +233,13 @@ async def scan_internal(
263
233
  out_dir: Path,
264
234
  idx: str,
265
235
  ) -> None:
236
+ sandbox_arguments = SandboxArguments(
237
+ type=ScanType.SCAN,
238
+ sandbox_key_name=key.name,
239
+ sandbox_options=sandbox_options.sandbox,
240
+ )
241
+ save_scan_arguments(out_dir, sandbox_arguments)
242
+
266
243
  # try:
267
244
  await process_file(sandbox_options, file_path, out_dir, idx)
268
245
  # except Exception as ex:
@@ -14,11 +14,16 @@ from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, TimeElaps
14
14
 
15
15
  from sandbox_cli.console import console
16
16
  from sandbox_cli.internal.config import VMImage, settings
17
- from sandbox_cli.internal.helpers import get_key_by_name
17
+ from sandbox_cli.internal.helpers import (
18
+ format_link,
19
+ get_key_by_name,
20
+ open_link,
21
+ save_scan_arguments,
22
+ )
23
+ from sandbox_cli.models.sandbox_arguments import SandboxArguments, ScanType
18
24
  from sandbox_cli.utils.compiler import compile_rules_internal
19
25
  from sandbox_cli.utils.downloader import download
20
26
  from sandbox_cli.utils.merge_dll_hooks import merge_dll_hooks
21
- from sandbox_cli.utils.scanner import format_link
22
27
  from sandbox_cli.utils.unpack import Unpack
23
28
 
24
29
  DELIMETER = "\n"
@@ -200,6 +205,7 @@ async def scan_internal_advanced(
200
205
  crashdumps: bool,
201
206
  procdumps: bool,
202
207
  decompress: bool,
208
+ open_browser: bool,
203
209
  ) -> None:
204
210
  key = get_key_by_name(key_name)
205
211
  sandbox_sem = asyncio.Semaphore(value=key.max_workers)
@@ -259,6 +265,9 @@ async def scan_internal_advanced(
259
265
  formatted_link = f"[medium_purple]{format_link(scan_result, key=key)}[/]"
260
266
  final_output = f"{image_string} • [yellow]{file_path.name}[/] • {formatted_link}"
261
267
 
268
+ if open_browser:
269
+ open_link(format_link(scan_result, key=key))
270
+
262
271
  progress.update(
263
272
  task_id=task_id,
264
273
  description=f"Waiting [yellow]{file_path.name}[/]",
@@ -313,6 +322,13 @@ async def scan_internal_advanced(
313
322
  out_dir: Path,
314
323
  idx: str,
315
324
  ) -> None:
325
+ sandbox_arguments = SandboxArguments(
326
+ type=ScanType.SCAN_NEW,
327
+ sandbox_key_name=key.name,
328
+ sandbox_options=sandbox_options,
329
+ )
330
+ save_scan_arguments(out_dir, sandbox_arguments)
331
+
316
332
  # try:
317
333
  await process_file(sandbox_options, file_path, out_dir, idx)
318
334
  # except Exception as ex:
@@ -18,10 +18,15 @@ from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, TimeElaps
18
18
 
19
19
  from sandbox_cli.console import console
20
20
  from sandbox_cli.internal.config import settings
21
- from sandbox_cli.internal.helpers import get_key_by_name
21
+ from sandbox_cli.internal.helpers import (
22
+ format_link,
23
+ get_key_by_name,
24
+ open_link,
25
+ save_scan_arguments,
26
+ )
27
+ from sandbox_cli.models.sandbox_arguments import SandboxArguments, ScanType
22
28
  from sandbox_cli.utils.compiler import compile_rules_internal
23
29
  from sandbox_cli.utils.downloader import download
24
- from sandbox_cli.utils.scanner import format_link
25
30
  from sandbox_cli.utils.unpack import Unpack
26
31
 
27
32
 
@@ -92,6 +97,7 @@ async def rescan_internal(
92
97
  is_local: bool,
93
98
  unpack: bool,
94
99
  debug: bool,
100
+ open_browser: bool,
95
101
  ) -> None:
96
102
  key = get_key_by_name(key_name)
97
103
  sandbox_sem = asyncio.Semaphore(value=key.max_workers)
@@ -148,6 +154,9 @@ async def rescan_internal(
148
154
  formatted_link = f"[medium_purple]{format_link(rescan_result, key=key)}[/]"
149
155
  final_output = f"[yellow]{trace.name}[/] • {formatted_link}"
150
156
 
157
+ if open_browser:
158
+ open_link(format_link(rescan_result, key=key))
159
+
151
160
  progress.update(
152
161
  task_id=task_id,
153
162
  description=f"Waiting for full report for [yellow]{trace.name}[/]",
@@ -237,10 +246,16 @@ async def rescan_internal(
237
246
  tasks: list[Coroutine[Any, Any, None]] = []
238
247
  with progress:
239
248
  sandbox, sandbox_options = await _prepare_rescan_options(progress, rules_dir, key, is_local)
249
+ sandbox_arguments = SandboxArguments(
250
+ type=ScanType.RE_SCAN,
251
+ sandbox_key_name=key.name,
252
+ sandbox_options=sandbox_options.sandbox,
253
+ )
240
254
 
241
255
  if len(traces) == 1:
242
256
  local_out_dir = out_dir / "rescan"
243
257
  local_out_dir.mkdir(parents=True, exist_ok=True)
258
+ save_scan_arguments(local_out_dir, sandbox_arguments)
244
259
  tasks.append(wrapper(traces[0], local_out_dir, "1/1"))
245
260
  else:
246
261
  for i, trace in enumerate(traces):
@@ -251,6 +266,7 @@ async def rescan_internal(
251
266
  local_out_dir = out_dir / trace.stem
252
267
 
253
268
  local_out_dir.mkdir(parents=True, exist_ok=True)
269
+ save_scan_arguments(local_out_dir, sandbox_arguments)
254
270
  idx = f"{i + 1}/{len(traces)}"
255
271
  tasks.append(wrapper(trace, local_out_dir, idx))
256
272
 
@@ -15,7 +15,7 @@ class SortByPlugins(BasePlugin):
15
15
  console.warning(f"{file} not exist")
16
16
  return
17
17
 
18
- with open(file, errors="ignore") as fd:
18
+ with open(file, errors="ignore", encoding="utf-8") as fd:
19
19
  raw_trace = fd.readlines()
20
20
 
21
21
  plugins: defaultdict[str, list[str]] = defaultdict(list)
@@ -1,32 +0,0 @@
1
- import sys
2
- from typing import Any
3
-
4
- from ptsandbox import SandboxKey
5
-
6
- from sandbox_cli.console import console
7
- from sandbox_cli.internal.config import settings
8
-
9
-
10
- def get_key_by_name(key_name: str) -> SandboxKey:
11
- for sandbox_key in settings.sandbox_keys:
12
- if sandbox_key.name == key_name:
13
- return sandbox_key
14
- raise KeyError()
15
-
16
-
17
- def get_sandbox_key_by_host(task_host: str) -> SandboxKey:
18
- for sandbox_key in settings.sandbox_keys:
19
- if sandbox_key.host == task_host:
20
- return sandbox_key
21
-
22
- raise KeyError()
23
-
24
-
25
- def validate_key(_: Any, value: Any) -> None:
26
- try:
27
- get_key_by_name(value)
28
- except KeyError:
29
- console.error(
30
- f'Key "{value}" doesn\'t exists in config. Available keys: "{'","'.join(x.name for x in settings.sandbox_keys)}"'
31
- )
32
- sys.exit(1)
File without changes
File without changes
File without changes
File without changes