PyVaultRCE 2.4.0__tar.gz → 2.5.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyVaultRCE
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: Remote Code Execution & Hosting client for PyVault — source never exposed
5
5
  Author: PyVault
6
6
  Keywords: remote execution code hosting vault rce session
@@ -47,7 +47,7 @@ sid = CodeManager.enc("my_script.py")
47
47
  # Upload from a paste service URL
48
48
  sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
49
49
 
50
- # Execute remotely (source stays on server)
50
+ # Execute in a short-lived isolated subprocess by default
51
51
  CodeManager.run(sid)
52
52
 
53
53
  # Upload with a short share link and library metadata
@@ -55,6 +55,9 @@ sid = CodeManager.enc(
55
55
  "my_script.py",
56
56
  label="analytics demo",
57
57
  libraries=["requests", "pandas"],
58
+ username="your_name",
59
+ description="A public demo post",
60
+ tags=["python", "demo"],
58
61
  )
59
62
 
60
63
  # Hide PyVault status messages while running
@@ -72,11 +75,12 @@ CodeManager.ping()
72
75
  Set `PYVAULT_URL` to point at your hosted PyVault server:
73
76
 
74
77
  ```bash
75
- export PYVAULT_URL=https://your-server.replit.app
78
+ export PYVAULT_URL=https://secure-code-runner--diwasrepl.replit.app
76
79
  ```
77
80
 
78
- For local development the client uses `http://localhost:5000` when
79
- `PYVAULT_URL` is not set. You can also control status output globally:
81
+ The package uses the public PyVault deployment when `PYVAULT_URL` is not set.
82
+ For local development, explicitly use `PYVAULT_URL=http://localhost:5000`.
83
+ You can also control status output globally:
80
84
 
81
85
  ```bash
82
86
  export PYVAULT_TERMINAL=off
@@ -91,9 +95,9 @@ where `CodeManager.run()` executes.
91
95
  | Method | Description |
92
96
  |--------|-------------|
93
97
  | `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
94
- | `CodeManager.enc(file_path, label=..., libraries=[...])` | Upload with a label and visible library metadata |
98
+ | `CodeManager.enc(file_path, label=..., libraries=[...], username=..., description=..., tags=...)` | Upload with public profile metadata and visible library metadata |
95
99
  | `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
96
- | `CodeManager.run(session_id, show_terminal=False)` | Fetch and execute code by Session ID with optional quiet status output |
100
+ | `CodeManager.run(session_id, show_terminal=False, isolated=True)` | Fetch and execute code in an isolated subprocess with timeout/resource limits |
97
101
  | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
98
102
  | `CodeManager.ping()` | Check server connectivity |
99
103
  | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
@@ -124,6 +128,10 @@ where `CodeManager.run()` executes.
124
128
  - Session IDs are 21-character cryptographically random hex strings
125
129
  - Source code is never written to disk on the client
126
130
  - Code is encrypted at rest with Fernet on the server
131
+ - Code runs in a short-lived subprocess by default with a clean environment,
132
+ temporary working directory, CPU/memory/file-size limits, and a hard timeout
133
+ - `isolated=False` is an explicit trusted-code escape hatch for shared namespaces;
134
+ it is not recommended for untrusted sessions
127
135
  - Share links expose only session metadata and declared library names, never source
128
136
  - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
129
137
  - Sessions never expire unless an expiry is explicitly selected
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyVaultRCE
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: Remote Code Execution & Hosting client for PyVault — source never exposed
5
5
  Author: PyVault
6
6
  Keywords: remote execution code hosting vault rce session
@@ -47,7 +47,7 @@ sid = CodeManager.enc("my_script.py")
47
47
  # Upload from a paste service URL
48
48
  sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
49
49
 
50
- # Execute remotely (source stays on server)
50
+ # Execute in a short-lived isolated subprocess by default
51
51
  CodeManager.run(sid)
52
52
 
53
53
  # Upload with a short share link and library metadata
@@ -55,6 +55,9 @@ sid = CodeManager.enc(
55
55
  "my_script.py",
56
56
  label="analytics demo",
57
57
  libraries=["requests", "pandas"],
58
+ username="your_name",
59
+ description="A public demo post",
60
+ tags=["python", "demo"],
58
61
  )
59
62
 
60
63
  # Hide PyVault status messages while running
@@ -72,11 +75,12 @@ CodeManager.ping()
72
75
  Set `PYVAULT_URL` to point at your hosted PyVault server:
73
76
 
74
77
  ```bash
