PyVaultRCE 2.1.0__tar.gz → 2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyVaultRCE
3
- Version: 2.1.0
3
+ Version: 2.3.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
@@ -40,7 +40,7 @@ pip install PyVaultRCE
40
40
  ```python
41
41
  from pyvaultrce import CodeManager
42
42
 
43
- # Upload a local .py file → get Session ID
43
+ # Upload a local .py file → get a 21-character Session ID
44
44
  sid = CodeManager.enc("my_script.py")
45
45
  # [PyVault] ✓ Uploaded — Session ID : 3a7f1c9b2d0e8a5f41c7e
46
46
 
@@ -74,7 +74,8 @@ export PYVAULT_URL=https://your-server.replit.app
74
74
  | `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
75
75
  | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
76
76
  | `CodeManager.ping()` | Check server connectivity |
77
- | `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin only) |
77
+ | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
78
+ | `CodeManager.delete(session_id, owner_token)` | Delete your own session |
78
79
 
79
80
  ## Supported Paste Services (`enc_url`)
80
81
 
@@ -100,8 +101,9 @@ export PYVAULT_URL=https://your-server.replit.app
100
101
 
101
102
  - Session IDs are 21-character cryptographically random hex strings
102
103
  - Source code is never written to disk on the client
103
- - Edit operations require a server-side admin token
104
- - Regular users cannot modify stored code
104
+ - Code is encrypted at rest with Fernet on the server
105
+ - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
106
+ - Sessions never expire unless an expiry is explicitly selected
105
107
 
106
108
  ## License
107
109
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyVaultRCE
3
- Version: 2.1.0
3
+ Version: 2.3.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
@@ -40,7 +40,7 @@ pip install PyVaultRCE
40
40
  ```python
41
41
  from pyvaultrce import CodeManager
42
42
 
43
- # Upload a local .py file → get Session ID
43
+ # Upload a local .py file → get a 21-character Session ID
44
44
  sid = CodeManager.enc("my_script.py")
45
45
  # [PyVault] ✓ Uploaded — Session ID : 3a7f1c9b2d0e8a5f41c7e
46
46
 
@@ -74,7 +74,8 @@ export PYVAULT_URL=https://your-server.replit.app
74
74
  | `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
75
75
  | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
76
76
  | `CodeManager.ping()` | Check server connectivity |
77
- | `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin only) |
77
+ | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
78
+ | `CodeManager.delete(session_id, owner_token)` | Delete your own session |
78
79
 
79
80
  ## Supported Paste Services (`enc_url`)
80
81
 
@@ -100,8 +101,9 @@ export PYVAULT_URL=https://your-server.replit.app
100
101
 
101
102
  - Session IDs are 21-character cryptographically random hex strings
102
103
  - Source code is never written to disk on the client
103
- - Edit operations require a server-side admin token
104
- - Regular users cannot modify stored code
104
+ - Code is encrypted at rest with Fernet on the server
105
+ - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
106
+ - Sessions never expire unless an expiry is explicitly selected
105
107
 
106
108
  ## License
107
109
 
@@ -15,7 +15,7 @@ pip install PyVaultRCE
15
15
  ```python
16
16
  from pyvaultrce import CodeManager
17
17
 
18
- # Upload a local .py file → get Session ID
18
+ # Upload a local .py file → get a 21-character Session ID
19
19
  sid = CodeManager.enc("my_script.py")
20
20
  # [PyVault] ✓ Uploaded — Session ID : 3a7f1c9b2d0e8a5f41c7e
21
21
 
@@ -49,7 +49,8 @@ export PYVAULT_URL=https://your-server.replit.app
49
49
  | `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
50
50
  | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
51
51
  | `CodeManager.ping()` | Check server connectivity |
52
- | `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin only) |
52
+ | `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
53
+ | `CodeManager.delete(session_id, owner_token)` | Delete your own session |
53
54
 
54
55
  ## Supported Paste Services (`enc_url`)
55
56
 
@@ -75,8 +76,9 @@ export PYVAULT_URL=https://your-server.replit.app
75
76
 
76
77
  - Session IDs are 21-character cryptographically random hex strings
77
78
  - Source code is never written to disk on the client
