From 3be8b6dc34f00e40f2b085df91abcd18d64a503e Mon Sep 17 00:00:00 2001 From: Brandon Keiji Date: Tue, 13 May 2025 21:13:54 +0000 Subject: [PATCH] fix(sandbox): default to current user profile for debian/ubuntu env (#337) --- packages/cli/src/utils/sandbox.ts | 62 +++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/utils/sandbox.ts b/packages/cli/src/utils/sandbox.ts index b1605184..a70aced1 100644 --- a/packages/cli/src/utils/sandbox.ts +++ b/packages/cli/src/utils/sandbox.ts @@ -8,12 +8,67 @@ import { execSync, spawnSync, spawn } from 'node:child_process'; import os from 'node:os'; import path from 'node:path'; import fs from 'node:fs'; +import { readFile } from 'node:fs/promises'; import { quote } from 'shell-quote'; import { USER_SETTINGS_DIR, SETTINGS_DIRECTORY_NAME, } from '../config/settings.js'; +/** + * Determines whether the sandbox container should be run with the current user's UID and GID. + * This is often necessary on Linux systems (especially Debian/Ubuntu based) when using + * rootful Docker without userns-remap configured, to avoid permission issues with + * mounted volumes. + * + * The behavior is controlled by the `SANDBOX_SET_UID_GID` environment variable: + * - If `SANDBOX_SET_UID_GID` is "1" or "true", this function returns `true`. + * - If `SANDBOX_SET_UID_GID` is "0" or "false", this function returns `false`. + * - If `SANDBOX_SET_UID_GID` is not set: + * - On Debian/Ubuntu Linux, it defaults to `true`. + * - On other OSes, or if OS detection fails, it defaults to `false`. + * + * For more context on running Docker containers as non-root, see: + * https://medium.com/redbubble/running-a-docker-container-as-a-non-root-user-7d2e00f8ee15 + * + * @returns {Promise} A promise that resolves to true if the current user's UID/GID should be used, false otherwise. + */ +async function shouldUseCurrentUserInSandbox(): Promise { + const envVar = process.env.SANDBOX_SET_UID_GID?.toLowerCase().trim(); + + if (envVar === '1' || envVar === 'true') { + return true; + } + if (envVar === '0' || envVar === 'false') { + return false; + } + + // If environment variable is not explicitly set, check for Debian/Ubuntu Linux + if (os.platform() === 'linux') { + try { + const osReleaseContent = await readFile('/etc/os-release', 'utf8'); + if ( + osReleaseContent.includes('ID=debian') || + osReleaseContent.includes('ID=ubuntu') || + osReleaseContent.match(/^ID_LIKE=.*debian.*/m) || // Covers derivatives + osReleaseContent.match(/^ID_LIKE=.*ubuntu.*/m) // Covers derivatives + ) { + console.log( + 'INFO: Defaulting to use current user UID/GID for Debian/Ubuntu-based Linux.', + ); + return true; + } + } catch (_err) { + // Silently ignore if /etc/os-release is not found or unreadable. + // The default (false) will be applied in this case. + console.warn( + 'Warning: Could not read /etc/os-release to auto-detect Debian/Ubuntu for UID/GID default.', + ); + } + } + return false; // Default to false if no other condition is met +} + // node.js equivalent of scripts/sandbox_command.sh export function sandbox_command(sandbox?: string | boolean): string { // note environment variable takes precedence over argument (from command line or settings) @@ -388,10 +443,9 @@ export async function start_sandbox(sandbox: string) { args.push('--authfile', emptyAuthFilePath); } - // specify --user as "$(id -u):$(id -g)" if SANDBOX_SET_UID_GID is 1|true - // only necessary if user mapping is not handled by sandboxing setup on host - // (e.g. rootful docker on linux w/o userns-remap configured) - if (['1', 'true'].includes(process.env.SANDBOX_SET_UID_GID ?? '')) { + // Determine if the current user's UID/GID should be passed to the sandbox. + // See shouldUseCurrentUserInSandbox for more details. + if (await shouldUseCurrentUserInSandbox()) { const uid = execSync('id -u').toString().trim(); const gid = execSync('id -g').toString().trim(); args.push('--user', `${uid}:${gid}`);