PyVaultRCE 2.0.0__tar.gz → 2.1.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.0.0 → pyvaultrce-2.1.0}/PKG-INFO +2 -1
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.0}/PyVaultRCE.egg-info/PKG-INFO +2 -1
- pyvaultrce-2.1.0/PyVaultRCE.egg-info/requires.txt +2 -0
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.0}/pyvaultrce/__init__.py +1 -1
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.0}/pyvaultrce/manager.py +73 -30
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.0}/setup.py +2 -1
- pyvaultrce-2.0.0/PyVaultRCE.egg-info/requires.txt +0 -1
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.0}/PyVaultRCE.egg-info/SOURCES.txt +0 -0
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.0}/PyVaultRCE.egg-info/dependency_links.txt +0 -0
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.0}/PyVaultRCE.egg-info/top_level.txt +0 -0
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.0}/README.md +0 -0
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.0}/codemanager/__init__.py +0 -0
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.0}/codemanager/manager.py +0 -0
- {pyvaultrce-2.0.0 → pyvaultrce-2.1.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.1.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
|
|
@@ -13,6 +13,7 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
13
13
|
Requires-Python: >=3.8
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
Requires-Dist: requests>=2.28.0
|
|
16
|
+
Requires-Dist: cryptography>=41.0.0
|
|
16
17
|
Dynamic: author
|
|
17
18
|
Dynamic: classifier
|
|
18
19
|
Dynamic: description
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyVaultRCE
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.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
|
|
@@ -13,6 +13,7 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
13
13
|
Requires-Python: >=3.8
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
Requires-Dist: requests>=2.28.0
|
|
16
|
+
Requires-Dist: cryptography>=41.0.0
|
|
16
17
|
Dynamic: author
|
|
17
18
|
Dynamic: classifier
|
|
18
19
|
Dynamic: description
|
|
@@ -15,12 +15,36 @@ except ImportError:
|
|
|
15
15
|
"The 'requests' library is required. Install it with: pip install requests"
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
try:
|
|
19
|
+
from cryptography.fernet import Fernet, InvalidToken as _InvalidToken
|
|
20
|
+
_CIPHER_KEY = b"aK3vytd8SaduKCt6D8-yL-DA2pDNOGiNLfZhnBPJebo="
|
|
21
|
+
_cipher = Fernet(_CIPHER_KEY)
|
|
22
|
+
_HAS_CRYPTO = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
_HAS_CRYPTO = False
|
|
25
|
+
|
|
26
|
+
_DEFAULT_BASE = os.environ.get(
|
|
27
|
+
"PYVAULT_URL",
|
|
28
|
+
"https://secure-code-runner--diwasreplit.replit.app"
|
|
29
|
+
)
|
|
30
|
+
_MAX_LINES = 10_000
|
|
31
|
+
_SID_LEN = 21
|
|
32
|
+
_HEX_SET = frozenset("0123456789abcdef")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ── Encryption helpers ─────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
def _decrypt(data: str) -> str:
|
|
38
|
+
"""Decrypt a Fernet-encrypted payload. Falls back to plaintext on failure."""
|
|
39
|
+
if not _HAS_CRYPTO:
|
|
40
|
+
return data
|
|
41
|
+
try:
|
|
42
|
+
return _cipher.decrypt(data.encode("ascii")).decode("utf-8")
|
|
43
|
+
except (_InvalidToken, Exception):
|
|
44
|
+
return data
|
|
45
|
+
|
|
22
46
|
|
|
23
|
-
# ──
|
|
47
|
+
# ── HTTP helpers ───────────────────────────────────────────────────────────────
|
|
24
48
|
|
|
25
49
|
def _base(url: Optional[str]) -> str:
|
|
26
50
|
return (url or _DEFAULT_BASE).rstrip("/")
|
|
@@ -58,25 +82,21 @@ def _resolve_paste_url(url: str) -> str:
|
|
|
58
82
|
Supports pastebin.com, hastebin.com, dpaste.com, paste.ofcode.org,
|
|
59
83
|
GitHub Gist raw URLs, and any URL already pointing to raw text.
|
|
60
84
|
"""
|
|
61
|
-
# pastebin.com/XXXX → pastebin.com/raw/XXXX
|
|
62
85
|
url = re.sub(
|
|
63
86
|
r"(https?://pastebin\.com/)(?!raw/)([A-Za-z0-9]+)(\?.*)?$",
|
|
64
87
|
r"\1raw/\2",
|
|
65
88
|
url,
|
|
66
89
|
)
|
|
67
|
-
# hastebin.com/XXXX → hastebin.com/raw/XXXX
|
|
68
90
|
url = re.sub(
|
|
69
91
|
r"(https?://hastebin\.com/)(?!raw/)([A-Za-z0-9]+)(\?.*)?$",
|
|
70
92
|
r"\1raw/\2",
|
|
71
93
|
url,
|
|
72
94
|
)
|
|
73
|
-
# dpaste.com/XXXX → dpaste.com/XXXX.txt
|
|
74
95
|
url = re.sub(
|
|
75
96
|
r"(https?://dpaste\.com/)([A-Z0-9]+)(?!\.txt)(\?.*)?$",
|
|
76
97
|
r"\1\2.txt",
|
|
77
98
|
url,
|
|
78
99
|
)
|
|
79
|
-
# paste.ofcode.org/XXXX → paste.ofcode.org/raw/XXXX
|
|
80
100
|
url = re.sub(
|
|
81
101
|
r"(https?://paste\.ofcode\.org/)(?!raw/)([A-Za-z0-9]+)(\?.*)?$",
|
|
82
102
|
r"\1raw/\2",
|
|
@@ -89,9 +109,9 @@ def _http_post(url: str, payload: dict, timeout: int) -> requests.Response:
|
|
|
89
109
|
try:
|
|
90
110
|
return requests.post(url, json=payload, timeout=timeout)
|
|
91
111
|
except requests.exceptions.ConnectionError:
|
|
112
|
+
_hint = _localhost_hint(url)
|
|
92
113
|
raise ConnectionError(
|
|
93
|
-
f"Unable to connect to PyVault at '{url}'.
|
|
94
|
-
"Ensure the server is running and PYVAULT_URL is set correctly."
|
|
114
|
+
f"Unable to connect to PyVault at '{url}'.{_hint}"
|
|
95
115
|
)
|
|
96
116
|
except requests.exceptions.Timeout:
|
|
97
117
|
raise TimeoutError(f"Request to '{url}' timed out after {timeout}s.")
|
|
@@ -103,9 +123,9 @@ def _http_get(url: str, timeout: int, headers: Optional[dict] = None) -> request
|
|
|
103
123
|
try:
|
|
104
124
|
return requests.get(url, timeout=timeout, headers=headers or {})
|
|
105
125
|
except requests.exceptions.ConnectionError:
|
|
126
|
+
_hint = _localhost_hint(url)
|
|
106
127
|
raise ConnectionError(
|
|
107
|
-
f"Unable to connect at '{url}'.
|
|
108
|
-
"Ensure the server is running and PYVAULT_URL is set correctly."
|
|
128
|
+
f"Unable to connect to PyVault at '{url}'.{_hint}"
|
|
109
129
|
)
|
|
110
130
|
except requests.exceptions.Timeout:
|
|
111
131
|
raise TimeoutError(f"Request to '{url}' timed out after {timeout}s.")
|
|
@@ -113,13 +133,28 @@ def _http_get(url: str, timeout: int, headers: Optional[dict] = None) -> request
|
|
|
113
133
|
raise ConnectionError(f"HTTP request failed: {exc}") from exc
|
|
114
134
|
|
|
115
135
|
|
|
136
|
+
def _localhost_hint(url: str) -> str:
|
|
137
|
+
if "localhost" in url or "127.0.0.1" in url:
|
|
138
|
+
return (
|
|
139
|
+
"\n\n ✗ PYVAULT_URL is not set — defaulting to localhost will not work "
|
|
140
|
+
"on a phone or remote machine.\n"
|
|
141
|
+
" → Set it before running:\n"
|
|
142
|
+
" import os\n"
|
|
143
|
+
" os.environ['PYVAULT_URL'] = 'https://secure-code-runner--diwasreplit.replit.app'\n"
|
|
144
|
+
" or in your shell:\n"
|
|
145
|
+
" export PYVAULT_URL=https://secure-code-runner--diwasreplit.replit.app"
|
|
146
|
+
)
|
|
147
|
+
return ""
|
|
148
|
+
|
|
149
|
+
|
|
116
150
|
def _upload_code(code: str, base_url: Optional[str], timeout: int) -> str:
|
|
117
151
|
"""Internal: push code string to /pyv/save and return the session ID."""
|
|
118
152
|
if not code.strip():
|
|
119
153
|
raise ValueError("Code is empty — nothing to upload.")
|
|
120
|
-
|
|
154
|
+
line_count = len(code.splitlines())
|
|
155
|
+
if line_count > _MAX_LINES:
|
|
121
156
|
raise ValueError(
|
|
122
|
-
f"Code is {
|
|
157
|
+
f"Code is {line_count:,} lines, which exceeds the server limit of {_MAX_LINES:,} lines."
|
|
123
158
|
)
|
|
124
159
|
url = _base(base_url) + "/pyv/save"
|
|
125
160
|
resp = _http_post(url, {"code": code}, timeout)
|
|
@@ -151,7 +186,8 @@ class CodeManager:
|
|
|
151
186
|
CodeManager.run(sid)
|
|
152
187
|
|
|
153
188
|
Environment variables:
|
|
154
|
-
PYVAULT_URL — base URL of the PyVault server
|
|
189
|
+
PYVAULT_URL — base URL of the PyVault server
|
|
190
|
+
(default: https://secure-code-runner--diwasreplit.replit.app)
|
|
155
191
|
"""
|
|
156
192
|
|
|
157
193
|
@staticmethod
|
|
@@ -180,7 +216,7 @@ class CodeManager:
|
|
|
180
216
|
sid = _upload_code(code, base_url, timeout)
|
|
181
217
|
print(f"[PyVault] ✓ Uploaded — Session ID : {sid}", flush=True)
|
|
182
218
|
print(f"[PyVault] Source : {os.path.abspath(file_path)}", flush=True)
|
|
183
|
-
print(f"[PyVault]
|
|
219
|
+
print(f"[PyVault] Lines : {len(code.splitlines()):,}", flush=True)
|
|
184
220
|
return sid
|
|
185
221
|
|
|
186
222
|
@staticmethod
|
|
@@ -211,7 +247,7 @@ class CodeManager:
|
|
|
211
247
|
resp = requests.get(
|
|
212
248
|
raw_url,
|
|
213
249
|
timeout=timeout,
|
|
214
|
-
headers={"User-Agent": "PyVaultRCE/2.
|
|
250
|
+
headers={"User-Agent": "PyVaultRCE/2.1"},
|
|
215
251
|
)
|
|
216
252
|
resp.raise_for_status()
|
|
217
253
|
except requests.exceptions.ConnectionError:
|
|
@@ -232,7 +268,7 @@ class CodeManager:
|
|
|
232
268
|
sid = _upload_code(code, base_url, timeout)
|
|
233
269
|
print(f"[PyVault] ✓ Uploaded from URL — Session ID : {sid}", flush=True)
|
|
234
270
|
print(f"[PyVault] Source : {paste_url}", flush=True)
|
|
235
|
-
print(f"[PyVault]
|
|
271
|
+
print(f"[PyVault] Lines : {len(code.splitlines()):,}", flush=True)
|
|
236
272
|
return sid
|
|
237
273
|
|
|
238
274
|
@staticmethod
|
|
@@ -243,10 +279,11 @@ class CodeManager:
|
|
|
243
279
|
_ns: Optional[dict] = None,
|
|
244
280
|
) -> None:
|
|
245
281
|
"""
|
|
246
|
-
Fetch the code for a Session ID from PyVault
|
|
282
|
+
Fetch the encrypted code for a Session ID from PyVault, decrypt it,
|
|
283
|
+
and execute it locally.
|
|
247
284
|
|
|
248
|
-
The source code is transmitted
|
|
249
|
-
|
|
285
|
+
The source code is transmitted encrypted, decrypted in-memory, never
|
|
286
|
+
written to disk, and is not accessible after execution.
|
|
250
287
|
|
|
251
288
|
Args:
|
|
252
289
|
session_id: The 21-character hex Session ID.
|
|
@@ -280,11 +317,16 @@ class CodeManager:
|
|
|
280
317
|
raise RuntimeError(f"Server error (HTTP {resp.status_code}): {err}")
|
|
281
318
|
|
|
282
319
|
data = resp.json()
|
|
283
|
-
|
|
320
|
+
encrypted = data.get("code", "")
|
|
284
321
|
run_count = data.get("execution_count", "?")
|
|
285
322
|
|
|
286
|
-
if not
|
|
287
|
-
raise RuntimeError(f"Server returned empty
|
|
323
|
+
if not encrypted:
|
|
324
|
+
raise RuntimeError(f"Server returned empty payload for session '{session_id}'.")
|
|
325
|
+
|
|
326
|
+
code = _decrypt(encrypted)
|
|
327
|
+
|
|
328
|
+
if not code.strip():
|
|
329
|
+
raise RuntimeError(f"Decrypted code is empty for session '{session_id}'.")
|
|
288
330
|
|
|
289
331
|
print(f"[PyVault] ▶ Running session '{session_id}' (execution #{run_count})…", flush=True)
|
|
290
332
|
|
|
@@ -341,7 +383,7 @@ class CodeManager:
|
|
|
341
383
|
f"[PyVault] ℹ Session : {data['session_id']}\n"
|
|
342
384
|
f"[PyVault] Executions: {data['execution_count']}\n"
|
|
343
385
|
f"[PyVault] Created : {data['created_at']}\n"
|
|
344
|
-
f"[PyVault] Code size : {data['code_size']:,}
|
|
386
|
+
f"[PyVault] Code size : {data['code_size']:,} bytes (encrypted)",
|
|
345
387
|
flush=True,
|
|
346
388
|
)
|
|
347
389
|
return data
|
|
@@ -404,8 +446,9 @@ class CodeManager:
|
|
|
404
446
|
code = _read_file(file_path)
|
|
405
447
|
if not code.strip():
|
|
406
448
|
raise ValueError(f"The file '{file_path}' is empty — nothing to upload.")
|
|
407
|
-
|
|
408
|
-
|
|
449
|
+
line_count = len(code.splitlines())
|
|
450
|
+
if line_count > _MAX_LINES:
|
|
451
|
+
raise ValueError(f"Code is {line_count:,} lines, exceeds {_MAX_LINES:,} line limit.")
|
|
409
452
|
|
|
410
453
|
url = _base(base_url) + f"/pyv/edit/{session_id}"
|
|
411
454
|
try:
|
|
@@ -434,5 +477,5 @@ class CodeManager:
|
|
|
434
477
|
raise RuntimeError(f"Server rejected edit (HTTP {resp.status_code}): {err}")
|
|
435
478
|
|
|
436
479
|
print(f"[PyVault] ✓ Session '{session_id}' updated.", flush=True)
|
|
437
|
-
print(f"[PyVault] File
|
|
438
|
-
print(f"[PyVault]
|
|
480
|
+
print(f"[PyVault] File : {os.path.abspath(file_path)}", flush=True)
|
|
481
|
+
print(f"[PyVault] Lines : {line_count:,}", flush=True)
|
|
@@ -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.1.0",
|
|
9
9
|
author="PyVault",
|
|
10
10
|
description="Remote Code Execution & Hosting client for PyVault — source never exposed",
|
|
11
11
|
long_description=long_description,
|
|
@@ -14,6 +14,7 @@ setup(
|
|
|
14
14
|
python_requires=">=3.8",
|
|
15
15
|
install_requires=[
|
|
16
16
|
"requests>=2.28.0",
|
|
17
|
+
"cryptography>=41.0.0",
|
|
17
18
|
],
|
|
18
19
|
classifiers=[
|
|
19
20
|
"Programming Language :: Python :: 3",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
requests>=2.28.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|