75
- export PYVAULT_URL=https://your-server.replit.app
78
+ export PYVAULT_URL=https://secure-code-runner--diwasrepl.replit.app
76
79
  ```
77
80
 
78
- For local development the client uses `http://localhost:5000` when
79
- `PYVAULT_URL` is not set. You can also control status output globally:
81
+ The package uses the public PyVault deployment when `PYVAULT_URL` is not set.
82
+ For local development, explicitly use `PYVAULT_URL=http://localhost:5000`.
83
+ You can also control status output globally:
80
84
 
81
85
  ```bash
82
86
  export PYVAULT_TERMINAL=off
@@ -91,9 +95,9 @@ where `CodeManager.run()` executes.
91
95
  | Method | Description |
92
96
  |--------|-------------|
93
97
  | `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
94
- | `CodeManager.enc(file_path, label=..., libraries=[...])` | Upload with a label and visible library metadata |
98
+ | `CodeManager.enc(file_path, label=..., libraries=[...], username=..., description=..., tags=...)` | Upload with public profile metadata and visible library metadata |
95
99
  | `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
96
- | `CodeManager.run(session_id, show_terminal=False)` | Fetch and execute code by Session ID with optional quiet status output |
100
+ | `CodeManager.run(session_id, show_terminal=False, isolated=True)` | Fetch and execute code in an isolated subprocess with timeout/resource limits |
97
101
  | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
98
102
  | `CodeManager.ping()` | Check server connectivity |
99
103
  | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
@@ -124,6 +128,10 @@ where `CodeManager.run()` executes.
124
128
  - Session IDs are 21-character cryptographically random hex strings
125
129
  - Source code is never written to disk on the client
126
130
  - Code is encrypted at rest with Fernet on the server
131
+ - Code runs in a short-lived subprocess by default with a clean environment,
132
+ temporary working directory, CPU/memory/file-size limits, and a hard timeout
133
+ - `isolated=False` is an explicit trusted-code escape hatch for shared namespaces;
134
+ it is not recommended for untrusted sessions
127
135
  - Share links expose only session metadata and declared library names, never source
128
136
  - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
129
137
  - Sessions never expire unless an expiry is explicitly selected
@@ -22,7 +22,7 @@ sid = CodeManager.enc("my_script.py")
22
22
  # Upload from a paste service URL
23
23
  sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
24
24
 
25
- # Execute remotely (source stays on server)
25
+ # Execute in a short-lived isolated subprocess by default
26
26
  CodeManager.run(sid)
27
27
 
28
28
  # Upload with a short share link and library metadata
@@ -30,6 +30,9 @@ sid = CodeManager.enc(
30
30
  "my_script.py",
31
31
  label="analytics demo",
32
32
  libraries=["requests", "pandas"],
33
+ username="your_name",
34
+ description="A public demo post",
35
+ tags=["python", "demo"],
33
36
  )
34
37
 
35
38
  # Hide PyVault status messages while running
@@ -47,11 +50,12 @@ CodeManager.ping()
47
50
  Set `PYVAULT_URL` to point at your hosted PyVault server:
48
51
 
49
52
  ```bash
50
- export PYVAULT_URL=https://your-server.replit.app
53
+ export PYVAULT_URL=https://secure-code-runner--diwasrepl.replit.app
51
54
  ```
52
55
 
53
- For local development the client uses `http://localhost:5000` when
54
- `PYVAULT_URL` is not set. You can also control status output globally:
56
+ The package uses the public PyVault deployment when `PYVAULT_URL` is not set.
57
+ For local development, explicitly use `PYVAULT_URL=http://localhost:5000`.
58
+ You can also control status output globally:
55
59
 
56
60
  ```bash
57
61
  export PYVAULT_TERMINAL=off
@@ -66,9 +70,9 @@ where `CodeManager.run()` executes.
66
70
  | Method | Description |
67
71
  |--------|-------------|
68
72
  | `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
