Skip to content

Commit 05c39bf

Browse files
sunny-seclaude
andcommitted
fix(security): add extraction size limit to prevent decompression bomb
F-015 / DEVA11Y-484 — bsdtar extraction had no size or entry-count limit (CWE-400), allowing decompression bomb DoS. Add a 100 MB post- extraction size check that removes the output and errors on violation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0428b32 commit 05c39bf

1 file changed

Lines changed: 25 additions & 0 deletions

File tree

Plugins/BrowserStackAccessibilityLint/BrowserStackAccessibilityLint.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ private struct BrowserStackCLIDownloader {
285285
let message = String(data: tarError.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
286286
forwardExit(code: bsdtar.terminationStatus, message: message.isEmpty ? "bsdtar failed to extract BrowserStack CLI." : message)
287287
}
288+
289+
try validateExtractedSize(of: directory)
288290
}
289291

290292
private func extractLocalArchive(at archiveURL: URL, into directory: URL) throws {
@@ -301,6 +303,11 @@ private struct BrowserStackCLIDownloader {
301303
throw PluginError("Failed to launch bsdtar: \(error.localizedDescription)")
302304
}
303305

306+
if process.terminationReason == .exit && process.terminationStatus == 0 {
307+
try validateExtractedSize(of: directory)
308+
return
309+
}
310+
304311
if process.terminationReason != .exit || process.terminationStatus != 0 {
305312
// Fall back to copying the file directly if it's already an executable.
306313
let message = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
@@ -315,6 +322,24 @@ private struct BrowserStackCLIDownloader {
315322
}
316323
}
317324
}
325+
326+
private func validateExtractedSize(of directory: URL, maxBytes: UInt64 = 100 * 1024 * 1024) throws {
327+
var totalSize: UInt64 = 0
328+
let enumerator = fileManager.enumerator(
329+
at: directory,
330+
includingPropertiesForKeys: [.fileSizeKey],
331+
options: [.skipsHiddenFiles]
332+
)
333+
while let fileURL = enumerator?.nextObject() as? URL {
334+
if let size = try? fileURL.resourceValues(forKeys: [.fileSizeKey]).fileSize {
335+
totalSize += UInt64(size)
336+
if totalSize > maxBytes {
337+
try? fileManager.removeItem(at: directory)
338+
throw PluginError("Extracted archive exceeds maximum allowed size (\(maxBytes / (1024 * 1024)) MB). Possible decompression bomb.")
339+
}
340+
}
341+
}
342+
}
318343
#endif
319344

320345
private func resolveOverrideArtifact(from url: URL) async throws -> ArtifactInfo {

0 commit comments

Comments
 (0)