11import * as path from "path" ;
22import { type Runtime } from "@/node/runtime/Runtime" ;
3- import { getScriptPath , getScriptsDir } from "@/utils/scripts/discovery" ;
3+ import {
4+ getScriptPath ,
5+ getScriptsDir ,
6+ getLegacyScriptPath ,
7+ getLegacyScriptsDir ,
8+ } from "@/utils/scripts/discovery" ;
49import { createBashTool } from "@/node/services/tools/bash" ;
510import { writeFileString , readFileString , execBuffered } from "@/node/utils/runtime/helpers" ;
611import { Ok , Err , type Result } from "@/common/types/result" ;
@@ -60,43 +65,34 @@ export async function runWorkspaceScript(
6065 }
6166
6267 // Resolve real paths to handle symlinks and prevent escape
63- const scriptPath = getScriptPath ( workspacePath , scriptName ) ;
64- const scriptsDir = getScriptsDir ( workspacePath ) ;
68+ const canonicalScriptPath = getScriptPath ( workspacePath , scriptName ) ;
69+ const canonicalScriptsDir = getScriptsDir ( workspacePath ) ;
70+
71+ const legacyScriptPath = getLegacyScriptPath ( workspacePath , scriptName ) ;
72+ const legacyScriptsDir = getLegacyScriptsDir ( workspacePath ) ;
6573
6674 let resolvedScriptPath : string ;
6775 let resolvedScriptsDir : string ;
6876
6977 try {
70- // Use runtime.resolvePath (which should behave like realpath) if available,
71- // otherwise rely on the runtime-specific normalization.
72- // Ideally, we want `realpath` behavior here.
73- // Since the Runtime interface doesn't strictly expose `realpath`, we'll rely on
74- // the filesystem (via runtime.exec or similar) or assume normalizePath+standard checks are mostly sufficient.
75- // HOWEVER, for local runtime we can use fs.realpath. For SSH, we might need a command.
76- // To keep it simple and robust within the existing abstractions:
77- // We will use the runtime to resolve the path if possible, but `runtime.resolvePath`
78- // is documented to expand tildes, not necessarily resolve symlinks (though it often does).
79-
80- // BUT, to address the specific review concern about symlinks:
81- // We should try to get the canonical path.
82- // Note: checking containment purely by string path on un-resolved paths is weak against symlinks.
83-
84- // Strategy:
85- // 1. Get the script path (constructed from workspace + script name).
86- // 2. Get the scripts dir.
87- // 3. Ask runtime to resolve them to absolute, canonical paths (resolving symlinks).
88- // (If runtime doesn't support explicit symlink resolution in its API, we might be limited).
89- // The review implies we *should* do this.
90- // Let's add a helper or use `runtime.resolvePath` which claims to resolve to "absolute, canonical form".
91-
92- resolvedScriptPath = await runtime . resolvePath ( scriptPath ) ;
93- resolvedScriptsDir = await runtime . resolvePath ( scriptsDir ) ;
78+ // Try canonical path first
79+ const candidatePath = await runtime . resolvePath ( canonicalScriptPath ) ;
80+ await runtime . stat ( candidatePath ) ; // Throws if not exists
81+ resolvedScriptPath = candidatePath ;
82+ resolvedScriptsDir = await runtime . resolvePath ( canonicalScriptsDir ) ;
9483 } catch {
95- // If we can't resolve paths (e.g. file doesn't exist), we can't verify containment securely.
96- // But we already established the script *must* exist in step 2 (which we moved up or will do).
97- // Actually step 2 is below. Let's do existence check + resolution together or accept that
98- // resolution failure implies non-existence.
99- return Err ( `Script not found or inaccessible: ${ scriptName } ` ) ;
84+ try {
85+ // Try legacy path fallback
86+ const candidateLegacyPath = await runtime . resolvePath ( legacyScriptPath ) ;
87+ await runtime . stat ( candidateLegacyPath ) ; // Throws if not exists
88+ resolvedScriptPath = candidateLegacyPath ;
89+ resolvedScriptsDir = await runtime . resolvePath ( legacyScriptsDir ) ;
90+ } catch {
91+ // Both missing. Default to canonical so the error message later (in step 2)
92+ // correctly reports the canonical path as missing.
93+ resolvedScriptPath = await runtime . resolvePath ( canonicalScriptPath ) ;
94+ resolvedScriptsDir = await runtime . resolvePath ( canonicalScriptsDir ) ;
95+ }
10096 }
10197
10298 // Use runtime-aware normalization on the RESOLVED paths
@@ -115,11 +111,11 @@ export async function runWorkspaceScript(
115111 try {
116112 const stat = await runtime . stat ( resolvedScriptPath ) ;
117113 if ( stat . isDirectory ) {
118- return Err ( `Script not found: .cmux/scripts/ ${ scriptName } ` ) ;
114+ return Err ( `Script is a directory: ${ scriptName } ` ) ;
119115 }
120116 } catch {
121117 return Err (
122- `Script not found: .cmux /scripts/${ scriptName } . Create the script in your workspace and make it executable (chmod +x).`
118+ `Script not found: .mux /scripts/${ scriptName } . Create the script in your workspace and make it executable (chmod +x).`
123119 ) ;
124120 }
125121
0 commit comments