Skip to content

Commit 3444aeb

Browse files
Release OpenProject 17.0.6
2 parents 6e12af7 + 3e1b0a8 commit 3444aeb

File tree

23 files changed

+847
-61
lines changed

23 files changed

+847
-61
lines changed

app/controllers/repositories_controller.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -446,11 +446,11 @@ def raw_or_to_large_or_non_text(content, path)
446446
end
447447

448448
def send_raw(content, path)
449-
# Force the download
450-
send_opt = { filename: filename_for_content_disposition(path.split("/").last) }
451-
send_type = OpenProject::MimeType.of(path)
452-
send_opt[:type] = send_type.to_s if send_type
453-
send_data content, send_opt
449+
# Force the download as binary to prevent CSP bypass
450+
send_data content,
451+
filename: filename_for_content_disposition(path.split("/").last),
452+
type: "application/octet-stream",
453+
disposition: :attachment
454454
end
455455

456456
def render_text_entry

app/helpers/repositories_helper.rb

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,13 @@ def calculate_folder_action(tree)
114114
end
115115

116116
def render_changes_tree(tree)
117-
return "" if tree.nil?
117+
return "".html_safe if tree.nil?
118118

119-
output = +"<ul>"
120-
tree.keys.sort.each do |file|
121-
style = +"change"
119+
items = tree.keys.sort.flat_map do |file|
120+
style = "change"
122121
text = File.basename(file)
123-
if s = tree[file][:s]
122+
123+
if (s = tree[file][:s])
124124
style += " folder"
125125
path_param = without_leading_slash(to_path_param(@repository.relative_path(file)))
126126
text = link_to(h(text),
@@ -129,38 +129,40 @@ def render_changes_tree(tree)
129129
rev: @changeset.identifier),
130130
title: I18n.t(:label_folder))
131131

132-
output += "<li class='#{style} icon icon-folder-#{calculate_folder_action(s)}'>#{text}</li>"
133-
output += render_changes_tree(s)
134-
elsif c = tree[file][:c]
132+
folder_li = content_tag(:li, text,
133+
class: "#{style} icon icon-folder-#{calculate_folder_action(s)}")
134+
[folder_li, render_changes_tree(s)]
135+
elsif (c = tree[file][:c])
135136
style += " change-#{c.action}"
136137
path_param = without_leading_slash(to_path_param(@repository.relative_path(c.path)))
137138

138-
unless c.action == "D"
139-
title_text = changes_tree_change_title c.action
139+
text_parts = []
140140

141+
unless c.action == "D"
141142
text = link_to(h(text),
142143
entry_revision_project_repository_path(project_id: @project,
143144
repo_path: path_param,
144145
rev: @changeset.identifier),
145-
title: title_text)
146+
title: changes_tree_change_title(c.action))
146147
end
147148

148-
text << raw(" - #{h(c.revision)}") if c.revision.present?
149+
text_parts << text
150+
text_parts << " - " << h(c.revision) if c.revision.present?
149151

150152
if c.action == "M"
151-
text << raw(" (" + link_to(I18n.t(:label_diff),
152-
diff_revision_project_repository_path(project_id: @project,
153-
repo_path: path_param,
154-
rev: @changeset.identifier)) + ") ")
153+
text_parts << " (" << link_to(I18n.t(:label_diff),
154+
diff_revision_project_repository_path(project_id: @project,
155+
repo_path: path_param,
156+
rev: @changeset.identifier)) << ") "
155157
end
156158

157-
text << raw(" " + content_tag("span", h(c.from_path), class: "copied-from")) if c.from_path.present?
159+
text_parts << " " << content_tag(:span, c.from_path, class: "copied-from") if c.from_path.present?
158160

159-
output += changes_tree_li_element(c.action, text, style)
161+
[changes_tree_li_element(c.action, safe_join(text_parts), style)]
160162
end
161-
end
162-
output += "</ul>"
163-
output.html_safe
163+
end.compact
164+
165+
content_tag(:ul, safe_join(items))
164166
end
165167

166168
def to_utf8_for_repositories(str)
@@ -296,19 +298,16 @@ def changes_tree_change_title(action)
296298

297299
def changes_tree_li_element(action, text, style)
298300
icon_name = case action
299-
when "A"
300-
"icon-add"
301-
when "D"
302-
"icon-delete"
303-
when "C"
304-
"icon-copy"
305-
when "R"
306-
"icon-rename"
301+
when "A" then "icon-add"
302+
when "D" then "icon-delete"
303+
when "C" then "icon-copy"
304+
when "R" then "icon-rename"
307305
else
308306
"icon-arrow-left-right"
309307
end
310308

311-
"<li class='#{style} icon #{icon_name}'
312-
title='#{changes_tree_change_title(action)}'>#{text}</li>"
309+
content_tag(:li, text,
310+
class: "#{style} icon #{icon_name}",
311+
title: changes_tree_change_title(action))
313312
end
314313
end

