A meeting-room window with a view into the office next door
OpenProject's GET /api/v3/meetings/:meeting_id/agenda_items/:agenda_item_id endpoint forgets to re-check project membership on the *linked* work package. Any authenticated user who can see a meeting can ask for an agenda item and get back fields from a work package that belongs to a project they have no rights to view. It's a classic broken-object-level-authorization (BOLA / IDOR) bug — the controller trusted the meeting's ACL and never re-authorized the join. Affected: all OpenProject versions prior to 17.4.1; the meeting agenda feature lands in modern 13.x+ branches, so anyone on a recent self-hosted release is in scope.
Vendor's MEDIUM (4.3) is fair-to-slightly-generous. The CVSS vector AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N reads correctly: network-reachable, low complexity, low-privilege required, partial confidentiality only. No integrity loss, no availability loss, no privilege escalation, no scope change. For a defender running 10,000 hosts this is a data-confidentiality hygiene issue, not a fire.
4 steps from start to impact.
Obtain an authenticated OpenProject account
- Network reachability to the OpenProject web UI/API
- Valid user credentials or an API token
- Most enterprise OpenProject deployments are internal-only behind SSO/VPN
- Self-registration is usually disabled in corporate tenants
Enumerate meeting and agenda item IDs
/api/v3/meetings for meetings they are invited to, then iterates numeric agenda_item_id values. IDs are sequential integers, so an ffuf/Burp Intruder sweep across small ranges is enough.- Membership in at least one meeting
- Knowledge of API path (public docs)
- Rate-limited reverse proxies blunt brute enumeration
- Meeting agendas are usually scoped to small project teams
Pull linked private work-package data
- Agenda item references a work package in a private project
- Vulnerable version <17.4.1
- Only agenda items that actually link cross-project work packages disclose anything
- Attacker only learns what was linked — not full project contents
nuclei template can flag it once writtenAggregate disclosed fields into intel
- Sustained access over time
- No lateral movement, no creds harvested, no shell
The supporting signals.
| In-the-wild exploitation | None observed. No CISA KEV, no public exploit campaigns, no GreyNoise tag as of 2026-06-30. |
|---|---|
| Public PoC | No standalone PoC published. Bug reproduction is trivial from the GHSA description — single curl against the documented endpoint. |
| EPSS | 0.00214 (≈0.2%) — bottom-quartile likelihood of exploitation in the next 30 days. |
| KEV status | Not listed. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N — network, low complexity, low-privilege required, partial confidentiality only. |
| Affected versions | All OpenProject releases prior to 17.4.1 that ship the Meetings module with agenda-item linking. |
| Fixed version | OpenProject 17.4.1 (released 2026-06-08). 17.5.0 also fixed. |
| Exposure data | Shodan/Censys shows roughly 6–9k internet-reachable OpenProject instances globally; the majority are corporate self-hosted behind SSO. |
| Disclosure date | 2026-06-26, GHSA-g387-6rm2-xw88. |
| Reporter | Researcher via YesWeHack OpenProject Bug Bounty program, sponsored by the European Commission. |
noisgate verdict.
The single most decisive factor is that exploitation requires an authenticated low-privilege account on the target instance and yields only partial confidentiality on linked work-package fields — no write, no auth bypass, no host compromise. OpenProject is not a canonical high-value-role component (it is a LOB collaboration server, not an IdP, hypervisor, CA, backup, or kernel agent), so the deployment-role floor does not raise the verdict above the friction-driven assessment.
Why this verdict
- Authentication required —
PR:Lin the vector is correct; an anonymous internet scan cannot trigger this. Drops the unauthenticated-RCE class of pressure off the verdict. - Confidentiality-only impact —
I:N/A:N/S:U. No code execution, no privilege escalation, no tenant boundary break, no host takeover. Worst case is leaked work-package subjects and custom fields. - Narrow data surface — only work packages actually *linked* into agenda items are exposed; the attacker does not get arbitrary read across all projects.
- EPSS 0.00214 and not in KEV — exploitation likelihood is in the bottom quartile and there is no observed in-the-wild activity.
- Role multiplier — LOB collaboration tier: Even in its most sensitive realistic deployment (PMO/engineering tracking confidential roadmap), the chain ends at *data disclosure of linked work packages*. It does not pivot to fleet compromise, identity takeover, or supply-chain impact. OpenProject is not in the high-value-role catalog, so the CRITICAL/HIGH floor does not apply.
Why not higher?
MEDIUM would require either a meaningfully larger blast radius (cross-tenant read of *all* projects, not just linked work packages) or active exploitation evidence. Neither is present — the bug is a narrow joined-object authorization gap, EPSS is ~0.2%, KEV is silent, and no public PoC campaigns exist.
Why not lower?
IGNORE is wrong because regulated workloads (GDPR personal data, M&A codenames, customer support tickets containing PII) routinely live in OpenProject work-package descriptions, and disclosure of those fields to any authenticated user is a real compliance exposure. The bug deserves a tracked patch on the standard hygiene window, not silent backlog.
What to do — in priority order.
- Patch to OpenProject 17.4.1 or later — The only complete fix is the vendor patch. Apply within the noisgate 365-day remediation SLA for LOW; if your instance hosts regulated data, accelerate to the next routine maintenance window.
- Restrict who can be added to meetings holding cross-project work packages — Until patched, audit meeting membership. The bug only discloses what the attacker can see an agenda item for — tightening meeting invitee lists reduces the attack surface immediately.
- Disable or front the
/api/v3/meetings/*/agenda_items/*route at the reverse proxy for non-essential users — An nginx/Apache rule scoped to user-group headers (if you front OpenProject with SSO that injects them) blocks the single vulnerable endpoint while keeping the rest of the API live. - Turn on OpenProject audit logging and ship to SIEM — Detect anomalous high-volume reads of
agenda_items/:idfrom a single account. A simple rate-anomaly rule will catch enumeration attempts. - Review what lives in work-package custom fields — Pull credentials, customer PII, or pasted secrets out of free-text fields. This both shrinks the impact of *this* bug and every future OpenProject IDOR.
- WAF signature rules — there is no malicious payload to match; the request is a normal authenticated GET. Generic OWASP CRS rules will not catch it.
- MFA — the attacker is already authenticated as a legitimate user; a second factor at login does nothing.
- Network segmentation alone — most exploiters of this bug will be on-prem authenticated users, not external scanners. Putting OpenProject behind a VPN doesn't help against insiders or compromised employee accounts.
- EDR on the OpenProject host — there is no malicious process; the application itself serves the over-privileged response.
Crowdsourced verification payload.
Run this on an auditor workstation with network reach to your OpenProject server. It checks the running version via the public /api/v3 root and decides VULNERABLE / PATCHED. Requires curl and jq. Example: ./check_op_49355.sh https://openproject.corp.example.com. No credentials needed.
#!/usr/bin/env bash
# noisgate verification — CVE-2026-49355 (OpenProject meeting agenda IDOR)
# Usage: ./check_op_49355.sh https://openproject.example.com
set -u
BASE="${1:-}"
if [[ -z "$BASE" ]]; then
echo "usage: $0 https://openproject.example.com" >&2
exit 2
fi
command -v curl >/dev/null || { echo "curl required"; exit 2; }
command -v jq >/dev/null || { echo "jq required"; exit 2; }
ROOT=$(curl -fsSL --max-time 10 "$BASE/api/v3" 2>/dev/null) || {
echo "UNKNOWN — cannot reach $BASE/api/v3"; exit 3; }
VER=$(echo "$ROOT" | jq -r '.coreVersion // empty')
if [[ -z "$VER" ]]; then
echo "UNKNOWN — coreVersion not in API root response"; exit 3
fi
# Compare against 17.4.1
FIX="17.4.1"
lowest=$(printf '%s\n%s\n' "$VER" "$FIX" | sort -V | head -n1)
if [[ "$lowest" == "$FIX" && "$VER" != "$FIX" ]]; then
echo "PATCHED ($VER >= $FIX)"
exit 0
elif [[ "$VER" == "$FIX" ]]; then
echo "PATCHED ($VER == $FIX)"
exit 0
else
echo "VULNERABLE ($VER < $FIX) — CVE-2026-49355"
exit 1
fi
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.