sandbox-cli 0.3.1__tar.gz → 0.3.3__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 (53) hide show
  1. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/PKG-INFO +5 -5
  2. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/pyproject.toml +4 -4
  3. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/__init__.py +52 -53
  4. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/download/artifacts.py +10 -0
  5. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/downloader.py +12 -1
  6. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/scanner/advanced.py +28 -5
  7. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/scanner/basic.py +23 -4
  8. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/scanner/rescan.py +11 -2
  9. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/.gitignore +0 -0
  10. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/LICENSE +0 -0
  11. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/NOTICE +0 -0
  12. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/README.md +0 -0
  13. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/__main__.py +0 -0
  14. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/_converters.py +0 -0
  15. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/browser.py +0 -0
  16. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/download/__init__.py +0 -0
  17. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/download/email.py +0 -0
  18. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/images.py +0 -0
  19. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/report.py +0 -0
  20. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/rules/__init__.py +0 -0
  21. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/rules/compile.py +0 -0
  22. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/rules/test.py +0 -0
  23. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/scanner/__init__.py +0 -0
  24. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/scanner/re_scan.py +0 -0
  25. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/scanner/scan.py +0 -0
  26. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/scanner/scan_new.py +0 -0
  27. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/cli/unpack.py +0 -0
  28. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/console.py +0 -0
  29. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/core/__init__.py +0 -0
  30. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/core/browser.py +0 -0
  31. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/core/config.py +0 -0
  32. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/core/exceptions.py +0 -0
  33. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/core/progress.py +0 -0
  34. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/core/sandbox.py +0 -0
  35. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/core/scan.py +0 -0
  36. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/__init__.py +0 -0
  37. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/compiler/__init__.py +0 -0
  38. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/compiler/abc.py +0 -0
  39. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/compiler/docker.py +0 -0
  40. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/compiler/ssh.py +0 -0
  41. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/report.py +0 -0
  42. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/scanner/__init__.py +0 -0
  43. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/scanner/compile.py +0 -0
  44. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/scanner/hooks.py +0 -0
  45. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/scanner/images.py +0 -0
  46. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/scanner/tasks.py +0 -0
  47. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/scanner/uploads.py +0 -0
  48. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/scanner/utils.py +0 -0
  49. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/unpack/__init__.py +0 -0
  50. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/unpack/plugins/__init__.py +0 -0
  51. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/unpack/plugins/abc.py +0 -0
  52. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/unpack/plugins/correlation.py +0 -0
  53. {sandbox_cli-0.3.1 → sandbox_cli-0.3.3}/sandbox_cli/services/unpack/plugins/sort_by_plugins.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: sandbox-cli
3
- Version: 0.3.1
3
+ Version: 0.3.3
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
@@ -14,10 +14,10 @@ Requires-Python: >=3.11
14
14
  Requires-Dist: aiofiles>=25.1.0
15
15
  Requires-Dist: asyncssh>=2.24.0
16
16
  Requires-Dist: colorama>=0.4.6
17
- Requires-Dist: cryptography>=49.0.0
18
- Requires-Dist: cyclopts>=4.20.0
17
+ Requires-Dist: cryptography>=50.0.0
18
+ Requires-Dist: cyclopts>=4.25.0
19
19
  Requires-Dist: docker>=7.2.0
20
- Requires-Dist: ptsandbox>=5.1.0
20
+ Requires-Dist: ptsandbox>=5.1.5
21
21
  Requires-Dist: rich>=15.0.0
22
22
  Requires-Dist: zstandard>=0.25.0
23
23
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sandbox-cli"
3
- version = "0.3.1"
3
+ version = "0.3.3"
4
4
  description = "Command line tool for interaction with sandboxes"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -11,10 +11,10 @@ dependencies = [
11
11
  "aiofiles>=25.1.0",
12
12
  "asyncssh>=2.24.0",
13
13
  "colorama>=0.4.6",
14
- "cryptography>=49.0.0",
15
- "cyclopts>=4.20.0",
14
+ "cryptography>=50.0.0",
15
+ "cyclopts>=4.25.0",
16
16
  "docker>=7.2.0",
17
- "ptsandbox>=5.1.0",
17
+ "ptsandbox>=5.1.5",
18
18
  "rich>=15.0.0",
19
19
  "zstandard>=0.25.0",
20
20
  ]
