@bniladridas/cursor 0.1.7
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.
- package/.clang-tidy +28 -0
- package/.dockerignore +56 -0
- package/.env.example +29 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/ISSUE_TEMPLATE/blank.md +27 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/SECURITY.md +24 -0
- package/.github/codeql/codeql-config.yml +8 -0
- package/.github/dependabot.yml +14 -0
- package/.github/labeler.yml +50 -0
- package/.github/packaging/brand-cursor.png +0 -0
- package/.github/packaging/database/init.sql +48 -0
- package/.github/packaging/docker/Dockerfile +111 -0
- package/.github/packaging/docker/docker-compose.yml +56 -0
- package/.github/packaging/scripts/preflight.sh +413 -0
- package/.github/packaging/scripts/prepare-release.sh +141 -0
- package/.github/packaging/scripts/release.sh +22 -0
- package/.github/packaging/scripts/setup-git-hooks.sh +73 -0
- package/.github/pull_request_template.md +31 -0
- package/.github/signed.json +9 -0
- package/.github/workflows/README.md +23 -0
- package/.github/workflows/ci.yml +181 -0
- package/.github/workflows/cla.yml +33 -0
- package/.github/workflows/formula-sha.yml +63 -0
- package/.github/workflows/issue-response.yml +44 -0
- package/.github/workflows/labeler.yml +42 -0
- package/.github/workflows/pr-body.yml +49 -0
- package/.github/workflows/release.yml +176 -0
- package/.github/workflows/security.yml +94 -0
- package/.github/workflows/stale.yml +38 -0
- package/AGENTS.md +49 -0
- package/CHANGELOG.md +3 -0
- package/CMakeLists.txt +646 -0
- package/Formula/cursor.rb +46 -0
- package/LICENSE +201 -0
- package/Makefile +28 -0
- package/README.md +46 -0
- package/cli.js +16 -0
- package/include/agent.h +86 -0
- package/include/agent_mode.h +17 -0
- package/include/memory_manager.h +102 -0
- package/include/services/ai_service.h +31 -0
- package/include/services/auth_service.h +87 -0
- package/include/services/checkpoint_service.h +69 -0
- package/include/services/codebase_service.h +38 -0
- package/include/services/command_service.h +23 -0
- package/include/services/context_service.h +74 -0
- package/include/services/database_service.h +56 -0
- package/include/services/error_service.h +106 -0
- package/include/services/file_service.h +51 -0
- package/include/services/git_service.h +29 -0
- package/include/services/github_service.h +85 -0
- package/include/services/mcp_service.h +85 -0
- package/include/services/multi_file_service.h +93 -0
- package/include/services/sandbox_service.h +96 -0
- package/include/services/theme_service.h +67 -0
- package/include/services/web_service.h +52 -0
- package/include/utils/config.h +68 -0
- package/include/utils/memory_utils.h +79 -0
- package/include/utils/platform.h +56 -0
- package/include/utils/ui.h +43 -0
- package/include/utils/validation.h +63 -0
- package/include/utils/version.h.in +17 -0
- package/install.js +49 -0
- package/package.json +16 -0
- package/release/checksums.txt +3 -0
- package/release/cursor-linux/cursor_v0.1.7_linux_amd64.tar.gz +0 -0
- package/release/cursor-macos/cursor_v0.1.7_darwin_arm64.tar.gz +0 -0
- package/release/cursor-windows/cursor__windows_amd64.zip +0 -0
- package/src/agent.cpp +2026 -0
- package/src/main.cpp +97 -0
- package/src/memory_manager.cpp +814 -0
- package/src/services/ai_service.cpp +366 -0
- package/src/services/auth_service.cpp +779 -0
- package/src/services/checkpoint_service.cpp +465 -0
- package/src/services/codebase_service.cpp +233 -0
- package/src/services/command_service.cpp +82 -0
- package/src/services/context_service.cpp +348 -0
- package/src/services/database_service.cpp +148 -0
- package/src/services/error_service.cpp +438 -0
- package/src/services/file_service.cpp +349 -0
- package/src/services/git_service.cpp +148 -0
- package/src/services/github_service.cpp +435 -0
- package/src/services/mcp_service.cpp +481 -0
- package/src/services/multi_file_service.cpp +591 -0
- package/src/services/sandbox_service.cpp +678 -0
- package/src/services/theme_service.cpp +429 -0
- package/src/services/web_service.cpp +532 -0
- package/src/utils/config.cpp +77 -0
- package/src/utils/memory_utils.cpp +93 -0
- package/src/utils/ui.cpp +307 -0
- package/src/utils/validation.cpp +306 -0
- package/src/utils/version.cpp +175 -0
- package/tests/e2e/docker-compose.yml +195 -0
- package/tests/e2e/run_e2e_tests.sh +70 -0
- package/tests/e2e/run_tests_in_docker.sh +115 -0
- package/tests/main_test.cpp +16 -0
- package/tests/mocks/mock_ollama.py +98 -0
- package/tests/mocks/start_nginx.sh +64 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
#include "services/sandbox_service.h"
|
|
2
|
+
#include "utils/platform.h"
|
|
3
|
+
#include "utils/validation.h"
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
#include <chrono>
|
|
6
|
+
#include <cstdio>
|
|
7
|
+
#include <cstdlib>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
#include <ctime>
|
|
10
|
+
#include <filesystem>
|
|
11
|
+
#include <fstream>
|
|
12
|
+
#include <iostream>
|
|
13
|
+
#include <random>
|
|
14
|
+
#include <regex>
|
|
15
|
+
#include <sstream>
|
|
16
|
+
#ifdef _WIN32
|
|
17
|
+
#include <windows.h>
|
|
18
|
+
#else
|
|
19
|
+
#include <sys/wait.h>
|
|
20
|
+
#include <unistd.h>
|
|
21
|
+
#endif
|
|
22
|
+
#include <nlohmann/json.hpp>
|
|
23
|
+
|
|
24
|
+
namespace Services {
|
|
25
|
+
|
|
26
|
+
std::map<std::string, SandboxConfig> SandboxService::sandbox_configs_;
|
|
27
|
+
|
|
28
|
+
std::string SandboxService::get_sandbox_config_path() {
|
|
29
|
+
return "data/sandbox_config.json";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void SandboxService::ensure_sandbox_directory() {
|
|
33
|
+
std::filesystem::create_directories("data");
|
|
34
|
+
std::filesystem::create_directories("data/sandbox");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void SandboxService::initialize_default_configs() {
|
|
38
|
+
// Default sandbox for general use
|
|
39
|
+
SandboxConfig default_config;
|
|
40
|
+
default_config.name = "default";
|
|
41
|
+
default_config.image = "ubuntu:22.04";
|
|
42
|
+
default_config.memory_limit_mb = 512;
|
|
43
|
+
default_config.cpu_limit_percent = 50;
|
|
44
|
+
default_config.timeout_seconds = 300;
|
|
45
|
+
default_config.network_access = false;
|
|
46
|
+
default_config.working_directory = "/workspace";
|
|
47
|
+
default_config.allowed_commands = {"ls", "cat", "echo", "grep",
|
|
48
|
+
"find", "head", "tail", "wc"};
|
|
49
|
+
sandbox_configs_["default"] = default_config;
|
|
50
|
+
|
|
51
|
+
// Python sandbox
|
|
52
|
+
SandboxConfig python_config;
|
|
53
|
+
python_config.name = "python";
|
|
54
|
+
python_config.image = "python:3.11-slim";
|
|
55
|
+
python_config.memory_limit_mb = 1024;
|
|
56
|
+
python_config.cpu_limit_percent = 75;
|
|
57
|
+
python_config.timeout_seconds = 600;
|
|
58
|
+
python_config.network_access = false;
|
|
59
|
+
python_config.working_directory = "/workspace";
|
|
60
|
+
python_config.allowed_commands = {"python", "python3", "pip",
|
|
61
|
+
"ls", "cat", "echo"};
|
|
62
|
+
sandbox_configs_["python"] = python_config;
|
|
63
|
+
|
|
64
|
+
// Node.js sandbox
|
|
65
|
+
SandboxConfig node_config;
|
|
66
|
+
node_config.name = "nodejs";
|
|
67
|
+
node_config.image = "node:18-slim";
|
|
68
|
+
node_config.memory_limit_mb = 1024;
|
|
69
|
+
node_config.cpu_limit_percent = 75;
|
|
70
|
+
node_config.timeout_seconds = 600;
|
|
71
|
+
node_config.network_access = false;
|
|
72
|
+
node_config.working_directory = "/workspace";
|
|
73
|
+
node_config.allowed_commands = {"node", "npm", "ls", "cat", "echo"};
|
|
74
|
+
sandbox_configs_["nodejs"] = node_config;
|
|
75
|
+
|
|
76
|
+
// Secure shell sandbox
|
|
77
|
+
SandboxConfig shell_config;
|
|
78
|
+
shell_config.name = "shell";
|
|
79
|
+
shell_config.image = "alpine:latest";
|
|
80
|
+
shell_config.memory_limit_mb = 256;
|
|
81
|
+
shell_config.cpu_limit_percent = 25;
|
|
82
|
+
shell_config.timeout_seconds = 120;
|
|
83
|
+
shell_config.network_access = false;
|
|
84
|
+
shell_config.working_directory = "/workspace";
|
|
85
|
+
shell_config.allowed_commands = {"sh", "ls", "cat", "echo",
|
|
86
|
+
"grep", "find", "head", "tail"};
|
|
87
|
+
sandbox_configs_["shell"] = shell_config;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
std::string SandboxService::generate_container_name() {
|
|
91
|
+
static int counter = 1;
|
|
92
|
+
auto now = std::chrono::system_clock::now();
|
|
93
|
+
auto time_t = std::chrono::system_clock::to_time_t(now);
|
|
94
|
+
|
|
95
|
+
std::stringstream ss;
|
|
96
|
+
ss << "cursor_sandbox_" << time_t << "_" << counter++;
|
|
97
|
+
return ss.str();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
bool SandboxService::is_docker_available() {
|
|
101
|
+
FILE *pipe = Utils::Platform::open_process("docker --version 2>&1", "r");
|
|
102
|
+
if (!pipe)
|
|
103
|
+
return false;
|
|
104
|
+
|
|
105
|
+
char buffer[128];
|
|
106
|
+
bool success = false;
|
|
107
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
108
|
+
if (strstr(buffer, "Docker version") != nullptr) {
|
|
109
|
+
success = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
Utils::Platform::close_process(pipe);
|
|
114
|
+
return success;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
std::string SandboxService::escape_shell_arg(const std::string &arg) {
|
|
118
|
+
std::string escaped = arg;
|
|
119
|
+
// Basic shell escaping - replace single quotes
|
|
120
|
+
size_t pos = 0;
|
|
121
|
+
while ((pos = escaped.find("'", pos)) != std::string::npos) {
|
|
122
|
+
escaped.replace(pos, 1, "'\"'\"'");
|
|
123
|
+
pos += 5;
|
|
124
|
+
}
|
|
125
|
+
return "'" + escaped + "'";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
void SandboxService::initialize() {
|
|
129
|
+
initialize_default_configs();
|
|
130
|
+
load_sandbox_config();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
bool SandboxService::create_sandbox_config(const std::string &name,
|
|
134
|
+
const SandboxConfig &config) {
|
|
135
|
+
sandbox_configs_[name] = config;
|
|
136
|
+
return save_sandbox_config();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
bool SandboxService::remove_sandbox_config(const std::string &name) {
|
|
140
|
+
// Don't allow removal of built-in configs
|
|
141
|
+
if (name == "default" || name == "python" || name == "nodejs" ||
|
|
142
|
+
name == "shell") {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
auto it = sandbox_configs_.find(name);
|
|
147
|
+
if (it == sandbox_configs_.end()) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
sandbox_configs_.erase(it);
|
|
152
|
+
return save_sandbox_config();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
std::vector<std::string> SandboxService::list_sandbox_configs() {
|
|
156
|
+
std::vector<std::string> config_names;
|
|
157
|
+
for (const auto &[name, config] : sandbox_configs_) {
|
|
158
|
+
config_names.push_back(name);
|
|
159
|
+
}
|
|
160
|
+
return config_names;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
SandboxConfig SandboxService::get_sandbox_config(const std::string &name) {
|
|
164
|
+
auto it = sandbox_configs_.find(name);
|
|
165
|
+
if (it != sandbox_configs_.end()) {
|
|
166
|
+
return it->second;
|
|
167
|
+
}
|
|
168
|
+
return SandboxConfig{}; // Return empty config if not found
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
SandboxResult SandboxService::execute_command(const std::string &command,
|
|
172
|
+
const std::string &sandbox_name,
|
|
173
|
+
const std::string &working_dir) {
|
|
174
|
+
SandboxResult result;
|
|
175
|
+
|
|
176
|
+
if (!is_docker_available()) {
|
|
177
|
+
result.error_message = "Docker is not available";
|
|
178
|
+
result.exit_code = -1;
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
auto it = sandbox_configs_.find(sandbox_name);
|
|
183
|
+
if (it == sandbox_configs_.end()) {
|
|
184
|
+
result.error_message = "Sandbox configuration not found: " + sandbox_name;
|
|
185
|
+
result.exit_code = -1;
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const SandboxConfig &config = it->second;
|
|
190
|
+
|
|
191
|
+
// Security check
|
|
192
|
+
if (!is_command_safe(command)) {
|
|
193
|
+
result.error_message = "Command failed security validation";
|
|
194
|
+
result.exit_code = -1;
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Build docker command
|
|
199
|
+
std::string container_name = generate_container_name();
|
|
200
|
+
std::stringstream docker_cmd;
|
|
201
|
+
|
|
202
|
+
docker_cmd << "docker run --rm";
|
|
203
|
+
docker_cmd << " --name " << container_name;
|
|
204
|
+
docker_cmd << " --memory=" << config.memory_limit_mb << "m";
|
|
205
|
+
docker_cmd << " --cpus=" << (config.cpu_limit_percent / 100.0);
|
|
206
|
+
|
|
207
|
+
if (!config.network_access) {
|
|
208
|
+
docker_cmd << " --network=none";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Set working directory
|
|
212
|
+
std::string work_dir =
|
|
213
|
+
working_dir.empty() ? config.working_directory : working_dir;
|
|
214
|
+
docker_cmd << " --workdir=" << work_dir;
|
|
215
|
+
|
|
216
|
+
// Add environment variables
|
|
217
|
+
for (const auto &[key, value] : config.environment_vars) {
|
|
218
|
+
docker_cmd << " -e " << key << "=" << escape_shell_arg(value);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Add volumes (be careful with security)
|
|
222
|
+
for (const auto &volume : config.volumes) {
|
|
223
|
+
docker_cmd << " -v " << volume;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
docker_cmd << " " << config.image;
|
|
227
|
+
docker_cmd << " sh -c " << escape_shell_arg(command);
|
|
228
|
+
|
|
229
|
+
// Execute with timeout
|
|
230
|
+
auto start_time = std::chrono::high_resolution_clock::now();
|
|
231
|
+
|
|
232
|
+
std::string full_command = docker_cmd.str() + " 2>&1";
|
|
233
|
+
FILE *pipe = Utils::Platform::open_process(full_command, "r");
|
|
234
|
+
|
|
235
|
+
if (!pipe) {
|
|
236
|
+
result.error_message = "Failed to execute docker command";
|
|
237
|
+
result.exit_code = -1;
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
std::stringstream output;
|
|
242
|
+
char buffer[128];
|
|
243
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
244
|
+
output << buffer;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
result.exit_code = Utils::Platform::close_process(pipe);
|
|
248
|
+
result.stdout_output = output.str();
|
|
249
|
+
|
|
250
|
+
auto end_time = std::chrono::high_resolution_clock::now();
|
|
251
|
+
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
252
|
+
end_time - start_time);
|
|
253
|
+
result.execution_time_seconds = duration.count() / 1000.0;
|
|
254
|
+
|
|
255
|
+
// Check for timeout
|
|
256
|
+
if (result.execution_time_seconds > config.timeout_seconds) {
|
|
257
|
+
result.timed_out = true;
|
|
258
|
+
result.error_message = "Command execution timed out";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
SandboxResult SandboxService::execute_script(const std::string &script_content,
|
|
265
|
+
const std::string &script_type,
|
|
266
|
+
const std::string &sandbox_name) {
|
|
267
|
+
SandboxResult result;
|
|
268
|
+
|
|
269
|
+
// Create temporary script file
|
|
270
|
+
std::string script_path = "data/sandbox/temp_script." + script_type;
|
|
271
|
+
std::ofstream script_file(script_path);
|
|
272
|
+
script_file << script_content;
|
|
273
|
+
script_file.close();
|
|
274
|
+
|
|
275
|
+
// Execute based on script type
|
|
276
|
+
std::string command;
|
|
277
|
+
if (script_type == "py" || script_type == "python") {
|
|
278
|
+
command = "python " + script_path;
|
|
279
|
+
} else if (script_type == "js" || script_type == "javascript") {
|
|
280
|
+
command = "node " + script_path;
|
|
281
|
+
} else if (script_type == "sh" || script_type == "bash") {
|
|
282
|
+
command = "sh " + script_path;
|
|
283
|
+
} else {
|
|
284
|
+
result.error_message = "Unsupported script type: " + script_type;
|
|
285
|
+
result.exit_code = -1;
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
result = execute_command(command, sandbox_name);
|
|
290
|
+
|
|
291
|
+
// Clean up temporary file
|
|
292
|
+
std::filesystem::remove(script_path);
|
|
293
|
+
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
SandboxResult SandboxService::execute_file(const std::string &file_path,
|
|
298
|
+
const std::string &sandbox_name) {
|
|
299
|
+
SandboxResult result;
|
|
300
|
+
|
|
301
|
+
if (!std::filesystem::exists(file_path)) {
|
|
302
|
+
result.error_message = "File not found: " + file_path;
|
|
303
|
+
result.exit_code = -1;
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Determine execution command based on file extension
|
|
308
|
+
std::string extension = std::filesystem::path(file_path).extension().string();
|
|
309
|
+
std::string command;
|
|
310
|
+
|
|
311
|
+
if (extension == ".py") {
|
|
312
|
+
command = "python " + file_path;
|
|
313
|
+
} else if (extension == ".js") {
|
|
314
|
+
command = "node " + file_path;
|
|
315
|
+
} else if (extension == ".sh") {
|
|
316
|
+
command = "sh " + file_path;
|
|
317
|
+
} else {
|
|
318
|
+
command = file_path; // Try to execute directly
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return execute_command(command, sandbox_name);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
bool SandboxService::copy_file_to_sandbox(const std::string &host_path,
|
|
325
|
+
const std::string &container_path,
|
|
326
|
+
const std::string &container_name) {
|
|
327
|
+
std::string command = "docker cp " + escape_shell_arg(host_path) + " " +
|
|
328
|
+
container_name + ":" + escape_shell_arg(container_path);
|
|
329
|
+
FILE *pipe = Utils::Platform::open_process(command.c_str(), "r");
|
|
330
|
+
if (!pipe)
|
|
331
|
+
return false;
|
|
332
|
+
|
|
333
|
+
// Read and discard output
|
|
334
|
+
char buffer[128];
|
|
335
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
int status = Utils::Platform::close_process(pipe);
|
|
339
|
+
#ifdef _WIN32
|
|
340
|
+
return status == 0; // On Windows, pclose returns the exit status directly
|
|
341
|
+
#else
|
|
342
|
+
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
343
|
+
#endif
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
bool SandboxService::copy_file_from_sandbox(const std::string &container_path,
|
|
347
|
+
const std::string &host_path,
|
|
348
|
+
const std::string &container_name) {
|
|
349
|
+
std::string command = "docker cp " + container_name + ":" +
|
|
350
|
+
escape_shell_arg(container_path) + " " +
|
|
351
|
+
escape_shell_arg(host_path);
|
|
352
|
+
FILE *pipe = Utils::Platform::open_process(command.c_str(), "r");
|
|
353
|
+
if (!pipe)
|
|
354
|
+
return false;
|
|
355
|
+
|
|
356
|
+
// Read and discard output
|
|
357
|
+
char buffer[128];
|
|
358
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
int status = Utils::Platform::close_process(pipe);
|
|
362
|
+
#ifdef _WIN32
|
|
363
|
+
return status == 0; // On Windows, pclose returns the exit status directly
|
|
364
|
+
#else
|
|
365
|
+
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
366
|
+
#endif
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
std::vector<std::string> SandboxService::list_active_containers() {
|
|
370
|
+
std::vector<std::string> containers;
|
|
371
|
+
|
|
372
|
+
FILE *pipe = Utils::Platform::open_process(
|
|
373
|
+
"docker ps --filter name=cursor_sandbox_ --format '{{.Names}}'", "r");
|
|
374
|
+
if (pipe) {
|
|
375
|
+
char buffer[256];
|
|
376
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
377
|
+
std::string name(buffer);
|
|
378
|
+
name.erase(name.find_last_not_of(" \n\r\t") + 1);
|
|
379
|
+
if (!name.empty()) {
|
|
380
|
+
containers.push_back(name);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
Utils::Platform::close_process(pipe);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return containers;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
bool SandboxService::stop_container(const std::string &container_name) {
|
|
390
|
+
std::string command = "docker stop " + escape_shell_arg(container_name);
|
|
391
|
+
FILE *pipe = Utils::Platform::open_process(command.c_str(), "r");
|
|
392
|
+
if (!pipe)
|
|
393
|
+
return false;
|
|
394
|
+
|
|
395
|
+
// Read and discard output
|
|
396
|
+
char buffer[128];
|
|
397
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
int status = Utils::Platform::close_process(pipe);
|
|
401
|
+
#ifdef _WIN32
|
|
402
|
+
return status == 0; // On Windows, pclose returns the exit status directly
|
|
403
|
+
#else
|
|
404
|
+
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
405
|
+
#endif
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
bool SandboxService::remove_container(const std::string &container_name) {
|
|
409
|
+
std::string command = "docker rm -f " + escape_shell_arg(container_name);
|
|
410
|
+
FILE *pipe = Utils::Platform::open_process(command.c_str(), "r");
|
|
411
|
+
if (!pipe)
|
|
412
|
+
return false;
|
|
413
|
+
|
|
414
|
+
// Read and discard output
|
|
415
|
+
char buffer[128];
|
|
416
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
int status = Utils::Platform::close_process(pipe);
|
|
420
|
+
#ifdef _WIN32
|
|
421
|
+
return status == 0; // On Windows, pclose returns the exit status directly
|
|
422
|
+
#else
|
|
423
|
+
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
424
|
+
#endif
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
void SandboxService::cleanup_old_containers() {
|
|
428
|
+
std::string command =
|
|
429
|
+
"docker container prune -f --filter label=cursor_sandbox";
|
|
430
|
+
FILE *pipe = Utils::Platform::open_process(command.c_str(), "r");
|
|
431
|
+
if (pipe) {
|
|
432
|
+
// Read and discard output
|
|
433
|
+
char buffer[128];
|
|
434
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
435
|
+
}
|
|
436
|
+
Utils::Platform::close_process(pipe);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
bool SandboxService::is_command_safe(const std::string &command) {
|
|
441
|
+
// List of dangerous commands/patterns
|
|
442
|
+
std::vector<std::string> dangerous_patterns = {
|
|
443
|
+
"rm -rf", "format", "del /f", "sudo", "su -", "chmod 777",
|
|
444
|
+
"wget", "curl", "nc ", "netcat", "telnet", "ssh",
|
|
445
|
+
"scp", "iptables", "ufw", "firewall", "systemctl", "service",
|
|
446
|
+
"mount", "umount", "fdisk", "mkfs", "dd if=", "dd of=",
|
|
447
|
+
">/dev/", "< /dev/", "| /dev/", "& /dev/", "$(", "`",
|
|
448
|
+
"eval", "exec", "source", ".", "bash -c", "sh -c",
|
|
449
|
+
"python -c", "perl -e", "ruby -e"};
|
|
450
|
+
|
|
451
|
+
std::string lower_command = command;
|
|
452
|
+
std::transform(lower_command.begin(), lower_command.end(),
|
|
453
|
+
lower_command.begin(), ::tolower);
|
|
454
|
+
|
|
455
|
+
for (const auto &pattern : dangerous_patterns) {
|
|
456
|
+
if (lower_command.find(pattern) != std::string::npos) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
bool SandboxService::validate_sandbox_config(const SandboxConfig &config,
|
|
465
|
+
std::string &error_message) {
|
|
466
|
+
if (config.name.empty()) {
|
|
467
|
+
error_message = "Sandbox name cannot be empty";
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (config.image.empty()) {
|
|
472
|
+
error_message = "Docker image cannot be empty";
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (config.memory_limit_mb < 64 || config.memory_limit_mb > 8192) {
|
|
477
|
+
error_message = "Memory limit must be between 64MB and 8192MB";
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (config.cpu_limit_percent < 1 || config.cpu_limit_percent > 100) {
|
|
482
|
+
error_message = "CPU limit must be between 1% and 100%";
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (config.timeout_seconds < 1 || config.timeout_seconds > 3600) {
|
|
487
|
+
error_message = "Timeout must be between 1 and 3600 seconds";
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
std::vector<std::string>
|
|
495
|
+
SandboxService::get_security_warnings(const std::string &command) {
|
|
496
|
+
std::vector<std::string> warnings;
|
|
497
|
+
|
|
498
|
+
if (command.find("sudo") != std::string::npos) {
|
|
499
|
+
warnings.push_back("Command contains 'sudo' - elevated privileges");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (command.find("rm") != std::string::npos) {
|
|
503
|
+
warnings.push_back("Command contains 'rm' - file deletion");
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (command.find("wget") != std::string::npos ||
|
|
507
|
+
command.find("curl") != std::string::npos) {
|
|
508
|
+
warnings.push_back("Command contains network access tools");
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return warnings;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
bool SandboxService::save_sandbox_config() {
|
|
515
|
+
try {
|
|
516
|
+
ensure_sandbox_directory();
|
|
517
|
+
|
|
518
|
+
nlohmann::json config;
|
|
519
|
+
config["sandboxes"] = nlohmann::json::object();
|
|
520
|
+
|
|
521
|
+
for (const auto &[name, sandbox] : sandbox_configs_) {
|
|
522
|
+
nlohmann::json sandbox_json;
|
|
523
|
+
sandbox_json["name"] = sandbox.name;
|
|
524
|
+
sandbox_json["image"] = sandbox.image;
|
|
525
|
+
sandbox_json["volumes"] = sandbox.volumes;
|
|
526
|
+
sandbox_json["environment_vars"] = sandbox.environment_vars;
|
|
527
|
+
sandbox_json["allowed_commands"] = sandbox.allowed_commands;
|
|
528
|
+
sandbox_json["memory_limit_mb"] = sandbox.memory_limit_mb;
|
|
529
|
+
sandbox_json["cpu_limit_percent"] = sandbox.cpu_limit_percent;
|
|
530
|
+
sandbox_json["timeout_seconds"] = sandbox.timeout_seconds;
|
|
531
|
+
sandbox_json["network_access"] = sandbox.network_access;
|
|
532
|
+
sandbox_json["working_directory"] = sandbox.working_directory;
|
|
533
|
+
|
|
534
|
+
config["sandboxes"][name] = sandbox_json;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
std::ofstream file(get_sandbox_config_path());
|
|
538
|
+
file << config.dump(2);
|
|
539
|
+
|
|
540
|
+
return true;
|
|
541
|
+
|
|
542
|
+
} catch (const std::exception &e) {
|
|
543
|
+
std::cerr << "Failed to save sandbox config: " << e.what() << std::endl;
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
bool SandboxService::load_sandbox_config() {
|
|
549
|
+
try {
|
|
550
|
+
std::string config_path = get_sandbox_config_path();
|
|
551
|
+
if (!std::filesystem::exists(config_path)) {
|
|
552
|
+
return true; // Use defaults
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
std::ifstream file(config_path);
|
|
556
|
+
nlohmann::json config;
|
|
557
|
+
file >> config;
|
|
558
|
+
|
|
559
|
+
if (config.contains("sandboxes")) {
|
|
560
|
+
for (const auto &[name, sandbox_json] : config["sandboxes"].items()) {
|
|
561
|
+
// Skip built-in configs
|
|
562
|
+
if (name == "default" || name == "python" || name == "nodejs" ||
|
|
563
|
+
name == "shell") {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
SandboxConfig sandbox;
|
|
568
|
+
sandbox.name = sandbox_json.value("name", name);
|
|
569
|
+
sandbox.image = sandbox_json.value("image", "ubuntu:22.04");
|
|
570
|
+
sandbox.memory_limit_mb = sandbox_json.value("memory_limit_mb", 512);
|
|
571
|
+
sandbox.cpu_limit_percent = sandbox_json.value("cpu_limit_percent", 50);
|
|
572
|
+
sandbox.timeout_seconds = sandbox_json.value("timeout_seconds", 300);
|
|
573
|
+
sandbox.network_access = sandbox_json.value("network_access", false);
|
|
574
|
+
sandbox.working_directory =
|
|
575
|
+
sandbox_json.value("working_directory", "/workspace");
|
|
576
|
+
|
|
577
|
+
if (sandbox_json.contains("volumes")) {
|
|
578
|
+
sandbox.volumes =
|
|
579
|
+
sandbox_json["volumes"].get<std::vector<std::string>>();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (sandbox_json.contains("environment_vars")) {
|
|
583
|
+
sandbox.environment_vars =
|
|
584
|
+
sandbox_json["environment_vars"]
|
|
585
|
+
.get<std::map<std::string, std::string>>();
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (sandbox_json.contains("allowed_commands")) {
|
|
589
|
+
sandbox.allowed_commands =
|
|
590
|
+
sandbox_json["allowed_commands"].get<std::vector<std::string>>();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
sandbox_configs_[name] = sandbox;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return true;
|
|
598
|
+
|
|
599
|
+
} catch (const std::exception &e) {
|
|
600
|
+
std::cerr << "Failed to load sandbox config: " << e.what() << std::endl;
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
bool SandboxService::update_sandbox_limits(const std::string &name,
|
|
606
|
+
size_t memory_mb,
|
|
607
|
+
size_t cpu_percent) {
|
|
608
|
+
auto it = sandbox_configs_.find(name);
|
|
609
|
+
if (it == sandbox_configs_.end()) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
it->second.memory_limit_mb = memory_mb;
|
|
614
|
+
it->second.cpu_limit_percent = cpu_percent;
|
|
615
|
+
|
|
616
|
+
return save_sandbox_config();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
bool SandboxService::check_docker_installation() {
|
|
620
|
+
return is_docker_available();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
std::string SandboxService::get_docker_version() {
|
|
624
|
+
FILE *pipe = Utils::Platform::open_process(
|
|
625
|
+
"docker --version" + Utils::Platform::get_shell_redirect(), "r");
|
|
626
|
+
if (!pipe)
|
|
627
|
+
return "Not available";
|
|
628
|
+
|
|
629
|
+
char buffer[256];
|
|
630
|
+
std::string version;
|
|
631
|
+
if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
632
|
+
version = buffer;
|
|
633
|
+
version.erase(version.find_last_not_of(" \n\r\t") + 1);
|
|
634
|
+
}
|
|
635
|
+
Utils::Platform::close_process(pipe);
|
|
636
|
+
|
|
637
|
+
return version.empty() ? "Not available" : version;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
bool SandboxService::pull_docker_image(const std::string &image) {
|
|
641
|
+
std::string command = "docker pull " + escape_shell_arg(image);
|
|
642
|
+
FILE *pipe = Utils::Platform::open_process(command.c_str(), "r");
|
|
643
|
+
if (!pipe)
|
|
644
|
+
return false;
|
|
645
|
+
|
|
646
|
+
// Read and discard output
|
|
647
|
+
char buffer[128];
|
|
648
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
int status = Utils::Platform::close_process(pipe);
|
|
652
|
+
#ifdef _WIN32
|
|
653
|
+
return status == 0; // On Windows, pclose returns the exit status directly
|
|
654
|
+
#else
|
|
655
|
+
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
656
|
+
#endif
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
std::vector<std::string> SandboxService::list_available_images() {
|
|
660
|
+
std::vector<std::string> images;
|
|
661
|
+
|
|
662
|
+
FILE *pipe = Utils::Platform::open_process(
|
|
663
|
+
"docker images --format '{{.Repository}}:{{.Tag}}'", "r");
|
|
664
|
+
if (pipe) {
|
|
665
|
+
char buffer[256];
|
|
666
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
667
|
+
std::string image(buffer);
|
|
668
|
+
image.erase(image.find_last_not_of(" \n\r\t") + 1);
|
|
669
|
+
if (!image.empty()) {
|
|
670
|
+
images.push_back(image);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
Utils::Platform::close_process(pipe);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
return images;
|
|
677
|
+
}
|
|
678
|
+
} // namespace Services
|