69
- | `CodeManager.enc(file_path, label=..., libraries=[...])` | Upload with a label and visible library metadata |
73
+ | `CodeManager.enc(file_path, label=..., libraries=[...], username=..., description=..., tags=...)` | Upload with public profile metadata and visible library metadata |
70
74
  | `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
71
- | `CodeManager.run(session_id, show_terminal=False)` | Fetch and execute code by Session ID with optional quiet status output |
75
+ | `CodeManager.run(session_id, show_terminal=False, isolated=True)` | Fetch and execute code in an isolated subprocess with timeout/resource limits |
72
76
  | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
73
77
  | `CodeManager.ping()` | Check server connectivity |
74
78
  | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
@@ -99,6 +103,10 @@ where `CodeManager.run()` executes.
99
103
  - Session IDs are 21-character cryptographically random hex strings
100
104
  - Source code is never written to disk on the client
101
105
  - Code is encrypted at rest with Fernet on the server
106
+ - Code runs in a short-lived subprocess by default with a clean environment,
107
+ temporary working directory, CPU/memory/file-size limits, and a hard timeout
108
+ - `isolated=False` is an explicit trusted-code escape hatch for shared namespaces;
109
+ it is not recommended for untrusted sessions
102
110
  - Share links expose only session metadata and declared library names, never source
103
111
  - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
104
112
  - Sessions never expire unless an expiry is explicitly selected
@@ -21,5 +21,5 @@ Usage:
21
21
  from .manager import CodeManager
22
22
 
23
23
  __all__ = ["CodeManager"]
24
- __version__ = "2.4.0"
24
+ __version__ = "2.5.0"
25
25
  __author__ = "PyVault"
@@ -3,7 +3,9 @@ manager.py — CodeManager implementation
3
3
  """
4
4
 
5
5
  import os
6
+ import subprocess
6
7
  import sys
8
+ import tempfile
7
9
  import traceback
8
10
  import textwrap
9
11
  from typing import Optional
@@ -15,7 +17,7 @@ except ImportError:
15
17
  "The 'requests' library is required. Install it with: pip install requests"
16
18
  )
17
19
 
18
- _DEFAULT_BASE_URL = "http://localhost:5000"
20
+ _DEFAULT_BASE_URL = "https://secure-code-runner--diwasrepl.replit.app"
19
21
 
20
22
  # ── Validation helpers ───────────────────────────────────────────────────────
21
23
 
@@ -53,6 +55,57 @@ def _emit(message: str, show_terminal=None, *, error=False) -> None:
53
55
  print(message, file=sys.stderr if error else sys.stdout, flush=True)
54
56
 
55
57
 
58
+ def _isolated_run(code: str, session_id: str, timeout: int) -> subprocess.CompletedProcess:
59
+ """Execute in a short-lived process with clean environment and limits."""
60
+ def limit_resources():
61
+ if os.name != "posix":
62
+ return
63
+ try:
64
+ import resource
65
+ cpu = max(1, min(int(timeout), 30))
66
+ # Python's runtime reserves virtual address space during startup;
67
+ # 512 MiB breaks otherwise tiny scripts on some Linux builds.
68
+ memory = 2 * 1024 * 1024 * 1024
69
+ resource.setrlimit(resource.RLIMIT_CPU, (cpu, cpu))
70
+ resource.setrlimit(resource.RLIMIT_AS, (memory, memory))
71
+ resource.setrlimit(resource.RLIMIT_FSIZE, (10 * 1024 * 1024, 10 * 1024 * 1024))
72
+ except (ImportError, OSError, ValueError):
73
+ pass
74
+
75
+ clean_env = {
76
+ key: os.environ[key]
77
+ for key in ("PATH", "SystemRoot", "SYSTEMROOT", "TEMP", "TMP", "HOME", "LANG", "LC_ALL")
78
+ if key in os.environ
79
+ }
80
+ clean_env.update({"PYTHONNOUSERSITE": "1", "PYTHONUNBUFFERED": "1"})
81
+ with tempfile.TemporaryDirectory(prefix="pyvault-run-") as workdir:
82
+ script_path = os.path.join(workdir, "session.py")
83
+ with open(script_path, "w", encoding="utf-8") as handle:
84
+ handle.write(code)
85
+ try:
86
+ return subprocess.run(
87
+ [sys.executable, "-I", "-u", script_path],
88
+ cwd=workdir,
89
+ env=clean_env,
90
+ capture_output=True,
91
+ text=True,
92
+ timeout=max(1, int(timeout)),
93
+ check=False,
94
+ preexec_fn=limit_resources if os.name == "posix" else None,
95
+ )
96
+ except subprocess.TimeoutExpired as exc:
97
+ raise TimeoutError(
98
+ f"Isolated session '{session_id}' exceeded the {timeout}s execution timeout."
99
+ ) from exc
100
+
101
+
102
+ def _emit_child_output(result: subprocess.CompletedProcess) -> None:
103
+ if result.stdout:
104
+ print(result.stdout, end="", flush=True)
105
+ if result.stderr:
106
+ print(result.stderr, end="", file=sys.stderr, flush=True)
107
+
108
+
56
109
  # ── CodeManager ─────────────────────────────────────────────────────────────
57
110
 
58
111
  class CodeManager:
@@ -74,6 +127,9 @@ class CodeManager:
74
127
  timeout: int = 30,
75
128
  label: str = "",
76
129
  libraries = "",
130
+ username: str = "",
131
+ description: str = "",
132
+ tags = "",
77
133
  ) -> str:
78
134
  """
79
135
  Read a local Python file and upload it to the PyVault backend.
@@ -113,7 +169,14 @@ class CodeManager:
113
169
  try:
114
170
  response = requests.post(
115
171
  url,
116
- json={"code": code, "label": label, "libraries": libraries},
172
+ json={
173
+ "code": code,
174
+ "label": label,
175
+ "libraries": libraries,
176
+ "username": username,
177
+ "description": description,
178
+ "tags": tags,
179
+ },
117
180
  timeout=timeout,
118
181
  )
119
182
  except requests.exceptions.ConnectionError:
@@ -158,12 +221,16 @@ class CodeManager:
158
221
  timeout: int = 30,
159
222
  globals_dict: Optional[dict] = None,
160
223
  show_terminal=None,
224
+ isolated: bool = True,
161
225
  ) -> None:
162
226
  """
163
- Fetch the code for a Session ID from PyVault and execute it locally.
227
+ Fetch the code for a Session ID from PyVault and execute it in a
228
+ short-lived isolated subprocess by default.
164
229
 
165
- The source code is fetched over the network and run via exec() —
166
- the caller never has direct access to the source file on disk.
230
+ The source code is fetched over the network. The default subprocess
231
+ uses a clean environment, a temporary working directory, and resource
232
+ limits. Use isolated=False only for trusted code that needs a shared
233
+ in-process namespace.
167
234
 
168
235
  Args:
169
236
  session_id: The 21-character hex Session ID.
@@ -224,28 +291,41 @@ class CodeManager:
224
291
  f"Server returned empty code for session '{session_id}'."
225
292
  )
226
293
 
227
- _emit(f"[PyVault] Executing session '{session_id}' (run #{exec_count})…", show_terminal)
228
-
229
- namespace = globals_dict if globals_dict is not None else {
230
- "__name__": "__pyvault__",
231
- "__builtins__": __builtins__,
232
- }
294
+ _emit(
295
+ f"[PyVault] Executing session '{session_id}' "
296
+ f"(run #{exec_count}, isolated={isolated})…",
297
+ show_terminal,
298
+ )
233
299
 
234
- try:
235
- compiled = compile(code, f"<pyvault:{session_id}>", "exec")
236
- exec(compiled, namespace)
237
- except SyntaxError as exc:
238
- _emit("\n[PyVault] ✕ Syntax error in remote code:", show_terminal, error=True)
239
- _emit(f" Line {exc.lineno}: {exc.msg}", show_terminal, error=True)
240
- if exc.text:
241
- _emit(f" >>> {exc.text.strip()}", show_terminal, error=True)
242
- raise
243
- except Exception as exc:
244
- _emit("\n[PyVault] Runtime error during execution:", show_terminal, error=True)
245
- tb_lines = traceback.format_exc().splitlines()
246
- for line in tb_lines:
247
- _emit(f" {line}", show_terminal, error=True)
248
- raise
300
+ if isolated:
301
+ if globals_dict is not None:
302
+ raise ValueError("isolated=True cannot use globals_dict; pass isolated=False for a shared namespace.")
303
+ result = _isolated_run(code, session_id, timeout)
304
+ _emit_child_output(result)
305
+ if result.returncode != 0:
306
+ raise RuntimeError(
307
+ f"Isolated session '{session_id}' exited with code {result.returncode}."
308
+ )
309
+ else:
310
+ namespace = globals_dict if globals_dict is not None else {
311
+ "__name__": "__pyvault__",
312
+ "__builtins__": __builtins__,
313
+ }
314
+ try:
315
+ compiled = compile(code, f"<pyvault:{session_id}>", "exec")
316
+ exec(compiled, namespace)
317
+ except SyntaxError as exc:
318
+ _emit("\n[PyVault] ✕ Syntax error in remote code:", show_terminal, error=True)
319
+ _emit(f" Line {exc.lineno}: {exc.msg}", show_terminal, error=True)
320
+ if exc.text:
321
+ _emit(f" >>> {exc.text.strip()}", show_terminal, error=True)
322
+ raise
323
+ except Exception:
324
+ _emit("\n[PyVault] ✕ Runtime error during execution:", show_terminal, error=True)
325
+ tb_lines = traceback.format_exc().splitlines()
326
+ for line in tb_lines:
327
+ _emit(f" {line}", show_terminal, error=True)
328
+ raise
249
329
 
250
330
  _emit("[PyVault] ✓ Execution complete.", show_terminal)
251
331
 
@@ -18,5 +18,5 @@ Quick start:
18
18
  from .manager import CodeManager
19
19
 
20
20
  __all__ = ["CodeManager"]
21
- __version__ = "2.4.0"
21
+ __version__ = "2.5.0"
22
22
  __author__ = "PyVault"
@@ -4,7 +4,9 @@ manager.py — CodeManager implementation for PyVaultRCE
4
4
 
5
5
  import os
6
6
  import re
7
+ import subprocess
7
8
  import sys
9
+ import tempfile
8
10
  import traceback
9
11
  from typing import Optional
10
12
 
@@ -23,7 +25,7 @@ try:
23
25
  except ImportError:
24
26
  _HAS_CRYPTO = False
25
27
 
26
- _DEFAULT_BASE = "http://localhost:5000"
28
+ _DEFAULT_BASE = "https://secure-code-runner--diwasrepl.replit.app"
27
29
  _MAX_LINES = 10_000
28
30
  _SID_LEN = 21
29
31
  _HEX_SET = frozenset("0123456789abcdef")
@@ -171,6 +173,62 @@ def _localhost_hint(url: str) -> str:
171
173
  return ""
172
174
 
173
175
 
176
+ def _isolated_run(code: str, session_id: str, timeout: int) -> subprocess.CompletedProcess:
177
+ """Run fetched code in a short-lived process with a clean environment.
178
+
179
+ This is a defense-in-depth boundary for the client. It is intentionally
180
+ not described as a container or a complete OS sandbox: callers who need
181
+ hostile-code isolation should use a dedicated VM/container policy.
182
+ """
183
+ def limit_resources():
184
+ if os.name != "posix":
185
+ return
186
+ try:
187
+ import resource
188
+ cpu = max(1, min(int(timeout), 30))
189
+ # Python's runtime reserves virtual address space during startup;
190
+ # 512 MiB breaks otherwise tiny scripts on some Linux builds.
191
+ memory = 2 * 1024 * 1024 * 1024
192
+ resource.setrlimit(resource.RLIMIT_CPU, (cpu, cpu))
193
+ resource.setrlimit(resource.RLIMIT_AS, (memory, memory))
194
+ resource.setrlimit(resource.RLIMIT_FSIZE, (10 * 1024 * 1024, 10 * 1024 * 1024))
195
+ except (ImportError, OSError, ValueError):
196
+ pass
197
+
198
+ clean_env = {}
199
+ for key in ("PATH", "SystemRoot", "SYSTEMROOT", "TEMP", "TMP", "HOME", "LANG", "LC_ALL"):
200
+ if key in os.environ:
201
+ clean_env[key] = os.environ[key]
202
+ clean_env.update({"PYTHONNOUSERSITE": "1", "PYTHONUNBUFFERED": "1"})
203
+
204
+ with tempfile.TemporaryDirectory(prefix="pyvault-run-") as workdir:
205
+ script_path = os.path.join(workdir, "session.py")
206
+ with open(script_path, "w", encoding="utf-8") as handle:
207
+ handle.write(code)
208
+ try:
209
+ return subprocess.run(
210
+ [sys.executable, "-I", "-u", script_path],
211
+ cwd=workdir,
212
+ env=clean_env,
213
+ capture_output=True,
214
+ text=True,
215
+ timeout=max(1, int(timeout)),
216
+ check=False,
217
+ preexec_fn=limit_resources if os.name == "posix" else None,
218
+ )
219
+ except subprocess.TimeoutExpired as exc:
220
+ raise TimeoutError(
221
+ f"Isolated session '{session_id}' exceeded the {timeout}s execution timeout."
222
+ ) from exc
223
+
224
+
225
+ def _emit_child_output(result: subprocess.CompletedProcess) -> None:
226
+ if result.stdout:
227
+ print(result.stdout, end="", flush=True)
228
+ if result.stderr:
229
+ print(result.stderr, end="", file=sys.stderr, flush=True)
230
+
231
+
174
232
  def _upload_code(
175
233
  code: str,
176
234
  base_url: Optional[str],
@@ -179,6 +237,9 @@ def _upload_code(
179
237
  max_executions: int = 0,
180
238
  label: str = "",
181
239
  libraries = "",
240
+ username: str = "",
241
+ description: str = "",
242
+ tags = "",
182
243
  ) -> tuple:
183
244
  """Internal: push code string to /pyv/save. Returns session metadata."""
184
245
  if not code.strip():
@@ -197,6 +258,12 @@ def _upload_code(
197
258
  payload["label"] = str(label)[:200]
198
259
  if libraries:
199
260
  payload["libraries"] = _normalise_libraries(libraries)
261
+ if username:
262
+ payload["username"] = str(username)[:24]
263
+ if description:
264
+ payload["description"] = str(description)[:1000]
265
+ if tags:
266
+ payload["tags"] = tags
200
267
 
201
268
  url = _base(base_url) + "/pyv/save"
202
269
  resp = _http_post(url, payload, timeout)
@@ -230,7 +297,7 @@ class CodeManager:
230
297
 
231
298
  Environment variables:
232
299
  PYVAULT_URL — base URL of the PyVault server
233
- (default: http://localhost:5000; set PYVAULT_URL for a hosted server)
300
+ (default: the PyVault public deployment; set PYVAULT_URL to override)
234
301
  PYVAULT_TERMINAL — on/off terminal status messages (default: on)
235
302
  """
236
303
 
@@ -243,6 +310,9 @@ class CodeManager:
243
310
  max_executions: int = 0,
244
311
  label: str = "",
245
312
  libraries = "",
313
+ username: str = "",
314
+ description: str = "",
315
+ tags = "",
246
316
  show_terminal: Optional[bool] = None,
247
317
  ) -> str:
248
318
  """
@@ -265,7 +335,8 @@ class CodeManager:
265
335
  if not code.strip():
266
336
  raise ValueError(f"The file '{file_path}' is empty — nothing to upload.")
267
337
  sid, owner_token, share_url, safe_libraries = _upload_code(
268
- code, base_url, timeout, expires_in_hours, max_executions, label, libraries
338
+ code, base_url, timeout, expires_in_hours, max_executions, label, libraries,
339
+ username, description, tags
269
340
  )
270
341
  _emit(f"[PyVault] ✓ Uploaded — Session ID : {sid}", show_terminal)
271
342
  _emit(f"[PyVault] Source : {os.path.abspath(file_path)}", show_terminal)
@@ -287,6 +358,9 @@ class CodeManager:
287
358
  timeout: int = 30,
288
359
  label: str = "",
289
360
  libraries = "",
361
+ username: str = "",
362
+ description: str = "",
363
+ tags = "",
290
364
  show_terminal: Optional[bool] = None,
291
365
  ) -> str:
292
366
  """
@@ -311,7 +385,7 @@ class CodeManager:
311
385
  resp = requests.get(
312
386
  raw_url,
313
387
  timeout=timeout,
314
- headers={"User-Agent": "PyVaultRCE/2.4"},
388
+ headers={"User-Agent": "PyVaultRCE/2.5"},
315
389
  )
316
390
  resp.raise_for_status()
317
391
  except requests.exceptions.ConnectionError:
@@ -330,7 +404,8 @@ class CodeManager:
330
404
  raise ValueError("Downloaded content is empty.")
331
405
 
332
406
  sid, owner_token, share_url, safe_libraries = _upload_code(
333
- code, base_url, timeout, label=label, libraries=libraries
407
+ code, base_url, timeout, label=label, libraries=libraries,
408
+ username=username, description=description, tags=tags
334
409
  )
335
410
  _emit(f"[PyVault] ✓ Uploaded from URL — Session ID : {sid}", show_terminal)
336
411
  _emit(f"[PyVault] Source : {paste_url}", show_terminal)
@@ -352,20 +427,26 @@ class CodeManager:
352
427
  timeout: int = 30,
353
428
  _ns: Optional[dict] = None,
354
429
  show_terminal: Optional[bool] = None,
430
+ isolated: bool = True,
355
431
  ) -> None:
356
432
  """
357
433
  Fetch the encrypted code for a Session ID from PyVault, decrypt it,
358
- and execute it locally.
434
+ and execute it in a short-lived isolated subprocess by default.
359
435
 
360
- The source code is transmitted encrypted, decrypted in-memory, never
361
- written to disk, and is not accessible after execution.
436
+ The source code is transmitted encrypted and decrypted in memory. The
437
+ default subprocess writes it only to a temporary file for the child
438
+ process, then removes that directory after execution.
362
439
 
363
440
  Args:
364
441
  session_id: The 21-character hex Session ID.
365
442
  base_url: Override the server URL.
366
443
  timeout: HTTP timeout in seconds.
367
444
  _ns: Optional namespace dict passed to exec(). Leave as None
368
- unless you intentionally want to share a namespace.
445
+ unless you intentionally want to share a namespace;
446
+ providing it requires isolated=False.
447
+ isolated: Use the short-lived subprocess boundary (default True).
448
+ Set False only for trusted code that needs an in-process
449
+ namespace.
369
450
 
370
451
  Raises:
371
452
  ValueError, ConnectionError, RuntimeError, plus any exception
@@ -416,26 +497,39 @@ class CodeManager:
416
497
  if not code.strip():
417
498
  raise RuntimeError(f"Decrypted code is empty for session '{session_id}'.")
418
499
 
419
- _emit(f"[PyVault] ▶ Running session '{session_id}' (execution #{run_count})…", show_terminal)
420
-
421
- namespace = _ns if _ns is not None else {
422
- "__name__": "__pyvault__",
423
- "__builtins__": __builtins__,
424
- }
500
+ _emit(
501
+ f"[PyVault] ▶ Running session '{session_id}' "
502
+ f"(execution #{run_count}, isolated={isolated})…",
503
+ show_terminal,
504
+ )
425
505
 
426
- try:
427
- exec(compile(code, f"<vault:{session_id[:8]}…>", "exec"), namespace)
428
- except SyntaxError as exc:
429
- _emit("\n[PyVault] Syntax error:", show_terminal, error=True)
430
- _emit(f" Line {exc.lineno}: {exc.msg}", show_terminal, error=True)
431
- if exc.text:
432
- _emit(f" >>> {exc.text.strip()}", show_terminal, error=True)
433
- raise
434
- except Exception:
435
- _emit("\n[PyVault] ✕ Runtime error:", show_terminal, error=True)
436
- for line in traceback.format_exc().splitlines():
437
- _emit(f" {line}", show_terminal, error=True)
438
- raise
506
+ if isolated:
507
+ if _ns is not None:
508
+ raise ValueError("isolated=True cannot use _ns; pass isolated=False for a shared namespace.")
509
+ result = _isolated_run(code, session_id, timeout)
510
+ _emit_child_output(result)
511
+ if result.returncode != 0:
512
+ raise RuntimeError(
513
+ f"Isolated session '{session_id}' exited with code {result.returncode}."
514
+ )
515
+ else:
516
+ namespace = _ns if _ns is not None else {
517
+ "__name__": "__pyvault__",
518
+ "__builtins__": __builtins__,
519
+ }
520
+ try:
521
+ exec(compile(code, f"<vault:{session_id[:8]}…>", "exec"), namespace)
522
+ except SyntaxError as exc:
523
+ _emit("\n[PyVault] ✕ Syntax error:", show_terminal, error=True)
524
+ _emit(f" Line {exc.lineno}: {exc.msg}", show_terminal, error=True)
525
+ if exc.text:
526
+ _emit(f" >>> {exc.text.strip()}", show_terminal, error=True)
527
+ raise
528
+ except Exception:
529
+ _emit("\n[PyVault] ✕ Runtime error:", show_terminal, error=True)
530
+ for line in traceback.format_exc().splitlines():
531
+ _emit(f" {line}", show_terminal, error=True)
532
+ raise
439
533
 
440
534
  _emit("[PyVault] ✓ Execution complete.", show_terminal)
441
535
 
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="PyVaultRCE",
8
- version="2.4.0",
8
+ version="2.5.0",
9
9
  author="PyVault",
10
10
  description="Remote Code Execution & Hosting client for PyVault — source never exposed",
11
11
  long_description=long_description,
File without changes