diff --git a/lib/utopia/content/node.rb b/lib/utopia/content/node.rb index 593e020a..3f5ac450 100644 --- a/lib/utopia/content/node.rb +++ b/lib/utopia/content/node.rb @@ -51,14 +51,12 @@ def lookup_node(path) def local_path(path = ".", base = nil) path = Path[path] - root = Pathname.new(@controller.root) - - if path.absolute? - return root.join(*path.components) - else + if path.relative? base ||= uri_path.dirname - return root.join(*(base + path).components) + path = base + path end + + return Pathname.new(path.to_url_path.local_path(@controller.root)) end # Resolve a path relative to this node's containing URI path. diff --git a/releases.md b/releases.md index 38470144..dbf7cb73 100644 --- a/releases.md +++ b/releases.md @@ -5,6 +5,7 @@ - **Breaking** Remove support for JavaScript packages installed in `lib/components`; use `node_modules` instead. - **Breaking** Expose {ruby Utopia::Content::Middleware#links} as the content link resolver rather than an indexed lookup method. - **Security** Authenticate encrypted session cookies using AES-256-GCM. Existing session cookies are invalidated. + - Constrain content node local paths to the configured content root. - **Security** Redact sensitive exception report fields and make bounded request body attachments opt-in. - Return `416 Range Not Satisfiable` for unsatisfiable static file byte ranges. diff --git a/test/utopia/content/node.rb b/test/utopia/content/node.rb index f2800026..0e58a8bb 100644 --- a/test/utopia/content/node.rb +++ b/test/utopia/content/node.rb @@ -84,6 +84,20 @@ expect(node.local_path("/shared/preview.jpg")).to be == (base + "shared/preview.jpg") end + + it "rejects absolute paths which escape the content root" do + node = content.lookup_node(Utopia::Path["/ordered/index"]) + + expect do + node.local_path("/../../outside") + end.to raise_exception(ArgumentError, message: be =~ /escapes the specified root/) + end + + it "contains relative paths within the content root" do + node = content.lookup_node(Utopia::Path["/ordered/index"]) + + expect(node.local_path("../../../outside")).to be == (base + "outside") + end end with "#relative_path" do