CVE-2026-52772: Yeswiki/yeswiki Cross-Site Scripting Vulnerability

MODERATE
Advisory Identifier: GHSA-xc7j-3g8q-9vh4
CVSS 5.5
CWE-116 CWE-79
Published
Advisory Summary

CVE-2026-52772 is a MODERATE (CVSS 5.5) cross-site scripting vulnerability (CWE-116: Improper Encoding or Escaping of Output) affecting Yeswiki/yeswiki libraries in the Packagist ecosystem. The issue involves yeswiki has stored xss in bazar form-field templates via unescaped field.label / field.hint (|raw('html')). Remediation is available by upgrading affected packages to version 4.6.6.

CVE-2026-52772 at a Glance

CVE Identifier CVE-2026-52772
GHSA Identifier GHSA-xc7j-3g8q-9vh4
Severity MODERATE
CVSS v3.1 Base Score 5.5
CWE Classification CWE-116: Improper Encoding or Escaping of Output
Affected Product Yeswiki/yeswiki
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 5.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required High (PR:H)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality Low (C:L)
Integrity Low (I:L)
Availability None (A:N)

How to Fix CVE-2026-52772

To remediate this vulnerability, upgrade all affected Yeswiki/yeswiki dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

yeswiki/yeswiki fixed: 4.6.6
Upgrade yeswiki/yeswiki to 4.6.6
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for yeswiki/yeswiki

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-52772, inspect your project dependencies for Yeswiki/yeswiki:

inspect dependencies for yeswiki/yeswiki

Analyze the terminal output to locate any version of Yeswiki/yeswiki in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.6.6), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-116: Improper Encoding or Escaping of Output, where the product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. as a result, the intended structure of the message is not preserved. In Yeswiki/yeswiki, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