@@ -58,7 +58,7 @@ def completion(
58
58
  path = app.install_completion(shell=shell)
59
59
  console.info(f"Completion installed to {path}")
60
60
  else:
61
- console.print(app.generate_completion(shell=shell), end="")
61
+ print(app.generate_completion(shell=shell))
62
62
 
63
63
 
64
64
  app.command(name="completion")(completion)
@@ -81,55 +81,54 @@ app.command(
81
81
  help="Open sandbox link in the default browser.",
82
82
  )
83
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
- )
84
+ scanner_app = App(name="scanner", help="Scan with the sandbox.", help_format="markdown", console=console)
85
+ scanner_app.command(
86
+ "sandbox_cli.cli.scanner.scan:scan",
87
+ name="scan",
88
+ help="Send files to scan with the sandbox.",
89
+ )
90
+ scanner_app.command(
91
+ "sandbox_cli.cli.scanner.scan_new:scan_new",
92
+ name="scan-new",
93
+ help="Send files to scan with the sandbox (advanced scan).",
94
+ )
95
+ scanner_app.command(
96
+ "sandbox_cli.cli.scanner.re_scan:re_scan",
97
+ name="re-scan",
98
+ help="Send traces to re-scan.",
99
+ )
100
+ app.command(scanner_app)
101
+
102
+ rules_app = App(
103
+ name="rules",
104
+ help="Working with raw sandbox rules.",
105
+ help_format="markdown",
106
+ console=console,
107
+ )
108
+ rules_app.command(
109
+ "sandbox_cli.cli.rules.compile:compile_rules",
110
+ name="compile",
111
+ help="Get compiled rules for working with third-party services.",
112
+ )
113
+ rules_app.command(
114
+ "sandbox_cli.cli.rules.test:test_rules",
115
+ name="test",
116
+ help="Testing written rules.",
117
+ )
118
+ app.command(rules_app)
119
+
120
+ app.command(
121
+ "sandbox_cli.cli.images:get_images",
122
+ name="images",
123
+ help="Get available images in the sandbox.",
124
+ )
125
+ app.command(
126
+ "sandbox_cli.cli.download:download_command",
127
+ name="download",
128
+ help="Download any artifact from the sandbox.",
129
+ )
130
+ app.command(
131
+ "sandbox_cli.cli.download:download_email",
132
+ name="email",
133
+ help="Upload an email and get its headers.",
134
+ )
@@ -229,6 +229,15 @@ async def download_command(
229
229
  group="Download options",
230
230
  ),
231
231
  ] = False,