78
- - Edit operations require a server-side admin token
79
- - Regular users cannot modify stored code
79
+ - Code is encrypted at rest with Fernet on the server
80
+ - Admin edits require the admin access code; owner edits/deletes require the one-time owner token
81
+ - Sessions never expire unless an expiry is explicitly selected
80
82
 
81
83
  ## License
82
84
 
@@ -15,7 +15,7 @@ except ImportError:
15
15
  "The 'requests' library is required. Install it with: pip install requests"
16
16
  )
17
17
 
18
- _DEFAULT_BASE_URL = os.environ.get("PYVAULT_URL", "http://localhost:5000")
18
+ _DEFAULT_BASE_URL = "https://secure-code-runner--diwasreplit.replit.app"
19
19
 
20
20
  # ── Validation helpers ───────────────────────────────────────────────────────
21
21
 
@@ -36,7 +36,7 @@ def _validate_session_id(session_id: str) -> None:
36
36
 
37
37
 
38
38
  def _get_base_url(base_url: Optional[str]) -> str:
39
- url = (base_url or _DEFAULT_BASE_URL).rstrip("/")
39
+ url = (base_url or os.environ.get("PYVAULT_URL") or _DEFAULT_BASE_URL).rstrip("/")
40
40
  return url
41
41
 
42
42
 
@@ -65,7 +65,7 @@ class CodeManager:
65
65
 
66
66
  Args:
67
67
  file_path: Path to the local .py file to upload.
68
- base_url: Override the server URL (default: PYVAULT_URL env or localhost:5000).
68
+ base_url: Override the server URL (default: PYVAULT_URL env or hosted PyVault URL).
69
69
  timeout: HTTP timeout in seconds.
70
70
 
71
71
  Returns:
@@ -18,5 +18,5 @@ Quick start:
18
18
  from .manager import CodeManager
19
19
 
20
20
  __all__ = ["CodeManager"]
21
- __version__ = "2.1.0"
21
+ __version__ = "2.3.0"
22
22
  __author__ = "PyVault"
@@ -23,10 +23,7 @@ try:
23
23
  except ImportError:
24
24
  _HAS_CRYPTO = False
25
25
 
26
- _DEFAULT_BASE = os.environ.get(
27
- "PYVAULT_URL",
28
- "https://secure-code-runner--diwasreplit.replit.app"
29
- )
26
+ _DEFAULT_BASE = "https://secure-code-runner--diwasreplit.replit.app"
30
27
  _MAX_LINES = 10_000
31
28
  _SID_LEN = 21
32
29
  _HEX_SET = frozenset("0123456789abcdef")
@@ -47,7 +44,7 @@ def _decrypt(data: str) -> str:
47
44
  # ── HTTP helpers ───────────────────────────────────────────────────────────────
48
45
 
49
46
  def _base(url: Optional[str]) -> str:
50
- return (url or _DEFAULT_BASE).rstrip("/")
47
+ return (url or os.environ.get("PYVAULT_URL") or _DEFAULT_BASE).rstrip("/")
51
48
 
52
49
 
53
50
  def _valid_sid(sid: str) -> bool:
@@ -147,8 +144,14 @@ def _localhost_hint(url: str) -> str:
147
144
  return ""
148
145
 
149
146
 
150
- def _upload_code(code: str, base_url: Optional[str], timeout: int) -> str:
151
- """Internal: push code string to /pyv/save and return the session ID."""
147
+ def _upload_code(
148
+ code: str,
149
+ base_url: Optional[str],
150
+ timeout: int,
151
+ expires_in_hours: Optional[float] = None,
152
+ max_executions: int = 0,
153
+ ) -> tuple:
154
+ """Internal: push code string to /pyv/save. Returns (session_id, owner_token)."""
152
155
  if not code.strip():
153
156
  raise ValueError("Code is empty — nothing to upload.")
154
157
  line_count = len(code.splitlines())
@@ -156,19 +159,26 @@ def _upload_code(code: str, base_url: Optional[str], timeout: int) -> str:
156
159
  raise ValueError(
157
160
  f"Code is {line_count:,} lines, which exceeds the server limit of {_MAX_LINES:,} lines."
158
161
  )
