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.
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/PKG-INFO +7 -5
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/PyVaultRCE.egg-info/PKG-INFO +7 -5
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/README.md +6 -4
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/codemanager/manager.py +3 -3
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/pyvaultrce/__init__.py +1 -1
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/pyvaultrce/manager.py +125 -36
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/setup.py +1 -1
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/PyVaultRCE.egg-info/SOURCES.txt +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/PyVaultRCE.egg-info/dependency_links.txt +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/PyVaultRCE.egg-info/requires.txt +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/PyVaultRCE.egg-info/top_level.txt +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/codemanager/__init__.py +0 -0
- {pyvaultrce-2.1.0 → pyvaultrce-2.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyVaultRCE
|
|
3
|
-
Version: 2.
|
|
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
|
|
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
|
-
-
|
|
104
|
-
-
|
|
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.
|
|
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
|
|
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
|
-
-
|
|
104
|
-
-
|
|
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
|
|
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
|
-
-
|
|
79
|
-
-
|
|
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 =
|
|
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
|
|
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:
|
|
@@ -23,10 +23,7 @@ try:
|
|
|
23
23
|
except ImportError:
|
|
24
24
|
_HAS_CRYPTO = False
|
|
25
25
|
|
|
26
|
-
_DEFAULT_BASE =
|
|
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(
|
|
151
|
-
|
|
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,
|
|
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
|
|
168
|
-
sid
|
|
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:
|
|
204
|
-
base_url:
|
|
205
|
-
timeout:
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 : {
|
|
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
|
|
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.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|