feat: complete WorkBuddy parity and Windows release workflow

This commit is contained in:
xiaohan.yuan committed 2026-10-18 12:45:21 +08:00
1 parent 143516b3a8
commit d5512df549
434 files changed
+54977 -1854

No files matched your search

+444
View File
@@ -0,0 +1,444 @@
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <userenv.h>
#include <sddl.h>
#include <aclapi.h>
#include <tlhelp32.h>
#include <algorithm>
#include <cstdio>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
namespace fs = std::filesystem;
struct Handle {
HANDLE value = nullptr;
Handle() = default;
explicit Handle(HANDLE handle) : value(handle) {}
~Handle() { if (value && value != INVALID_HANDLE_VALUE) CloseHandle(value); }
Handle(const Handle&) = delete;
Handle& operator=(const Handle&) = delete;
};
static void require(bool ok, const char* operation) {
if (!ok) throw std::runtime_error(std::string(operation) + ": win32=" + std::to_string(GetLastError()));
}
static void security(DWORD error, const char* operation) {
if (error != ERROR_SUCCESS) throw std::runtime_error(std::string(operation) + ": win32=" + std::to_string(error));
}
static bool inContainer(HANDLE process) {
Handle token;
require(OpenProcessToken(process, TOKEN_QUERY, &token.value) != FALSE, "OpenProcessToken");
DWORD value = 0, bytes = 0;
require(GetTokenInformation(token.value, TokenIsAppContainer, &value, sizeof(value), &bytes) != FALSE, "TokenIsAppContainer");
return value != 0;
}
static bool matchesContainer(HANDLE process, PSID expected) {
Handle token;
require(OpenProcessToken(process, TOKEN_QUERY, &token.value) != FALSE, "Open child token");
DWORD bytes = 0; GetTokenInformation(token.value, TokenAppContainerSid, nullptr, 0, &bytes);
std::vector<BYTE> buffer(bytes);
require(GetTokenInformation(token.value, TokenAppContainerSid, buffer.data(), bytes, &bytes) != FALSE, "Child AppContainer SID");
return EqualSid(reinterpret_cast<TOKEN_APPCONTAINER_INFORMATION*>(buffer.data())->TokenAppContainer, expected) != FALSE;
}
static std::wstring quote(const std::wstring& value) {
std::wstring result = L"\"";
size_t slashes = 0;
for (wchar_t ch : value) {
if (ch == L'\\') { ++slashes; continue; }
if (ch == L'\"') result.append(slashes * 2 + 1, L'\\');
else result.append(slashes, L'\\');
slashes = 0;
result += ch;
}
result.append(slashes * 2, L'\\');
return result + L"\"";
}
static std::string jsonText(const std::wstring& value) {
const int count = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), nullptr, 0, nullptr, nullptr);
std::string bytes(count, 0), escaped;
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), bytes.data(), count, nullptr, nullptr);
for (char ch : bytes) { if (ch == '\\' || ch == '"') escaped += '\\'; escaped += ch; }
return escaped;
}
static void writeJson(const fs::path& target, const std::string& json) {
Handle output(CreateFileW(target.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr));
require(output.value != INVALID_HANDLE_VALUE, "Create control report");
DWORD written = 0;
require(WriteFile(output.value, json.data(), static_cast<DWORD>(json.size()), &written, nullptr) && written == json.size(), "Write control report");
require(FlushFileBuffers(output.value) != FALSE, "Flush control report");
}
static std::wstring windowsDirectory() {
wchar_t buffer[MAX_PATH] = {};
require(GetWindowsDirectoryW(buffer, MAX_PATH) != 0, "GetWindowsDirectory");
return buffer;
}
static void nonReparse(const fs::path& target) {
auto current = target;
for (;;) {
const DWORD attributes = GetFileAttributesW(current.c_str());
require(attributes != INVALID_FILE_ATTRIBUTES, "GetFileAttributes");
if (attributes & FILE_ATTRIBUTE_REPARSE_POINT) throw std::runtime_error("Reparse point in sandbox control path");
const auto parent = current.parent_path();
if (parent == current || parent.empty()) break;
current = parent;
}
}
static void privateDirectory(const fs::path& path) {
Handle token;
require(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token.value) != FALSE, "OpenProcessToken");
DWORD bytes = 0;
GetTokenInformation(token.value, TokenUser, nullptr, 0, &bytes);
std::vector<BYTE> user(bytes);
require(GetTokenInformation(token.value, TokenUser, user.data(), bytes, &bytes) != FALSE, "TokenUser");
LPWSTR sid = nullptr;
require(ConvertSidToStringSidW(reinterpret_cast<TOKEN_USER*>(user.data())->User.Sid, &sid) != FALSE, "ConvertSidToStringSid");
const std::wstring sddl = L"D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;" + std::wstring(sid) + L")";
LocalFree(sid);
PSECURITY_DESCRIPTOR descriptor = nullptr;
require(ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), SDDL_REVISION_1, &descriptor, nullptr) != FALSE, "Private DACL");
PACL acl = nullptr; BOOL present = FALSE, defaulted = FALSE;
require(GetSecurityDescriptorDacl(descriptor, &present, &acl, &defaulted) != FALSE, "GetSecurityDescriptorDacl");
const DWORD error = SetNamedSecurityInfoW(const_cast<LPWSTR>(path.c_str()), SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, nullptr, nullptr, acl, nullptr);
LocalFree(descriptor);
security(error, "Set private control DACL");
}
static void grantStage(const fs::path& target, PSID sid, bool writable = true) {
if (!writable) privateDirectory(target);
PSECURITY_DESCRIPTOR old = nullptr;
PACL acl = nullptr, next = nullptr;
security(GetNamedSecurityInfoW(target.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &acl, nullptr, &old), "Read stage DACL");
EXPLICIT_ACCESSW access = {};
access.grfAccessPermissions = FILE_GENERIC_READ | FILE_GENERIC_EXECUTE;
if (writable) access.grfAccessPermissions |= FILE_GENERIC_WRITE | DELETE;
access.grfAccessMode = GRANT_ACCESS;
access.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
access.Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
access.Trustee.ptstrName = static_cast<LPWSTR>(sid);
const DWORD combined = SetEntriesInAclW(1, &access, acl, &next);
if (combined != ERROR_SUCCESS) { LocalFree(old); security(combined, "Build stage DACL"); }
const DWORD applied = SetNamedSecurityInfoW(const_cast<LPWSTR>(target.c_str()), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, next, nullptr);
LocalFree(next); LocalFree(old);
security(applied, "Grant stage DACL");
PSECURITY_DESCRIPTOR label = nullptr;
require(ConvertStringSecurityDescriptorToSecurityDescriptorW(L"S:(ML;OICI;NW;;;LW)", SDDL_REVISION_1, &label, nullptr) != FALSE, "Stage integrity label");
PACL sacl = nullptr; BOOL present = FALSE, defaulted = FALSE;
require(GetSecurityDescriptorSacl(label, &present, &sacl, &defaulted) != FALSE, "GetSecurityDescriptorSacl");
const DWORD labeled = SetNamedSecurityInfoW(const_cast<LPWSTR>(target.c_str()), SE_FILE_OBJECT, LABEL_SECURITY_INFORMATION, nullptr, nullptr, nullptr, sacl);
LocalFree(label);
security(labeled, "Set stage integrity label");
}
struct Profile {
std::wstring name;
PSID sid = nullptr;
explicit Profile(std::wstring value) : name(std::move(value)) {
if (name.size() != 42 || name.substr(0, 10) != L"YCode.Run." ||
name.substr(10).find_first_not_of(L"0123456789abcdef") != std::wstring::npos) throw std::runtime_error("Invalid profile identity");
const HRESULT hr = CreateAppContainerProfile(name.c_str(), L"YCode isolated command", L"YCode per-command workspace snapshot", nullptr, 0, &sid);
if (FAILED(hr)) throw std::runtime_error("CreateAppContainerProfile: hresult=" + std::to_string(static_cast<unsigned long>(hr)));
}
bool remove() {
if (!sid) return true;
FreeSid(sid); sid = nullptr;
return SUCCEEDED(DeleteAppContainerProfile(name.c_str()));
}
~Profile() { remove(); }
};
static bool validProfile(const std::wstring& name) {
return name.size() == 42 && name.substr(0, 10) == L"YCode.Run." && name.substr(10).find_first_not_of(L"0123456789abcdef") == std::wstring::npos;
}
static HANDLE parentProcess() {
Handle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
require(snapshot.value != INVALID_HANDLE_VALUE, "CreateToolhelp32Snapshot");
PROCESSENTRY32W entry = {}; entry.dwSize = sizeof(entry);
require(Process32FirstW(snapshot.value, &entry) != FALSE, "Process32First");
do {
if (entry.th32ProcessID == GetCurrentProcessId()) {
HANDLE parent = OpenProcess(SYNCHRONIZE, FALSE, entry.th32ParentProcessID);
require(parent != nullptr, "Open parent monitor");
return parent;
}
} while (Process32NextW(snapshot.value, &entry));
throw std::runtime_error("Parent process identity unavailable");
}
struct Drive {
std::wstring name, target;
explicit Drive(const fs::path& stage) : target(L"\\??\\" + stage.wstring()) {
Handle mutex(CreateMutexW(nullptr, FALSE, L"Local\\YCode.Sandbox.DriveAllocator.v1"));
require(mutex.value != nullptr, "Create drive allocator mutex");
const DWORD lock = WaitForSingleObject(mutex.value, 5000);
if (lock != WAIT_OBJECT_0 && lock != WAIT_ABANDONED) throw std::runtime_error("Drive allocator busy");
for (wchar_t letter = L'Z'; letter >= L'D'; --letter) {
const std::wstring candidate = std::wstring(1, letter) + L":";
wchar_t existing[32768] = {};
if (QueryDosDeviceW(candidate.c_str(), existing, 32768)) continue;
if (DefineDosDeviceW(DDD_RAW_TARGET_PATH | DDD_NO_BROADCAST_SYSTEM, candidate.c_str(), target.c_str())) { name = candidate; break; }
}
ReleaseMutex(mutex.value);
if (name.empty()) throw std::runtime_error("No free drive for sandbox workspace");
}
bool release() {
if (name.empty()) return true;
const bool removed = DefineDosDeviceW(DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE | DDD_RAW_TARGET_PATH | DDD_NO_BROADCAST_SYSTEM, name.c_str(), target.c_str()) != FALSE;
if (removed) name.clear();
return removed;
}
~Drive() { release(); }
fs::path root() const { return fs::path(name + L"\\"); }
};
static std::vector<wchar_t> childEnvironment(const fs::path& stage) {
const auto win = windowsDirectory();
std::vector<std::wstring> entries = {
L"=" + stage.root_name().wstring() + L"=" + (stage / L"workspace").wstring(),
L"APPDATA=" + (stage / L"home/AppData/Roaming").wstring(), L"COMSPEC=" + win + L"\\System32\\cmd.exe",
L"HOME=" + (stage / L"home").wstring(), L"LOCALAPPDATA=" + (stage / L"home/AppData/Local").wstring(),
L"PATH=" + (stage / L"tools/node").wstring() + L";" + (stage / L"tools/python").wstring() + L";" + (stage / L"tools/git").wstring() + L";" + (stage / L"tools").wstring() + L";" + win + L"\\System32;" + win + L"\\System32\\WindowsPowerShell\\v1.0",
L"NODE_REPL_HISTORY=" + (stage / L"home/node_repl_history").wstring(),
L"NPM_CONFIG_CACHE=" + (stage / L"home/npm-cache").wstring(), L"NPM_CONFIG_OFFLINE=true",
L"NPM_CONFIG_USERCONFIG=" + (stage / L"home/npmrc").wstring(),
L"PYTHONHOME=" + (stage / L"tools/python").wstring(), L"PYTHONNOUSERSITE=1", L"PYTHONDONTWRITEBYTECODE=1",
L"PIP_CONFIG_FILE=NUL", L"PIP_NO_INPUT=1", L"PIP_NO_INDEX=1", L"PIP_DISABLE_PIP_VERSION_CHECK=1",
L"PIP_CACHE_DIR=" + (stage / L"home/pip-cache").wstring(),
L"PYTHONPATH=" + (stage / L"tools/python-adapter").wstring() + L";" + (stage / L"home/python-site").wstring(),
L"GIT_CONFIG_NOSYSTEM=1", L"GIT_CONFIG_GLOBAL=NUL", L"GIT_CONFIG_SYSTEM=NUL", L"GIT_TERMINAL_PROMPT=0",
L"GIT_OPTIONAL_LOCKS=0", L"GIT_PAGER=", L"GIT_EDITOR=" + (stage / L"tools/ycode-git-editor.exe").generic_wstring(), L"GIT_SEQUENCE_EDITOR=" + (stage / L"tools/ycode-git-editor.exe").generic_wstring(), L"GIT_ALLOW_PROTOCOL=",
L"GIT_EXEC_PATH=" + (stage / L"tools/git/empty-hooks").wstring(), L"GIT_TEMPLATE_DIR=" + (stage / L"tools/git/empty-template").wstring(),
L"GIT_CONFIG_COUNT=7", L"GIT_CONFIG_KEY_0=core.hooksPath", L"GIT_CONFIG_VALUE_0=" + (stage / L"tools/git/empty-hooks").wstring(),
L"GIT_CONFIG_KEY_1=core.fsmonitor", L"GIT_CONFIG_VALUE_1=false", L"GIT_CONFIG_KEY_2=credential.helper", L"GIT_CONFIG_VALUE_2=",
L"GIT_CONFIG_KEY_3=commit.gpgSign", L"GIT_CONFIG_VALUE_3=false", L"GIT_CONFIG_KEY_4=tag.gpgSign", L"GIT_CONFIG_VALUE_4=false",
L"GIT_CONFIG_KEY_5=maintenance.auto", L"GIT_CONFIG_VALUE_5=false",
L"GIT_CONFIG_KEY_6=diff.autoRefreshIndex", L"GIT_CONFIG_VALUE_6=false",
L"PATHEXT=.COM;.EXE;.BAT;.CMD", L"SystemRoot=" + win, L"TEMP=" + (stage / L"tmp").wstring(),
L"TMP=" + (stage / L"tmp").wstring(), L"USERPROFILE=" + (stage / L"home").wstring(),
L"WINDIR=" + win, L"YCODE_SANDBOX=appcontainer", L"YCODE_SANDBOX_BRIDGE=" + (stage / L"tools/ycode-bridge.cjs").wstring(),
L"YCODE_SANDBOX_PROCESS=" + (stage / L"tools/ycode-process.cjs").wstring(), L"YCODE_SANDBOX_ROOT=" + stage.wstring()
};
if (fs::is_directory(stage / L"repository")) {
entries.push_back(L"GIT_DIR=" + (stage / L"repository").wstring());
entries.push_back(L"GIT_WORK_TREE=" + (stage / L"workspace").wstring());
}
std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { return _wcsicmp(a.c_str(), b.c_str()) < 0; });
std::vector<wchar_t> block;
for (const auto& entry : entries) { block.insert(block.end(), entry.begin(), entry.end()); block.push_back(0); }
block.push_back(0);
return block;
}
static void forward(HANDLE input, HANDLE output) {
char buffer[16384]; DWORD read = 0;
while (ReadFile(input, buffer, sizeof(buffer), &read, nullptr) && read) {
DWORD offset = 0;
while (offset < read) {
DWORD written = 0;
if (!WriteFile(output, buffer + offset, read - offset, &written, nullptr) || !written) return;
offset += written;
}
}
}
static bool brokerInput(const std::string& line, std::string& decoded) {
if (line.size() > 65536 || line.rfind("input:", 0) != 0) return false;
const std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const auto encoded = line.substr(6);
if (encoded.empty() || encoded.size() % 4) return false;
unsigned int accumulator = 0; int bits = 0; bool padding = false; size_t paddingCount = 0;
for (size_t index = 0; index < encoded.size(); ++index) {
const char ch = encoded[index];
if (ch == '=') { padding = true; if (++paddingCount > 2 || index < encoded.size() - 2) return false; continue; }
const auto value = alphabet.find(ch);
if (padding || value == std::string::npos) return false;
accumulator = (accumulator << 6) | static_cast<unsigned int>(value); bits += 6;
if (bits >= 8) { bits -= 8; decoded += static_cast<char>((accumulator >> bits) & 255); }
}
return decoded.size() <= 32768 && bits == static_cast<int>(paddingCount) * 2 && (bits == 0 || (accumulator & ((1U << bits) - 1)) == 0);
}
struct AttributeList {
std::vector<BYTE> bytes;
LPPROC_THREAD_ATTRIBUTE_LIST list = nullptr;
AttributeList() {
SIZE_T size = 0; InitializeProcThreadAttributeList(nullptr, 2, 0, &size);
bytes.resize(size); list = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(bytes.data());
require(InitializeProcThreadAttributeList(list, 2, 0, &size) != FALSE, "InitializeProcThreadAttributeList");
}
~AttributeList() { if (list) DeleteProcThreadAttributeList(list); }
};
namespace git_control { static std::vector<std::wstring> fields(const fs::path& path); }
static int run(const std::wstring& name, const fs::path& stage, const fs::path& report, DWORD timeout, bool broker = false) {
if (inContainer(GetCurrentProcess())) throw std::runtime_error("Runner cannot broker from inside an AppContainer");
if (!stage.is_absolute() || report.parent_path() != stage.parent_path() || report.filename() != L"result.json" ||
stage.filename() != L"stage" || fs::exists(report)) throw std::runtime_error("Invalid sandbox control layout");
nonReparse(stage);
privateDirectory(stage.parent_path());
Profile profile(name);
// Windows derives the AppContainer TEMP path from LOCALAPPDATA even when TEMP
// was supplied explicitly. Provision that private path before applying ACLs.
fs::create_directories(stage / L"home/AppData/Local/Packages" / name / L"AC/Temp");
grantStage(stage, profile.sid);
for (const auto& entry : fs::recursive_directory_iterator(stage)) {
if (GetFileAttributesW(entry.path().c_str()) & FILE_ATTRIBUTE_REPARSE_POINT) throw std::runtime_error("Reparse point in stage");
const auto relative = entry.path().lexically_relative(stage).generic_wstring();
const bool tool = relative == L"tools" || relative.rfind(L"tools/", 0) == 0 ||
relative == L"workspace/node_modules" || relative.rfind(L"workspace/node_modules/", 0) == 0 ||
relative == L"home/python-site" || relative.rfind(L"home/python-site/", 0) == 0;
grantStage(entry.path(), profile.sid, !tool);
}
Drive drive(stage);
writeJson(stage.parent_path() / L"lease.json", "{\"version\":1,\"pid\":" + std::to_string(GetCurrentProcessId()) +
",\"profile\":\"" + jsonText(name) + "\",\"drive\":\"" + jsonText(drive.name) + "\",\"stage\":\"" + jsonText(stage.wstring()) + "\"}\n");
Handle parent(parentProcess());
Handle job(CreateJobObjectW(nullptr, nullptr)); require(job.value != nullptr, "CreateJobObject");
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = {};
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_ACTIVE_PROCESS | JOB_OBJECT_LIMIT_JOB_MEMORY;
limits.BasicLimitInformation.ActiveProcessLimit = 32;
limits.JobMemoryLimit = static_cast<SIZE_T>(1024) * 1024 * 1024;
require(SetInformationJobObject(job.value, JobObjectExtendedLimitInformation, &limits, sizeof(limits)) != FALSE, "Job limits");
JOBOBJECT_BASIC_UI_RESTRICTIONS ui = {}; ui.UIRestrictionsClass = JOB_OBJECT_UILIMIT_ALL;
require(SetInformationJobObject(job.value, JobObjectBasicUIRestrictions, &ui, sizeof(ui)) != FALSE, "Job UI limits");
SECURITY_ATTRIBUTES inherited = {sizeof(inherited), nullptr, TRUE};
Handle outRead, outWrite, errRead, errWrite;
require(CreatePipe(&outRead.value, &outWrite.value, &inherited, 0) != FALSE, "stdout pipe");
require(CreatePipe(&errRead.value, &errWrite.value, &inherited, 0) != FALSE, "stderr pipe");
require(SetHandleInformation(outRead.value, HANDLE_FLAG_INHERIT, 0) != FALSE, "stdout inheritance");
require(SetHandleInformation(errRead.value, HANDLE_FLAG_INHERIT, 0) != FALSE, "stderr inheritance");
Handle input, inputWrite;
if (broker) {
require(CreatePipe(&input.value, &inputWrite.value, &inherited, 65536) != FALSE, "broker child input");
require(SetHandleInformation(inputWrite.value, HANDLE_FLAG_INHERIT, 0) != FALSE, "broker input writer inheritance");
} else input.value = CreateFileW(L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &inherited, OPEN_EXISTING, 0, nullptr);
require(input.value != INVALID_HANDLE_VALUE, "NUL input");
AttributeList attributes;
SECURITY_CAPABILITIES caps = {}; caps.AppContainerSid = profile.sid;
require(UpdateProcThreadAttribute(attributes.list, 0, PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, &caps, sizeof(caps), nullptr, nullptr) != FALSE, "AppContainer capability attribute");
HANDLE handles[] = {input.value, outWrite.value, errWrite.value};
require(UpdateProcThreadAttribute(attributes.list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, handles, sizeof(handles), nullptr, nullptr) != FALSE, "Handle allowlist attribute");
STARTUPINFOEXW startup = {}; startup.StartupInfo.cb = sizeof(startup);
startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
startup.StartupInfo.hStdInput = input.value; startup.StartupInfo.hStdOutput = outWrite.value; startup.StartupInfo.hStdError = errWrite.value;
startup.lpAttributeList = attributes.list;
auto executable = windowsDirectory() + L"\\System32\\cmd.exe";
std::wstring command = quote(executable) + L" /d /s /c \"\"" + (drive.root() / L"command.cmd").wstring() + L"\"\"";
if (fs::exists(stage / L"git-arguments")) {
executable = (drive.root() / L"tools/git/git.exe").wstring(); command = quote(executable);
for (const auto& arg : git_control::fields(stage / L"git-arguments")) command += L" " + quote(arg);
if (command.size() > 32000) throw std::runtime_error("Isolated Git argv exceeds Windows limit");
}
auto environment = childEnvironment(drive.root());
PROCESS_INFORMATION process = {};
require(CreateProcessW(executable.c_str(), command.data(), nullptr, nullptr, TRUE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED | CREATE_NO_WINDOW,
environment.data(), (drive.root() / L"workspace").c_str(), &startup.StartupInfo, &process) != FALSE, "Create AppContainer process");
Handle child(process.hProcess), thread(process.hThread);
if (!AssignProcessToJobObject(job.value, child.value) || !inContainer(child.value) || !matchesContainer(child.value, profile.sid)) {
TerminateProcess(child.value, 125); throw std::runtime_error("Child isolation verification failed");
}
CloseHandle(outWrite.value); outWrite.value = nullptr;
CloseHandle(errWrite.value); errWrite.value = nullptr;
const ULONGLONG started = GetTickCount64();
require(ResumeThread(thread.value) != static_cast<DWORD>(-1), "Resume isolated process");
std::thread stdoutThread(forward, outRead.value, GetStdHandle(STD_OUTPUT_HANDLE));
std::thread stderrThread(forward, errRead.value, GetStdHandle(STD_ERROR_HANDLE));
Handle cancelled(CreateEventW(nullptr, TRUE, FALSE, nullptr));
std::thread controlThread([&cancelled, &inputWrite, broker]() {
char buffer[4096] = {}; DWORD read = 0; std::string pending;
while (ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &read, nullptr) && read) {
pending.append(buffer, read);
if (pending.size() > 65536) { SetEvent(cancelled.value); return; }
size_t newline = 0;
while ((newline = pending.find('\n')) != std::string::npos) {
const auto line = pending.substr(0, newline); pending.erase(0, newline + 1);
if (line == "cancel") { SetEvent(cancelled.value); return; }
std::string decoded;
if (!broker || !brokerInput(line, decoded)) { SetEvent(cancelled.value); return; }
DWORD written = 0;
if (!WriteFile(inputWrite.value, decoded.data(), static_cast<DWORD>(decoded.size()), &written, nullptr) || written != decoded.size()) { SetEvent(cancelled.value); return; }
}
}
});
HANDLE monitored[] = {child.value, parent.value, cancelled.value};
const DWORD waited = WaitForMultipleObjects(3, monitored, FALSE, timeout);
DWORD exit = 125;
const char* status = "error";
if (waited == WAIT_OBJECT_0) { GetExitCodeProcess(child.value, &exit); status = "completed"; }
else if (waited == WAIT_TIMEOUT) { exit = 124; status = "timeout"; }
else if (waited == WAIT_OBJECT_0 + 1) { exit = 130; status = "parent-exited"; }
else if (waited == WAIT_OBJECT_0 + 2) { exit = 130; status = "cancelled"; }
// No breakaway flags: close/terminate the job also stops still-running descendants.
const bool terminated = TerminateJobObject(job.value, exit) != FALSE;
WaitForSingleObject(child.value, 5000);
bool quiescent = false;
for (int attempt = 0; terminated && attempt < 500; ++attempt) {
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION accounting = {};
if (!QueryInformationJobObject(job.value, JobObjectBasicAccountingInformation, &accounting, sizeof(accounting), nullptr)) break;
if (accounting.ActiveProcesses == 0) { quiescent = true; break; }
Sleep(10);
}
if (!quiescent) { status = "error"; exit = 125; }
CancelSynchronousIo(controlThread.native_handle());
controlThread.join();
stdoutThread.join(); stderrThread.join();
const bool cleaned = profile.remove();
const bool unmapped = drive.release();
const auto elapsed = GetTickCount64() - started;
const std::string json = "{\"protocol\":1,\"backend\":\"windows-appcontainer\",\"isolated\":true,\"network\":\"deny\",\"status\":\"" +
std::string(status) + "\",\"exitCode\":" + std::to_string(exit) + ",\"durationMs\":" + std::to_string(elapsed) +
",\"profileRemoved\":" + (cleaned ? "true" : "false") + ",\"driveRemoved\":" + (unmapped ? "true" : "false") + ",\"broker\":" + (broker ? "\"bound-stdio-v1\"" : "null") + "}\n";
writeJson(report, json);
return 0;
}
#include "filesystem-read.h"
#include "filesystem-write.h"
#include "workspace-state.h"
#include "git-control.h"
int wmain(int argc, wchar_t** argv) {
try {
if (_wcsicmp(fs::path(argv[0]).filename().c_str(), L"ycode-git-editor.exe") == 0) return 0;
if (_wcsicmp(fs::path(argv[0]).filename().c_str(), L"git-ssh.exe") == 0) return git_control::ssh(argc, argv);
if (argc == 2 && (std::wstring(argv[1]) == L"--git-protect" || std::wstring(argv[1]) == L"--git-unprotect")) return git_control::credential(std::wstring(argv[1]) == L"--git-protect");
if (argc == 4 && std::wstring(argv[1]) == L"--git-control") {
size_t end = 0; const auto timeout = std::stoul(argv[3], &end);
if (end != wcslen(argv[3]) || timeout > 600000) throw std::runtime_error("Invalid Git control timeout");
return git_control::run(argv[2], static_cast<DWORD>(timeout));
}
if (argc == 3 && std::wstring(argv[1]) == L"--seal-workspace") return workspace_state::run(argv[2]);
if (argc == 4 && (std::wstring(argv[1]) == L"--workspace-write" || std::wstring(argv[1]) == L"--git-metadata-write")) {
const std::wstring expected(argv[3]);
if (expected.find_first_not_of(L"0123456789abcdef:-") != std::wstring::npos) throw std::runtime_error("Invalid workspace identity");
std::string expectedBytes;
for (wchar_t ch : expected) expectedBytes += static_cast<char>(ch);
return workspace_write::run(argv[2], expectedBytes, std::wstring(argv[1]) == L"--git-metadata-write");
}
if (argc == 7 && std::wstring(argv[1]) == L"--workspace-read") {
const std::wstring expected(argv[5]);
if (expected.find_first_not_of(L"0123456789abcdef:-") != std::wstring::npos) throw std::runtime_error("Invalid workspace identity");
size_t end = 0; const auto limit = std::stoul(argv[6], &end);
if (end != wcslen(argv[6]) || limit > 8 * 1024 * 1024) throw std::runtime_error("Invalid workspace limit");
std::string expectedBytes;
for (wchar_t ch : expected) expectedBytes += static_cast<char>(ch);
return workspace_read::run(argv[2], argv[3], argv[4], expectedBytes, static_cast<DWORD>(limit));
}
if (argc == 2 && std::wstring(argv[1]) == L"--version") {
std::puts("{\"protocol\":1,\"backend\":\"windows-appcontainer\",\"version\":\"0.7.0\",\"network\":\"deny\"}"); return 0;
}
if (argc == 3 && std::wstring(argv[1]) == L"--cleanup") {
if (inContainer(GetCurrentProcess()) || !validProfile(argv[2])) throw std::runtime_error("Invalid profile cleanup request");
const HRESULT cleaned = DeleteAppContainerProfile(argv[2]);
return SUCCEEDED(cleaned) || cleaned == HRESULT_FROM_WIN32(ERROR_NOT_FOUND) || cleaned == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || cleaned == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) ? 0 : 125;
}
if (argc == 4 && std::wstring(argv[1]) == L"--release-drive") {
const std::wstring drive = argv[2];
const fs::path stage(argv[3]);
if (inContainer(GetCurrentProcess()) || drive.size() != 2 || drive[0] < L'D' || drive[0] > L'Z' || drive[1] != L':' || !stage.is_absolute() || stage.filename() != L"stage") throw std::runtime_error("Invalid drive cleanup request");
const auto target = L"\\??\\" + stage.wstring();
const BOOL removed = DefineDosDeviceW(DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE | DDD_RAW_TARGET_PATH | DDD_NO_BROADCAST_SYSTEM, drive.c_str(), target.c_str());
return removed || GetLastError() == ERROR_FILE_NOT_FOUND ? 0 : 125;
}
const std::wstring mode = argc > 1 ? argv[1] : L"";
if (argc != 6 || (mode != L"--run" && mode != L"--run-stdio")) throw std::runtime_error("Usage: --run or --run-stdio profile stage result.json timeout-ms");
size_t end = 0; const auto timeout = std::stoul(argv[5], &end);
if (end != wcslen(argv[5]) || timeout < 100 || timeout > 600000) throw std::runtime_error("Invalid timeout");
return run(argv[2], fs::path(argv[3]), fs::path(argv[4]), static_cast<DWORD>(timeout), mode == L"--run-stdio");
} catch (const std::exception& error) { std::fprintf(stderr, "YCODE_SANDBOX_ERROR %s\n", error.what()); return 125; }
}