162
+ payload = {"code": code}
163
+ if expires_in_hours and expires_in_hours > 0:
164
+ payload["expires_in_hours"] = expires_in_hours
165
+ if max_executions and max_executions > 0:
166
+ payload["max_executions"] = max_executions
167
+
159
168
  url = _base(base_url) + "/pyv/save"
160
- resp = _http_post(url, {"code": code}, timeout)
169
+ resp = _http_post(url, payload, timeout)
161
170
  if resp.status_code != 201:
162
171
  try:
163
172
  err = resp.json().get("error", resp.text)
164
173
  except Exception:
165
174
  err = resp.text
166
175
  raise RuntimeError(f"Server rejected upload (HTTP {resp.status_code}): {err}")
167
- data = resp.json()
168
- sid = data.get("session_id", "")
176
+ data = resp.json()
177
+ sid = data.get("session_id", "")
178
+ owner_token = data.get("owner_token", "")
169
179
  if not _valid_sid(sid):
170
180
  raise RuntimeError(f"Server returned unexpected Session ID: '{sid}'")
171
- return sid
181
+ return sid, owner_token
172
182
 
173
183
 
174
184
  # ── CodeManager ───────────────────────────────────────────────────────────────
@@ -195,14 +205,18 @@ class CodeManager:
195
205
  file_path: str,
196
206
  base_url: Optional[str] = None,
197
207
  timeout: int = 30,
208
+ expires_in_hours: Optional[float] = None,
209
+ max_executions: int = 0,
198
210
  ) -> str:
