PyVaultRCE 2.1.0__tar.gz → 2.2.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.2.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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyVaultRCE
3
- Version: 2.1.0
3
+ Version: 2.2.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
@@ -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.2.0"
22
22
  __author__ = "PyVault"
@@ -147,8 +147,14 @@ def _localhost_hint(url: str) -> str:
147
147
  return ""
148
148
 
149
149
 
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."""
150
+ def _upload_code(
151
+ code: str,
152
+ base_url: Optional[str],
153
+ timeout: int,
154
+ expires_in_hours: Optional[float] = None,
155
+ max_executions: int = 0,
156
+ ) -> tuple:
157
+ """Internal: push code string to /pyv/save. Returns (session_id, owner_token)."""
152
158
  if not code.strip():
153
159
  raise ValueError("Code is empty — nothing to upload.")
154
160
  line_count = len(code.splitlines())
@@ -156,19 +162,26 @@ def _upload_code(code: str, base_url: Optional[str], timeout: int) -> str:
156
162
  raise ValueError(
157
163
  f"Code is {line_count:,} lines, which exceeds the server limit of {_MAX_LINES:,} lines."
158
164
  )
165
+ payload = {"code": code}
166
+ if expires_in_hours and expires_in_hours > 0:
167
+ payload["expires_in_hours"] = expires_in_hours
168
+ if max_executions and max_executions > 0:
169
+ payload["max_executions"] = max_executions
170
+
159
171
  url = _base(base_url) + "/pyv/save"
160
- resp = _http_post(url, {"code": code}, timeout)
172
+ resp = _http_post(url, payload, timeout)
161
173
  if resp.status_code != 201:
162
174
  try:
163
175
  err = resp.json().get("error", resp.text)
164
176
  except Exception:
165
177
  err = resp.text
166
178
  raise RuntimeError(f"Server rejected upload (HTTP {resp.status_code}): {err}")
167
- data = resp.json()
168
- sid = data.get("session_id", "")
179
+ data = resp.json()
180
+ sid = data.get("session_id", "")
181
+ owner_token = data.get("owner_token", "")
169
182
  if not _valid_sid(sid):
170
183
  raise RuntimeError(f"Server returned unexpected Session ID: '{sid}'")
171
- return sid
184
+ return sid, owner_token
172
185
 
173
186
 
174
187
  # ── CodeManager ───────────────────────────────────────────────────────────────
@@ -195,14 +208,18 @@ class CodeManager:
195
208
  file_path: str,
196
209
  base_url: Optional[str] = None,
197
210
  timeout: int = 30,
211
+ expires_in_hours: Optional[float] = None,
212
+ max_executions: int = 0,
198
213
  ) -> str:
199
214
  """
200
215
  Read a local .py file and upload it to PyVault.
201
216
 
202
217
  Args:
203
- file_path: Path to the local Python file.
204
- base_url: Override the server URL.
205
- timeout: HTTP timeout in seconds.
218
+ file_path: Path to the local Python file.
219
+ base_url: Override the server URL.
220
+ timeout: HTTP timeout in seconds.
221
+ expires_in_hours: Optional auto-expiry in hours from now.
222
+ max_executions: Max times the session can be executed (0 = unlimited).
206
223
 
207
224
  Returns:
208
225
  The 21-character hex Session ID.
@@ -213,10 +230,14 @@ class CodeManager:
213
230
  code = _read_file(file_path)
214
231
  if not code.strip():
215
232
  raise ValueError(f"The file '{file_path}' is empty — nothing to upload.")
216
- sid = _upload_code(code, base_url, timeout)
233
+ sid, owner_token = _upload_code(code, base_url, timeout, expires_in_hours, max_executions)
217
234
  print(f"[PyVault] ✓ Uploaded — Session ID : {sid}", flush=True)
218
235
  print(f"[PyVault] Source : {os.path.abspath(file_path)}", flush=True)
219
236
  print(f"[PyVault] Lines : {len(code.splitlines()):,}", flush=True)
237
+ if owner_token:
238
+ print(f"\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", flush=True)
239
+ print(f"[PyVault] {owner_token}", flush=True)
240
+ print(f"[PyVault] Use this to delete or edit your session later.\n", flush=True)
220
241
  return sid
221
242
 
222
243
  @staticmethod
@@ -265,10 +286,14 @@ class CodeManager:
265
286
  if not code.strip():
266
287
  raise ValueError("Downloaded content is empty.")
267
288
 
268
- sid = _upload_code(code, base_url, timeout)
289
+ sid, owner_token = _upload_code(code, base_url, timeout)
269
290
  print(f"[PyVault] ✓ Uploaded from URL — Session ID : {sid}", flush=True)
270
291
  print(f"[PyVault] Source : {paste_url}", flush=True)
271
292
  print(f"[PyVault] Lines : {len(code.splitlines()):,}", flush=True)
293
+ if owner_token:
294
+ print(f"\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", flush=True)
295
+ print(f"[PyVault] {owner_token}", flush=True)
296
+ print(f"[PyVault] Use this to delete or edit your session later.\n", flush=True)
272
297
  return sid
273
298
 
274
299
  @staticmethod
@@ -306,9 +331,22 @@ class CodeManager:
306
331
  err = resp.json().get("error", "Session not found.")
307
332
  except Exception:
308
333
  err = "Session not found."
309
- raise RuntimeError(
310
- f"Session '{session_id}' not found on the server. {err}"
311
- )
334
+ raise RuntimeError(f"Session '{session_id}' not found on the server. {err}")
335
+
336
+ if resp.status_code == 410:
337
+ try:
338
+ d = resp.json()
339
+ err = d.get("error", "Session is no longer available.")
340
+ if d.get("expired"):
341
+ raise RuntimeError(f"Session '{session_id}' has expired. {err}")
342
+ if d.get("maxed"):
343
+ raise RuntimeError(f"Session '{session_id}' has reached its execution limit. {err}")
344
+ except RuntimeError:
345
+ raise
346
+ except Exception:
347
+ pass
348
+ raise RuntimeError(f"Session '{session_id}' is no longer available (HTTP 410).")
349
+
312
350
  if resp.status_code != 200:
313
351
  try:
314
352
  err = resp.json().get("error", resp.text)
@@ -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.2.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
File without changes