Skip to content

fix: remove redundant htmlspecialchars from JSON API responses#94

Merged
CybotTM merged 2 commits intomainfrom
fix/review-fixes
Mar 5, 2026
Merged

fix: remove redundant htmlspecialchars from JSON API responses#94
CybotTM merged 2 commits intomainfrom
fix/review-fixes

Conversation

@CybotTM
Copy link
Member

@CybotTM CybotTM commented Mar 5, 2026

Summary

Remove redundant htmlspecialchars() encoding from all JSON API responses. JSON encoding (json_encode) inherently prevents XSS — content is transmitted as data, not rendered markup. The frontend already sanitizes via DOMParser before DOM insertion.

The old pattern created an unnecessary encode→decode roundtrip:

  1. Server: htmlspecialchars() escapes <, >, &, ', "
  2. Frontend: _decodeEntities() reverses the escaping via a hidden textarea
  3. Frontend: _sanitizeHtml() sanitizes before DOM insertion

Now the flow is simply:

  1. Server: returns raw content in JSON
  2. Frontend: _sanitizeHtml() sanitizes before DOM insertion

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Related Issues

Checklist

  • I have run make lint and it passes
  • I have run make test and all tests pass
  • I have updated the documentation if needed
  • I have added tests that prove my fix/feature works
  • My code follows the project's code style (PSR-12)
  • I have updated CHANGELOG.md if this is a user-facing change

Test Plan

  • 388 PHP unit tests pass (including updated assertions for raw content)
  • 122 JS tests pass (including removal of _decodeEntities)
  • Verify in browser: open cowriter dialog, run a completion, confirm content renders correctly (HTML tags in LLM responses should appear as-is in CKEditor, not as escaped entities)

Files Changed

File Change
Classes/Controller/AjaxController.php Remove htmlspecialchars() from chat, stream, tasks responses
Classes/Domain/DTO/CompleteResponse.php Remove htmlspecialchars() from success() factory
Resources/Public/JavaScript/Ckeditor/CowriterDialog.js Remove _decodeEntities() method and usage
Tests/Unit/Controller/AjaxControllerTest.php Expect raw content in assertions
Tests/Unit/Domain/DTO/CompleteResponseTest.php Expect raw content in assertions
Tests/E2E/CowriterWorkflowTest.php Expect raw content in assertions

JSON encoding inherently prevents XSS — content is data, not rendered
markup. The server-side HTML escaping created an unnecessary
encode-then-decode roundtrip (server escapes → frontend _decodeEntities
→ raw HTML for CKEditor). The frontend already sanitizes via DOMParser
before DOM insertion.

Changes:
- Remove htmlspecialchars() from AjaxController (chat, stream, tasks)
- Remove htmlspecialchars() from CompleteResponse DTO
- Remove _decodeEntities() from CowriterDialog.js frontend
- Update all unit and E2E tests to expect raw content

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copilot AI review requested due to automatic review settings March 5, 2026 06:59
@CybotTM CybotTM requested a review from a team as a code owner March 5, 2026 06:59
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the application's XSS prevention strategy by removing redundant HTML escaping from JSON API responses on the backend. The change leverages the inherent XSS protection provided by JSON encoding and centralizes content sanitization to the frontend's _sanitizeHtml() function before DOM insertion. This simplifies the data flow, eliminates an unnecessary encode-decode cycle, and improves the efficiency of content delivery.

Highlights

  • Removed Redundant HTML Escaping: Eliminated htmlspecialchars() calls from PHP backend JSON API responses, as JSON encoding inherently prevents XSS.
  • Simplified Frontend Content Handling: Removed the _decodeEntities() function and its usage from the JavaScript frontend, as it is no longer needed to reverse server-side escaping.
  • Updated Test Assertions: Modified unit and end-to-end tests to reflect the change, asserting that JSON responses now contain raw, unescaped content.
Changelog
  • Classes/Controller/AjaxController.php
    • Removed htmlspecialchars() from chat, stream, and task responses.
    • Updated class docblock to clarify XSS prevention strategy.
  • Classes/Domain/DTO/CompleteResponse.php
    • Removed htmlspecialchars() from the success() factory method.
    • Updated class and method docblocks to reflect raw content handling.
  • Resources/Public/JavaScript/Ckeditor/CowriterDialog.js
    • Removed the _decodeEntities() private method.
    • Updated content processing to directly use raw result.content for sanitization.
  • Tests/E2E/CowriterWorkflowTest.php
    • Renamed test methods to reflect raw content handling (e.g., completeWorkflowEscapesXssFromLlm to completeWorkflowReturnsRawLlmContent).
    • Modified assertions to expect raw, unescaped content in JSON responses.
  • Tests/Unit/Controller/AjaxControllerTest.php
    • Renamed test methods to reflect raw content handling (e.g., chatActionEscapesHtmlInResponse to chatActionReturnsRawContentInResponse).
    • Adjusted assertions to verify raw content, model names, and finish reasons.
  • Tests/Unit/Domain/DTO/CompleteResponseTest.php
    • Renamed test methods (e.g., successEscapesHtmlInContent to successPreservesRawHtmlContent).
    • Updated data providers and assertions to confirm CompleteResponse::success preserves raw content.
