@@ -85,6 +85,60 @@ function sanitizeTarballPath(filePath: string): string {
8585 return segments . join ( path . sep )
8686}
8787
88+ /**
89+ * Remove a file or directory with safety protections.
90+ * Minimal inline version of @socketsecurity/registry/lib/fs remove().
91+ * Prevents catastrophic deletes by checking paths are within safe boundaries.
92+ * @throws {Error } When attempting to delete protected paths.
93+ */
94+ async function remove (
95+ filepath : string ,
96+ options ?: { force ?: boolean } ,
97+ ) : Promise < void > {
98+ const absolutePath = path . resolve ( filepath )
99+ const cwd = process . cwd ( )
100+
101+ // Safety check: prevent deleting cwd or parent directories unless forced.
102+ if ( ! options ?. force ) {
103+ // Check if trying to delete cwd itself.
104+ if ( absolutePath === cwd ) {
105+ throw new Error ( 'Cannot delete the current working directory' )
106+ }
107+
108+ // Check if trying to delete outside SOCKET_HOME (catastrophic delete protection).
109+ const relation = path . relative ( SOCKET_HOME , absolutePath )
110+ const isInside = Boolean (
111+ relation &&
112+ relation !== '..' &&
113+ ! relation . startsWith ( `..${ path . sep } ` ) &&
114+ ! path . isAbsolute ( relation ) ,
115+ )
116+
117+ if ( ! isInside ) {
118+ throw new Error (
119+ `Cannot delete files/directories outside SOCKET_HOME (${ SOCKET_HOME } ). ` +
120+ `Attempted to delete: ${ absolutePath } ` ,
121+ )
122+ }
123+ }
124+
125+ // Perform deletion.
126+ try {
127+ const stats = await fs . stat ( absolutePath )
128+ if ( stats . isDirectory ( ) ) {
129+ await fs . rm ( absolutePath , { recursive : true , force : true } )
130+ } else {
131+ await fs . unlink ( absolutePath )
132+ }
133+ } catch ( error ) {
134+ const code = ( error as NodeJS . ErrnoException ) ?. code
135+ // Silently ignore if file doesn't exist.
136+ if ( code !== 'ENOENT' ) {
137+ throw error
138+ }
139+ }
140+ }
141+
88142// ============================================================================
89143// Installation lock management
90144// ============================================================================
@@ -119,9 +173,7 @@ async function acquireLock(): Promise<string> {
119173 // Process exists, wait and retry.
120174 } catch {
121175 // Process doesn't exist, remove stale lock.
122- await fs . unlink ( lockPath ) . catch ( ( ) => {
123- // Ignore, may have been removed by another process.
124- } )
176+ await remove ( lockPath )
125177 continue
126178 }
127179 }
@@ -154,15 +206,12 @@ async function acquireLock(): Promise<string> {
154206 */
155207async function releaseLock ( lockPath : string ) : Promise < void > {
156208 try {
157- await fs . unlink ( lockPath )
209+ await remove ( lockPath )
158210 debugLog ( `Released installation lock: ${ lockPath } ` )
159211 } catch ( error ) {
160- const code = ( error as NodeJS . ErrnoException ) ?. code
161- if ( code !== 'ENOENT' ) {
162- console . error (
163- `Warning: Failed to release lock ${ lockPath } : ${ formatError ( error ) } ` ,
164- )
165- }
212+ console . error (
213+ `Warning: Failed to release lock ${ lockPath } : ${ formatError ( error ) } ` ,
214+ )
166215 }
167216}
168217
@@ -202,7 +251,7 @@ async function downloadAndInstallPackage(version: string): Promise<void> {
202251 await extractTarball ( tarballPath )
203252
204253 // Remove tarball after successful extraction.
205- await fs . unlink ( tarballPath ) . catch ( error => {
254+ await remove ( tarballPath ) . catch ( error => {
206255 console . error (
207256 `Warning: Failed to remove tarball ${ tarballPath } : ${ formatError ( error ) } ` ,
208257 )
@@ -216,7 +265,7 @@ async function downloadAndInstallPackage(version: string): Promise<void> {
216265
217266 // Clean up tarball if extraction failed.
218267 if ( tarballPath ) {
219- await fs . unlink ( tarballPath ) . catch ( ( ) => {
268+ await remove ( tarballPath ) . catch ( ( ) => {
220269 // Ignore - best effort cleanup.
221270 } )
222271 }
0 commit comments