Skip to content

Conversation

@Zain-Mahmoud
Copy link
Contributor

@Zain-Mahmoud Zain-Mahmoud commented Jan 21, 2026

Proposed Changes

This PR fixes a bug that caused user input containing certain markdown characters to be rendered as markdown by the markdown renderer (issue #1285)

...

Screenshots of your changes (if applicable) Before: image

After:
image

Type of Change

(Write an X or a brief description next to the type or types that best describe your changes.)

Type Applies?
🚨 Breaking change (fix or feature that would cause existing functionality to change)
New feature (non-breaking change that adds functionality)
🐛 Bug fix (non-breaking change that fixes an issue) x
♻️ Refactoring (internal change to codebase, without changing functionality)
🚦 Test update (change that only adds or modifies tests)
📚 Documentation update (change that only updates documentation)
📦 Dependency update (change that updates a dependency)
🔧 Internal (change that only affects developers or continuous integration)

Checklist

(Complete each of the following items for your pull request. Indicate that you have completed an item by changing the [ ] into a [x] in the raw text, or by clicking on the checkbox in the rendered description on GitHub.)

Before opening your pull request:

  • I have performed a self-review of my changes.
    • Check that all changed files included in this pull request are intentional changes.
    • Check that all changes are relevant to the purpose of this pull request, as described above.
  • I have added tests for my changes, if applicable.
    • This is required for all bug fixes and new features.
  • I have updated the project documentation, if applicable.
    • This is required for new features.
  • I have updated the project Changelog (this is required for all changes).
  • If this is my first contribution, I have added myself to the list of contributors.

After opening your pull request:

  • I have verified that the pre-commit.ci checks have passed.
  • I have verified that the CI tests have passed.
  • I have reviewed the test coverage changes reported by Coveralls.
  • I have requested a review from a project maintainer.

Questions and Comments

@Zain-Mahmoud Zain-Mahmoud marked this pull request as ready for review January 21, 2026 01:43
@Zain-Mahmoud Zain-Mahmoud marked this pull request as draft January 21, 2026 03:46
@Zain-Mahmoud Zain-Mahmoud requested review from david-yz-liu and removed request for david-yz-liu January 21, 2026 03:46
This is used by our patched version of MessagesHandlerMixIn.add_message
(see python_ta/patches/messages.py).
"""
msg_definition.msg = ExtendedMarkup(msg_definition.msg)
Copy link
Contributor

Choose a reason for hiding this comment

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

@Zain-Mahmoud I noticed in your presentation yesterday: this isn't the right place to do this conversion. Look for where the PythonTA error messages are first loaded; this is part of how we handle the configuration files, before starting the "linting" process.

@Zain-Mahmoud Zain-Mahmoud marked this pull request as ready for review January 29, 2026 19:31
@coveralls
Copy link
Collaborator

coveralls commented Jan 29, 2026

Pull Request Test Coverage Report for Build 21813417438

Details

  • 8 of 8 (100.0%) changed or added relevant lines in 2 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.02%) to 89.854%

Totals Coverage Status
Change from base Build 21736461175: 0.02%
Covered Lines: 3436
Relevant Lines: 3824

💛 - Coveralls

linter.msgs_store.register_message(message_definition)

if linter.reporter.name == "pyta-html":
msg_ids = linter.msgs_store.messages
Copy link
Contributor

Choose a reason for hiding this comment

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

This variable name is misleading as it's not just a list of ids that are being returned, but full "message" objects. Please pick a better variable name here and below for the loop variable.

from pylint.reporters import BaseReporter

from .node_printers import LineType, render_message
from python_ta.util.extended_markup import ExtendedMarkup
Copy link
Contributor

Choose a reason for hiding this comment

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

Revert all changes in this file

import os
import socket
import sys
from html import unescape
Copy link
Contributor

Choose a reason for hiding this comment

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

Revert changes in this file

Try fixing the above code by adding spaces around the `+`, and replacing `print(result)` with `return result`.
After making these changes, re-run the file: PythonTA's analysis should not find any more issues!

Note: any "𝔄" characters in the source code will be rendered as an "&" in the error message's code snippet.
Copy link
Contributor

Choose a reason for hiding this comment

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

I looked into this a bit more and unicode has some Private Use Areas, which has code points that are guaranteed not to be used. We should be able to use one of these instead, rather than 𝔄. That way we don't need to mention this in the documentation either.

Note that you can use \u to write unicode code points directly in string literals (see here).


class ExtendedMarkup(Markup):
"""
Extends the standard Markup class defined in markupsafe to include escaping of markdown characters
Copy link
Contributor

Choose a reason for hiding this comment

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

In general for docstrings, write one-line descriptions on the same line as the """. (Same for the escape method below.)

"""

@classmethod
def escape(cls, s):
Copy link
Contributor

Choose a reason for hiding this comment

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

Add type annotations here

Escape all markdown characters in s by replacing them with their corresponding escape sequence
"""
if isinstance(s, str):
markdown_chars = [
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be pulled out and defined as a top-level constant

<div class="message">
<p class="message-name">
<span> [Line 20] Forbidden top-level code found on line 20 </span>
<span> [Line 20] Forbidden top-level code found on line None </span>
Copy link
Contributor

Choose a reason for hiding this comment

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

Be careful when updating snapshots. This test shouldn't have been updated, and indicates a bug in this implementation.

@Zain-Mahmoud
Copy link
Contributor Author

Hi @david-yz-liu,
I wrote a test for the script injection. Just wanted to make sure I'm on the right track before I write more tests

"server_port": 2000
})

inject_script() No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

This file doesn't need to call the function or really be executed at all; see my comments on the in the test file below.

"~",
":",
]
unmapped_code_point = "\ue000"
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I forgot to mention this earlier, but for all top-level constants, please use the ALL_CAPS naming style for them. This applies to both markdown_chars and unmapped_code_point.

output_path = os.path.normpath(
os.path.join(__file__, "../../test_reporters/output_files/injection_script_output.html")
)
check_all(module_name=script_path, output=output_path)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is good but can be improved further: you actually do not need to write the output to a file. Instead, use a StringIO buffer to store the output in memory (you can ask Herena about this, as she's the one who implemented this functionality for check_all)

def inject_script():
a = "hello"
x = f'{a + "<script>alert(2);</script>"}'
return x No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

make sure this file ends with a newline

def markdown_chars():
y = "hello"
z = f'{y + "` ##world `"}'

Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to the above comment, I recommend running this file through black to fix some formatting issues

packages/python-ta/tests/test_reporters/snapshots/test_html_server/test_watch_persistence/watch_html_server_snapshot.html|
packages/python-ta/tests/test_reporters/snapshots/test_html_reporter/test_injection/script_injection.html|
packages/python-ta/tests/test_reporters/snapshots/test_html_reporter/test_markdown_escape/markdown_escape.html|
packages/python-ta/tests/test_reporters/output_files/injection_script_output.html|
Copy link
Contributor

Choose a reason for hiding this comment

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

Relating to my above comment, please remove the "output_files" from this PR and this file

Copy link
Contributor

@david-yz-liu david-yz-liu left a comment

Choose a reason for hiding this comment

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

Nice work, @Zain-Mahmoud!

@david-yz-liu david-yz-liu merged commit f06042d into pyta-uoft:master Feb 9, 2026
30 checks passed
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.

3 participants