# Bazar form-field templates still apply `|raw('html')` to `field.label` / `field.hint` in attribute and label-body contexts — stored XSS in form renders (sibling class of commit `e6b66aa`) **CWE**: CWE-79 (Improper Neutralization of Input During Web Page Generation, "Cross-site Scripting") via CWE-116 (Improper Encoding or Escaping of Output) — same class as the partial fix at commit `e6b66aa` **CVSS v3.1**: `CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N` → 4.7 (Medium) (Privileges Required = High because writing the field definitions requires `saisie_formulaire`, which `tools/bazar/services/Guard.php:58-61` grants only to admins by default; Scope = Changed because the XSS payload set by a form-editor admin executes in the origin context of arbitrary viewers, including unauthenticated visitors.) ## Summary Commit `e6b66aa` ("fix(bazar): leave the twig escape placeholder as is", 2026-05-19) recognised that emitting `field.label` through Twig's `raw('html')` filter into an HTML attribute is unsafe — Twig's `raw` marker suppresses the attribute auto-escape, `striptags` removes `<…>` tags but not `"`, so a label containing `"` can break out of the attribute and inject event-handler attributes. The commit fixed `tools/bazar/templates/inputs/text.twig:19` and `tools/bazar/templates/inputs/textarea.twig:3`. At least seven additional templates have the same pattern and were not touched by the fix: - `tools/bazar/templates/inputs/range.twig:19` — `placeholder="{{ field.label|raw('html')|striptags }}"` - `tools/bazar/templates/inputs/email.twig:13` — `placeholder="{{ field.label|raw('html')|striptags }}"` - `tools/bazar/templates/layouts/input.twig:7` — `title="{{ field.hint|raw('html') }}" alt="{{ field.hint|raw('html') }}"` - `tools/bazar/templates/inputs/textarea.twig:14` — same `title=`/`alt=` pattern (the commit only fixed line 3, line 14 remains) - `tools/bazar/templates/inputs/user.twig:41, 55` — same - `tools/bazar/templates/inputs/bookmarklet.twig:4` — same - `tools/bazar/templates/layouts/input.twig:9`, `tools/bazar/templates/layouts/field.twig:5`, `tools/bazar/templates/inputs/subscribe.twig:16`, `tools/bazar/templates/inputs/linked-entry.twig:4`, `tools/bazar/templates/inputs/textarea.twig:16`, `tools/bazar/templates/inputs/bookmarklet.twig:6` — `{{ field.label|raw }}` *outside* an attribute (label-body), with no `striptags` at all, so direct tag injection (`<img src=x onerror=…>`) executes The `layouts/input.twig` and `layouts/field.twig` files are base layouts inherited by **every** Bazar field type, so a single malicious `field.hint` reaches into every form that uses that field. ## Affected - YesWiki `doryphore` at HEAD `6c653dd` (the audit checkout) - All releases that ship the listed templates with the `|raw('html')` / `|raw` filter in attribute or label-body context ## Vulnerability details ### [A] — Source: `field.label` and `field.hint` are populated from form definitions `tools/bazar/fields/BazarField.php:46-53`: ```php $this->label = empty($values[self::FIELD_LABEL]) ? '' : html_entity_decode($values[self::FIELD_LABEL]); $this->size = $values[self::FIELD_SIZE]; $this->maxChars = $values[self::FIELD_MAX_CHARS]; $this->default = $values[self::FIELD_DEFAULT]; $this->required = $values[self::FIELD_REQUIRED] == 1; $this->searchable = $values[self::FIELD_SEARCHABLE]; $this->hint = $values[self::FIELD_HINT]; // [A] no decoding/escaping ``` `field.label` is `html_entity_decode($values[FIELD_LABEL])` — the decode actively *turns* HTML-entity-encoded payloads (`&quot;`, `&#34;`) back into raw `"`, defeating any entity-encoded mitigation a form author might apply. `field.hint` is the raw string from the form definition. Both flow into the field's `__toString`-like context unchanged. Form definitions are written by users with the `saisie_formulaire` ACL (`tools/bazar/services/Guard.php:45-62` — admins by default; the same ACL the audit team chose to gate `imported-form` POST handling under in commit `fe7244b`). ### [B] — Sink class 1: attribute-context `|raw('html')|striptags` (placeholder breakout) `tools/bazar/templates/inputs/range.twig:19`: ```twig placeholder="{{ field.label|raw('html')|striptags }}" ``` `tools/bazar/templates/inputs/email.twig:13`: ```twig placeholder="{{ field.label|raw('html')|striptags }}" ``` `raw('html')` marks the value as a `Markup` object, which causes Twig's HTML auto-escaper to skip it (`Twig\Markup::__toString`). `striptags` removes `<…>` sequences but does not touch `"`, `'`, or `=`. A `field.label` of: ``` hi" onmouseover="alert(document.cookie)" x=" ``` passes `striptags` unchanged, is marked safe by `raw('html')`, and lands inside the attribute as: ```html placeholder="hi" onmouseover="alert(document.cookie)" x="" ``` The injected `onmouseover` fires when a viewer hovers the input. Same vector as the pre-fix `text.twig:19`. ### [C] — Sink class 2: attribute-context `|raw('html')` *without* `striptags` (worse) `tools/bazar/templates/layouts/input.twig:7`: ```twig {% if field.hint %} <img loading="lazy" class="tooltip_aide" title="{{ field.hint|raw('html') }}" alt="{{ field.hint|raw('html') }}" src="tools/bazar/presentation/images/aide.png" width="16" height="16" /> {% endif %} ``` Identical patterns in `tools/bazar/templates/inputs/textarea.twig:14`, `tools/bazar/templates/inputs/user.twig:41`, `tools/bazar/templates/inputs/user.twig:55`, `tools/bazar/templates/inputs/bookmarklet.twig:4`. There is no `striptags` here at all, so the attacker has the full attribute-injection alphabet plus full HTML if the parser desynchronises. Setting `field.hint = '"><script>alert(1)</script>'` gives: ```html <img … title=""><script>alert(1)</script>" alt="…" … ``` The `<script>` runs at page parse time. Because `layouts/input.twig` is extended by **every** field-type template, a single malicious `field.hint` on any field in any form propagates into every form render. ### [D] — Sink class 3: label-body `|raw` (direct DOM injection) `tools/bazar/templates/layouts/input.twig:9`: ```twig {{ field.label|raw }} ``` `tools/bazar/templates/layouts/field.twig:5`: ```twig {%- block label -%}{{ field.label|raw }}{%- endblock -%} ``` Plus `subscribe.twig:16`, `linked-entry.twig:4`, `textarea.twig:16`, `bookmarklet.twig:6`. These are outside any attribute, in the body of a `<label>` element. `raw` suppresses escaping, there is no `striptags`. `field.label = '<img src=x onerror=alert(1)>'` injects an `<img>` tag straight into the label DOM; the `onerror` fires the moment the page renders, with no user interaction. ### Why the fix at `e6b66aa` is incomplete The fix correctly replaced `field.label | raw('html') | striptags` with `field.label | striptags | trim` (no `raw`) in `text.twig`'s placeholder and `textarea.twig`'s textarea placeholder. The fix is the right pattern — drop the `raw` so Twig's attribute-context autoescaper does its job — but it was applied at two specific call sites instead of being treated as a class-wide replacement. The siblings above use the same `|raw('html')|striptags` or `|raw('html')` idiom and are all currently exploitable. ## Proof of concept ### Setup 1. Install YesWiki and log in as admin (or as any user with the `saisie_formulaire` ACL). 2. Navigate to *Bazar → Formulaires → Nouveau formulaire* and create a form. Add any field of type `range`, `email`, or any other field type (every field type renders through `layouts/input.twig`, so the `title=` / `alt=` / label-body vectors apply universally). ### PoC 1 — `range.twig` placeholder attribute breakout (Sink class [B]) Set the field's label to: ``` Enter value" onmouseover="alert('XSS via field.label in range.twig')" x=" ``` Save the form. Have any visitor (including unauthenticated guests if the form is published) open a page that renders the form. Hovering the range input fires the injected handler. Rendered HTML: ```html <input type="range" … placeholder="Enter value" onmouseover="alert('XSS via field.label in range.twig')" x="" required /> ``` ### PoC 2 — `layouts/input.twig` tooltip injection (Sink class [C]) Set the field's `hint` (Aide) to: ``` "><script>alert('XSS via field.hint in layouts/input.twig — fires on EVERY field type')</script><span x=" ``` Save. Any page that renders the form executes the script at parse time, before any user interaction. The vector is universal because `layouts/input.twig` is the base template extended by every field type. ### PoC 3 — `layouts/input.twig` label-body injection (Sink class [D]) Set the field's label to: ``` <img src=x onerror="alert('XSS via field.label in layouts/input.twig')"> ``` Save. Page render fires the `onerror` immediately — no hover, no click, no `striptags` filter in the way. ## Impact ### Direct - **Stored XSS on every visitor of any Bazar form page** — a privileged form editor injects script into a field's label/hint and the script runs in the wiki origin against every viewer of the form, including unauthenticated guests. Cookie theft, session hijack of any admin who visits, full content modification, phishing overlays. - **Universal sink in `layouts/input.twig`** — sinks [C] and [D] live in the base layout extended by every field type, so a single field with a malicious hint poisons every form render across the wiki, not just forms using a specific input type. ### Indirect / second-order - **Privilege amplification despite `saisie_formulaire` being admin-only by default** — many deployments grant `saisie_formulaire` to specific user groups (per-deployment ACL configured via `config['permissions']['action']['saisie_formulaire']`). For those deployments, the bug is exploitable by any user in those groups against any visitor. The audit pattern at commit `fe7244b` (the same team explicitly gated `imported-form` POST handling on `saisie_formulaire`) demonstrates that `saisie_formulaire` is in fact a "trusted-input" boundary — outputs of that boundary should not assume HTML-safety. - **Composability with the unpatched POI/CSRF in `BazarImportAction` (reported separately as `01-bazarimport-poi-csrf.md`)** — once any XSS exists in the wiki origin, an attacker can fetch a CSRF token (if added as part of the POI fix) and chain XSS → POI → RCE without needing to phish the admin onto a third-party origin. - **The pre-`fe7244b` window** — for any deployment still running a build that predates `fe7244b` (the `imported-form` auth fix from 2026-05-12), the source of `field.label` / `field.hint` was reachable from **unauthenticated** POST to the `imported-form` handler, making this finding unauth-stored-XSS on those builds. The current code path closes that source side, but reinforces that the sink-side fix at `e6b66aa` should be applied class-wide. ## Suggested fix Apply the same transformation `e6b66aa` applied to `text.twig` / `textarea.twig` placeholders, class-wide: - For attribute contexts (`placeholder=`, `title=`, `alt=`, etc.) — drop the `raw` filter. Let Twig's attribute-context autoescape handle the value: ```twig placeholder="{{ field.label|striptags|trim }}" title="{{ field.hint|striptags|trim }}" alt="{{ field.hint|striptags|trim }}" ``` `striptags` is fine to keep if there's a UX reason to strip incidental HTML; the security is in the absence of `raw`. - For label-body contexts (`<label>{{ field.label|raw }}</label>`) — decide which is the design intent and apply it everywhere: - If labels really need to render bold/italic/links: pass `field.label` through `HtmlPurifierService::cleanHTML()` at the point where the field object is constructed (i.e. `BazarField::__construct`'s `$this->label = …` line), so any subsequent template emits already-purified HTML and `raw` becomes safe. - If labels are plain text: drop the `raw` filter and let `{{ field.label }}` autoescape. The label-body case in `layouts/input.twig:9`, `layouts/field.twig:5`, and the four `inputs/*.twig` files is the highest-impact patch target because it's reached by every field type; the attribute-context cases are more surgical. Sweep target list (all in `tools/bazar/templates/`): - `inputs/range.twig:19` - `inputs/email.twig:13` - `layouts/input.twig:7, 9` - `layouts/field.twig:5` - `inputs/textarea.twig:14, 16` - `inputs/user.twig:41, 55` - `inputs/bookmarklet.twig:4, 6` - `inputs/subscribe.twig:16` - `inputs/linked-entry.twig:4` A grep-driven CI check for `|raw('html')` and `|raw` inside Bazar twig templates would surface any future reintroduction.

