Repository navigation
Extend the XSS PR guardrail to the V2 React/TypeScript UI - #1576
Merged
Paul Lizer (paullizer) merged 2 commits intoSep 29, 2026
Conversation
scripts/check_xss_sinks.py now inspects .ts, .tsx, .jsx and .mjs files with a small TypeScript/JSX lexer and eleven React-aware rules: raw HTML through dangerouslySetInnerHTML or DOM HTML sinks, code evaluation, javascript: URLs, dynamic JSX and DOM URL attributes, navigation sinks, raw-HTML markdown options, and ports of the inline-handler, attribute-interpolation and marked.parse checks. Declaration files and application/v2_ui/public/vendor/ are skipped. Output for existing .js, .html and .py files is unchanged apart from the vendored V2 libraries, which are now skipped too. A full-mode run over the 462 inspected V2 source files goes from 59 raw hits to 28 after tuning, and every remaining hit has been reviewed as safe. The per-finding reasons are in XSS_PR_GUARDRAILS_V2_BASELINE_TRIAGE.md. xss-sink-check.yml now covers the new suffixes. It, broken-access-control-check, swagger-route-check, python-syntax-check, malicious-pr-security-review and codeql (push and pull_request) also run for paullizer-react-v2-ui. Each entry is marked temporary until the V2 branch merges into Development, following the feature/admin-settings-ia precedent. xss-full-scan.yml adds an on-demand whole-file scan that mirrors the Broken Access Control full scan. The XSS instruction applies to the new file types and gains a React/TypeScript section. The audit prompt covers the V2 source and batches its file list, because the whole-application list is longer than the Windows command-line limit. The feature doc gains a V2 section, and the Broken Access Control and Swagger docs note the temporary trigger. The self-test gains eight TypeScript tests that raise explicitly, so they still check under python -O. Tooling only: no application change and no version bump. Refs microsoft#1571 Refs microsoft#1543 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CodeQL flagged the bare `except OSError: pass` in the self-test's `remove_tree` helper (py/empty-except, note) on microsoft#1576. The permission reset is best effort: `shutil.rmtree(..., ignore_errors=True)` runs next, so a file that stays read-only is simply left behind. The comment now says so. Refs microsoft#1571 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
marked this pull request as ready for review
September 29, 2026 23:33
Paul Lizer (paullizer)
merged commit Sep 29, 2026
23587dd
into
microsoft:paullizer-react-v2-ui
11 checks passed
Paul Lizer (paullizer)
added a commit
that referenced
this pull request
Sep 30, 2026
Brings #1578 (flow canvas stable re-renders, 0.261.205), #1579 and #1576 (pytest return-False guard, XSS guardrail updates). Conflicts: config.py keeps Phase 4's 0.261.207; release_notes.md puts the v0.261.207 section above v0.261.205 and restores the unindented v0.261.204 heading. Refs #1547 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
added a commit
to paullizer/simplechat
that referenced
this pull request
Sep 30, 2026
Record 3b (microsoft#1577, v0.261.208) and Phase 4 (microsoft#1580, v0.261.207) as shipped and mark 3c (the Ask AI tab) in progress. Add what later phases build on, their follow-ups, the side fixes from microsoft#1576, microsoft#1578 and microsoft#1579, and gotcha 61 (Microsoft 365 pending-action run links). Docs only; no version bump. Refs microsoft#1543 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Sep 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The XSS PR guardrail (
scripts/check_xss_sinks.pyandxss-sink-check.yml) only read.js,.htmland.pyfiles, and none of the PR guardrail workflows ran on PRs intopaullizer-react-v2-ui. So the V2 React/TypeScript UI (463 files, about 143k lines inapplication/v2_ui/src) had no XSS sink check at all, and V2 PRs got onlyenforce-branch-flowand the CLA check.This PR:
.ts,.tsx,.jsxand.mjswith 11 React-aware rules. A small TypeScript/JSX lexer blanks comments, strings, template chunks, regex bodies and JSX text, so rules match real code and not type annotations or comments.paullizer-react-v2-ui. Each entry is marked temporary until V2 merges into Development.xss-full-scan.yml, an on-demand whole-file scan that mirrorsbroken-access-control-full-scan.yml.applyToplus a React/TypeScript section), the audit prompt, the feature doc and two related docs.This is tooling only: no
application/change, noVERSIONbump and no release-notes entry. That matches the SHA-pinning and CodeQL-PR-pipeline precedents.Rules added
All rules apply to
.ts,.tsx,.jsxand.mjs. Declaration files (.d.ts,.d.mts,.d.cts) andapplication/v2_ui/public/vendor/are skipped. Suppress a finding withxss-check: ignore <reason>on the same line. In these files a bare token without a reason does not suppress.ts-dangerous-inner-htmldangerouslySetInnerHTMLwhose__htmlisn't a sanitized valueDOMPurify.sanitize(...),sanitize*orescape*Htmlhelpers, literals, and auseMemothat returns one of thesets-html-sinkinnerHTML/outerHTMLassignment,insertAdjacentHTML,document.write,createContextualFragment, andDOMParseroutput inserted into the DOMts-code-evaleval,new Function, and a string first argument tosetTimeout/setIntervalts-javascript-urljavascript:scheme inside a string or template literalts-jsx-url-attributehref,xlinkHreforformAction;srcDoc;srconscript/iframe/frame/embed;actionon<form>;dataon<object>;toon react-routerLink/NavLink/NavigateencodeURIComponentsegments;sanitize*/normalize*/safe*URL helpers; reviewed builders;isSafeOriginHrefor^https?://guards; upper-case constantsts-navigation-sinkwindow.open,location.href =,location =, andlocation.assign/replacewith a dynamic valueif (!ok) return;ts-markdown-raw-htmlrehype-raw,allowDangerousHtml, or an identityurlTransformdefaultUrlTransformandsanitize*/safe*/normalize*transformsts-dom-url-property.href,.src,.action,.formActionand.srcdocassignments, andsetAttribute(...)for those attributes, with a dynamic valuesrc;.actiononly on form receiversts-inline-event-handleron*=handlers in HTML strings, and inline-handler APIsts-attribute-interpolation${...}interpolated intohref/src/title/style/data-*attributes of HTML stringsts-marked-parsemarked.parse(...)withoutDOMPurify.sanitize(nearbyThe existing
.js,.htmland.pychecks are unchanged, and the four original self-tests pass unmodified. I compared the old and new checker on all 2,881 tracked classic files. The old one reports 498 findings and the new one 472. The only difference is 26 findings in the vendored DOMPurify (1) and Mermaid (25) bundles underapplication/v2_ui/public/vendor/, which are now skipped.Baseline triage (counts)
Full mode (no SHAs) over the 462 inspected V2 files:
ts-dangerous-inner-htmlts-html-sinkts-code-evalts-javascript-urlts-jsx-url-attributets-navigation-sinkts-markdown-raw-htmlts-dom-url-propertyts-inline-event-handlerts-attribute-interpolationts-marked-parsedangerouslySetInnerHTMLsites sanitize KaTeX (trust: false) or Mermaid (securityLevel: 'strict') output with DOMPurify.test_v2_rich_rendering.pyalready guards that boundary.^https?://orisSafeOriginHrefguards, encoded ids, or fixed constants.docs/explanation/features/v0.241.022/XSS_PR_GUARDRAILS_V2_BASELINE_TRIAGE.md, along with the tuning table and the lexer audit.href/src/action/formActionJSX attributes in 42 files (the quick scan found 53 in 40). Of those, 22 are<img src>(not a script sink), 16 areactionprops on custom components (none is on a<form>), 16 are<a href>, 1 is an<iframe src>and 1 is an image-canvassrc.src, not 464.vite.config.tssits outsidesrc, and two declaration files are skipped.Workflows
xss-sink-check.ymlpull_request.branches;application/**/*.ts,*.tsx,*.jsxand*.mjsadded to thepathsfilter and thexss_surfacelist;xss-full-scan.ymladded to the guardrail pathsbroken-access-control-check.ymlpull_request.branchesswagger-route-check.ymlpull_request.branchespython-syntax-check.ymlpull_request.branchesmalicious-pr-security-review.ymlpull_request.branchescodeql.ymlpushandpull_request(block style, so the comment fits)xss-full-scan.yml(new)workflow_dispatchonly:target_paths(defaultapplication),fail_on_findings(defaultfalse),run_self_tests(defaulttrue), and thexss-full-scan-reportartifact. All actions are pinned to full SHAs# Temporary: remove when paullizer-react-v2-ui merges into Development (#1571). The self-test fails if a workflow names the V2 branch without that comment.paullizer/simplechatinto Development (Fix group document filename XSS in Share controls and cards #1500, Revert accidental OneNote rollout from Development #1538, Add admin defaults for new chat model and reasoning effort #1520) ran all six workflows, including CodeQL's SARIF upload, and passed. So none had to be left out.d89707852("Run the quality gates on PRs into the IA staging branch") did the same forfeature/admin-settings-iaon the same six workflows, anda5ed90f55reverted it before the final merge.pushalone. The coordinator's review asked for bothpushandpull_requesthere, which also gives V2 PRs a base-branch analysis to compare against.release-notes-check.ymlstays Development-only, as instructed. It also fails on fork PRs for token reasons.enforce-branch-flowworkflows are unchanged.uses:lines are left for Pin GitHub Actions to full-length commit SHAs #1474.workflow_dispatchworkflow only once the file is on the default branch (main). Until then, the feature doc and audit prompt give the local batched command.What changes for V2 PRs
paullizer-react-v2-uinow get the same guardrail checks as PRs into Development, subject to each workflow'spathsfilter.xss-check: ignore <reason>on the same line.ts-jsx-url-attribute. They're<Link to={workflowProposalLink(...)}>inWorkflowProposalCard.tsx. Traced safe:workflowProposalLinkreturns the literal path/workspace/workflows?with the id encoded byURLSearchParams. It's flagged only because the helper is new on that branch and isn't a reviewed builder yet. If this PR lands first, that branch should addworkflowProposalLinktoTS_SAME_ORIGIN_URL_BUILDERSin its own diff, so the helper and its approval get reviewed together. If Phase 4 lands first, I'll add it here when I merge V2 in.2f8dab475..b9316eebf): 0 findings in its 3 application files, 2 of them TS-family. The coordinator's review found the same.Validation
All local runs were on Windows, one process at a time.
PYTHONPATH=application/single_app;functional_testsandPYTHONIOENCODING=utf-8.PYTHONPATH, which is how the workflow invokes the self-test.python functional_tests/test_xss_guardrails_checker.pypython -O functional_tests/test_xss_guardrails_checker.pypython functional_tests/test_xss_guardrails_checker.py-Otest_chat_sidebar_conversations_escape_html.pyroute_tests/test_route_policy_test_coverage.py,test_route_blueprint_policy_inventory.py,test_route_unauthenticated_policy_contract.pytest_broken_access_control_guardrails_checker.pygit archiveexport ofc33ad4d16. Pre-existing: it assertsactions/upload-artifact@v4, but the base pins v7.0.1test_swagger_route_pr_workflow.pytj-actions/changed-files@v46.0.1, but the base pins v47.0.6test_malicious_pr_security_review_checker.pyactions/upload-artifact@v4, but the base uses@v7test_docs_app_surface_coverage.py,test_docs_site_quality.pytest_docs_link_integrity.pygit diff --cached --checkapplication/(1,288 files, 7 batches of 200)hrefis reported; full mode reports both lines; a comment-only change reports nothinge31772eb0..c33ad4d16(72 commits, 85 changed files, 57 of them TS-family)8e9586844; about 12 sscripts/check_malicious_pr_security_review.py --base-sha c33ad4d16 --head-sha HEAD --verify-release-ageeval,Functionand sanitizers. The 7 Low are pre-existing tag refs incodeql.ymlandmalicious-pr-security-review.yml(#1474)run_v2_guardrails.py <worktree> c33ad4d16 HEAD 1571Checks on this PR
The six workflows ran on this fork PR into
paullizer-react-v2-ui, so the new triggers work end to end for cross-repo PRs.First run, head
f77c3eedd(merge commit0e30f8690, base2f8dab475, the V2 tip):xss-sink-checkbroken-access-control-checkupload-artifact@v4assertionswagger-route-checksyntax-checkmalicious-pr-security-reviewAnalyze(actions / javascript-typescript / python)CodeQLenforce-branch-flow,license/claSecond run, head
f5e5cc758(the CodeQL fix; merge commit2097aeb71, same base): every check passed again. The XSS self-test is 12/12, andCodeQLreports no new alerts in code changed by this pull request, with 0 annotations. Analyze took 54 s / 3 m 6 s / 9 m 47 s.CodeQL triage
py/empty-excepton theexcept OSError: passin the self-test'sremove_treehelper (functional_tests/test_xss_guardrails_checker.py), which I added in this PR. The permission reset is best effort, becauseshutil.rmtree(..., ignore_errors=True)runs right after it.f5e5cc758adds a comment saying so, and the self-test still passes 12/12 normally and under-O. On the second run, theCodeQLcheck shows no new alerts.scripts/check_xss_sinks.pyor the edited workflows. The workflow runs thesecurity-and-qualitysuite, which includes the ReDoS queries.ubuntu-latestlabel moves to Ubuntu 26. They aren't code-scanning alerts.CodeQLcheck run and its annotations instead. The full branch view is under Security → Code scanning, filtered topr:1576.Decisions made autonomously
Scope
<img src>and other mediasrcvalues aren't checked, because an image source can't run script.srconscript/iframe/frame/embedanddataon<object>are checked strictly.application/v2_ui/public/vendor/joins the vendored-asset skip list. That also removes 26 pre-existing classic findings in the vendored bundles..mjsgoes through the TypeScript pipeline with JSX off..jsstays on the classic pipeline, even underapplication/v2_ui/.ts-dom-url-propertygoes beyond the issue's list and covers DOM.href/.srcassignments andsetAttribute. React-routertois covered too.marked.parsechecks are ported. jQuery.html(isn't checked in TypeScript files, because V2 has no jQuery.Precision
useMemoreturns.emailDraftMailtoUrlformailto:URLs in href contexts only. Phase 4's newworkflowProposalLinkisn't pre-approved: approving a helper that isn't on V2 yet would let later edits to it skip review. The two findings it causes are traced safe above.sanitize*,normalize*orsafe*and ending inUrl/Href/Src/Uriare trusted, following the repo's existing philosophy. Upper-case constants andapiUrlare trusted too.//is accepted in link contexts, because it's an open-redirect concern, not XSS.URL.createObjectURLis accepted in link contexts only.encodeURIComponent(...)is accepted only after a safe prefix.useStatesetter tracking and block scoping. Following parameters to their call sites was left out, and the few parameter-driven findings were traced by hand.Suppression
Workflows
pushas well as PRs.fail_on_findingsdefaults tofalse, because full mode reports 355 pre-existing classic findings.xss-full-scan.ymlchanges trigger the XSS self-test.CHAT_ORCHESTRATION_WORKFLOWS_ROADMAP.mdL54–58 is now stale, but that doc is left to the coordinator.Docs
python scripts/check_xss_sinks.py --full-file $xssFilesexceeds Windows' 32,767-character command-line limit. The classic list alone is about 47k characters. The feature doc and audit prompt now use a batched loop of 200 files per batch.Follow-ups (not in this PR)
react/no-dangerandreact/jsx-no-script-url.sanitizeHttpUrlhelper.dangerouslySetInnerHTMLsites.paths-ignorefor vendored bundles, if needed.Fixes #1571
Found during #1543