Skip to content

Commit 30b5d2d

Browse files
Release OpenProject 16.6.7
2 parents 319164a + 6ec4ba1 commit 30b5d2d

File tree

9 files changed

+351
-151
lines changed

9 files changed

+351
-151
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
title: OpenProject 16.6.7
3+
sidebar_navigation:
4+
title: 16.6.7
5+
release_version: 16.6.7
6+
release_date: 2026-02-06
7+
---
8+
9+
# OpenProject 16.6.7
10+
11+
Release date: 2026-02-06
12+
13+
We released OpenProject [OpenProject 16.6.7](https://community.openproject.org/versions/2265).
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+
### GHSA-q523-c695-h3hp - Stored HTML injection on time tracking
24+
25+
An HTML injection vulnerability occurs in the time tracking function of OpenProject version 17.0.2. The application does not escape HTML tags, an attacker with administrator privileges can create a work package with the name containing the HTML tags and add it to the `Work package` section when creating time tracking.
26+
27+
28+
29+
Responsibly disclosed by Researcher: Nguyen Truong Son ([truongson526@gmail.com](mailto:truongson526@gmail.com)) through the GitHub advisory.
30+
31+
32+
33+
For more information, please see the [GitHub advisory #GHSA-q523-c695-h3hp](https://github.com/opf/openproject/security/advisories/GHSA-q523-c695-h3hp)
34+
35+
36+
37+
### GHSA-x37c-hcg5-r5m7 - Command Injection on OpenProject repositories leads to Remote Code Execution
38+
39+
An arbitrary file write vulnerability exists in OpenProject’s repository changes endpoint (`/projects/:project_id/repository/changes`) when rendering the “latest changes” view via `git log`.
40+
41+
42+
43+
By supplying a specially crafted `rev` value (for example, `rev=--output=/tmp/poc.txt`), an attacker can inject `git log` command-line options. When OpenProject executes the SCM command, Git interprets the attacker-controlled `rev` as an option and writes the output to an attacker-chosen path.
44+
45+
46+
47+
As a result, any user with the `:browse_repository` permission on the project can create or overwrite arbitrary files that the OpenProject process user is permitted to write. The written contents consist of `git log` output, but by crafting custom commits the attacker can still upload valid shell scripts, ultimately leading to RCE. The RCE lets the attacker create a reverse shell to the target host and view confidential files outside of OpenProject, such as `/etc/passwd`.
48+
49+
50+
51+
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.
52+
53+
54+
55+
For more information, please see the [GitHub advisory #GHSA-x37c-hcg5-r5m7](https://github.com/opf/openproject/security/advisories/GHSA-x37c-hcg5-r5m7)
56+
57+
58+
<!-- END CVE AUTOMATED SECTION -->
59+
60+
<!--more-->
61+
62+
## Bug fixes and changes
63+
64+
<!-- Warning: Anything within the below lines will be automatically removed by the release script -->
65+
<!-- BEGIN AUTOMATED SECTION -->
66+
67+
68+
<!-- END AUTOMATED SECTION -->
69+
<!-- 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.7
17+
18+
Release date: 2026-02-06
19+
20+
[Release Notes](16-6-7/)
21+
22+
1623
## 16.6.6
1724

1825
Release date: 2026-01-27

frontend/package-lock.json

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"jquery.cookie": "^1.4.1",
149149
"jquery.flot": "^0.8.3",
150150
"json5": "^2.2.2",
151+
"lit-html": "^3.3.2",
151152
"lodash": "^4.17.21",
152153
"mark.js": "^8.11.0",
153154
"mdx-embed": "^1.1.2",

frontend/src/stimulus/controllers/dynamic/my/time-tracking.controller.ts

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ActionEvent, Controller } from '@hotwired/stimulus';
2-
import { Calendar, EventApi } from '@fullcalendar/core';
2+
import { Calendar, EventApi, EventContentArg } from '@fullcalendar/core';
33
import timeGridPlugin from '@fullcalendar/timegrid';
44
import dayGridPlugin from '@fullcalendar/daygrid';
55
import interactionPlugin from '@fullcalendar/interaction';
@@ -12,6 +12,8 @@ import allLocales from '@fullcalendar/core/locales-all';
1212
import { renderStreamMessage } from '@hotwired/turbo';
1313
import { opStopwatchStopIconData, toDOMString } from '@openproject/octicons-angular';
1414
import { useMeta } from 'stimulus-use';
15+
import { html, render, TemplateResult } from 'lit-html';
16+
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
1517

1618
export default class MyTimeTrackingController extends Controller {
1719
private turboRequests:TurboRequestsService;
@@ -124,38 +126,12 @@ export default class MyTimeTrackingController extends Controller {
124126
return classes;
125127
},
126128
eventContent: (info) => {
127-
let timeDetails = '';
128-
let stopTimerButton = '';
129-
let duration = info.event.extendedProps.hours as number;
129+
const wrapper = document.createElement('div');
130+
wrapper.classList.add('fc-event-main-frame');
130131

131-
if (info.isResizing && info.event.start && info.event.end) {
132-
duration = this.calculateHours(info.event);
133-
}
132+
render(this.createEventContent(info), wrapper);
134133

135-
if (!info.event.allDay) {
136-
const time = `${toMoment(info.event.start!, this.calendar).format('LT')} - ${toMoment(info.event.end!, this.calendar).format('LT')}`;
137-
timeDetails = `<div class="fc-event-times" title="${time}">${time}</div>`;
138-
}
139-
140-
if (info.event.extendedProps.ongoing) {
141-
stopTimerButton = toDOMString(opStopwatchStopIconData, 'small', { 'aria-hidden': 'true', class: 'octicon stop-timer-button' });
142-
}
143-
144-
return {
145-
html: `
146-
<div class="fc-event-main-frame">
147-
<div class="fc-event-time">${stopTimerButton} ${this.displayDuration(duration)}</div>
148-
<div class="fc-event-title-container">
149-
<div class="fc-event-title fc-event-wp" title="${info.event.extendedProps.workPackageSubject}">
150-
<a class="Link--primary Link" href="${this.pathHelper.workPackageShortPath(info.event.extendedProps.workPackageId as string)}">
151-
${info.event.extendedProps.workPackageSubject}
152-
</a>
153-
</div>
154-
<div class="fc-event-project" title="${info.event.extendedProps.projectName}">${info.event.extendedProps.projectName}</div>
155-
${timeDetails}
156-
</div>
157-
</div>`,
158-
};
134+
return { domNodes: [wrapper] };
159135
},
160136
select: (info) => {
161137
let dialogParams = 'onlyMe=true';
@@ -261,6 +237,46 @@ export default class MyTimeTrackingController extends Controller {
261237
this.calendar.render();
262238
}
263239

240+
createEventContent(info:EventContentArg) {
241+
let timeDetails:string|TemplateResult = '';
242+
let stopTimerButton = '';
243+
let duration = info.event.extendedProps.hours as number;
244+
245+
if (info.isResizing && info.event.start && info.event.end) {
246+
duration = this.calculateHours(info.event);
247+
}
248+
249+
if (!info.event.allDay) {
250+
const time = `${toMoment(info.event.start!, this.calendar).format('LT')} - ${toMoment(info.event.end!, this.calendar).format('LT')}`;
251+
timeDetails = html`<div class="fc-event-times" title="${time}">${time}</div>`;
252+
}
253+
254+
if (info.event.extendedProps.ongoing) {
255+
stopTimerButton = toDOMString(opStopwatchStopIconData, 'small', {
256+
'aria-hidden': 'true',
257+
class: 'octicon stop-timer-button',
258+
});
259+
}
260+
261+
return html`
262+
<div class="fc-event-time">
263+
${unsafeHTML(stopTimerButton)}
264+
${this.displayDuration(duration)}
265+
</div>
266+
<div class="fc-event-title-container">
267+
<div class="fc-event-title fc-event-wp" title="${info.event.extendedProps.workPackageSubject}">
268+
<a class="Link--primary Link"
269+
href="${this.pathHelper.workPackageShortPath(info.event.extendedProps.workPackageId as string)}">
270+
${info.event.extendedProps.workPackageSubject}
271+
</a>
272+
</div>
273+
<div class="fc-event-project" title="${info.event.extendedProps.projectName}">
274+
${info.event.extendedProps.projectName}
275+
</div>
276+
${timeDetails}
277+
</div>`;
278+
}
279+
264280
addTotalFooter() {
265281
if (!this.calendar) return;
266282
const calendarScrollGridWrapper = document.querySelector('.fc-timegrid .fc-scrollgrid tbody');
@@ -368,7 +384,7 @@ export default class MyTimeTrackingController extends Controller {
368384
return tr;
369385
}
370386

371-
updateTimeEntry(timeEntryId:string, spentOn:string, startTime:string | null, hours:number, revertFunction:() => void) {
387+
updateTimeEntry(timeEntryId:string, spentOn:string, startTime:string|null, hours:number, revertFunction:() => void) {
372388
fetch(this.pathHelper.timeEntryUpdate(timeEntryId), {
373389
method: 'PATCH',
374390
headers: {
@@ -470,7 +486,10 @@ export default class MyTimeTrackingController extends Controller {
470486
interface AdditionalDialogCloseData {
471487
spent_on?:string;
472488
}
473-
const { detail: { dialog, additional, submitted } } = event as { detail:{ dialog:HTMLDialogElement; additional:AdditionalDialogCloseData|undefined; submitted:boolean } };
489+
490+
const { detail: { dialog, additional, submitted } } = event as {
491+
detail:{ dialog:HTMLDialogElement; additional:AdditionalDialogCloseData|undefined; submitted:boolean }
492+
};
474493
if (dialog.id !== 'time-entry-dialog' || !submitted) { return; }
475494

476495
// we simply refresh the calendar page

0 commit comments

Comments
 (0)