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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyVaultRCE
3
- Version: 2.0.0
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.0.0
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
@@ -0,0 +1,2 @@
1
+ requests>=2.28.0
2
+ cryptography>=41.0.0
@@ -18,5 +18,5 @@ Quick start:
18
18
  from .manager import CodeManager
19
19
 
20
20
  __all__ = ["CodeManager"]
21
- __version__ = "2.0.0"
21
+ __version__ = "2.1.0"
22
22
  __author__ = "PyVault"
@@ -15,12 +15,36 @@ except ImportError:
15
15
  "The 'requests' library is required. Install it with: pip install requests"
16
16
  )
17
17
 
18
- _DEFAULT_BASE = os.environ.get("PYVAULT_URL", "http://localhost:5000")
19
- _MAX_CHARS = 10_000
20
- _SID_LEN = 21
21
- _HEX_SET = frozenset("0123456789abcdef")
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
- # ── Helpers ───────────────────────────────────────────────────────────────────
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
- if len(code) > _MAX_CHARS:
154
+ line_count = len(code.splitlines())
155
+ if line_count > _MAX_LINES:
121
156
  raise ValueError(
122
- f"Code is {len(code):,} characters, which exceeds the server limit of {_MAX_CHARS:,}."
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 (default: http://localhost:5000)
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] Size : {len(code):,} chars", flush=True)
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.0"},
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] Size : {len(code):,} chars", flush=True)
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 and execute it locally.
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 over the network and run via exec().
249
- It is never written to disk and is not accessible after execution.
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
- code = data.get("code", "")
320
+ encrypted = data.get("code", "")
284
321
  run_count = data.get("execution_count", "?")
285
322
 
286
- if not code:
287
- raise RuntimeError(f"Server returned empty code for session '{session_id}'.")
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']:,} chars",
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
- if len(code) > _MAX_CHARS:
408
- raise ValueError(f"Code is {len(code):,} chars, exceeds {_MAX_CHARS:,} limit.")
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 : {os.path.abspath(file_path)}", flush=True)
438
- print(f"[PyVault] Size : {len(code):,} chars", flush=True)
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.0.0",
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