Skip to content

Commit 3ff369f

Browse files
committed
feat: add catastrophic delete protection to bootstrap remove()
- Add inline remove() function with safety checks similar to del package - Prevent deleting cwd or directories outside SOCKET_HOME - Replace all fs.unlink() calls with safe remove() - Protects against accidental system-wide deletions - Can be overridden with force option if needed
1 parent 9bd91a5 commit 3ff369f

File tree

1 file changed

+61
-12
lines changed

1 file changed

+61
-12
lines changed

src/sea/bootstrap.mts

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
155207
async 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

Comments
 (0)