Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions grove.hml
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,17 @@ fn load_config() {
// Secure mode - enable bubblewrap sandboxing
let secure = getenv("GROVE_SECURE_MODE");
if (secure != null && (secure == "1" || to_lower(secure) == "true")) {
if (CONFIG["bwrap_available"]) {
CONFIG["secure_mode"] = true;
} else {
if (!CONFIG["bwrap_available"]) {
print("WARNING: GROVE_SECURE_MODE requested but bubblewrap not found");
print(" Install bubblewrap: apt install bubblewrap");
print(" Falling back to basic sandbox mode");
} else if (!check_user_namespaces()) {
print("WARNING: GROVE_SECURE_MODE requested but user namespaces not available");
print(" Enable with: sudo sysctl -w kernel.unprivileged_userns_clone=1");
print(" On Ubuntu 24.04+: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0");
print(" Falling back to basic sandbox mode");
} else {
CONFIG["secure_mode"] = true;
}
}

Expand Down Expand Up @@ -492,11 +497,13 @@ fn build_bwrap_command(temp_file, stdin_file, timeout_sec) {

// Construct bwrap command
// Note: Using bash -c to set ulimits inside the sandbox (sh/dash doesn't support -u)
// Note: We don't use --unshare-net because setting up loopback requires CAP_NET_ADMIN
// which unprivileged users don't have. Network restrictions are handled by
// hemlock's --sandbox flag instead.
let bwrap_cmd = `"${CONFIG["bwrap_path"]}" ` +
// Namespace isolation
`--unshare-user ` +
`--unshare-pid ` +
`--unshare-net ` +
`--unshare-ipc ` +
`--unshare-uts ` +
`--unshare-cgroup ` +
Expand Down Expand Up @@ -651,11 +658,11 @@ fn handle_version() {
fn build_bwrap_check_command(temp_file) {
// Build bubblewrap command for type checking (lighter than execute)
// hemlockc only parses, so we can be more restrictive
// Note: We don't use --unshare-net because setting up loopback requires CAP_NET_ADMIN
let check_cmd = `"${CONFIG["hemlockc_path"]}" --check "${temp_file}"`;

let bwrap_cmd = `"${CONFIG["bwrap_path"]}" ` +
`--unshare-user ` +
`--unshare-net ` +
`--unshare-ipc ` +
`--uid 65534 ` +
`--gid 65534 ` +
Expand Down Expand Up @@ -751,11 +758,11 @@ fn handle_check(body) {

fn build_bwrap_format_command(temp_file) {
// Build bubblewrap command for formatting (needs write access to temp file)
// Note: We don't use --unshare-net because setting up loopback requires CAP_NET_ADMIN
let format_cmd = `"${CONFIG["hemlock_path"]}" format "${temp_file}"`;

let bwrap_cmd = `"${CONFIG["bwrap_path"]}" ` +
`--unshare-user ` +
`--unshare-net ` +
`--unshare-ipc ` +
`--uid 65534 ` +
`--gid 65534 ` +
Expand Down