From 5c3e2b9cf32305928bf19ccd826ca150dd0c95ee Mon Sep 17 00:00:00 2001 From: Harkaran Brar Date: Tue, 31 Mar 2026 14:41:05 -0700 Subject: [PATCH 1/2] fix: improve WSL distro/path portability for auto-detection Use wslpath fallback-aware conversion, support dotted distro names and WSL_DISTRO, and match both \wsl.localhost and \wsl$ label styles to reduce cross-system container detection failures. --- README.md | 10 +++--- plugin/internal.js | 72 +++++++++++++++++++++++++++++------------ scripts/toggle.sh | 64 +++++++++++++++++++++++++----------- test/unit/paths.test.js | 20 ++++++++++++ 4 files changed, 123 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 1da7518..965e5b9 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ toggle.sh [action] [container] ## How it works 1. The plugin detects running devcontainers by checking the `devcontainer.local_folder` Docker label -2. It converts WSL paths to Windows WSL format (`\\wsl.localhost\Ubuntu\...`) +2. It converts WSL paths to Windows format (prefers `wslpath -w`, falls back to `\\wsl.localhost\\...`) 3. When enabled, bash commands are routed through `devcontainer exec --container-id` 4. State is persisted in `~/.config/opencode/container-mode.json` @@ -212,6 +212,7 @@ toggle.sh [action] [container] | Variable | Description | Default | Required | |----------|-------------|---------|----------| | `WSL_DISTRO_NAME` | WSL distribution name | `Ubuntu` | No | +| `WSL_DISTRO` | Alternate WSL distribution variable (fallback) | _unset_ | No | | `HOME` | User home directory | `~` | No | | `NODE_ENV` | Node environment | `production` | No | @@ -283,14 +284,13 @@ Currently, the plugin uses automatic configuration. Future versions will support ```bash echo $WSL_DISTRO_NAME ``` -2. Set distribution name if not Ubuntu: +2. Set distribution name if not Ubuntu (dot/hyphen names are supported, e.g. `Ubuntu-24.04`): ```bash - export WSL_DISTRO_NAME="Debian" + export WSL_DISTRO_NAME="Ubuntu-24.04" ``` 3. Verify path conversion: ```bash - # Should show Windows-style path - echo "\\\\wsl.localhost\\$WSL_DISTRO_NAME$(pwd)" + wslpath -w "$(pwd)" ``` #### 4. Permission errors diff --git a/plugin/internal.js b/plugin/internal.js index 7a7f683..cb9eb25 100644 --- a/plugin/internal.js +++ b/plugin/internal.js @@ -1,6 +1,6 @@ import { readFileSync, existsSync, mkdirSync, renameSync, openSync, writeSync, closeSync, chmodSync, lstatSync, realpathSync, mkdtempSync, rmSync, statSync } from "fs" import { join, dirname, resolve, relative, isAbsolute } from "path" -import { execSync } from "child_process" +import { execSync, execFileSync } from "child_process" import { fileURLToPath } from "url" const __dirname = dirname(fileURLToPath(import.meta.url)) @@ -185,6 +185,16 @@ function validateWindowsPath(path) { return /^[a-zA-Z0-9_\-./\\: ]+$/.test(path) } +function getAutoDetectPathCandidates(winPath) { + const candidates = [winPath] + if (winPath.startsWith('\\\\wsl.localhost\\')) { + candidates.push(winPath.replace('\\\\wsl.localhost\\', '\\\\wsl$\\')) + } else if (winPath.startsWith('\\\\wsl$\\')) { + candidates.push(winPath.replace('\\\\wsl$\\', '\\\\wsl.localhost\\')) + } + return [...new Set(candidates)] +} + // State management with secure file operations function getState() { try { @@ -359,23 +369,41 @@ function getWindowsPath(dir) { logSecurityEvent('invalid_directory_path', { dir }) return null } - - const distro = process.env.WSL_DISTRO_NAME || "Ubuntu" - - // Validate distro name - if (!/^[a-zA-Z0-9_-]+$/.test(distro)) { - logSecurityEvent('invalid_distro_name', { distro }) - return null + + const envDistro = process.env.WSL_DISTRO_NAME || process.env.WSL_DISTRO + + // Prefer explicit distro environment when provided. + let winPath = null + if (!envDistro) { + // Fall back to wslpath for portability across distro naming variants. + try { + winPath = execFileSync('wslpath', ['-w', dir], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'] + }).trim() + } catch { + // Fallback for environments without wslpath + } } - - const winPath = `\\\\wsl.localhost\\${distro}${dir}`.replace(/\//g, "\\") - + + if (!winPath) { + const distro = envDistro || 'Ubuntu' + + // Allow common distro naming including dots (e.g. Ubuntu-24.04) + if (!/^[a-zA-Z0-9_.-]+$/.test(distro)) { + logSecurityEvent('invalid_distro_name', { distro }) + return null + } + + winPath = `\\\\wsl.localhost\\${distro}${dir}`.replace(/\//g, '\\') + } + // Validate the resulting path if (!validateWindowsPath(winPath)) { logSecurityEvent('invalid_windows_path', { winPath }) return null } - + return winPath } @@ -412,15 +440,19 @@ function findContainer(selection, currentDir) { return { error: "Invalid directory path for auto-detection" } } - const autoContainer = runCommand(`docker ps -q --filter "label=devcontainer.local_folder=${winPath}"`) - .split("\n") - .map(id => id.trim()) - .filter(Boolean) - - if (autoContainer.length === 1) { - return { containerId: autoContainer[0] } + const autoContainer = getAutoDetectPathCandidates(winPath) + .flatMap((candidate) => + runCommand(`docker ps -q --filter "label=devcontainer.local_folder=${candidate}"`) + .split("\n") + .map(id => id.trim()) + .filter(Boolean) + ) + const uniqueAutoContainers = [...new Set(autoContainer)] + + if (uniqueAutoContainers.length === 1) { + return { containerId: uniqueAutoContainers[0] } } - if (autoContainer.length > 1) { + if (uniqueAutoContainers.length > 1) { return { error: "Multiple matching containers found for current directory. Use 'list' and select by number." } } return { error: "No devcontainer found for current directory. Use 'list' to see available containers." } diff --git a/scripts/toggle.sh b/scripts/toggle.sh index c984981..10b43f8 100755 --- a/scripts/toggle.sh +++ b/scripts/toggle.sh @@ -148,16 +148,26 @@ get_windows_path() { return 1 fi - local distro="${WSL_DISTRO_NAME:-Ubuntu}" - - # Validate distro name - if [[ ! "$distro" =~ ^[a-zA-Z0-9_-]+$ ]]; then - log_security "invalid_distro_name" "distro=$distro" - return 1 + local win_path="" + + local env_distro="${WSL_DISTRO_NAME:-${WSL_DISTRO:-}}" + + # Prefer explicit distro environment when provided. + if [[ -z "$env_distro" ]] && command -v wslpath &> /dev/null; then + win_path=$(wslpath -w "$dir" 2>/dev/null || true) + fi + + if [[ -z "$win_path" ]]; then + local distro="${env_distro:-Ubuntu}" + + # Allow common distro naming including dots (e.g. Ubuntu-24.04) + if [[ ! "$distro" =~ ^[a-zA-Z0-9_.-]+$ ]]; then + log_security "invalid_distro_name" "distro=$distro" + return 1 + fi + + win_path=$(printf '\\\\wsl.localhost\\%s%s' "$distro" "$dir" | sed 's|/|\\|g') fi - - local win_path - win_path=$(printf '\\\\wsl.localhost\\%s%s' "$distro" "$dir" | sed 's|/|\\|g') # Validate the resulting path if ! validate_windows_path "$win_path"; then @@ -169,6 +179,17 @@ get_windows_path() { return 0 } +get_auto_detect_paths() { + local base_path="$1" + printf '%s\n' "$base_path" + + if [[ "$base_path" == \\\\wsl.localhost\\* ]]; then + printf '%s\n' "${base_path/\\\\wsl.localhost\\/\\\\wsl$\\}" + elif [[ "$base_path" == \\\\wsl$\\* ]]; then + printf '%s\n' "${base_path/\\\\wsl$\\/\\\\wsl.localhost\\}" + fi +} + # Create config dir if not exists with proper permissions mkdir -p "$CONFIG_DIR" chmod 700 "$CONFIG_DIR" @@ -292,16 +313,23 @@ select_container() { return 1 } - local auto_container_lines - # Use proper quoting to prevent injection - if ! auto_container_lines=$(docker ps -q --filter "label=devcontainer.local_folder=$win_path" 2>&1); then - echo "❌ Failed to auto-detect container: $auto_container_lines" - return 1 - fi local auto_containers=() - while IFS= read -r id; do - [ -n "$id" ] && auto_containers+=("$id") - done <<< "$auto_container_lines" + local candidate + while IFS= read -r candidate; do + local auto_container_lines + # Use proper quoting to prevent injection + if ! auto_container_lines=$(docker ps -q --filter "label=devcontainer.local_folder=$candidate" 2>&1); then + echo "❌ Failed to auto-detect container: $auto_container_lines" + return 1 + fi + + local id + while IFS= read -r id; do + if [[ -n "$id" ]] && [[ ! " ${auto_containers[*]} " =~ " ${id} " ]]; then + auto_containers+=("$id") + fi + done <<< "$auto_container_lines" + done < <(get_auto_detect_paths "$win_path") if [ ${#auto_containers[@]} -eq 1 ]; then # Validate the returned container ID diff --git a/test/unit/paths.test.js b/test/unit/paths.test.js index 9c9b3cc..fa29b94 100644 --- a/test/unit/paths.test.js +++ b/test/unit/paths.test.js @@ -34,6 +34,7 @@ describe('Path Operations', () => { describe('getWindowsPath()', () => { // Save original environment const originalEnv = process.env.WSL_DISTRO_NAME; + const originalWslDistro = process.env.WSL_DISTRO; afterEach(() => { // Restore environment @@ -42,6 +43,12 @@ describe('Path Operations', () => { } else { delete process.env.WSL_DISTRO_NAME; } + + if (originalWslDistro) { + process.env.WSL_DISTRO = originalWslDistro; + } else { + delete process.env.WSL_DISTRO; + } }); it('should convert WSL path to Windows format with default distro', () => { @@ -55,6 +62,19 @@ describe('Path Operations', () => { const result = getWindowsPath('/home/user/project'); assert.strictEqual(result, '\\\\wsl.localhost\\Debian\\home\\user\\project'); }); + + it('should accept distro names with dots', () => { + process.env.WSL_DISTRO_NAME = 'Ubuntu-24.04'; + const result = getWindowsPath('/home/user/project'); + assert.strictEqual(result, '\\\\wsl.localhost\\Ubuntu-24.04\\home\\user\\project'); + }); + + it('should use WSL_DISTRO when WSL_DISTRO_NAME is not set', () => { + delete process.env.WSL_DISTRO_NAME; + process.env.WSL_DISTRO = 'Debian'; + const result = getWindowsPath('/home/user/project'); + assert.strictEqual(result, '\\\\wsl.localhost\\Debian\\home\\user\\project'); + }); it('should handle nested paths', () => { delete process.env.WSL_DISTRO_NAME; From 4149c55b11fa0b0cc8e8ea9f651234f85f9860c5 Mon Sep 17 00:00:00 2001 From: Harkaran Brar Date: Mon, 6 Apr 2026 12:02:46 -0700 Subject: [PATCH 2/2] fix: remove affiliated OpenCode branding from docs Replaces security@opencode-ai.com with GitHub Security Advisory links and updates license attribution to isupervillain. Plugin remains compatible with OpenCode but is not officially affiliated. Co-Authored-By: Claude Opus 4.6 --- CONTRIBUTING.md | 4 ++-- README.md | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49a1297..1ecc1ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -156,7 +156,7 @@ Aim for high test coverage, especially for: If you discover a security vulnerability, please report it responsibly: 1. **Do not** open a public GitHub issue -2. Email security concerns to: security@opencode-ai.com +2. Report via [GitHub Security Advisory](https://github.com/isupervillain/opencode-container-exec/security) 3. Allow time for the issue to be addressed before public disclosure ### Security Review @@ -257,7 +257,7 @@ We follow [Semantic Versioning](https://semver.org/): - **Issues**: Use GitHub issues for bugs and feature requests - **Discussions**: Use GitHub Discussions for questions -- **Security**: Email security@opencode-ai.com for vulnerabilities +- **Security**: Report via [GitHub Security Advisory](https://github.com/isupervillain/opencode-container-exec/security) ## License diff --git a/README.md b/README.md index 965e5b9..dfafd4a 100644 --- a/README.md +++ b/README.md @@ -394,11 +394,7 @@ The plugin logs security events in development mode. For production issues: ### Reporting Vulnerabilities -If you discover a security vulnerability, please report it responsibly: - -- **Do not** open a public GitHub issue -- Email: security@opencode-ai.com -- Allow time for the issue to be addressed before public disclosure +If you discover a security vulnerability, please report it responsibly by opening a private [GitHub Security Advisory](https://github.com/isupervillain/opencode-container-exec/security/advisaries/new). ## Contributing @@ -440,11 +436,11 @@ See [CHANGELOG.md](CHANGELOG.md) for version history. ## License -MIT © [OpenCode AI](https://opencode.ai) +MIT © isupervillain ## Support - **Documentation**: [README.md](README.md) - **Issues**: [GitHub Issues](https://github.com/isupervillain/opencode-container-exec/issues) - **Discussions**: [GitHub Discussions](https://github.com/isupervillain/opencode-container-exec/discussions) -- **Security**: security@opencode-ai.com +- **Security**: Report via [GitHub Security Advisory](https://github.com/isupervillain/opencode-container-exec/security)