199
211
  """
200
212
  Read a local .py file and upload it to PyVault.
201
213
 
202
214
  Args:
203
- file_path: Path to the local Python file.
204
- base_url: Override the server URL.
205
- timeout: HTTP timeout in seconds.
215
+ file_path: Path to the local Python file.
216
+ base_url: Override the server URL.
217
+ timeout: HTTP timeout in seconds.
218
+ expires_in_hours: Optional auto-expiry in hours from now.
219
+ max_executions: Max times the session can be executed (0 = unlimited).
206
220
 
207
221
  Returns:
208
222
  The 21-character hex Session ID.
@@ -213,10 +227,14 @@ class CodeManager:
213
227
  code = _read_file(file_path)
214
228
  if not code.strip():
215
229
  raise ValueError(f"The file '{file_path}' is empty — nothing to upload.")
216
- sid = _upload_code(code, base_url, timeout)
230
+ sid, owner_token = _upload_code(code, base_url, timeout, expires_in_hours, max_executions)
217
231
  print(f"[PyVault] ✓ Uploaded — Session ID : {sid}", flush=True)
218
232
  print(f"[PyVault] Source : {os.path.abspath(file_path)}", flush=True)
219
233
  print(f"[PyVault] Lines : {len(code.splitlines()):,}", flush=True)
234
+ if owner_token:
235
+ print(f"\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", flush=True)
236
+ print(f"[PyVault] {owner_token}", flush=True)
237
+ print(f"[PyVault] Use this to delete or edit your session later.\n", flush=True)
220
238
  return sid
221
239
 
222
240
  @staticmethod
@@ -247,7 +265,7 @@ class CodeManager:
247
265
  resp = requests.get(
248
266
  raw_url,
249
267
  timeout=timeout,
250
- headers={"User-Agent": "PyVaultRCE/2.1"},
268
+ headers={"User-Agent": "PyVaultRCE/2.3"},
251
269
  )
252
270
  resp.raise_for_status()
253
271
  except requests.exceptions.ConnectionError:
@@ -265,10 +283,14 @@ class CodeManager:
265
283
  if not code.strip():
266
284
  raise ValueError("Downloaded content is empty.")
267
285
 
268
- sid = _upload_code(code, base_url, timeout)
286
+ sid, owner_token = _upload_code(code, base_url, timeout)
269
287
  print(f"[PyVault] ✓ Uploaded from URL — Session ID : {sid}", flush=True)
270
288
  print(f"[PyVault] Source : {paste_url}", flush=True)
271
289
  print(f"[PyVault] Lines : {len(code.splitlines()):,}", flush=True)
290
+ if owner_token:
291
+ print(f"\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", flush=True)
292
+ print(f"[PyVault] {owner_token}", flush=True)
293
+ print(f"[PyVault] Use this to delete or edit your session later.\n", flush=True)
272
294
  return sid
273
295
 
274
296
  @staticmethod
@@ -306,9 +328,22 @@ class CodeManager:
306
328
  err = resp.json().get("error", "Session not found.")
307
329
  except Exception:
308
330
  err = "Session not found."
309
- raise RuntimeError(
310
- f"Session '{session_id}' not found on the server. {err}"
311
- )
331
+ raise RuntimeError(f"Session '{session_id}' not found on the server. {err}")
332
+
333
+ if resp.status_code == 410:
334
+ try:
335
+ d = resp.json()
336
+ err = d.get("error", "Session is no longer available.")
337
+ if d.get("expired"):
338
+ raise RuntimeError(f"Session '{session_id}' has expired. {err}")
339
+ if d.get("maxed"):
340
+ raise RuntimeError(f"Session '{session_id}' has reached its execution limit. {err}")
341
+ except RuntimeError:
342
+ raise
343
+ except Exception:
344
+ pass
345
+ raise RuntimeError(f"Session '{session_id}' is no longer available (HTTP 410).")
346
+
312
347
  if resp.status_code != 200:
313
348
  try:
314
349
  err = resp.json().get("error", resp.text)
@@ -362,7 +397,8 @@ class CodeManager:
362
397
  incrementing the execution counter.
363
398
 
364
399
  Returns:
365
- dict with keys: session_id, execution_count, created_at, code_size
400
+ dict with keys: session_id, execution_count, created_at,
401
+ encrypted_size, expires_at, max_executions, status
366
402
 
367
403
  Raises:
368
404
  ValueError, ConnectionError, RuntimeError
@@ -379,11 +415,13 @@ class CodeManager:
379
415
  err = resp.text
380
416
  raise RuntimeError(f"Server error (HTTP {resp.status_code}): {err}")
381
417
  data = resp.json()
418
+ encrypted_size = data.get("encrypted_size", data.get("code_size", 0))
382
419
  print(
383
- f"[PyVault] ℹ Session : {data['session_id']}\n"
420
+ f"[PyVault] ℹ Session : {data['session_id']}\n"
384
421
  f"[PyVault] Executions: {data['execution_count']}\n"
385
422
  f"[PyVault] Created : {data['created_at']}\n"
386
- f"[PyVault] Code size : {data['code_size']:,} bytes (encrypted)",
423
+ f"[PyVault] Code size : {encrypted_size:,} bytes (encrypted)\n"
424
+ f"[PyVault] Status : {data.get('status', 'unknown')}",
387
425
  flush=True,
388
426
  )
389
427
  return data
@@ -416,23 +454,73 @@ class CodeManager:
416
454
  print(f"[PyVault] ✕ Server unreachable at '{_base(base_url)}'.", flush=True)
417
455
  return False
418
456
 
457
+ @staticmethod
458
+ def delete(
459
+ session_id: str,
460
+ owner_token: str,
461
+ base_url: Optional[str] = None,
462
+ timeout: int = 30,
463
+ ) -> None:
464
+ """
465
+ Delete your own session using the owner token received when it was created.
466
+
467
+ Args:
468
+ session_id: The 21-character hex Session ID.
469
+ owner_token: The owner token printed when the session was first uploaded.
470
+ base_url: Override the server URL.
471
+ timeout: HTTP timeout in seconds.
472
+
473
+ Raises:
474
+ ValueError, ConnectionError, RuntimeError
475
+ """
476
+ _check_sid(session_id)
477
+ if not owner_token or not owner_token.strip():
478
+ raise ValueError("owner_token is required.")
479
+ url = _base(base_url) + f"/pyv/my/{session_id}"
480
+ try:
481
+ resp = requests.delete(
482
+ url,
483
+ headers={"X-Owner-Token": owner_token.strip()},
484
+ timeout=timeout,
485
+ )
486
+ except requests.exceptions.ConnectionError:
487
+ raise ConnectionError(f"Unable to connect to PyVault at '{url}'.")
488
+ except requests.exceptions.Timeout:
489
+ raise TimeoutError(f"Request timed out after {timeout}s.")
490
+ except requests.exceptions.RequestException as exc:
491
+ raise ConnectionError(f"HTTP request failed: {exc}") from exc
492
+
493
+ if resp.status_code == 403:
494
+ raise RuntimeError("Owner token rejected — check the token and try again.")
495
+ if resp.status_code == 404:
496
+ raise RuntimeError(f"Session '{session_id}' not found.")
497
+ if resp.status_code != 200:
498
+ try:
499
+ err = resp.json().get("error", resp.text)
500
+ except Exception:
501
+ err = resp.text
502
+ raise RuntimeError(f"Server error (HTTP {resp.status_code}): {err}")
503
+ print(f"[PyVault] ✓ Session '{session_id}' deleted.", flush=True)
504
+
419
505
  @staticmethod
420
506
  def edit(
421
507
  session_id: str,
422
508
  file_path: str,
423
- admin_token: str,
509
+ admin_token: str = "",
510
+ owner_token: str = "",
424
511
  base_url: Optional[str] = None,
425
512
  timeout: int = 30,
426
513
  ) -> None:
427
514
  """
