diff --git a/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift b/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift index a37ad6912..285112389 100644 --- a/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift +++ b/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift @@ -283,6 +283,8 @@ public actor ContainersService { } try await self.lock.withLock(logMetadata: ["acquirer": "\(#function)", "id": "\(configuration.id)"]) { context in + try Utility.validEntityName(configuration.id) + guard await self.containers[configuration.id] == nil else { throw ContainerizationError( .exists, @@ -892,6 +894,8 @@ public actor ContainersService { ) } + try Utility.validEntityName(id) + let containerPath = self.containerRoot.appendingPathComponent(id).path return FileManager.default.allocatedSize(of: URL(fileURLWithPath: containerPath)) @@ -900,6 +904,8 @@ public actor ContainersService { public func exportRootfs(id: String, archive: URL) async throws { self.log.debug("\(#function)") + try Utility.validEntityName(id) + let state = try self._getContainerState(id: id) guard state.snapshot.status == .stopped else { throw ContainerizationError(.invalidState, message: "container is not stopped") diff --git a/Tests/ContainerAPIClientTests/UtilityTests.swift b/Tests/ContainerAPIClientTests/UtilityTests.swift index 3a9b3a495..a538cce59 100644 --- a/Tests/ContainerAPIClientTests/UtilityTests.swift +++ b/Tests/ContainerAPIClientTests/UtilityTests.swift @@ -113,6 +113,26 @@ struct UtilityTests { #expect(Utility.trimDigest(digest: "sha256:abc") == "abc") } + @Test("Valid entity names are accepted") + func testValidEntityNames() throws { + try Utility.validEntityName("my-container") + try Utility.validEntityName("test.container") + try Utility.validEntityName("abc123") + try Utility.validEntityName("a1") + try Utility.validEntityName("container_1.test-abc") + } + + @Test("Path traversal sequences are rejected") + func testTraversalSequencesRejected() { + #expect(throws: Error.self) { try Utility.validEntityName("../../tmp/evil") } + #expect(throws: Error.self) { try Utility.validEntityName("../foo") } + #expect(throws: Error.self) { try Utility.validEntityName("foo/bar") } + #expect(throws: Error.self) { try Utility.validEntityName("/tmp/evil") } + #expect(throws: Error.self) { try Utility.validEntityName("") } + #expect(throws: Error.self) { try Utility.validEntityName(".hidden") } + #expect(throws: Error.self) { try Utility.validEntityName("-bad") } + } + @Test func testPublishPortParser() throws { let ports = try Parser.publishPorts([