# XSS in ZenTao integration affecting self hosted instances without strict CSP

ZenTao integration serializer passes attacker-controlled issue URL into breadcrumb link unsanitized, enabling javascript: XSS on self-hosted GitLab when victim clicks.

- Payout: hidden
- Severity: high
- Program: GitLab
- Found by: joaxcar
- Disclosed: 2022-09-22
- Write-up quality: 7/10
- Original: https://hackerone.com/reports/1542510
- Library page: https://bugbountylibrary.com/r/h1%3A1542510
- Tags: area:web domain:web impact:integrity kind:bounty platform:hackerone sev:high tech:ruby technique:chained technique:source-review vuln:xss

**Target:** GitLab ZenTao external issue tracker integration (issue detail page)
**Root cause:** The ZenTao issue serializer exposes web_url directly from the API response's `url` field without validating the scheme, and `id` is Ruby-sanitized but not HTML-encoded, so both flow into breadcrumb links on the issue detail page.
**Steps:**
1. Configure a project's ZenTao integration pointing server to attacker host `https://joaxcar.com`
2. Host a server returning `application/json` at `/api.php/v1/issues/story-1` with payload where `url` is `javascript:alert(document.domain)` and `id` is `<img src=# height=10000 width=10000>`
3. Visit `https://gitlab.example.com/user1/project1/-/integrations/zentao/issues/story-1`
4. Click the injected giant image/breadcrumb link to trigger `javascript:` XSS
**How found:** Prior related report on another integration (1533976) prompted reviewing the ZenTao serializer; reading issue_entity.rb revealed `web_url` exposes `item['url']` unchecked and `id` is sanitized but not encoded, then tested by pointing integration at a controlled server.
**Exploitation:** Attacker controls the configured ZenTao server, returns a crafted JSON issue whose `url` is a javascript: URI rendered as the breadcrumb link; victim clicks it and script executes in GitLab origin. Requires premium self-hosted instance without strict CSP; GitLab.com CSP blocks it.
**Insight:** When an integration renders fields from a third-party API response, check every exposed field for scheme validation (javascript:/data:) and HTML-encoding, not just the ones that look like IDs; a Ruby sanitize call on one field doesn't protect sibling fields like URLs.
