prqlc Debug Report HTML Does Not Escape Source File Name, Enabling Stored XSS via PRQLC_DEBUG_LOG

Open Beginner friendly
#6,502 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 3 days

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
75/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
security

Research direction

Start with prqlc/prqlc/src/debug/render_html.rs, especially write_repr_prql() and write_key_values(), where the issue identifies the path rendering and escaping order. Check existing HTML escaping tests and add coverage for a file name containing a backslash and double quote. Done when the debug report renders that path without allowing injected HTML to execute.

Written by the indexing model from the issue text.

Description

bug
What happened?

prqlc's debug-report renderer write_repr_prql() in prqlc/prqlc/src/debug/render_html.rs correctly HTML-escapes the source contents it embeds in <code id="source-{id}">, but the file path rendered one line earlier by write_key_values(w, &[("path", &path), ...]) is passed through escape_html(&format!("{v:?}")), i.e. escape_html is applied to the Debug representation of the PathBuf rather than to the raw path string. For a file name containing both a backslash and a double quote, the Debug formatter first turns the quote into \", after which escape_html emits the backslash literally. The HTML parser then treats that backslash as a literal \ followed by a real attribute-closing quote, breaking out of the attribute context and allowing an attacker-controlled <svg/onload=...> (or any tag) to execute in the context of the debug report. Because the report is generated by design and is commonly treated as a trusted, shareable artifact (support troubleshooting, issue attachments, docs sites), the injection lands in a high-trust product.

PRQL input
prqlc's debug-report renderer `write_repr_prql()` in `prqlc/prqlc/src/debug/render_html.rs` correctly HTML-escapes the source *contents* it embeds in `<code id="source-{id}">`, but the *file path* rendered one line earlier by `write_key_values(w, &[("path", &path), ...])` is passed through `escape_html(&format!("{v:?}"))`, i.e. `escape_html` is applied to the `Debug` representation of the `PathBuf` rather than to the raw path string. For a file name containing both a backslash and a double quote, the `Debug` formatter first turns the quote into `\"`, after which `escape_html` emits the backslash literally. The HTML parser then treats that backslash as a literal `\` followed by a real attribute-closing quote, breaking out of the attribute context and allowing an attacker-controlled `<svg/onload=...>` (or any tag) to execute in the context of the debug report. Because the report is generated by design and is commonly treated as a trusted, shareable artifact (support troubleshooting, issue attachments, docs sites), the injection lands in a high-trust product.
SQL output
prqlc's debug-report renderer `write_repr_prql()` in `prqlc/prqlc/src/debug/render_html.rs` correctly HTML-escapes the source *contents* it embeds in `<code id="source-{id}">`, but the *file path* rendered one line earlier by `write_key_values(w, &[("path", &path), ...])` is passed through `escape_html(&format!("{v:?}"))`, i.e. `escape_html` is applied to the `Debug` representation of the `PathBuf` rather than to the raw path string. For a file name containing both a backslash and a double quote, the `Debug` formatter first turns the quote into `\"`, after which `escape_html` emits the backslash literally. The HTML parser then treats that backslash as a literal `\` followed by a real attribute-closing quote, breaking out of the attribute context and allowing an attacker-controlled `<svg/onload=...>` (or any tag) to execute in the context of the debug report. Because the report is generated by design and is commonly treated as a trusted, shareable artifact (support troubleshooting, issue attachments, docs sites), the injection lands in a high-trust product.
Expected SQL output

MVCE confirmation
  • Minimal example
  • New issue
Anything else?

No response

Dominant language
Rust
Stars
10.9k
Forks
261
Avg merge
3d 21h
Merged PRs (30d)
84

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from PRQL/prql

All issues in PRQL/prql

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.