Skip to content

Commit badbe3d

Browse files
Release OpenProject 16.6.8
2 parents 30b5d2d + a48cea8 commit badbe3d

File tree

8 files changed

+146
-69
lines changed

8 files changed

+146
-69
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
title: OpenProject 16.6.8
3+
sidebar_navigation:
4+
title: 16.6.8
5+
release_version: 16.6.8
6+
release_date: 2026-02-18
7+
---
8+
9+
# OpenProject 16.6.8
10+
11+
Release date: 2026-02-18
12+
13+
We released OpenProject [OpenProject 16.6.8](https://community.openproject.org/versions/2276).
14+
The release contains several bug fixes and we recommend updating to the newest version.
15+
Below you will find a complete list of all changes and bug fixes.
16+
17+
<!-- BEGIN CVE AUTOMATED SECTION -->
18+
19+
## Security fixes
20+
21+
22+
23+
### CVE-2026-27019 - Path Traversal via Incoming Email Attachments Leads to Arbitrary File Write and RCE
24+
25+
When OpenProject is configured to accept and handle incoming emails, it was possible that an attacker could send an email with a specially crafted attachment that would be written to a predefined location in the filesystem. All files that can be written by the `openproject` system user could be written. This could even be evaluated to a Remote Code Execution vulnerability.
26+
27+
28+
29+
This vulnerability was reported by user [sam91281](https://yeswehack.com/hunters/sam91281) as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission.
30+
31+
32+
33+
For more information, please see the [GitHub advisory #GHSA-r85w-rv9m-q784](https://github.com/opf/openproject/security/advisories/GHSA-r85w-rv9m-q784)
34+
35+
36+
<!-- END CVE AUTOMATED SECTION -->
37+
38+
<!--more-->
39+
40+
## Bug fixes and changes
41+
42+
<!-- Warning: Anything within the below lines will be automatically removed by the release script -->
43+
<!-- BEGIN AUTOMATED SECTION -->
44+
45+
46+
<!-- END AUTOMATED SECTION -->
47+
<!-- Warning: Anything above this line will be automatically removed by the release script -->

docs/release-notes/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ Stay up to date and get an overview of the new features included in the releases
1313
<!--- New release notes are generated below. Do not remove comment. -->
1414
<!--- RELEASE MARKER -->
1515

16+
## 16.6.8
17+
18+
Release date: 2026-02-18
19+
20+
[Release Notes](16-6-8/)
21+
22+
1623
## 16.6.7
1724

1825
Release date: 2026-02-06

lib/open_project/files.rb

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,43 +30,29 @@ module OpenProject
3030
module Files
3131
module_function
3232

33-
##
34-
# Creates a temp file with the given file name.
35-
# It will reside in some temporary directory.
36-
def create_temp_file(name: "test.txt", content: "test content", binary: false)
37-
tmp = Tempfile.new name
38-
path = Pathname(tmp)
39-
40-
tmp.delete # delete temp file
41-
path.mkdir # create temp directory
42-
43-
file_path = path.join name
44-
File.open(file_path, "w" + (binary ? "b" : "")) do |f|
45-
f.write content
46-
end
47-
48-
File.new file_path
33+
def build_uploaded_file(tempfile, type, binary: true, file_name: nil)
34+
Rack::Multipart::UploadedFile.new tempfile.path,
35+
type,
36+
binary,
37+
filename: file_name
4938
end
5039

51-
def build_uploaded_file(tempfile, type, binary: true, file_name: nil)
52-
uploaded_file = Rack::Multipart::UploadedFile.new tempfile.path,
53-
type,
54-
binary
55-
if file_name
56-
# I wish I could set the file name in a better way *sigh*
57-
uploaded_file.instance_variable_set(:@original_filename, file_name)
40+
def create_uploaded_file(name:, content_type:, content:, binary: false)
41+
create_temp_file(name:, content:, binary:) do |f|
42+
build_uploaded_file f, content_type, binary:, file_name: File.basename(name)
5843
end
59-
60-
uploaded_file
6144
end
6245

63-
def create_uploaded_file(name: "test.txt",
64-
content_type: "text/plain",
65-
content: "test content",
66-
binary: false)
46+
def create_temp_file(name:, content:, binary: false, &)
47+
basename = name
48+
Tempfile.create(basename) do |f|
49+
f.binmode if binary
6750

68-
tmp = create_temp_file(name:, content:, binary:)
69-
build_uploaded_file tmp, content_type, binary:
51+
f.write content
52+
f.rewind
53+
54+
yield f
55+
end
7056
end
7157
end
7258
end

lib/open_project/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module OpenProject
3333
module VERSION # :nodoc:
3434
MAJOR = 16
3535
MINOR = 6
36-
PATCH = 7
36+
PATCH = 8
3737

3838
class << self
3939
# Used by semver to define the special version (if any).

modules/bim/lib/open_project/bim/bcf_xml/exporter.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,14 @@ def topic_markup_file(issue_dir, issue)
111111

112112
##
113113
# Write viewpoints
114-
def viewpoints_for(issue_dir, issue)
114+
def viewpoints_for(issue_dir, issue) # rubocop:disable Metrics/AbcSize
115115
[].tap do |files|
116116
issue.viewpoints.find_each do |vp|
117+
# Sanity check for the viewpoints GUID, to protect against
118+
# path traversal when generating the viewpoint file name
119+
uuid_regex = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
120+
next unless vp.uuid.match?(uuid_regex)
121+
117122
vp_file = File.join(issue_dir, "#{vp.uuid}.bcfv")
118123
snapshot_file = File.join(issue_dir, "#{vp.uuid}#{vp.snapshot.extension}")
119124

publiccode.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ name: OpenProject
77
applicationSuite: openDesk
88
url: 'https://github.com/opf/openproject'
99
roadmap: 'https://www.openproject.org/roadmap'
10-
releaseDate: '2026-02-06'
11-
softwareVersion: '16.6.7'
10+
releaseDate: '2026-02-18'
11+
softwareVersion: '16.6.8'
1212
developmentStatus: stable
1313
softwareType: standalone/web
1414
logo: 'publiccode_logo.svg'

spec/lib/open_project/files_spec.rb

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@
5959
require "spec_helper"
6060

6161
RSpec.describe OpenProject::Files do
62-
describe "build_uploaded_file" do
62+
describe ".build_uploaded_file" do
6363
let(:original_filename) { "test.png" }
6464
let(:content_type) { "image/png" }
6565
let(:file) do
66-
OpenProject::Files.create_temp_file(name: original_filename)
66+
Tempfile.new(File.basename(original_filename))
6767
end
6868

69-
subject { OpenProject::Files.build_uploaded_file(file, content_type) }
69+
subject { described_class.build_uploaded_file(file, content_type, file_name: original_filename) }
7070

7171
it "has the original file name" do
7272
expect(subject.original_filename).to eql(original_filename)
@@ -79,47 +79,24 @@
7979
context "with custom file name" do
8080
let(:file_name) { "my-custom-filename.png" }
8181

82-
subject { OpenProject::Files.build_uploaded_file(file, content_type, file_name:) }
82+
subject { described_class.build_uploaded_file(file, content_type, file_name:) }
8383

8484
it "has the custom file name" do
8585
expect(subject.original_filename).to eql(file_name)
8686
end
8787
end
8888
end
8989

90-
describe "create_uploaded_file" do
91-
context "without parameters" do
92-
let(:file) { OpenProject::Files.create_uploaded_file }
93-
94-
it 'creates a file with the default name "test.txt"' do
95-
expect(file.original_filename).to eq "test.txt"
96-
end
97-
98-
it "creates distinct files even with identical names" do
99-
file_2 = OpenProject::Files.create_uploaded_file
100-
101-
expect(file.original_filename).to eq file_2.original_filename
102-
expect(file.path).not_to eq file_2.path
103-
end
104-
105-
it 'writes some default content "test content"' do
106-
expect(file.read).to eq "test content"
107-
end
108-
109-
it 'set default content type "text/plain"' do
110-
expect(file.content_type).to eq "text/plain"
111-
end
112-
end
113-
90+
describe ".create_uploaded_file" do
11491
context "with a custom name, content and content type" do
11592
let(:name) { "foo.jpg" }
11693
let(:content) { "not-really-a-jpg" }
11794
let(:content_type) { "image/jpeg" }
11895

11996
let(:file) do
120-
OpenProject::Files.create_uploaded_file name:,
121-
content:,
122-
content_type:
97+
described_class.create_uploaded_file name:,
98+
content:,
99+
content_type:
123100
end
124101

125102
it 'creates a file called "foo.jpg"' do
@@ -138,7 +115,14 @@
138115
context "with binary content" do
139116
let(:content) { "\xD1\x9B\x86".b }
140117
let(:binary) { false }
141-
let(:file) { OpenProject::Files.create_uploaded_file content:, binary: }
118+
let(:file) do
119+
described_class.create_uploaded_file(
120+
name: "binary_file",
121+
content_type: "application/octet-stream",
122+
content: content,
123+
binary: binary
124+
)
125+
end
142126

143127
it "fails when the content is not marked as binary" do
144128
expect { file }.to raise_error(Encoding::UndefinedConversionError)
@@ -152,5 +136,51 @@
152136
end
153137
end
154138
end
139+
140+
context "when a relative filename is provided" do
141+
let(:name) { "../../hello/../../../foo.txt" }
142+
let(:file) do
143+
described_class.create_uploaded_file name:,
144+
content: "content",
145+
content_type: "text/plain"
146+
end
147+
148+
it "sanitizes the file name" do
149+
expect(file.original_filename).to eq "foo.txt"
150+
end
151+
end
152+
end
153+
154+
describe ".create_temp_file" do
155+
let(:name) { "tempfile.txt" }
156+
let(:content) { "temporary content" }
157+
let(:binary) { false }
158+
159+
subject do
160+
described_class.create_temp_file(name:, content:, binary:, &:read)
161+
end
162+
163+
it "yields a file with the given content" do
164+
expect(subject).to eq content
165+
end
166+
167+
context "with binary content" do
168+
let(:content) { "\xD1\x9B\x86".b }
169+
let(:binary) { true }
170+
171+
it "yields the binary content" do
172+
expect(subject).to eq content
173+
end
174+
end
175+
176+
context "when a relative filename is provided" do
177+
let(:name) { "../../tempfile.txt" }
178+
179+
it "sanitizes the file name" do
180+
described_class.create_temp_file(name:, content:, binary:) do |f|
181+
expect(f.path).to eq(File.expand_path(f.path))
182+
end
183+
end
184+
end
155185
end
156186
end

spec/support/file_helpers.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ def mock_uploaded_file(name: "test.txt",
3535
content_type: "text/plain",
3636
content: "test content",
3737
binary: false)
38+
::OpenProject::Files.create_uploaded_file(name:, content_type:, content:, binary:)
3839

39-
tmp = ::OpenProject::Files.create_temp_file(name:, content:, binary:)
40-
Rack::Test::UploadedFile.new tmp.path, content_type, binary
40+
::OpenProject::Files.create_temp_file(name:, content:, binary:) do |tmp|
41+
Rack::Test::UploadedFile.new tmp.path, content_type, binary, original_filename: File.basename(name)
42+
end
4143
end
4244
end

0 commit comments

Comments
 (0)