428
- Replace the code stored for a Session ID (admin-only operation).
515
+ Replace the code stored for a Session ID.
429
516
 
430
- Requires the admin access token from the PyVault server.
517
+ Provide either admin_token (admin access) or owner_token (creator access).
431
518
 
432
519
  Args:
433
520
  session_id: The 21-character hex Session ID to update.
434
521
  file_path: Path to the .py file with the new code.
435
522
  admin_token: Admin access token (from server console on first start).
523
+ owner_token: Owner token printed when the session was first created.
436
524
  base_url: Override the server URL.
437
525
  timeout: HTTP timeout in seconds.
438
526
 
@@ -440,8 +528,8 @@ class CodeManager:
440
528
  ValueError, FileNotFoundError, ConnectionError, RuntimeError
441
529
  """
442
530
  _check_sid(session_id)
443
- if not admin_token or not admin_token.strip():
444
- raise ValueError("admin_token is required. Check the server console for your token.")
531
+ if not admin_token.strip() and not owner_token.strip():
532
+ raise ValueError("Provide admin_token or owner_token.")
445
533
 
446
534
  code = _read_file(file_path)
447
535
  if not code.strip():
@@ -450,14 +538,15 @@ class CodeManager:
450
538
  if line_count > _MAX_LINES:
451
539
  raise ValueError(f"Code is {line_count:,} lines, exceeds {_MAX_LINES:,} line limit.")
452
540
 
453
- url = _base(base_url) + f"/pyv/edit/{session_id}"
541
+ if owner_token.strip():
542
+ url = _base(base_url) + f"/pyv/my/{session_id}"
543
+ headers = {"Content-Type": "application/json", "X-Owner-Token": owner_token.strip()}
544
+ else:
545
+ url = _base(base_url) + f"/pyv/edit/{session_id}"
546
+ headers = {"Content-Type": "application/json", "X-Admin-Token": admin_token.strip()}
547
+
454
548
  try:
455
- resp = requests.put(
456
- url,
457
- json={"code": code},
458
- headers={"X-Admin-Token": admin_token},
459
- timeout=timeout,
460
- )
549
+ resp = requests.put(url, json={"code": code}, headers=headers, timeout=timeout)
461
550
  except requests.exceptions.ConnectionError:
462
551
  raise ConnectionError(f"Unable to connect to PyVault at '{url}'.")
463
552
  except requests.exceptions.Timeout:
@@ -466,7 +555,7 @@ class CodeManager:
466
555
  raise ConnectionError(f"HTTP request failed: {exc}") from exc
467
556
 
468
557
  if resp.status_code == 403:
469
- raise RuntimeError("Admin token rejected. Check your token and try again.")
558
+ raise RuntimeError("Token rejected. Check your admin_token or owner_token.")
470
559
  if resp.status_code == 404:
471
560
  raise RuntimeError(f"Session '{session_id}' not found.")
472
561
  if resp.status_code != 200:
@@ -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.1.0",
8
+ version="2.3.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