232
+ sample: Annotated[
233
+ bool,
234
+ Parameter(
235
+ name=["--sample", "-s"],
236
+ help="Download the original sample submitted for analysis",
237
+ negative="",
238
+ group="Download options",
239
+ ),
240
+ ] = False,
232
241
  query: Annotated[
233
242
  str | None,
234
243
  Parameter(
@@ -281,6 +290,7 @@ async def download_command(
281
290
  video=video,
282
291
  amsi=amsi,
283
292
  dex=dex,
293
+ sample=sample,
284
294
  logs=logs,
285
295
  decompress=decompress,
286
296
  )
@@ -52,6 +52,7 @@ class DownloadOptions:
52
52
  video: bool = False
53
53
  amsi: bool = False
54
54
  dex: bool = False
55
+ sample: bool = False
55
56
 
56
57
 
57
58
  # At first glance, it's counter-intuitive, but ArtifactType can contain not only predefined fields in it, but also newly added.
@@ -321,11 +322,12 @@ async def download(
321
322
  full_report: SandboxBaseTaskResponse | None = None,
322
323
  concurrency: int = DEFAULT_CONCURRENCY,
323
324
  read_timeout: int = DEFAULT_READ_TIMEOUT,
325
+ semaphore: asyncio.Semaphore | None = None,
324
326
  ) -> None:
325
327
  tasks: list[Coroutine[Any, Any, None]] = []
326
328
  saved_report_dirs: set[Path] = set()
327
329
  claimed: set[Path] = set()
328
- semaphore = asyncio.Semaphore(value=concurrency)
330
+ semaphore = semaphore or asyncio.Semaphore(value=concurrency)
329
331
 
330
332
  def add_task(out_dir: Path, name: str, file_uri: str, decompress: bool = False, overwrite: bool = False) -> None:
331
333
  tasks.append(
@@ -347,6 +349,15 @@ async def download(
347
349
  )
348
350
  )
349
351
 
352
+ if options.sample:
353
+ for artifact in report.artifacts:
354
+ fi = artifact.file_info
355
+ if fi is None or not fi.file_uri:
356
+ continue
357
+
358
+ name = Path(fi.file_path).name or fi.sha256
359
+ add_task(out_dir, f"{name}.bin", fi.file_uri)
360
+
350
361
  for artifact in report.artifacts:
351
362
  for sandbox_result in artifact.get_sandbox_results():
352
363
  if sandbox_result is None or sandbox_result.details is None or sandbox_result.details.sandbox is None:
@@ -28,12 +28,28 @@ from sandbox_cli.core.scan import (
28
28
  save_report,
29
29
  save_scan_arguments,
30
30
  )
31
- from sandbox_cli.services.downloader import DownloadOptions, download
31
+ from sandbox_cli.services.downloader import DEFAULT_CONCURRENCY as DEFAULT_DOWNLOAD_CONCURRENCY
32
+ from sandbox_cli.services.downloader import (
33
+ DownloadOptions,
34
+ download,
35
+ )
32
36
  from sandbox_cli.services.scanner.compile import compile_rules
33
- from sandbox_cli.services.scanner.images import fetch_available_images, resolve_scan_images
37
+ from sandbox_cli.services.scanner.images import (
38
+ fetch_available_images,
39
+ resolve_scan_images,
40
+ )
34
41
  from sandbox_cli.services.scanner.tasks import build_scan_tasks
35
- from sandbox_cli.services.scanner.uploads import gather_uploads, upload_dll_hooks, upload_file, upload_rules
36
- from sandbox_cli.services.scanner.utils import compute_wait_time, get_elapsed_time, shorten_name
42
+ from sandbox_cli.services.scanner.uploads import (
43
+ gather_uploads,
44
+ upload_dll_hooks,
45
+ upload_file,
46
+ upload_rules,
47
+ )
48
+ from sandbox_cli.services.scanner.utils import (
49
+ compute_wait_time,
50
+ get_elapsed_time,
51
+ shorten_name,
52
+ )
37
53
  from sandbox_cli.services.unpack import Unpack
38
54
 
39
55
  if TYPE_CHECKING:
@@ -210,6 +226,7 @@ async def scan_internal_advanced(
210
226
  ) -> None:
211
227
  key = get_key_by_name(key_name)
212
228
  sandbox_sem = asyncio.Semaphore(value=key.max_workers)
229
+ download_sem = asyncio.Semaphore(value=DEFAULT_DOWNLOAD_CONCURRENCY)
213
230
  progress = make_scan_progress(with_image=True, disable=True)
214
231
 
215
232
  async def process_file(
@@ -308,6 +325,7 @@ async def scan_internal_advanced(
308
325
  idx=idx,
309
326
  image=formatted_image,
310
327
  link=formatted_link,
328
+ semaphore=download_sem,
311
329
  )
312
330
  except aiohttp.SocketTimeoutError:
313
331
  console.error(f"{final_output} • got timeout while downloading results • {get_elapsed_time(task)}")
@@ -333,7 +351,12 @@ async def scan_internal_advanced(
333
351
  sandbox_options=sandbox_options,
334
352
  )
335
353
  save_scan_arguments(out_dir, sandbox_arguments)
336
- await process_file(sandbox_options, file_path, out_dir, idx)
354
+
355
+ image_string = rf"\[{sandbox_options.image_id}]"
356
+ try:
357
+ await process_file(sandbox_options, file_path, out_dir, idx)
358
+ except Exception as e:
359
+ console.error(f"{image_string} • [yellow]{shorten_name(file_path.name)}[/] • unexpected error • {e}")
337
360
 
338
361
  console.info(f"Using key: name={key.name} max_workers={key.max_workers}")
339
362
 
@@ -24,11 +24,23 @@ from sandbox_cli.core.scan import (
24
24
  save_report,
25
25
  save_scan_arguments,
26
26
  )
27
- from sandbox_cli.services.downloader import DownloadOptions, download
27
+ from sandbox_cli.services.downloader import DEFAULT_CONCURRENCY as DEFAULT_DOWNLOAD_CONCURRENCY
28
+ from sandbox_cli.services.downloader import (
29
+ DownloadOptions,
30
+ download,
31
+ )
28
32
  from sandbox_cli.services.scanner.compile import compile_rules
29
- from sandbox_cli.services.scanner.images import fetch_available_images, resolve_scan_images
33
+ from sandbox_cli.services.scanner.images import (
34
+ fetch_available_images,
35
+ resolve_scan_images,
36
+ )
30
37
  from sandbox_cli.services.scanner.tasks import build_scan_tasks
31
- from sandbox_cli.services.scanner.uploads import gather_uploads, upload_dll_hooks, upload_file, upload_rules
38
+ from sandbox_cli.services.scanner.uploads import (
39
+ gather_uploads,
40
+ upload_dll_hooks,
41
+ upload_file,
42
+ upload_rules,
43
+ )
32
44
  from sandbox_cli.services.scanner.utils import compute_wait_time, shorten_name
33
45
  from sandbox_cli.services.unpack import Unpack
34
46
 
@@ -107,6 +119,7 @@ async def scan_internal(
107
119
  ) -> None:
108
120
  key = get_key_by_name(key_name)
109
121
  sandbox_sem = asyncio.Semaphore(value=key.max_workers)
122
+ download_sem = asyncio.Semaphore(value=DEFAULT_DOWNLOAD_CONCURRENCY)
110
123
 
111
124
  async def process_file(
112
125
  sandbox_options: SandboxBaseScanTaskRequest.Options,
@@ -174,6 +187,7 @@ async def scan_internal(
174
187
  sandbox,
175
188
  out_dir,
176
189
  replace(download_options, video=True, logs=True),
190
+ semaphore=download_sem,
177
191
  )
178
192
  console.info(
179
193
  rf"\[[magenta]{sandbox_options.sandbox.image_id}[/magenta]] Scan [yellow]{shorten_name(file_path.name)}[/] completed. {link}"
@@ -194,7 +208,12 @@ async def scan_internal(
194
208
  sandbox_options=sandbox_options.sandbox,
195
209
  )
196
210
  save_scan_arguments(out_dir, sandbox_arguments)
197
- await process_file(sandbox_options, file_path, out_dir, idx)
211
+
212
+ image_string = f"[{sandbox_options.sandbox.image_id}]"
213
+ try:
214
+ await process_file(sandbox_options, file_path, out_dir, idx)
215
+ except Exception as e:
216
+ console.error(f"{idx} [{image_string}] [yellow]{shorten_name(file_path.name)}[/] • unexpected error • {e}")
198
217
 
199
218
  console.info(f"Using key: name={key.name} max_workers={key.max_workers}")
200
219
 
@@ -28,7 +28,11 @@ from sandbox_cli.core.scan import (
28
28
  save_report,
29
29
  save_scan_arguments,
30
30
  )
31
- from sandbox_cli.services.downloader import DownloadOptions, download
31
+ from sandbox_cli.services.downloader import DEFAULT_CONCURRENCY as DEFAULT_DOWNLOAD_CONCURRENCY
32
+ from sandbox_cli.services.downloader import (
33
+ DownloadOptions,
34
+ download,
35
+ )
32
36
  from sandbox_cli.services.scanner.compile import compile_rules
33
37
  from sandbox_cli.services.scanner.utils import shorten_name
34
38
  from sandbox_cli.services.unpack import Unpack
@@ -122,6 +126,7 @@ async def rescan_internal(
122
126
  ) -> None:
123
127
  key = get_key_by_name(key_name)
124
128
  sandbox_sem = asyncio.Semaphore(value=key.max_workers)
129
+ download_sem = asyncio.Semaphore(value=DEFAULT_DOWNLOAD_CONCURRENCY)
125
130
 
126
131
  progress = make_scan_progress(with_image=False, disable=True)
127
132
 
@@ -202,6 +207,7 @@ async def rescan_internal(
202
207
  sandbox,
203
208
  out_dir,
204
209
  replace(download_options, logs=True),
210
+ semaphore=download_sem,
205
211
  )
206
212
 
207
213
  console.done(final_output)
@@ -217,7 +223,10 @@ async def rescan_internal(
217
223
  console.error(str(e))
218
224
  return
219
225
 
220
- await process_trace(drakvuf_trace, tcpdump_pcap, trace, out_dir, idx)
226
+ try:
227
+ await process_trace(drakvuf_trace, tcpdump_pcap, trace, out_dir, idx)
228
+ except Exception as e:
229
+ console.error(f"[yellow]{shorten_name(trace.name)}[/] • unexpected error • {e}")
221
230
 
222
231
  console.info(f"Using key: name={key.name} max_workers={key.max_workers}")
223
232
 
File without changes
File without changes
File without changes
File without changes