CVE-2026-52772 Affected Yeswiki/yeswiki Versions

1 component(s)
Fixed Versions:
4.6.6
Product / Package Ecosystem Affected Versions Fixed Version
yeswiki/yeswiki
Packagist
< 4.6.6
4.6.6

References

Frequently Asked Questions

What is CVE-2026-52772?
CVE-2026-52772 is a security vulnerability in Yeswiki/yeswiki involving YesWiki has stored XSS in Bazar form-field templates via unescaped field.label / field.hint (|raw('html')) under CWE-116: Improper Encoding or Escaping of Output.
Which Yeswiki/yeswiki versions are affected?
Affected versions include releases prior to 4.6.6 across affected packages: yeswiki/yeswiki.
What version fixes CVE-2026-52772?
Upgrading to 4.6.6 (or the latest patched release for your respective branch) resolves the vulnerability.
Is yeswiki/yeswiki affected?
Yes, yeswiki/yeswiki is affected in releases prior to 4.6.6. Upgrade your dependency to 4.6.6 or later.
What is the CVSS score of CVE-2026-52772?
CVE-2026-52772 has a CVSS v3.1 base score of 5.5 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N.
What CWE is associated with CVE-2026-52772?
CVE-2026-52772 is classified under CWE-116: Improper Encoding or Escaping of Output (The product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. As a result, the intended structure of the message is not preserved.).
How can I check Packagist dependencies for CVE-2026-52772?
Run the dependency tree command: 'inspect dependencies for yeswiki/yeswiki'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-xc7j-3g8q-9vh4",
  "modified" : "2026-07-09T21:00:33Z",
  "published" : "2026-07-09T21:00:33Z",
  "aliases" : [ "CVE-2026-52772" ],
  "summary" : "YesWiki has stored XSS in Bazar form-field templates via unescaped field.label / field.hint (|raw('html'))",
  "details" : "# Bazar form-field templates still apply `|raw('html')` to `field.label` / `field.hint` in attribute and label-body contexts — stored XSS in form renders (sibling class of commit `e6b66aa`)\n\n**CWE**: CWE-79 (Improper Neutralization of Input During Web Page Generation, \"Cross-site Scripting\") via CWE-116 (Improper Encoding or Escaping of Output) — same class as the partial fix at commit `e6b66aa`\n\n**CVSS v3.1**: `CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N` → 4.7 (Medium)\n\n(Privileges Required = High because writing the field definitions requires `saisie_formulaire`, which `tools/bazar/services/Guard.php:58-61` grants only to admins by default; Scope = Changed because the XSS payload set by a form-editor admin executes in the origin context of arbitrary viewers, including unauthenticated visitors.)\n\n## Summary\n\nCommit `e6b66aa` (\"fix(bazar): leave the twig escape placeholder as is\", 2026-05-19) recognised that emitting `field.label` through Twig's `raw('html')` filter into an HTML attribute is unsafe — Twig's `raw` marker suppresses the attribute auto-escape, `striptags` removes `<…>` tags but not `\"`, so a label containing `\"` can break out of the attribute and inject event-handler attributes. The commit fixed `tools/bazar/templates/inputs/text.twig:19` and `tools/bazar/templates/inputs/textarea.twig:3`.\n\nAt least seven additional templates have the same pattern and were not touched by the fix:\n\n- `tools/bazar/templates/inputs/range.twig:19` — `placeholder=\"{{ field.label|raw('html')|striptags }}\"`\n- `tools/bazar/templates/inputs/email.twig:13` — `placeholder=\"{{ field.label|raw('html')|striptags }}\"`\n- `tools/bazar/templates/layouts/input.twig:7` — `title=\"{{ field.hint|raw('html') }}\" alt=\"{{ field.hint|raw('html') }}\"`\n- `tools/bazar/templates/inputs/textarea.twig:14` — same `title=`/`alt=` pattern (the commit only fixed line 3, line 14 remains)\n- `tools/bazar/templates/inputs/user.twig:41, 55` — same\n- `tools/bazar/templates/inputs/bookmarklet.twig:4` — same\n- `tools/bazar/templates/layouts/input.twig:9`, `tools/bazar/templates/layouts/field.twig:5`, `tools/bazar/templates/inputs/subscribe.twig:16`, `tools/bazar/templates/inputs/linked-entry.twig:4`, `tools/bazar/templates/inputs/textarea.twig:16`, `tools/bazar/templates/inputs/bookmarklet.twig:6` — `{{ field.label|raw }}` *outside* an attribute (label-body), with no `striptags` at all, so direct tag injection (`<img src=x onerror=…>`) executes\n\nThe `layouts/input.twig` and `layouts/field.twig` files are base layouts inherited by **every** Bazar field type, so a single malicious `field.hint` reaches into every form that uses that field.\n\n## Affected\n\n- YesWiki `doryphore` at HEAD `6c653dd` (the audit checkout)\n- All releases that ship the listed templates with the `|raw('html')` / `|raw` filter in attribute or label-body context\n\n## Vulnerability details\n\n### [A] — Source: `field.label` and `field.hint` are populated from form definitions\n\n`tools/bazar/fields/BazarField.php:46-53`:\n\n```php\n$this->label = empty($values[self::FIELD_LABEL]) ? '' : html_entity_decode($values[self::FIELD_LABEL]);\n$this->size = $values[self::FIELD_SIZE];\n$this->maxChars = $values[self::FIELD_MAX_CHARS];\n$this->default = $values[self::FIELD_DEFAULT];\n$this->required = $values[self::FIELD_REQUIRED] == 1;\n$this->searchable = $values[self::FIELD_SEARCHABLE];\n$this->hint = $values[self::FIELD_HINT];                       // [A] no decoding/escaping\n```\n\n`field.label` is `html_entity_decode($values[FIELD_LABEL])` — the decode actively *turns* HTML-entity-encoded payloads (`&quot;`, `&#34;`) back into raw `\"`, defeating any entity-encoded mitigation a form author might apply. `field.hint` is the raw string from the form definition. Both flow into the field's `__toString`-like context unchanged. Form definitions are written by users with the `saisie_formulaire` ACL (`tools/bazar/services/Guard.php:45-62` — admins by default; the same ACL the audit team chose to gate `imported-form` POST handling under in commit `fe7244b`).\n\n### [B] — Sink class 1: attribute-context `|raw('html')|striptags` (placeholder breakout)\n\n`tools/bazar/templates/inputs/range.twig:19`:\n\n```twig\nplaceholder=\"{{ field.label|raw('html')|striptags }}\"\n```\n\n`tools/bazar/templates/inputs/email.twig:13`:\n\n```twig\nplaceholder=\"{{ field.label|raw('html')|striptags }}\"\n```\n\n`raw('html')` marks the value as a `Markup` object, which causes Twig's HTML auto-escaper to skip it (`Twig\\Markup::__toString`). `striptags` removes `<…>` sequences but does not touch `\"`, `'`, or `=`. A `field.label` of:\n\n```\nhi\" onmouseover=\"alert(document.cookie)\" x=\"\n```\n\npasses `striptags` unchanged, is marked safe by `raw('html')`, and lands inside the attribute as:\n\n```html\nplaceholder=\"hi\" onmouseover=\"alert(document.cookie)\" x=\"\"\n```\n\nThe injected `onmouseover` fires when a viewer hovers the input. Same vector as the pre-fix `text.twig:19`.\n\n### [C] — Sink class 2: attribute-context `|raw('html')` *without* `striptags` (worse)\n\n`tools/bazar/templates/layouts/input.twig:7`:\n\n```twig\n{% if field.hint %}\n    <img loading=\"lazy\" class=\"tooltip_aide\" title=\"{{ field.hint|raw('html') }}\" alt=\"{{ field.hint|raw('html') }}\" src=\"tools/bazar/presentation/images/aide.png\" width=\"16\" height=\"16\" />\n{% endif %}\n```\n\nIdentical patterns in `tools/bazar/templates/inputs/textarea.twig:14`, `tools/bazar/templates/inputs/user.twig:41`, `tools/bazar/templates/inputs/user.twig:55`, `tools/bazar/templates/inputs/bookmarklet.twig:4`.\n\nThere is no `striptags` here at all, so the attacker has the full attribute-injection alphabet plus full HTML if the parser desynchronises. Setting `field.hint = '\"><script>alert(1)</script>'` gives:\n\n```html\n<img … title=\"\"><script>alert(1)</script>\" alt=\"…\" …\n```\n\nThe `<script>` runs at page parse time. Because `layouts/input.twig` is extended by **every** field-type template, a single malicious `field.hint` on any field in any form propagates into every form render.\n\n### [D] — Sink class 3: label-body `|raw` (direct DOM injection)\n\n`tools/bazar/templates/layouts/input.twig:9`:\n\n```twig\n{{ field.label|raw }}\n```\n\n`tools/bazar/templates/layouts/field.twig:5`:\n\n```twig\n{%- block label -%}{{ field.label|raw }}{%- endblock -%}\n```\n\nPlus `subscribe.twig:16`, `linked-entry.twig:4`, `textarea.twig:16`, `bookmarklet.twig:6`.\n\nThese are outside any attribute, in the body of a `<label>` element. `raw` suppresses escaping, there is no `striptags`. `field.label = '<img src=x onerror=alert(1)>'` injects an `<img>` tag straight into the label DOM; the `onerror` fires the moment the page renders, with no user interaction.\n\n### Why the fix at `e6b66aa` is incomplete\n\nThe fix correctly replaced `field.label | raw('html') | striptags` with `field.label | striptags | trim` (no `raw`) in `text.twig`'s placeholder and `textarea.twig`'s textarea placeholder. The fix is the right pattern — drop the `raw` so Twig's attribute-context autoescaper does its job — but it was applied at two specific call sites instead of being treated as a class-wide replacement. The siblings above use the same `|raw('html')|striptags` or `|raw('html')` idiom and are all currently exploitable.\n\n## Proof of concept\n\n### Setup\n\n1. Install YesWiki and log in as admin (or as any user with the `saisie_formulaire` ACL).\n2. Navigate to *Bazar → Formulaires → Nouveau formulaire* and create a form. Add any field of type `range`, `email`, or any other field type (every field type renders through `layouts/input.twig`, so the `title=` / `alt=` / label-body vectors apply universally).\n\n### PoC 1 — `range.twig` placeholder attribute breakout (Sink class [B])\n\nSet the field's label to:\n\n```\nEnter value\" onmouseover=\"alert('XSS via field.label in range.twig')\" x=\"\n```\n\nSave the form. Have any visitor (including unauthenticated guests if the form is published) open a page that renders the form. Hovering the range input fires the injected handler.\n\nRendered HTML:\n\n```html\n<input type=\"range\" … placeholder=\"Enter value\" onmouseover=\"alert('XSS via field.label in range.twig')\" x=\"\" required />\n```\n\n### PoC 2 — `layouts/input.twig` tooltip injection (Sink class [C])\n\nSet the field's `hint` (Aide) to:\n\n```\n\"><script>alert('XSS via field.hint in layouts/input.twig — fires on EVERY field type')</script><span x=\"\n```\n\nSave. Any page that renders the form executes the script at parse time, before any user interaction. The vector is universal because `layouts/input.twig` is the base template extended by every field type.\n\n### PoC 3 — `layouts/input.twig` label-body injection (Sink class [D])\n\nSet the field's label to:\n\n```\n<img src=x onerror=\"alert('XSS via field.label in layouts/input.twig')\">\n```\n\nSave. Page render fires the `onerror` immediately — no hover, no click, no `striptags` filter in the way.\n\n## Impact\n\n### Direct\n\n- **Stored XSS on every visitor of any Bazar form page** — a privileged form editor injects script into a field's label/hint and the script runs in the wiki origin against every viewer of the form, including unauthenticated guests. Cookie theft, session hijack of any admin who visits, full content modification, phishing overlays.\n- **Universal sink in `layouts/input.twig`** — sinks [C] and [D] live in the base layout extended by every field type, so a single field with a malicious hint poisons every form render across the wiki, not just forms using a specific input type.\n\n### Indirect / second-order\n\n- **Privilege amplification despite `saisie_formulaire` being admin-only by default** — many deployments grant `saisie_formulaire` to specific user groups (per-deployment ACL configured via `config['permissions']['action']['saisie_formulaire']`). For those deployments, the bug is exploitable by any user in those groups against any visitor. The audit pattern at commit `fe7244b` (the same team explicitly gated `imported-form` POST handling on `saisie_formulaire`) demonstrates that `saisie_formulaire` is in fact a \"trusted-input\" boundary — outputs of that boundary should not assume HTML-safety.\n- **Composability with the unpatched POI/CSRF in `BazarImportAction` (reported separately as `01-bazarimport-poi-csrf.md`)** — once any XSS exists in the wiki origin, an attacker can fetch a CSRF token (if added as part of the POI fix) and chain XSS → POI → RCE without needing to phish the admin onto a third-party origin.\n- **The pre-`fe7244b` window** — for any deployment still running a build that predates `fe7244b` (the `imported-form` auth fix from 2026-05-12), the source of `field.label` / `field.hint` was reachable from **unauthenticated** POST to the `imported-form` handler, making this finding unauth-stored-XSS on those builds. The current code path closes that source side, but reinforces that the sink-side fix at `e6b66aa` should be applied class-wide.\n\n## Suggested fix\n\nApply the same transformation `e6b66aa` applied to `text.twig` / `textarea.twig` placeholders, class-wide:\n\n- For attribute contexts (`placeholder=`, `title=`, `alt=`, etc.) — drop the `raw` filter. Let Twig's attribute-context autoescape handle the value:\n\n  ```twig\n  placeholder=\"{{ field.label|striptags|trim }}\"\n  title=\"{{ field.hint|striptags|trim }}\"\n  alt=\"{{ field.hint|striptags|trim }}\"\n  ```\n\n  `striptags` is fine to keep if there's a UX reason to strip incidental HTML; the security is in the absence of `raw`.\n\n- For label-body contexts (`<label>{{ field.label|raw }}</label>`) — decide which is the design intent and apply it everywhere:\n  - If labels really need to render bold/italic/links: pass `field.label` through `HtmlPurifierService::cleanHTML()` at the point where the field object is constructed (i.e. `BazarField::__construct`'s `$this->label = …` line), so any subsequent template emits already-purified HTML and `raw` becomes safe.\n  - If labels are plain text: drop the `raw` filter and let `{{ field.label }}` autoescape.\n\nThe label-body case in `layouts/input.twig:9`, `layouts/field.twig:5`, and the four `inputs/*.twig` files is the highest-impact patch target because it's reached by every field type; the attribute-context cases are more surgical.\n\nSweep target list (all in `tools/bazar/templates/`):\n\n- `inputs/range.twig:19`\n- `inputs/email.twig:13`\n- `layouts/input.twig:7, 9`\n- `layouts/field.twig:5`\n- `inputs/textarea.twig:14, 16`\n- `inputs/user.twig:41, 55`\n- `inputs/bookmarklet.twig:4, 6`\n- `inputs/subscribe.twig:16`\n- `inputs/linked-entry.twig:4`\n\nA grep-driven CI check for `|raw('html')` and `|raw` inside Bazar twig templates would surface any future reintroduction.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "yeswiki/yeswiki"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "4.6.6"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/YesWiki/yeswiki/security/advisories/GHSA-xc7j-3g8q-9vh4"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/YesWiki/yeswiki/commit/5d1a4d07fecb0706f33e5dfbbe6ff5ef1892b2a7"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/YesWiki/yeswiki"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-116", "CWE-79" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-09T21:00:33Z",
    "nvd_published_at" : null
  }
}