PyVaultRCE 2.0.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.
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyVaultRCE
3
+ Version: 2.0.0
4
+ Summary: Remote Code Execution & Hosting client for PyVault — source never exposed
5
+ Author: PyVault
6
+ Keywords: remote execution code hosting vault rce session
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Topic :: Software Development :: Libraries
11
+ Classifier: Topic :: Security
12
+ Classifier: Topic :: Internet :: WWW/HTTP
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests>=2.28.0
16
+ Dynamic: author
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: keywords
21
+ Dynamic: requires-dist
22
+ Dynamic: requires-python
23
+ Dynamic: summary
24
+
25
+ # PyVaultRCE
26
+
27
+ Remote Code Execution & Hosting client for **PyVault**.
28
+
29
+ Upload Python scripts to a PyVault server and execute them remotely — the source code **never leaves the server**. Clients only ever hold a 21-character hex Session ID.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ pip install PyVaultRCE
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```python
40
+ from pyvaultrce import CodeManager
41
+
42
+ # Upload a local .py file → get Session ID
43
+ sid = CodeManager.enc("my_script.py")
44
+ # [PyVault] ✓ Uploaded — Session ID : 3a7f1c9b2d0e8a5f41c7e
45
+
46
+ # Upload from a paste service URL
47
+ sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
48
+
49
+ # Execute remotely (source stays on server)
50
+ CodeManager.run(sid)
51
+
52
+ # Check metadata without incrementing execution counter
53
+ CodeManager.info(sid)
54
+
55
+ # Verify server is up
56
+ CodeManager.ping()
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ Set `PYVAULT_URL` to point at your hosted PyVault server:
62
+
63
+ ```bash
64
+ export PYVAULT_URL=https://your-server.replit.app
65
+ ```
66
+
67
+ ## API Reference
68
+
69
+ | Method | Description |
70
+ |--------|-------------|
71
+ | `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
72
+ | `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
73
+ | `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
74
+ | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
75
+ | `CodeManager.ping()` | Check server connectivity |
76
+ | `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin only) |
77
+
78
+ ## Supported Paste Services (`enc_url`)
79
+
80
+ - `pastebin.com`
81
+ - `hastebin.com`
82
+ - `dpaste.com`
83
+ - `paste.ofcode.org`
84
+ - GitHub raw URLs
85
+ - Any URL returning raw Python text
86
+
87
+ ## Error Handling
88
+
89
+ | Exception | When |
90
+ |---|---|
91
+ | `FileNotFoundError` | Local file not found |
92
+ | `ValueError` | Invalid Session ID or empty file |
93
+ | `ConnectionError` | Server unreachable |
94
+ | `TimeoutError` | Request timed out |
95
+ | `RuntimeError` | Server error (404, 403, 500, etc.) |
96
+ | `SyntaxError` | Remote code syntax error (during `run`) |
97
+
98
+ ## Security
99
+
100
+ - Session IDs are 21-character cryptographically random hex strings
101
+ - Source code is never written to disk on the client
102
+ - Edit operations require a server-side admin token
103
+ - Regular users cannot modify stored code
104
+
105
+ ## License
106
+
107
+ MIT
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyVaultRCE
3
+ Version: 2.0.0
4
+ Summary: Remote Code Execution & Hosting client for PyVault — source never exposed
5
+ Author: PyVault
6
+ Keywords: remote execution code hosting vault rce session
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Topic :: Software Development :: Libraries
11
+ Classifier: Topic :: Security
12
+ Classifier: Topic :: Internet :: WWW/HTTP
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests>=2.28.0
16
+ Dynamic: author
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: keywords
21
+ Dynamic: requires-dist
22
+ Dynamic: requires-python
23
+ Dynamic: summary
24
+
25
+ # PyVaultRCE
26
+
27
+ Remote Code Execution & Hosting client for **PyVault**.
28
+
29
+ Upload Python scripts to a PyVault server and execute them remotely — the source code **never leaves the server**. Clients only ever hold a 21-character hex Session ID.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ pip install PyVaultRCE
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```python
40
+ from pyvaultrce import CodeManager
41
+
42
+ # Upload a local .py file → get Session ID
43
+ sid = CodeManager.enc("my_script.py")
44
+ # [PyVault] ✓ Uploaded — Session ID : 3a7f1c9b2d0e8a5f41c7e
45
+
46
+ # Upload from a paste service URL
47
+ sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
48
+
49
+ # Execute remotely (source stays on server)
50
+ CodeManager.run(sid)
51
+
52
+ # Check metadata without incrementing execution counter
53
+ CodeManager.info(sid)
54
+
55
+ # Verify server is up
56
+ CodeManager.ping()
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ Set `PYVAULT_URL` to point at your hosted PyVault server:
62
+
63
+ ```bash
64
+ export PYVAULT_URL=https://your-server.replit.app
65
+ ```
66
+
67
+ ## API Reference
68
+
69
+ | Method | Description |
70
+ |--------|-------------|
71
+ | `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
72
+ | `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
73
+ | `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
74
+ | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
75
+ | `CodeManager.ping()` | Check server connectivity |
76
+ | `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin only) |
77
+
78
+ ## Supported Paste Services (`enc_url`)
79
+
80
+ - `pastebin.com`
81
+ - `hastebin.com`
82
+ - `dpaste.com`
83
+ - `paste.ofcode.org`
84
+ - GitHub raw URLs
85
+ - Any URL returning raw Python text
86
+
87
+ ## Error Handling
88
+
89
+ | Exception | When |
90
+ |---|---|
91
+ | `FileNotFoundError` | Local file not found |
92
+ | `ValueError` | Invalid Session ID or empty file |
93
+ | `ConnectionError` | Server unreachable |
94
+ | `TimeoutError` | Request timed out |
95
+ | `RuntimeError` | Server error (404, 403, 500, etc.) |
96
+ | `SyntaxError` | Remote code syntax error (during `run`) |
97
+
98
+ ## Security
99
+
100
+ - Session IDs are 21-character cryptographically random hex strings
101
+ - Source code is never written to disk on the client
102
+ - Edit operations require a server-side admin token
103
+ - Regular users cannot modify stored code
104
+
105
+ ## License
106
+
107
+ MIT
@@ -0,0 +1,11 @@
1
+ README.md
2
+ setup.py
3
+ PyVaultRCE.egg-info/PKG-INFO
4
+ PyVaultRCE.egg-info/SOURCES.txt
5
+ PyVaultRCE.egg-info/dependency_links.txt
6
+ PyVaultRCE.egg-info/requires.txt
7
+ PyVaultRCE.egg-info/top_level.txt
8
+ codemanager/__init__.py
9
+ codemanager/manager.py
10
+ pyvaultrce/__init__.py
11
+ pyvaultrce/manager.py
@@ -0,0 +1 @@
1
+ requests>=2.28.0
@@ -0,0 +1,2 @@
1
+ codemanager
2
+ pyvaultrce
@@ -0,0 +1,83 @@
1
+ # PyVaultRCE
2
+
3
+ Remote Code Execution & Hosting client for **PyVault**.
4
+
5
+ Upload Python scripts to a PyVault server and execute them remotely — the source code **never leaves the server**. Clients only ever hold a 21-character hex Session ID.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install PyVaultRCE
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from pyvaultrce import CodeManager
17
+
18
+ # Upload a local .py file → get Session ID
19
+ sid = CodeManager.enc("my_script.py")
20
+ # [PyVault] ✓ Uploaded — Session ID : 3a7f1c9b2d0e8a5f41c7e
21
+
22
+ # Upload from a paste service URL
23
+ sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
24
+
25
+ # Execute remotely (source stays on server)
26
+ CodeManager.run(sid)
27
+
28
+ # Check metadata without incrementing execution counter
29
+ CodeManager.info(sid)
30
+
31
+ # Verify server is up
32
+ CodeManager.ping()
33
+ ```
34
+
35
+ ## Configuration
36
+
37
+ Set `PYVAULT_URL` to point at your hosted PyVault server:
38
+
39
+ ```bash
40
+ export PYVAULT_URL=https://your-server.replit.app
41
+ ```
42
+
43
+ ## API Reference
44
+
45
+ | Method | Description |
46
+ |--------|-------------|
47
+ | `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
48
+ | `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
49
+ | `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
50
+ | `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
51
+ | `CodeManager.ping()` | Check server connectivity |
52
+ | `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin only) |
53
+
54
+ ## Supported Paste Services (`enc_url`)
55
+
56
+ - `pastebin.com`
57
+ - `hastebin.com`
58
+ - `dpaste.com`
59
+ - `paste.ofcode.org`
60
+ - GitHub raw URLs
61
+ - Any URL returning raw Python text
62
+
63
+ ## Error Handling
64
+
65
+ | Exception | When |
66
+ |---|---|
67
+ | `FileNotFoundError` | Local file not found |
68
+ | `ValueError` | Invalid Session ID or empty file |
69
+ | `ConnectionError` | Server unreachable |
70
+ | `TimeoutError` | Request timed out |
71
+ | `RuntimeError` | Server error (404, 403, 500, etc.) |
72
+ | `SyntaxError` | Remote code syntax error (during `run`) |
73
+
74
+ ## Security
75
+
76
+ - Session IDs are 21-character cryptographically random hex strings
77
+ - Source code is never written to disk on the client
78
+ - Edit operations require a server-side admin token
79
+ - Regular users cannot modify stored code
80
+
81
+ ## License
82
+
83
+ MIT
@@ -0,0 +1,25 @@
1
+ """
2
+ codemanager — Remote Code Execution & Hosting PyPI Module
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+ Provides the CodeManager class to upload, execute, and edit
5
+ Python scripts hosted on a PyVault backend — without exposing
6
+ the source code to the client.
7
+
8
+ Usage:
9
+ from codemanager import CodeManager
10
+
11
+ # Upload a local script and get a Session ID
12
+ session_id = CodeManager.enc("my_script.py")
13
+
14
+ # Execute the remote script by Session ID
15
+ CodeManager.run(session_id)
16
+
17
+ # Push an updated version of the script
18
+ CodeManager.edit(session_id, "my_script_v2.py")
19
+ """
20
+
21
+ from .manager import CodeManager
22
+
23
+ __all__ = ["CodeManager"]
24
+ __version__ = "1.0.0"
25
+ __author__ = "PyVault"
@@ -0,0 +1,318 @@
1
+ """
2
+ manager.py — CodeManager implementation
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import traceback
8
+ import textwrap
9
+ from typing import Optional
10
+
11
+ try:
12
+ import requests
13
+ except ImportError:
14
+ raise ImportError(
15
+ "The 'requests' library is required. Install it with: pip install requests"
16
+ )
17
+
18
+ _DEFAULT_BASE_URL = os.environ.get("PYVAULT_URL", "http://localhost:5000")
19
+
20
+ # ── Validation helpers ───────────────────────────────────────────────────────
21
+
22
+ def _is_valid_session_id(session_id: str) -> bool:
23
+ return (
24
+ isinstance(session_id, str)
25
+ and len(session_id) == 21
26
+ and all(c in "0123456789abcdef" for c in session_id)
27
+ )
28
+
29
+
30
+ def _validate_session_id(session_id: str) -> None:
31
+ if not _is_valid_session_id(session_id):
32
+ raise ValueError(
33
+ f"Invalid Session ID: '{session_id}'. "
34
+ "A Session ID must be exactly 21 lowercase hexadecimal characters."
35
+ )
36
+
37
+
38
+ def _get_base_url(base_url: Optional[str]) -> str:
39
+ url = (base_url or _DEFAULT_BASE_URL).rstrip("/")
40
+ return url
41
+
42
+
43
+ # ── CodeManager ─────────────────────────────────────────────────────────────
44
+
45
+ class CodeManager:
46
+ """
47
+ Remote code management client for PyVault.
48
+
49
+ All methods are static so no instantiation is needed:
50
+
51
+ from codemanager import CodeManager
52
+ sid = CodeManager.enc("script.py")
53
+ CodeManager.run(sid)
54
+ CodeManager.edit(sid, "script_v2.py")
55
+ """
56
+
57
+ @staticmethod
58
+ def enc(
59
+ file_path: str,
60
+ base_url: Optional[str] = None,
61
+ timeout: int = 30,
62
+ ) -> str:
63
+ """
64
+ Read a local Python file and upload it to the PyVault backend.
65
+
66
+ Args:
67
+ file_path: Path to the local .py file to upload.
68
+ base_url: Override the server URL (default: PYVAULT_URL env or localhost:5000).
69
+ timeout: HTTP timeout in seconds.
70
+
71
+ Returns:
72
+ The 21-character hex Session ID assigned to this upload.
73
+
74
+ Raises:
75
+ FileNotFoundError: If file_path does not exist.
76
+ ValueError: If the file is empty.
77
+ ConnectionError: If the server is unreachable.
78
+ RuntimeError: If the server returns an error response.
79
+ """
80
+ path = os.path.abspath(file_path)
81
+ if not os.path.isfile(path):
82
+ raise FileNotFoundError(
83
+ f"File not found: '{file_path}'. "
84
+ "Ensure the path is correct and the file exists."
85
+ )
86
+
87
+ try:
88
+ with open(path, "r", encoding="utf-8") as fh:
89
+ code = fh.read()
90
+ except UnicodeDecodeError:
91
+ with open(path, "r", encoding="latin-1") as fh:
92
+ code = fh.read()
93
+
94
+ if not code.strip():
95
+ raise ValueError(f"The file '{file_path}' is empty. Nothing to upload.")
96
+
97
+ url = _get_base_url(base_url) + "/pyv/save"
98
+ try:
99
+ response = requests.post(
100
+ url,
101
+ json={"code": code},
102
+ timeout=timeout,
103
+ )
104
+ except requests.exceptions.ConnectionError:
105
+ raise ConnectionError(
106
+ f"Unable to connect to PyVault at '{url}'. "
107
+ "Ensure the server is running and PYVAULT_URL is set correctly."
108
+ )
109
+ except requests.exceptions.Timeout:
110
+ raise TimeoutError(
111
+ f"Request to '{url}' timed out after {timeout}s. "
112
+ "Try increasing the timeout parameter or check the server."
113
+ )
114
+ except requests.exceptions.RequestException as exc:
115
+ raise ConnectionError(f"HTTP request failed: {exc}") from exc
116
+
117
+ if response.status_code != 201:
118
+ try:
119
+ err = response.json().get("error", response.text)
120
+ except Exception:
121
+ err = response.text
122
+ raise RuntimeError(
123
+ f"Server rejected the upload (HTTP {response.status_code}): {err}"
124
+ )
125
+
126
+ data = response.json()
127
+ session_id = data.get("session_id", "")
128
+ if not _is_valid_session_id(session_id):
129
+ raise RuntimeError(
130
+ f"Server returned an unexpected Session ID format: '{session_id}'"
131
+ )
132
+
133
+ print(f"[PyVault] ✓ Code uploaded successfully.")
134
+ print(f"[PyVault] Session ID : {session_id}")
135
+ print(f"[PyVault] File : {path}")
136
+ print(f"[PyVault] Size : {len(code)} characters")
137
+ return session_id
138
+
139
+ @staticmethod
140
+ def run(
141
+ session_id: str,
142
+ base_url: Optional[str] = None,
143
+ timeout: int = 30,
144
+ globals_dict: Optional[dict] = None,
145
+ ) -> None:
146
+ """
147
+ Fetch the code for a Session ID from PyVault and execute it locally.
148
+
149
+ The source code is fetched over the network and run via exec() —
150
+ the caller never has direct access to the source file on disk.
151
+
152
+ Args:
153
+ session_id: The 21-character hex Session ID.
154
+ base_url: Override the server URL.
155
+ timeout: HTTP timeout in seconds.
156
+ globals_dict: Optional globals dict passed to exec(). If None,
157
+ a clean namespace is used to avoid polluting the
158
+ caller's namespace.
159
+
160
+ Raises:
161
+ ValueError: If the Session ID format is invalid.
162
+ ConnectionError: If the server is unreachable.
163
+ RuntimeError: If the session is not found or the server errors.
164
+ Exception: Re-raises any exception that occurs during exec().
165
+ """
166
+ _validate_session_id(session_id)
167
+
168
+ url = _get_base_url(base_url) + f"/pyv/get/{session_id}"
169
+ try:
170
+ response = requests.get(url, timeout=timeout)
171
+ except requests.exceptions.ConnectionError:
172
+ raise ConnectionError(
173
+ f"Unable to connect to PyVault at '{url}'. "
174
+ "Ensure the server is running and PYVAULT_URL is set correctly."
175
+ )
176
+ except requests.exceptions.Timeout:
177
+ raise TimeoutError(
178
+ f"Request timed out after {timeout}s while fetching session '{session_id}'."
179
+ )
180
+ except requests.exceptions.RequestException as exc:
181
+ raise ConnectionError(f"HTTP request failed: {exc}") from exc
182
+
183
+ if response.status_code == 404:
184
+ try:
185
+ err = response.json().get("error", "Session not found.")
186
+ except Exception:
187
+ err = "Session not found."
188
+ raise RuntimeError(
189
+ f"Session ID '{session_id}' was not found on the server. "
190
+ f"Server message: {err}"
191
+ )
192
+
193
+ if response.status_code != 200:
194
+ try:
195
+ err = response.json().get("error", response.text)
196
+ except Exception:
197
+ err = response.text
198
+ raise RuntimeError(
199
+ f"Server error (HTTP {response.status_code}): {err}"
200
+ )
201
+
202
+ data = response.json()
203
+ code = data.get("code", "")
204
+ exec_count = data.get("execution_count", "?")
205
+
206
+ if not code:
207
+ raise RuntimeError(
208
+ f"Server returned empty code for session '{session_id}'."
209
+ )
210
+
211
+ print(f"[PyVault] Executing session '{session_id}' (run #{exec_count})…")
212
+
213
+ namespace = globals_dict if globals_dict is not None else {
214
+ "__name__": "__pyvault__",
215
+ "__builtins__": __builtins__,
216
+ }
217
+
218
+ try:
219
+ compiled = compile(code, f"<pyvault:{session_id}>", "exec")
220
+ exec(compiled, namespace)
221
+ except SyntaxError as exc:
222
+ print(f"\n[PyVault] ✕ Syntax error in remote code:", file=sys.stderr)
223
+ print(f" Line {exc.lineno}: {exc.msg}", file=sys.stderr)
224
+ if exc.text:
225
+ print(f" >>> {exc.text.strip()}", file=sys.stderr)
226
+ raise
227
+ except Exception as exc:
228
+ print(f"\n[PyVault] ✕ Runtime error during execution:", file=sys.stderr)
229
+ tb_lines = traceback.format_exc().splitlines()
230
+ for line in tb_lines:
231
+ print(f" {line}", file=sys.stderr)
232
+ raise
233
+
234
+ print(f"[PyVault] ✓ Execution complete.")
235
+
236
+ @staticmethod
237
+ def edit(
238
+ session_id: str,
239
+ file_path: str,
240
+ base_url: Optional[str] = None,
241
+ timeout: int = 30,
242
+ ) -> None:
243
+ """
244
+ Read a local Python file and push it as an update to an existing session.
245
+
246
+ Args:
247
+ session_id: The 21-character hex Session ID to update.
248
+ file_path: Path to the local .py file with the updated code.
249
+ base_url: Override the server URL.
250
+ timeout: HTTP timeout in seconds.
251
+
252
+ Raises:
253
+ ValueError: If the Session ID format is invalid.
254
+ FileNotFoundError: If file_path does not exist.
255
+ ValueError: If the file is empty.
256
+ ConnectionError: If the server is unreachable.
257
+ RuntimeError: If the session is not found or the server errors.
258
+ """
259
+ _validate_session_id(session_id)
260
+
261
+ path = os.path.abspath(file_path)
262
+ if not os.path.isfile(path):
263
+ raise FileNotFoundError(
264
+ f"File not found: '{file_path}'. "
265
+ "Ensure the path is correct and the file exists."
266
+ )
267
+
268
+ try:
269
+ with open(path, "r", encoding="utf-8") as fh:
270
+ code = fh.read()
271
+ except UnicodeDecodeError:
272
+ with open(path, "r", encoding="latin-1") as fh:
273
+ code = fh.read()
274
+
275
+ if not code.strip():
276
+ raise ValueError(f"The file '{file_path}' is empty. Nothing to upload.")
277
+
278
+ url = _get_base_url(base_url) + f"/pyv/edit/{session_id}"
279
+ try:
280
+ response = requests.put(
281
+ url,
282
+ json={"code": code},
283
+ timeout=timeout,
284
+ )
285
+ except requests.exceptions.ConnectionError:
286
+ raise ConnectionError(
287
+ f"Unable to connect to PyVault at '{url}'. "
288
+ "Ensure the server is running and PYVAULT_URL is set correctly."
289
+ )
290
+ except requests.exceptions.Timeout:
291
+ raise TimeoutError(
292
+ f"Request timed out after {timeout}s while editing session '{session_id}'."
293
+ )
294
+ except requests.exceptions.RequestException as exc:
295
+ raise ConnectionError(f"HTTP request failed: {exc}") from exc
296
+
297
+ if response.status_code == 404:
298
+ try:
299
+ err = response.json().get("error", "Session not found.")
300
+ except Exception:
301
+ err = "Session not found."
302
+ raise RuntimeError(
303
+ f"Session ID '{session_id}' was not found. Cannot edit a non-existent session. "
304
+ f"Server message: {err}"
305
+ )
306
+
307
+ if response.status_code != 200:
308
+ try:
309
+ err = response.json().get("error", response.text)
310
+ except Exception:
311
+ err = response.text
312
+ raise RuntimeError(
313
+ f"Server rejected the edit (HTTP {response.status_code}): {err}"
314
+ )
315
+
316
+ print(f"[PyVault] ✓ Session '{session_id}' updated successfully.")
317
+ print(f"[PyVault] File : {path}")
318
+ print(f"[PyVault] Size : {len(code)} characters")
@@ -0,0 +1,22 @@
1
+ """
2
+ pyvaultrce — Remote Code Execution & Hosting Module for PyVault
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+ Upload Python scripts to a PyVault server and execute them remotely.
5
+ The source code never leaves the server — clients only ever hold a
6
+ 21-character hex Session ID.
7
+
8
+ Quick start:
9
+ from pyvaultrce import CodeManager
10
+
11
+ sid = CodeManager.enc("script.py") # upload local file
12
+ sid = CodeManager.enc_url("https://...") # upload from paste URL
13
+ CodeManager.run(sid) # execute remotely
14
+ CodeManager.info(sid) # check metadata
15
+ CodeManager.ping() # verify server is up
16
+ """
17
+
18
+ from .manager import CodeManager
19
+
20
+ __all__ = ["CodeManager"]
21
+ __version__ = "2.0.0"
22
+ __author__ = "PyVault"
@@ -0,0 +1,438 @@
1
+ """
2
+ manager.py — CodeManager implementation for PyVaultRCE
3
+ """
4
+
5
+ import os
6
+ import re
7
+ import sys
8
+ import traceback
9
+ from typing import Optional
10
+
11
+ try:
12
+ import requests
13
+ except ImportError:
14
+ raise ImportError(
15
+ "The 'requests' library is required. Install it with: pip install requests"
16
+ )
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")
22
+
23
+ # ── Helpers ───────────────────────────────────────────────────────────────────
24
+
25
+ def _base(url: Optional[str]) -> str:
26
+ return (url or _DEFAULT_BASE).rstrip("/")
27
+
28
+
29
+ def _valid_sid(sid: str) -> bool:
30
+ return isinstance(sid, str) and len(sid) == _SID_LEN and all(c in _HEX_SET for c in sid)
31
+
32
+
33
+ def _check_sid(sid: str) -> None:
34
+ if not _valid_sid(sid):
35
+ raise ValueError(
36
+ f"Invalid Session ID: '{sid}'. "
37
+ f"Must be exactly {_SID_LEN} lowercase hexadecimal characters."
38
+ )
39
+
40
+
41
+ def _read_file(path: str) -> str:
42
+ path = os.path.abspath(path)
43
+ if not os.path.isfile(path):
44
+ raise FileNotFoundError(
45
+ f"File not found: '{path}'. Check the path and try again."
46
+ )
47
+ try:
48
+ with open(path, "r", encoding="utf-8") as fh:
49
+ return fh.read()
50
+ except UnicodeDecodeError:
51
+ with open(path, "r", encoding="latin-1") as fh:
52
+ return fh.read()
53
+
54
+
55
+ def _resolve_paste_url(url: str) -> str:
56
+ """
57
+ Rewrite common paste service URLs to their raw endpoints.
58
+ Supports pastebin.com, hastebin.com, dpaste.com, paste.ofcode.org,
59
+ GitHub Gist raw URLs, and any URL already pointing to raw text.
60
+ """
61
+ # pastebin.com/XXXX → pastebin.com/raw/XXXX
62
+ url = re.sub(
63
+ r"(https?://pastebin\.com/)(?!raw/)([A-Za-z0-9]+)(\?.*)?$",
64
+ r"\1raw/\2",
65
+ url,
66
+ )
67
+ # hastebin.com/XXXX → hastebin.com/raw/XXXX
68
+ url = re.sub(
69
+ r"(https?://hastebin\.com/)(?!raw/)([A-Za-z0-9]+)(\?.*)?$",
70
+ r"\1raw/\2",
71
+ url,
72
+ )
73
+ # dpaste.com/XXXX → dpaste.com/XXXX.txt
74
+ url = re.sub(
75
+ r"(https?://dpaste\.com/)([A-Z0-9]+)(?!\.txt)(\?.*)?$",
76
+ r"\1\2.txt",
77
+ url,
78
+ )
79
+ # paste.ofcode.org/XXXX → paste.ofcode.org/raw/XXXX
80
+ url = re.sub(
81
+ r"(https?://paste\.ofcode\.org/)(?!raw/)([A-Za-z0-9]+)(\?.*)?$",
82
+ r"\1raw/\2",
83
+ url,
84
+ )
85
+ return url
86
+
87
+
88
+ def _http_post(url: str, payload: dict, timeout: int) -> requests.Response:
89
+ try:
90
+ return requests.post(url, json=payload, timeout=timeout)
91
+ except requests.exceptions.ConnectionError:
92
+ raise ConnectionError(
93
+ f"Unable to connect to PyVault at '{url}'. "
94
+ "Ensure the server is running and PYVAULT_URL is set correctly."
95
+ )
96
+ except requests.exceptions.Timeout:
97
+ raise TimeoutError(f"Request to '{url}' timed out after {timeout}s.")
98
+ except requests.exceptions.RequestException as exc:
99
+ raise ConnectionError(f"HTTP request failed: {exc}") from exc
100
+
101
+
102
+ def _http_get(url: str, timeout: int, headers: Optional[dict] = None) -> requests.Response:
103
+ try:
104
+ return requests.get(url, timeout=timeout, headers=headers or {})
105
+ except requests.exceptions.ConnectionError:
106
+ raise ConnectionError(
107
+ f"Unable to connect at '{url}'. "
108
+ "Ensure the server is running and PYVAULT_URL is set correctly."
109
+ )
110
+ except requests.exceptions.Timeout:
111
+ raise TimeoutError(f"Request to '{url}' timed out after {timeout}s.")
112
+ except requests.exceptions.RequestException as exc:
113
+ raise ConnectionError(f"HTTP request failed: {exc}") from exc
114
+
115
+
116
+ def _upload_code(code: str, base_url: Optional[str], timeout: int) -> str:
117
+ """Internal: push code string to /pyv/save and return the session ID."""
118
+ if not code.strip():
119
+ raise ValueError("Code is empty — nothing to upload.")
120
+ if len(code) > _MAX_CHARS:
121
+ raise ValueError(
122
+ f"Code is {len(code):,} characters, which exceeds the server limit of {_MAX_CHARS:,}."
123
+ )
124
+ url = _base(base_url) + "/pyv/save"
125
+ resp = _http_post(url, {"code": code}, timeout)
126
+ if resp.status_code != 201:
127
+ try:
128
+ err = resp.json().get("error", resp.text)
129
+ except Exception:
130
+ err = resp.text
131
+ raise RuntimeError(f"Server rejected upload (HTTP {resp.status_code}): {err}")
132
+ data = resp.json()
133
+ sid = data.get("session_id", "")
134
+ if not _valid_sid(sid):
135
+ raise RuntimeError(f"Server returned unexpected Session ID: '{sid}'")
136
+ return sid
137
+
138
+
139
+ # ── CodeManager ───────────────────────────────────────────────────────────────
140
+
141
+ class CodeManager:
142
+ """
143
+ Remote code management client for PyVault.
144
+
145
+ All methods are static — no instantiation required.
146
+
147
+ Typical flow:
148
+ from pyvaultrce import CodeManager
149
+
150
+ sid = CodeManager.enc("script.py")
151
+ CodeManager.run(sid)
152
+
153
+ Environment variables:
154
+ PYVAULT_URL — base URL of the PyVault server (default: http://localhost:5000)
155
+ """
156
+
157
+ @staticmethod
158
+ def enc(
159
+ file_path: str,
160
+ base_url: Optional[str] = None,
161
+ timeout: int = 30,
162
+ ) -> str:
163
+ """
164
+ Read a local .py file and upload it to PyVault.
165
+
166
+ Args:
167
+ file_path: Path to the local Python file.
168
+ base_url: Override the server URL.
169
+ timeout: HTTP timeout in seconds.
170
+
171
+ Returns:
172
+ The 21-character hex Session ID.
173
+
174
+ Raises:
175
+ FileNotFoundError, ValueError, ConnectionError, RuntimeError
176
+ """
177
+ code = _read_file(file_path)
178
+ if not code.strip():
179
+ raise ValueError(f"The file '{file_path}' is empty — nothing to upload.")
180
+ sid = _upload_code(code, base_url, timeout)
181
+ print(f"[PyVault] ✓ Uploaded — Session ID : {sid}", flush=True)
182
+ print(f"[PyVault] Source : {os.path.abspath(file_path)}", flush=True)
183
+ print(f"[PyVault] Size : {len(code):,} chars", flush=True)
184
+ return sid
185
+
186
+ @staticmethod
187
+ def enc_url(
188
+ paste_url: str,
189
+ base_url: Optional[str] = None,
190
+ timeout: int = 30,
191
+ ) -> str:
192
+ """
193
+ Download Python code from a paste service URL and upload to PyVault.
194
+
195
+ Automatically rewrites paste service links to their raw endpoints
196
+ (pastebin.com, hastebin.com, dpaste.com, paste.ofcode.org, GitHub raw).
197
+
198
+ Args:
199
+ paste_url: URL pointing to Python source code.
200
+ base_url: Override the server URL.
201
+ timeout: HTTP timeout in seconds.
202
+
203
+ Returns:
204
+ The 21-character hex Session ID.
205
+
206
+ Raises:
207
+ ValueError, ConnectionError, RuntimeError
208
+ """
209
+ raw_url = _resolve_paste_url(paste_url.strip())
210
+ try:
211
+ resp = requests.get(
212
+ raw_url,
213
+ timeout=timeout,
214
+ headers={"User-Agent": "PyVaultRCE/2.0"},
215
+ )
216
+ resp.raise_for_status()
217
+ except requests.exceptions.ConnectionError:
218
+ raise ConnectionError(f"Could not fetch code from '{paste_url}'.")
219
+ except requests.exceptions.HTTPError as exc:
220
+ raise RuntimeError(
221
+ f"Failed to download from '{paste_url}': HTTP {exc.response.status_code}"
222
+ )
223
+ except requests.exceptions.Timeout:
224
+ raise TimeoutError(f"Timed out fetching '{paste_url}'.")
225
+ except requests.exceptions.RequestException as exc:
226
+ raise ConnectionError(f"HTTP request failed: {exc}") from exc
227
+
228
+ code = resp.text
229
+ if not code.strip():
230
+ raise ValueError("Downloaded content is empty.")
231
+
232
+ sid = _upload_code(code, base_url, timeout)
233
+ print(f"[PyVault] ✓ Uploaded from URL — Session ID : {sid}", flush=True)
234
+ print(f"[PyVault] Source : {paste_url}", flush=True)
235
+ print(f"[PyVault] Size : {len(code):,} chars", flush=True)
236
+ return sid
237
+
238
+ @staticmethod
239
+ def run(
240
+ session_id: str,
241
+ base_url: Optional[str] = None,
242
+ timeout: int = 30,
243
+ _ns: Optional[dict] = None,
244
+ ) -> None:
245
+ """
246
+ Fetch the code for a Session ID from PyVault and execute it locally.
247
+
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.
250
+
251
+ Args:
252
+ session_id: The 21-character hex Session ID.
253
+ base_url: Override the server URL.
254
+ timeout: HTTP timeout in seconds.
255
+ _ns: Optional namespace dict passed to exec(). Leave as None
256
+ unless you intentionally want to share a namespace.
257
+
258
+ Raises:
259
+ ValueError, ConnectionError, RuntimeError, plus any exception
260
+ raised by the remote code itself.
261
+ """
262
+ _check_sid(session_id)
263
+
264
+ url = _base(base_url) + f"/pyv/get/{session_id}"
265
+ resp = _http_get(url, timeout)
266
+
267
+ if resp.status_code == 404:
268
+ try:
269
+ err = resp.json().get("error", "Session not found.")
270
+ except Exception:
271
+ err = "Session not found."
272
+ raise RuntimeError(
273
+ f"Session '{session_id}' not found on the server. {err}"
274
+ )
275
+ if resp.status_code != 200:
276
+ try:
277
+ err = resp.json().get("error", resp.text)
278
+ except Exception:
279
+ err = resp.text
280
+ raise RuntimeError(f"Server error (HTTP {resp.status_code}): {err}")
281
+
282
+ data = resp.json()
283
+ code = data.get("code", "")
284
+ run_count = data.get("execution_count", "?")
285
+
286
+ if not code:
287
+ raise RuntimeError(f"Server returned empty code for session '{session_id}'.")
288
+
289
+ print(f"[PyVault] ▶ Running session '{session_id}' (execution #{run_count})…", flush=True)
290
+
291
+ namespace = _ns if _ns is not None else {
292
+ "__name__": "__pyvault__",
293
+ "__builtins__": __builtins__,
294
+ }
295
+
296
+ try:
297
+ exec(compile(code, f"<vault:{session_id[:8]}…>", "exec"), namespace)
298
+ except SyntaxError as exc:
299
+ print(f"\n[PyVault] ✕ Syntax error:", file=sys.stderr, flush=True)
300
+ print(f" Line {exc.lineno}: {exc.msg}", file=sys.stderr)
301
+ if exc.text:
302
+ print(f" >>> {exc.text.strip()}", file=sys.stderr)
303
+ raise
304
+ except Exception:
305
+ print(f"\n[PyVault] ✕ Runtime error:", file=sys.stderr, flush=True)
306
+ for line in traceback.format_exc().splitlines():
307
+ print(f" {line}", file=sys.stderr)
308
+ raise
309
+
310
+ print(f"[PyVault] ✓ Execution complete.", flush=True)
311
+
312
+ @staticmethod
313
+ def info(
314
+ session_id: str,
315
+ base_url: Optional[str] = None,
316
+ timeout: int = 30,
317
+ ) -> dict:
318
+ """
319
+ Fetch metadata for a Session ID without executing the code and without
320
+ incrementing the execution counter.
321
+
322
+ Returns:
323
+ dict with keys: session_id, execution_count, created_at, code_size
324
+
325
+ Raises:
326
+ ValueError, ConnectionError, RuntimeError
327
+ """
328
+ _check_sid(session_id)
329
+ url = _base(base_url) + f"/pyv/info/{session_id}"
330
+ resp = _http_get(url, timeout)
331
+ if resp.status_code == 404:
332
+ raise RuntimeError(f"Session '{session_id}' not found.")
333
+ if resp.status_code != 200:
334
+ try:
335
+ err = resp.json().get("error", resp.text)
336
+ except Exception:
337
+ err = resp.text
338
+ raise RuntimeError(f"Server error (HTTP {resp.status_code}): {err}")
339
+ data = resp.json()
340
+ print(
341
+ f"[PyVault] ℹ Session : {data['session_id']}\n"
342
+ f"[PyVault] Executions: {data['execution_count']}\n"
343
+ f"[PyVault] Created : {data['created_at']}\n"
344
+ f"[PyVault] Code size : {data['code_size']:,} chars",
345
+ flush=True,
346
+ )
347
+ return data
348
+
349
+ @staticmethod
350
+ def ping(
351
+ base_url: Optional[str] = None,
352
+ timeout: int = 10,
353
+ ) -> bool:
354
+ """
355
+ Check whether the PyVault server is reachable.
356
+
357
+ Returns:
358
+ True if server responded with valid stats, False otherwise.
359
+ """
360
+ url = _base(base_url) + "/pyv/stats"
361
+ try:
362
+ resp = requests.get(url, timeout=timeout)
363
+ if resp.status_code == 200:
364
+ d = resp.json()
365
+ print(
366
+ f"[PyVault] ✓ Server online — "
367
+ f"{d.get('total_sessions', '?')} sessions, "
368
+ f"{d.get('total_executions', '?')} total executions.",
369
+ flush=True,
370
+ )
371
+ return True
372
+ except Exception:
373
+ pass
374
+ print(f"[PyVault] ✕ Server unreachable at '{_base(base_url)}'.", flush=True)
375
+ return False
376
+
377
+ @staticmethod
378
+ def edit(
379
+ session_id: str,
380
+ file_path: str,
381
+ admin_token: str,
382
+ base_url: Optional[str] = None,
383
+ timeout: int = 30,
384
+ ) -> None:
385
+ """
386
+ Replace the code stored for a Session ID (admin-only operation).
387
+
388
+ Requires the admin access token from the PyVault server.
389
+
390
+ Args:
391
+ session_id: The 21-character hex Session ID to update.
392
+ file_path: Path to the .py file with the new code.
393
+ admin_token: Admin access token (from server console on first start).
394
+ base_url: Override the server URL.
395
+ timeout: HTTP timeout in seconds.
396
+
397
+ Raises:
398
+ ValueError, FileNotFoundError, ConnectionError, RuntimeError
399
+ """
400
+ _check_sid(session_id)
401
+ if not admin_token or not admin_token.strip():
402
+ raise ValueError("admin_token is required. Check the server console for your token.")
403
+
404
+ code = _read_file(file_path)
405
+ if not code.strip():
406
+ 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.")
409
+
410
+ url = _base(base_url) + f"/pyv/edit/{session_id}"
411
+ try:
412
+ resp = requests.put(
413
+ url,
414
+ json={"code": code},
415
+ headers={"X-Admin-Token": admin_token},
416
+ timeout=timeout,
417
+ )
418
+ except requests.exceptions.ConnectionError:
419
+ raise ConnectionError(f"Unable to connect to PyVault at '{url}'.")
420
+ except requests.exceptions.Timeout:
421
+ raise TimeoutError(f"Request timed out after {timeout}s.")
422
+ except requests.exceptions.RequestException as exc:
423
+ raise ConnectionError(f"HTTP request failed: {exc}") from exc
424
+
425
+ if resp.status_code == 403:
426
+ raise RuntimeError("Admin token rejected. Check your token and try again.")
427
+ if resp.status_code == 404:
428
+ raise RuntimeError(f"Session '{session_id}' not found.")
429
+ if resp.status_code != 200:
430
+ try:
431
+ err = resp.json().get("error", resp.text)
432
+ except Exception:
433
+ err = resp.text
434
+ raise RuntimeError(f"Server rejected edit (HTTP {resp.status_code}): {err}")
435
+
436
+ 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)
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,28 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="PyVaultRCE",
8
+ version="2.0.0",
9
+ author="PyVault",
10
+ description="Remote Code Execution & Hosting client for PyVault — source never exposed",
11
+ long_description=long_description,
12
+ long_description_content_type="text/markdown",
13
+ packages=find_packages(),
14
+ python_requires=">=3.8",
15
+ install_requires=[
16
+ "requests>=2.28.0",
17
+ ],
18
+ classifiers=[
19
+ "Programming Language :: Python :: 3",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Topic :: Software Development :: Libraries",
23
+ "Topic :: Security",
24
+ "Topic :: Internet :: WWW/HTTP",
25
+ ],
26
+ keywords="remote execution code hosting vault rce session",
27
+ entry_points={},
28
+ )