app/models/custom_field.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class CustomField < ApplicationRecord
3232
include CustomField::OrderStatements
3333
include CustomField::CalculatedValue
3434

35+
normalizes :name, with: OpenProject::RemoveAsciiControlCharacters
36+
3537
has_many :custom_values, dependent: :delete_all
3638
# WARNING: the inverse_of option is also required in order
3739
# for the 'touch: true' option on the custom_field association in CustomOption

config/initializers/00-load_plugins.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,16 @@
3131
# TODO: check if this can be postponed and if some plugins can make use of the ActiveSupport.on_load hooks
3232

3333
# Loads the core plugins located in lib_static/plugins
34-
Dir.glob(Rails.root.join("lib_static/plugins/*")).each do |directory|
34+
CORE_PLUGINS = %w[
35+
acts_as_attachable
36+
acts_as_customizable
37+
acts_as_event
38+
acts_as_journalized
39+
acts_as_searchable
40+
verification
41+
].freeze
42+
43+
CORE_PLUGINS.map { |name| Rails.root.join("lib_static/plugins", name).to_s }.each do |directory|
3544
if File.directory?(directory)
3645
lib = File.join(directory, "lib")
3746

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
class StripControlCharactersFromCustomFieldNames < ActiveRecord::Migration[7.1]
32+
def up
33+
execute <<~SQL.squish
34+
UPDATE custom_fields
35+
SET name = regexp_replace(name, E'[\\x01-\\x1F\\x7F]', '', 'g')
36+
WHERE name ~ E'[\\x01-\\x1F\\x7F]'
37+
SQL
38+
end
39+
40+
def down
41+
# Irreversible — stripped characters cannot be restored
42+
end
43+
end

docker/prod/Dockerfile

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,7 @@ ENV PGDATA=/var/openproject/pgdata
141141
COPY --from=openproject/gosu /go/bin/gosu /usr/local/bin/gosu
142142
RUN chmod +x /usr/local/bin/gosu && gosu nobody true
143143

144-
COPY --from=openproject/hocuspocus:17.2.0 --chown=$APP_USER:$APP_USER /app /opt/hocuspocus
145-
# Keep node/npm in all-in-one for bundled hocuspocus even when BIM support is disabled.
146-
COPY --from=build-base /usr/local/bin/node /usr/local/bin/node
147-
COPY --from=build-base /usr/local/lib/node_modules /usr/local/lib/node_modules
144+
COPY --from=openproject/hocuspocus:17.0.6 --chown=$APP_USER:$APP_USER /app /opt/hocuspocus
148145

