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.
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/PKG-INFO +1 -1
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/PyVaultRCE.egg-info/PKG-INFO +1 -1
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/pyvaultrce/__init__.py +1 -1
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/pyvaultrce/manager.py +116 -27
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/setup.py +1 -1
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/PyVaultRCE.egg-info/SOURCES.txt +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/PyVaultRCE.egg-info/dependency_links.txt +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/PyVaultRCE.egg-info/requires.txt +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/PyVaultRCE.egg-info/top_level.txt +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/README.md +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/codemanager/__init__.py +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/codemanager/manager.py +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.2.0}/setup.cfg +0 -0
|
@@ -147,8 +147,14 @@ def _localhost_hint(url: str) -> str:
|
|
|
147
147
|
return ""
|
|
148
148
|
|
|
149
149
|
|
|
150
|
-
def _upload_code(
|
|
151
|
-
|
|
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,
|
|
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
|
|
168
|
-
sid
|
|
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:
|
|
204
|
-
base_url:
|
|
205
|
-
timeout:
|
|
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
|
-
|
|
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
|
|
515
|
+
Replace the code stored for a Session ID.
|
|
429
516
|
|
|
430
|
-
|
|
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
|
|
444
|
-
raise ValueError("admin_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
|
-
|
|
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("
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|