PyVaultRCE 2.2.0__tar.gz → 2.4.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.2.0 → pyvaultrce-2.4.0}/PKG-INFO +32 -7
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/PyVaultRCE.egg-info/PKG-INFO +32 -7
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/README.md +31 -6
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/codemanager/__init__.py +1 -1
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/codemanager/manager.py +27 -11
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/pyvaultrce/__init__.py +1 -1
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/pyvaultrce/manager.py +99 -33
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/setup.py +1 -1
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/PyVaultRCE.egg-info/SOURCES.txt +0 -0
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/PyVaultRCE.egg-info/dependency_links.txt +0 -0
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/PyVaultRCE.egg-info/requires.txt +0 -0
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.0}/PyVaultRCE.egg-info/top_level.txt +0 -0
- {pyvaultrce-2.2.0 → pyvaultrce-2.4.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.4.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
|
|
@@ -25,7 +25,7 @@ Dynamic: summary
|
|
|
25
25
|
|
|
26
26
|
# PyVaultRCE
|
|
27
27
|
|
|
28
|
-
Remote Code
|
|
28
|
+
Remote Code Hosting client for **PyVault**.
|
|
29
29
|
|
|
30
30
|
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.
|
|
31
31
|
|
|
@@ -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
|
|
|
@@ -50,6 +50,16 @@ sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
|
|
|
50
50
|
# Execute remotely (source stays on server)
|
|
51
51
|
CodeManager.run(sid)
|
|
52
52
|
|
|
53
|
+
# Upload with a short share link and library metadata
|
|
54
|
+
sid = CodeManager.enc(
|
|
55
|
+
"my_script.py",
|
|
56
|
+
label="analytics demo",
|
|
57
|
+
libraries=["requests", "pandas"],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Hide PyVault status messages while running
|
|
61
|
+
CodeManager.run(sid, show_terminal=False)
|
|
62
|
+
|
|
53
63
|
# Check metadata without incrementing execution counter
|
|
54
64
|
CodeManager.info(sid)
|
|
55
65
|
|
|
@@ -65,16 +75,29 @@ Set `PYVAULT_URL` to point at your hosted PyVault server:
|
|
|
65
75
|
export PYVAULT_URL=https://your-server.replit.app
|
|
66
76
|
```
|
|
67
77
|
|
|
78
|
+
For local development the client uses `http://localhost:5000` when
|
|
79
|
+
`PYVAULT_URL` is not set. You can also control status output globally:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
export PYVAULT_TERMINAL=off
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Library names are metadata only. PyVault does not auto-install packages or
|
|
86
|
+
execute dependency installers; install the libraries in the Python environment
|
|
87
|
+
where `CodeManager.run()` executes.
|
|
88
|
+
|
|
68
89
|
## API Reference
|
|
69
90
|
|
|
70
91
|
| Method | Description |
|
|
71
92
|
|--------|-------------|
|
|
72
93
|
| `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
|
|
94
|
+
| `CodeManager.enc(file_path, label=..., libraries=[...])` | Upload with a label and visible library metadata |
|
|
73
95
|
| `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
|
|
74
|
-
| `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
|
|
96
|
+
| `CodeManager.run(session_id, show_terminal=False)` | Fetch and execute code by Session ID with optional quiet status output |
|
|
75
97
|
| `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
|
|
76
98
|
| `CodeManager.ping()` | Check server connectivity |
|
|
77
|
-
| `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin
|
|
99
|
+
| `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
|
|
100
|
+
| `CodeManager.delete(session_id, owner_token)` | Delete your own session |
|
|
78
101
|
|
|
79
102
|
## Supported Paste Services (`enc_url`)
|
|
80
103
|
|
|
@@ -100,8 +123,10 @@ export PYVAULT_URL=https://your-server.replit.app
|
|
|
100
123
|
|
|
101
124
|
- Session IDs are 21-character cryptographically random hex strings
|
|
102
125
|
- Source code is never written to disk on the client
|
|
103
|
-
-
|
|
104
|
-
-
|
|
126
|
+
- Code is encrypted at rest with Fernet on the server
|
|
127
|
+
- Share links expose only session metadata and declared library names, never source
|
|
128
|
+
- Admin edits require the admin access code; owner edits/deletes require the one-time owner token
|
|
129
|
+
- Sessions never expire unless an expiry is explicitly selected
|
|
105
130
|
|
|
106
131
|
## License
|
|
107
132
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyVaultRCE
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.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
|
|
@@ -25,7 +25,7 @@ Dynamic: summary
|
|
|
25
25
|
|
|
26
26
|
# PyVaultRCE
|
|
27
27
|
|
|
28
|
-
Remote Code
|
|
28
|
+
Remote Code Hosting client for **PyVault**.
|
|
29
29
|
|
|
30
30
|
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.
|
|
31
31
|
|
|
@@ -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
|
|
|
@@ -50,6 +50,16 @@ sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
|
|
|
50
50
|
# Execute remotely (source stays on server)
|
|
51
51
|
CodeManager.run(sid)
|
|
52
52
|
|
|
53
|
+
# Upload with a short share link and library metadata
|
|
54
|
+
sid = CodeManager.enc(
|
|
55
|
+
"my_script.py",
|
|
56
|
+
label="analytics demo",
|
|
57
|
+
libraries=["requests", "pandas"],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Hide PyVault status messages while running
|
|
61
|
+
CodeManager.run(sid, show_terminal=False)
|
|
62
|
+
|
|
53
63
|
# Check metadata without incrementing execution counter
|
|
54
64
|
CodeManager.info(sid)
|
|
55
65
|
|
|
@@ -65,16 +75,29 @@ Set `PYVAULT_URL` to point at your hosted PyVault server:
|
|
|
65
75
|
export PYVAULT_URL=https://your-server.replit.app
|
|
66
76
|
```
|
|
67
77
|
|
|
78
|
+
For local development the client uses `http://localhost:5000` when
|
|
79
|
+
`PYVAULT_URL` is not set. You can also control status output globally:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
export PYVAULT_TERMINAL=off
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Library names are metadata only. PyVault does not auto-install packages or
|
|
86
|
+
execute dependency installers; install the libraries in the Python environment
|
|
87
|
+
where `CodeManager.run()` executes.
|
|
88
|
+
|
|
68
89
|
## API Reference
|
|
69
90
|
|
|
70
91
|
| Method | Description |
|
|
71
92
|
|--------|-------------|
|
|
72
93
|
| `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
|
|
94
|
+
| `CodeManager.enc(file_path, label=..., libraries=[...])` | Upload with a label and visible library metadata |
|
|
73
95
|
| `CodeManager.enc_url(url)` | Download code from a paste URL and upload → returns Session ID |
|
|
74
|
-
| `CodeManager.run(session_id)` | Fetch and execute code by Session ID |
|
|
96
|
+
| `CodeManager.run(session_id, show_terminal=False)` | Fetch and execute code by Session ID with optional quiet status output |
|
|
75
97
|
| `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
|
|
76
98
|
| `CodeManager.ping()` | Check server connectivity |
|
|
77
|
-
| `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin
|
|
99
|
+
| `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
|
|
100
|
+
| `CodeManager.delete(session_id, owner_token)` | Delete your own session |
|
|
78
101
|
|
|
79
102
|
## Supported Paste Services (`enc_url`)
|
|
80
103
|
|
|
@@ -100,8 +123,10 @@ export PYVAULT_URL=https://your-server.replit.app
|
|
|
100
123
|
|
|
101
124
|
- Session IDs are 21-character cryptographically random hex strings
|
|
102
125
|
- Source code is never written to disk on the client
|
|
103
|
-
-
|
|
104
|
-
-
|
|
126
|
+
- Code is encrypted at rest with Fernet on the server
|
|
127
|
+
- Share links expose only session metadata and declared library names, never source
|
|
128
|
+
- Admin edits require the admin access code; owner edits/deletes require the one-time owner token
|
|
129
|
+
- Sessions never expire unless an expiry is explicitly selected
|
|
105
130
|
|
|
106
131
|
## License
|
|
107
132
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PyVaultRCE
|
|
2
2
|
|
|
3
|
-
Remote Code
|
|
3
|
+
Remote Code Hosting client for **PyVault**.
|
|
4
4
|
|
|
5
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
6
|
|
|
@@ -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
|
|
|
@@ -25,6 +25,16 @@ sid = CodeManager.enc_url("https://pastebin.com/abcXYZ123")
|
|
|
25
25
|
# Execute remotely (source stays on server)
|
|
26
26
|
CodeManager.run(sid)
|
|
27
27
|
|
|
28
|
+
# Upload with a short share link and library metadata
|
|
29
|
+
sid = CodeManager.enc(
|
|
30
|
+
"my_script.py",
|
|
31
|
+
label="analytics demo",
|
|
32
|
+
libraries=["requests", "pandas"],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Hide PyVault status messages while running
|
|
36
|
+
CodeManager.run(sid, show_terminal=False)
|
|
37
|
+
|
|
28
38
|
# Check metadata without incrementing execution counter
|
|
29
39
|
CodeManager.info(sid)
|
|
30
40
|
|
|
@@ -40,16 +50,29 @@ Set `PYVAULT_URL` to point at your hosted PyVault server:
|
|
|
40
50
|
export PYVAULT_URL=https://your-server.replit.app
|
|
41
51
|
```
|
|
42
52
|
|
|
53
|
+
For local development the client uses `http://localhost:5000` when
|
|
54
|
+
`PYVAULT_URL` is not set. You can also control status output globally:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export PYVAULT_TERMINAL=off
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Library names are metadata only. PyVault does not auto-install packages or
|
|
61
|
+
execute dependency installers; install the libraries in the Python environment
|
|
62
|
+
where `CodeManager.run()` executes.
|
|
63
|
+
|
|
43
64
|
## API Reference
|
|
44
65
|
|
|
45
66
|
| Method | Description |
|
|
46
67
|
|--------|-------------|
|
|
47
68
|
| `CodeManager.enc(file_path)` | Upload a local `.py` file → returns Session ID |
|
|
69
|
+
| `CodeManager.enc(file_path, label=..., libraries=[...])` | Upload with a label and visible library metadata |
|
|
48
70
|
| `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 |
|
|
71
|
+
| `CodeManager.run(session_id, show_terminal=False)` | Fetch and execute code by Session ID with optional quiet status output |
|
|
50
72
|
| `CodeManager.info(session_id)` | Get session metadata (no exec count increment) |
|
|
51
73
|
| `CodeManager.ping()` | Check server connectivity |
|
|
52
|
-
| `CodeManager.edit(session_id, file_path, admin_token)` | Replace stored code (admin
|
|
74
|
+
| `CodeManager.edit(session_id, file_path, admin_token=...)` | Replace stored code (admin or owner token) |
|
|
75
|
+
| `CodeManager.delete(session_id, owner_token)` | Delete your own session |
|
|
53
76
|
|
|
54
77
|
## Supported Paste Services (`enc_url`)
|
|
55
78
|
|
|
@@ -75,8 +98,10 @@ export PYVAULT_URL=https://your-server.replit.app
|
|
|
75
98
|
|
|
76
99
|
- Session IDs are 21-character cryptographically random hex strings
|
|
77
100
|
- Source code is never written to disk on the client
|
|
78
|
-
-
|
|
79
|
-
-
|
|
101
|
+
- Code is encrypted at rest with Fernet on the server
|
|
102
|
+
- Share links expose only session metadata and declared library names, never source
|
|
103
|
+
- Admin edits require the admin access code; owner edits/deletes require the one-time owner token
|
|
104
|
+
- Sessions never expire unless an expiry is explicitly selected
|
|
80
105
|
|
|
81
106
|
## License
|
|
82
107
|
|
|
@@ -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 = "http://localhost:5000"
|
|
19
19
|
|
|
20
20
|
# ── Validation helpers ───────────────────────────────────────────────────────
|
|
21
21
|
|
|
@@ -36,10 +36,23 @@ 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
|
|
|
43
|
+
def _terminal_enabled(show_terminal=None) -> bool:
|
|
44
|
+
if show_terminal is not None:
|
|
45
|
+
return bool(show_terminal)
|
|
46
|
+
return os.environ.get("PYVAULT_TERMINAL", "on").strip().lower() not in {
|
|
47
|
+
"0", "false", "off", "no", "quiet"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _emit(message: str, show_terminal=None, *, error=False) -> None:
|
|
52
|
+
if _terminal_enabled(show_terminal):
|
|
53
|
+
print(message, file=sys.stderr if error else sys.stdout, flush=True)
|
|
54
|
+
|
|
55
|
+
|
|
43
56
|
# ── CodeManager ─────────────────────────────────────────────────────────────
|
|
44
57
|
|
|
45
58
|
class CodeManager:
|
|
@@ -59,13 +72,15 @@ class CodeManager:
|
|
|
59
72
|
file_path: str,
|
|
60
73
|
base_url: Optional[str] = None,
|
|
61
74
|
timeout: int = 30,
|
|
75
|
+
label: str = "",
|
|
76
|
+
libraries = "",
|
|
62
77
|
) -> str:
|
|
63
78
|
"""
|
|
64
79
|
Read a local Python file and upload it to the PyVault backend.
|
|
65
80
|
|
|
66
81
|
Args:
|
|
67
82
|
file_path: Path to the local .py file to upload.
|
|
68
|
-
base_url: Override the server URL (default: PYVAULT_URL env or
|
|
83
|
+
base_url: Override the server URL (default: PYVAULT_URL env or hosted PyVault URL).
|
|
69
84
|
timeout: HTTP timeout in seconds.
|
|
70
85
|
|
|
71
86
|
Returns:
|
|
@@ -98,7 +113,7 @@ class CodeManager:
|
|
|
98
113
|
try:
|
|
99
114
|
response = requests.post(
|
|
100
115
|
url,
|
|
101
|
-
json={"code": code},
|
|
116
|
+
json={"code": code, "label": label, "libraries": libraries},
|
|
102
117
|
timeout=timeout,
|
|
103
118
|
)
|
|
104
119
|
except requests.exceptions.ConnectionError:
|
|
@@ -142,6 +157,7 @@ class CodeManager:
|
|
|
142
157
|
base_url: Optional[str] = None,
|
|
143
158
|
timeout: int = 30,
|
|
144
159
|
globals_dict: Optional[dict] = None,
|
|
160
|
+
show_terminal=None,
|
|
145
161
|
) -> None:
|
|
146
162
|
"""
|
|
147
163
|
Fetch the code for a Session ID from PyVault and execute it locally.
|
|
@@ -208,7 +224,7 @@ class CodeManager:
|
|
|
208
224
|
f"Server returned empty code for session '{session_id}'."
|
|
209
225
|
)
|
|
210
226
|
|
|
211
|
-
|
|
227
|
+
_emit(f"[PyVault] Executing session '{session_id}' (run #{exec_count})…", show_terminal)
|
|
212
228
|
|
|
213
229
|
namespace = globals_dict if globals_dict is not None else {
|
|
214
230
|
"__name__": "__pyvault__",
|
|
@@ -219,19 +235,19 @@ class CodeManager:
|
|
|
219
235
|
compiled = compile(code, f"<pyvault:{session_id}>", "exec")
|
|
220
236
|
exec(compiled, namespace)
|
|
221
237
|
except SyntaxError as exc:
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
_emit("\n[PyVault] ✕ Syntax error in remote code:", show_terminal, error=True)
|
|
239
|
+
_emit(f" Line {exc.lineno}: {exc.msg}", show_terminal, error=True)
|
|
224
240
|
if exc.text:
|
|
225
|
-
|
|
241
|
+
_emit(f" >>> {exc.text.strip()}", show_terminal, error=True)
|
|
226
242
|
raise
|
|
227
243
|
except Exception as exc:
|
|
228
|
-
|
|
244
|
+
_emit("\n[PyVault] ✕ Runtime error during execution:", show_terminal, error=True)
|
|
229
245
|
tb_lines = traceback.format_exc().splitlines()
|
|
230
246
|
for line in tb_lines:
|
|
231
|
-
|
|
247
|
+
_emit(f" {line}", show_terminal, error=True)
|
|
232
248
|
raise
|
|
233
249
|
|
|
234
|
-
|
|
250
|
+
_emit("[PyVault] ✓ Execution complete.", show_terminal)
|
|
235
251
|
|
|
236
252
|
@staticmethod
|
|
237
253
|
def edit(
|
|
@@ -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 = "http://localhost:5000"
|
|
30
27
|
_MAX_LINES = 10_000
|
|
31
28
|
_SID_LEN = 21
|
|
32
29
|
_HEX_SET = frozenset("0123456789abcdef")
|
|
@@ -47,7 +44,34 @@ 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("/")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _terminal_enabled(show_terminal: Optional[bool]) -> bool:
|
|
51
|
+
if show_terminal is not None:
|
|
52
|
+
return bool(show_terminal)
|
|
53
|
+
value = os.environ.get("PYVAULT_TERMINAL", "on").strip().lower()
|
|
54
|
+
return value not in {"0", "false", "off", "no", "quiet"}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _emit(message: str, show_terminal: Optional[bool] = None, *, error: bool = False) -> None:
|
|
58
|
+
if _terminal_enabled(show_terminal):
|
|
59
|
+
print(message, file=sys.stderr if error else sys.stdout, flush=True)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _normalise_libraries(value) -> str:
|
|
63
|
+
if isinstance(value, (list, tuple)):
|
|
64
|
+
values = value
|
|
65
|
+
elif isinstance(value, str):
|
|
66
|
+
values = re.split(r"[,\n]", value)
|
|
67
|
+
else:
|
|
68
|
+
values = []
|
|
69
|
+
cleaned = []
|
|
70
|
+
for item in values:
|
|
71
|
+
name = str(item).strip()
|
|
72
|
+
if name and re.fullmatch(r"[A-Za-z0-9_.-]{1,80}", name) and name not in cleaned:
|
|
73
|
+
cleaned.append(name)
|
|
74
|
+
return ", ".join(cleaned)[:500]
|
|
51
75
|
|
|
52
76
|
|
|
53
77
|
def _valid_sid(sid: str) -> bool:
|
|
@@ -153,8 +177,10 @@ def _upload_code(
|
|
|
153
177
|
timeout: int,
|
|
154
178
|
expires_in_hours: Optional[float] = None,
|
|
155
179
|
max_executions: int = 0,
|
|
180
|
+
label: str = "",
|
|
181
|
+
libraries = "",
|
|
156
182
|
) -> tuple:
|
|
157
|
-
"""Internal: push code string to /pyv/save. Returns
|
|
183
|
+
"""Internal: push code string to /pyv/save. Returns session metadata."""
|
|
158
184
|
if not code.strip():
|
|
159
185
|
raise ValueError("Code is empty — nothing to upload.")
|
|
160
186
|
line_count = len(code.splitlines())
|
|
@@ -167,6 +193,10 @@ def _upload_code(
|
|
|
167
193
|
payload["expires_in_hours"] = expires_in_hours
|
|
168
194
|
if max_executions and max_executions > 0:
|
|
169
195
|
payload["max_executions"] = max_executions
|
|
196
|
+
if label:
|
|
197
|
+
payload["label"] = str(label)[:200]
|
|
198
|
+
if libraries:
|
|
199
|
+
payload["libraries"] = _normalise_libraries(libraries)
|
|
170
200
|
|
|
171
201
|
url = _base(base_url) + "/pyv/save"
|
|
172
202
|
resp = _http_post(url, payload, timeout)
|
|
@@ -181,7 +211,7 @@ def _upload_code(
|
|
|
181
211
|
owner_token = data.get("owner_token", "")
|
|
182
212
|
if not _valid_sid(sid):
|
|
183
213
|
raise RuntimeError(f"Server returned unexpected Session ID: '{sid}'")
|
|
184
|
-
return sid, owner_token
|
|
214
|
+
return sid, owner_token, data.get("share_url", ""), data.get("libraries", "")
|
|
185
215
|
|
|
186
216
|
|
|
187
217
|
# ── CodeManager ───────────────────────────────────────────────────────────────
|
|
@@ -200,7 +230,8 @@ class CodeManager:
|
|
|
200
230
|
|
|
201
231
|
Environment variables:
|
|
202
232
|
PYVAULT_URL — base URL of the PyVault server
|
|
203
|
-
|
|
233
|
+
(default: http://localhost:5000; set PYVAULT_URL for a hosted server)
|
|
234
|
+
PYVAULT_TERMINAL — on/off terminal status messages (default: on)
|
|
204
235
|
"""
|
|
205
236
|
|
|
206
237
|
@staticmethod
|
|
@@ -210,6 +241,9 @@ class CodeManager:
|
|
|
210
241
|
timeout: int = 30,
|
|
211
242
|
expires_in_hours: Optional[float] = None,
|
|
212
243
|
max_executions: int = 0,
|
|
244
|
+
label: str = "",
|
|
245
|
+
libraries = "",
|
|
246
|
+
show_terminal: Optional[bool] = None,
|
|
213
247
|
) -> str:
|
|
214
248
|
"""
|
|
215
249
|
Read a local .py file and upload it to PyVault.
|
|
@@ -230,14 +264,20 @@ class CodeManager:
|
|
|
230
264
|
code = _read_file(file_path)
|
|
231
265
|
if not code.strip():
|
|
232
266
|
raise ValueError(f"The file '{file_path}' is empty — nothing to upload.")
|
|
233
|
-
sid, owner_token
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
267
|
+
sid, owner_token, share_url, safe_libraries = _upload_code(
|
|
268
|
+
code, base_url, timeout, expires_in_hours, max_executions, label, libraries
|
|
269
|
+
)
|
|
270
|
+
_emit(f"[PyVault] ✓ Uploaded — Session ID : {sid}", show_terminal)
|
|
271
|
+
_emit(f"[PyVault] Source : {os.path.abspath(file_path)}", show_terminal)
|
|
272
|
+
_emit(f"[PyVault] Lines : {len(code.splitlines()):,}", show_terminal)
|
|
273
|
+
if share_url:
|
|
274
|
+
_emit(f"[PyVault] Share link: {share_url}", show_terminal)
|
|
275
|
+
if safe_libraries:
|
|
276
|
+
_emit(f"[PyVault] Libraries: {safe_libraries}", show_terminal)
|
|
237
277
|
if owner_token:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
278
|
+
_emit("\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", show_terminal)
|
|
279
|
+
_emit(f"[PyVault] {owner_token}", show_terminal)
|
|
280
|
+
_emit("[PyVault] Use this to delete or edit your session later.\n", show_terminal)
|
|
241
281
|
return sid
|
|
242
282
|
|
|
243
283
|
@staticmethod
|
|
@@ -245,6 +285,9 @@ class CodeManager:
|
|
|
245
285
|
paste_url: str,
|
|
246
286
|
base_url: Optional[str] = None,
|
|
247
287
|
timeout: int = 30,
|
|
288
|
+
label: str = "",
|
|
289
|
+
libraries = "",
|
|
290
|
+
show_terminal: Optional[bool] = None,
|
|
248
291
|
) -> str:
|
|
249
292
|
"""
|
|
250
293
|
Download Python code from a paste service URL and upload to PyVault.
|
|
@@ -268,7 +311,7 @@ class CodeManager:
|
|
|
268
311
|
resp = requests.get(
|
|
269
312
|
raw_url,
|
|
270
313
|
timeout=timeout,
|
|
271
|
-
headers={"User-Agent": "PyVaultRCE/2.
|
|
314
|
+
headers={"User-Agent": "PyVaultRCE/2.4"},
|
|
272
315
|
)
|
|
273
316
|
resp.raise_for_status()
|
|
274
317
|
except requests.exceptions.ConnectionError:
|
|
@@ -286,14 +329,20 @@ class CodeManager:
|
|
|
286
329
|
if not code.strip():
|
|
287
330
|
raise ValueError("Downloaded content is empty.")
|
|
288
331
|
|
|
289
|
-
sid, owner_token = _upload_code(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
332
|
+
sid, owner_token, share_url, safe_libraries = _upload_code(
|
|
333
|
+
code, base_url, timeout, label=label, libraries=libraries
|
|
334
|
+
)
|
|
335
|
+
_emit(f"[PyVault] ✓ Uploaded from URL — Session ID : {sid}", show_terminal)
|
|
336
|
+
_emit(f"[PyVault] Source : {paste_url}", show_terminal)
|
|
337
|
+
_emit(f"[PyVault] Lines : {len(code.splitlines()):,}", show_terminal)
|
|
338
|
+
if share_url:
|
|
339
|
+
_emit(f"[PyVault] Share link: {share_url}", show_terminal)
|
|
340
|
+
if safe_libraries:
|
|
341
|
+
_emit(f"[PyVault] Libraries: {safe_libraries}", show_terminal)
|
|
293
342
|
if owner_token:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
343
|
+
_emit("\n[PyVault] ⚠ OWNER TOKEN (save this — shown only once!):", show_terminal)
|
|
344
|
+
_emit(f"[PyVault] {owner_token}", show_terminal)
|
|
345
|
+
_emit("[PyVault] Use this to delete or edit your session later.\n", show_terminal)
|
|
297
346
|
return sid
|
|
298
347
|
|
|
299
348
|
@staticmethod
|
|
@@ -302,6 +351,7 @@ class CodeManager:
|
|
|
302
351
|
base_url: Optional[str] = None,
|
|
303
352
|
timeout: int = 30,
|
|
304
353
|
_ns: Optional[dict] = None,
|
|
354
|
+
show_terminal: Optional[bool] = None,
|
|
305
355
|
) -> None:
|
|
306
356
|
"""
|
|
307
357
|
Fetch the encrypted code for a Session ID from PyVault, decrypt it,
|
|
@@ -366,7 +416,7 @@ class CodeManager:
|
|
|
366
416
|
if not code.strip():
|
|
367
417
|
raise RuntimeError(f"Decrypted code is empty for session '{session_id}'.")
|
|
368
418
|
|
|
369
|
-
|
|
419
|
+
_emit(f"[PyVault] ▶ Running session '{session_id}' (execution #{run_count})…", show_terminal)
|
|
370
420
|
|
|
371
421
|
namespace = _ns if _ns is not None else {
|
|
372
422
|
"__name__": "__pyvault__",
|
|
@@ -376,18 +426,18 @@ class CodeManager:
|
|
|
376
426
|
try:
|
|
377
427
|
exec(compile(code, f"<vault:{session_id[:8]}…>", "exec"), namespace)
|
|
378
428
|
except SyntaxError as exc:
|
|
379
|
-
|
|
380
|
-
|
|
429
|
+
_emit("\n[PyVault] ✕ Syntax error:", show_terminal, error=True)
|
|
430
|
+
_emit(f" Line {exc.lineno}: {exc.msg}", show_terminal, error=True)
|
|
381
431
|
if exc.text:
|
|
382
|
-
|
|
432
|
+
_emit(f" >>> {exc.text.strip()}", show_terminal, error=True)
|
|
383
433
|
raise
|
|
384
434
|
except Exception:
|
|
385
|
-
|
|
435
|
+
_emit("\n[PyVault] ✕ Runtime error:", show_terminal, error=True)
|
|
386
436
|
for line in traceback.format_exc().splitlines():
|
|
387
|
-
|
|
437
|
+
_emit(f" {line}", show_terminal, error=True)
|
|
388
438
|
raise
|
|
389
439
|
|
|
390
|
-
|
|
440
|
+
_emit("[PyVault] ✓ Execution complete.", show_terminal)
|
|
391
441
|
|
|
392
442
|
@staticmethod
|
|
393
443
|
def info(
|
|
@@ -400,7 +450,8 @@ class CodeManager:
|
|
|
400
450
|
incrementing the execution counter.
|
|
401
451
|
|
|
402
452
|
Returns:
|
|
403
|
-
dict with keys: session_id, execution_count, created_at,
|
|
453
|
+
dict with keys: session_id, execution_count, created_at,
|
|
454
|
+
encrypted_size, expires_at, max_executions, status
|
|
404
455
|
|
|
405
456
|
Raises:
|
|
406
457
|
ValueError, ConnectionError, RuntimeError
|
|
@@ -417,15 +468,30 @@ class CodeManager:
|
|
|
417
468
|
err = resp.text
|
|
418
469
|
raise RuntimeError(f"Server error (HTTP {resp.status_code}): {err}")
|
|
419
470
|
data = resp.json()
|
|
471
|
+
encrypted_size = data.get("encrypted_size", data.get("code_size", 0))
|
|
420
472
|
print(
|
|
421
|
-
f"[PyVault] ℹ Session
|
|
473
|
+
f"[PyVault] ℹ Session : {data['session_id']}\n"
|
|
422
474
|
f"[PyVault] Executions: {data['execution_count']}\n"
|
|
423
475
|
f"[PyVault] Created : {data['created_at']}\n"
|
|
424
|
-
f"[PyVault] Code size : {
|
|
476
|
+
f"[PyVault] Code size : {encrypted_size:,} bytes (encrypted)\n"
|
|
477
|
+
f"[PyVault] Status : {data.get('status', 'unknown')}",
|
|
425
478
|
flush=True,
|
|
426
479
|
)
|
|
427
480
|
return data
|
|
428
481
|
|
|
482
|
+
@staticmethod
|
|
483
|
+
def share(
|
|
484
|
+
session_id: str,
|
|
485
|
+
base_url: Optional[str] = None,
|
|
486
|
+
timeout: int = 30,
|
|
487
|
+
) -> str:
|
|
488
|
+
"""Return the short public metadata link for a session."""
|
|
489
|
+
data = CodeManager.info(session_id, base_url=base_url, timeout=timeout)
|
|
490
|
+
share_url = data.get("share_url", "")
|
|
491
|
+
if not share_url:
|
|
492
|
+
raise RuntimeError("This server did not return a share URL.")
|
|
493
|
+
return share_url
|
|
494
|
+
|
|
429
495
|
@staticmethod
|
|
430
496
|
def ping(
|
|
431
497
|
base_url: Optional[str] = None,
|
|
@@ -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.4.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
|