149146
RUN ./docker/prod/setup/postinstall-onprem.sh && \
150147
ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
title: OpenProject 16.6.9
3+
sidebar_navigation:
4+
title: 16.6.9
5+
release_version: 16.6.9
6+
release_date: 2026-03-16
7+
---
8+
9+
# OpenProject 16.6.9
10+
11+
Release date: 2026-03-16
12+
13+
We released OpenProject [OpenProject 16.6.9](https://community.openproject.org/versions/2285).
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-32698 - SQL Injection via Custom Field Name can be chained to Remote Code Execution
24+
25+
OpenProject is vulnerable to an SQL injection attack via a custom field&#39;s name. When that custom field was used in a Cost Report, the custom field&#39;s name was injected into the SQL query without proper sanitation. This allowed an attacker to execute arbitrary SQL commands during the generation of a Cost Report.&nbsp;
26+
27+
28+
29+
As custom fields can only be generated by users with full administrator privileges, the attack surface is somewhat reduced.
30+
31+
32+
33+
Together with another bug in the _Repositories_ module, that used the project identifier without sanitation to generate the checkout path for a git repository in the filesystem, this allowed an attacker to checkout a git repository to an arbitrarily chosen path on the server. If the checkout is done within certain paths within the OpenProject application, upon the next restart of the application, this allows the attacker to inject ruby code into the application.
34+
35+
36+
37+
As the project identifier cannot be manually edited to any string containing special characters like dots or slashes, this needs to be changed via the SQL injection described above.
38+
39+
40+
41+
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.
42+
43+
44+
45+
For more information, please see the [GitHub advisory #GHSA-jqhf-rf9x-9rhx](https://github.com/opf/openproject/security/advisories/GHSA-jqhf-rf9x-9rhx)
46+
47+
48+
49+
### CVE-2026-32703 - Repository files are served with the MIME type allowing them to be used to bypass Content Security Policy
50+
51+
When using the Repositories module in a project, it was possible to access the raw files via the browser with a URL like `/projects/{project}/repository/revisions/{commit_id}/raw/{file}.js.raw`. For those files, the MIME type was detected via the filename extension. For JavaScript and CSS files those files were then served from the same domain name as the application with the correct MIME type for active content and could be used to bypass the Content Security Policy. Together with other areas, where unsanitized HTML was served, this allowed persistent XSS attacks.
52+
53+
54+
55+
The MIME type detection for Repository files has been removed and files are served as `application/octet-stream` which will block their execution via the Content Security Policy.
56+
57+
58+
59+
Two places that could be used to abuse this vulnerability have been fixed:
60+
61+
62+
63+
The Repositories module did not properly escape filenames displayed from repositories. This allowed an attacker with push access into the repository to create commits with filenames that included HTML code that was injected in the page without proper sanitation. This allowed a persisted XSS attack against all members of this project that accessed the repositories page to display a changeset where the maliciously crafted file was deleted.
64+
65+
66+
67+
When a work package name contains HTML content and the work package is attached to a meeting, the work package name is rendered in the activities feed without proper sanitation.
68+
69+
70+
71+
All of those vulnerabilities were 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.
72+
73+
74+
75+
For more information, please see the [GitHub advisory #GHSA-p423-72h4-fjvp](https://github.com/opf/openproject/security/advisories/GHSA-p423-72h4-fjvp)
76+
77+
78+
<!-- END CVE AUTOMATED SECTION -->
79+
80+
<!--more-->
81+
82+
## Bug fixes and changes
83+
84+
<!-- Warning: Anything within the below lines will be automatically removed by the release script -->
85+
<!-- BEGIN AUTOMATED SECTION -->
86+
87+
88+
<!-- END AUTOMATED SECTION -->
89+
<!-- Warning: Anything above this line will be automatically removed by the release script -->
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
title: OpenProject 17.0.6
3+
sidebar_navigation:
4+
title: 17.0.6
5+
release_version: 17.0.6
6+
release_date: 2026-03-16
7+
---
8+
9+
# OpenProject 17.0.6
10+
11+
Release date: 2026-03-16
12+
13+
We released OpenProject [OpenProject 17.0.6](https://community.openproject.org/versions/2286).
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-32698 - SQL Injection via Custom Field Name can be chained to Remote Code Execution
24+
25+
OpenProject is vulnerable to an SQL injection attack via a custom field&#39;s name. When that custom field was used in a Cost Report, the custom field&#39;s name was injected into the SQL query without proper sanitation. This allowed an attacker to execute arbitrary SQL commands during the generation of a Cost Report.&nbsp;
26+
27+
28+
29+
As custom fields can only be generated by users with full administrator privileges, the attack surface is somewhat reduced.
30+
31+
32+
33+
Together with another bug in the _Repositories_ module, that used the project identifier without sanitation to generate the checkout path for a git repository in the filesystem, this allowed an attacker to checkout a git repository to an arbitrarily chosen path on the server. If the checkout is done within certain paths within the OpenProject application, upon the next restart of the application, this allows the attacker to inject ruby code into the application.
34+
35+
36+
37+
As the project identifier cannot be manually edited to any string containing special characters like dots or slashes, this needs to be changed via the SQL injection described above.
38+
39+
40+
41+
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.
42+
43+
44+
45+
For more information, please see the [GitHub advisory #GHSA-jqhf-rf9x-9rhx](https://github.com/opf/openproject/security/advisories/GHSA-jqhf-rf9x-9rhx)
46+
47+
48+
49+
### CVE-2026-32703 - Repository files are served with the MIME type allowing them to be used to bypass Content Security Policy
50+
51+
When using the Repositories module in a project, it was possible to access the raw files via the browser with a URL like `/projects/{project}/repository/revisions/{commit_id}/raw/{file}.js.raw`. For those files, the MIME type was detected via the filename extension. For JavaScript and CSS files those files were then served from the same domain name as the application with the correct MIME type for active content and could be used to bypass the Content Security Policy. Together with other areas, where unsanitized HTML was served, this allowed persistent XSS attacks.
52+
53+
54+
55+
The MIME type detection for Repository files has been removed and files are served as `application/octet-stream` which will block their execution via the Content Security Policy.
56+
57+
58+
59+
Two places that could be used to abuse this vulnerability have been fixed:
60+
61+
62+
63+
The Repositories module did not properly escape filenames displayed from repositories. This allowed an attacker with push access into the repository to create commits with filenames that included HTML code that was injected in the page without proper sanitation. This allowed a persisted XSS attack against all members of this project that accessed the repositories page to display a changeset where the maliciously crafted file was deleted.
64+
65+
66+
67+
When a work package name contains HTML content and the work package is attached to a meeting, the work package name is rendered in the activities feed without proper sanitation.
68+
69+
70+
71+
All of those vulnerabilities were 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.
72+
73+
74+
75+
For more information, please see the [GitHub advisory #GHSA-p423-72h4-fjvp](https://github.com/opf/openproject/security/advisories/GHSA-p423-72h4-fjvp)
76+
77+
78+
<!-- END CVE AUTOMATED SECTION -->
79+
80+
<!--more-->
81+
82+
## Bug fixes and changes
83+
84+
<!-- Warning: Anything within the below lines will be automatically removed by the release script -->
85+
<!-- BEGIN AUTOMATED SECTION -->
86+
87+
88+
<!-- END AUTOMATED SECTION -->
89+
<!-- Warning: Anything above this line will be automatically removed by the release script -->

0 commit comments

Comments
 (0)