Activity
  • No human activity (comments, reviews) has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Thanks for your first pull request! We are excited to have you contribute.
A maintainer will review your PR soon. Please ensure all CI checks pass.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

github-actions[bot]
github-actions bot previously approved these changes Mar 5, 2026
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for solo maintainer project

This PR has passed all automated quality gates:

  • ✅ Static analysis (PHPStan)
  • ✅ Code style (PHP-CS-Fixer)
  • ✅ Unit & functional tests
  • ✅ Security scanning
  • ✅ Dependency review

See SECURITY_CONTROLS.md for compensating controls documentation.

@codecov
Copy link

codecov bot commented Mar 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.66%. Comparing base (bdffb66) to head (077c932).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main      #94      +/-   ##
============================================
- Coverage     98.67%   98.66%   -0.01%     
  Complexity      229      229              
============================================
  Files            14       14              
  Lines          1282     1277       -5     
  Branches        110      110              
============================================
- Hits           1265     1260       -5     
  Misses           17       17              
Flag Coverage Δ
functional 0.00% <0.00%> (ø)
javascript 99.59% <100.00%> (-0.01%) ⬇️
php-e2e 34.87% <72.72%> (ø)
php-integration 12.82% <27.27%> (ø)
php-unit 98.97% <100.00%> (ø)
unit 98.97% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
Classes/Controller/AjaxController.php 99.16% <100.00%> (ø)
Classes/Domain/DTO/CompleteResponse.php 100.00% <100.00%> (ø)
...urces/Public/JavaScript/Ckeditor/CowriterDialog.js 99.67% <100.00%> (-0.01%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses the redundant HTML escaping in JSON API responses by removing htmlspecialchars() calls from the PHP backend and the corresponding _decodeEntities() method from the JavaScript frontend. The updated approach correctly relies on json_encode for inherent XSS prevention in JSON data and delegates content sanitization for DOM insertion to the frontend. The comprehensive updates to unit and E2E tests confirm the new behavior, ensuring that raw content is now correctly transmitted and expected. This change simplifies the data flow and removes unnecessary encode/decode cycles, which is a significant improvement.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes server-side htmlspecialchars() escaping from cowriter JSON/SSE responses and updates the CKEditor integration and tests to work with raw (unescaped) content.

Changes:

  • Stop HTML-escaping content, model, and finishReason in AjaxController JSON and SSE responses.
  • Stop HTML-escaping CompleteResponse::success() fields and update its documentation accordingly.
  • Remove the frontend _decodeEntities() roundtrip and update unit/E2E tests to assert raw values.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Classes/Controller/AjaxController.php Returns raw strings in JSON/SSE responses instead of HTML-escaped strings.
Classes/Domain/DTO/CompleteResponse.php Removes escaping from the success factory and updates DTO docs.
Resources/Public/JavaScript/Ckeditor/CowriterDialog.js Removes entity decoding and sanitizes preview directly from raw content.
Tests/Unit/Controller/AjaxControllerTest.php Updates expectations to raw values; adjusts SSE assertions.
Tests/Unit/Domain/DTO/CompleteResponseTest.php Updates assertions/provider to expect raw values.
Tests/E2E/CowriterWorkflowTest.php Updates E2E assertions to accept raw “malicious” payload strings.
Comments suppressed due to low confidence (2)

Tests/E2E/CowriterWorkflowTest.php:188

  • The assertion is fine, but the comment is inaccurate: JSON encoding is not inherently XSS-safe. XSS depends on how the client renders the decoded string. Please adjust the comment to avoid implying that transport as JSON provides protection; instead reference the actual sinks in this extension that sanitize/escape before DOM insertion (or state that consumers must do so).
        // Assert: Raw content returned in JSON (JSON encoding is inherently XSS-safe;
        // frontend sanitizes via DOMParser before DOM insertion)
        $data = json_decode((string) $result->getBody(), true, 512, JSON_THROW_ON_ERROR);

Tests/Unit/Controller/AjaxControllerTest.php:816

  • The comment “JSON encoding handles safety” is misleading here as well: JSON encoding ensures valid JSON inside the SSE payload, but it doesn’t make the decoded content safe to render as HTML. Suggest rephrasing to reflect that safety depends on downstream rendering (sanitization/escaping at the sink).
        // Raw content in SSE data (JSON encoding handles safety)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix misleading "JSON encoding prevents XSS" docblocks — XSS
  prevention is the frontend's responsibility via DOMParser sanitization
- Update integration and E2E tests to expect raw content instead of
  HTML-escaped entities (missed in initial commit)

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for solo maintainer project

This PR has passed all automated quality gates:

  • ✅ Static analysis (PHPStan)
  • ✅ Code style (PHP-CS-Fixer)
  • ✅ Unit & functional tests
  • ✅ Security scanning
  • ✅ Dependency review

See SECURITY_CONTROLS.md for compensating controls documentation.

@CybotTM CybotTM merged commit 72df2b5 into main Mar 5, 2026
51 checks passed
@CybotTM CybotTM deleted the fix/review-fixes branch March 5, 2026 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants