[Bug]: Desktop release-note normalizer replaces Object.prototype entity names such as &constructor; with native function text

オープン 初心者向け
#35,352 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
78/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
electron, typescript
領域
desktop

調査の方向性

apps/desktop/src/updates/releaseNotes.ts と対応するテストファイル apps/desktop/src/updates/releaseNotes.test.ts を読みます。issue では decodeHtmlEntity と継承プロパティの検索が指摘されています。既存のリリースノートテストを実行してから、サポート対象のすべてのノート形式について、継承された名前のテストを追加します。それらの名前がそのまま保持され、サポート対象のエンティティは引き続きデコードされ、未知のエンティティは変更されずに残ることを完了条件とします。

索引モデルが issue の本文から書いたものです。

説明

Before submitting
  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.
Area

apps/desktop

Steps to reproduce

Use a checkout of the baseline commit under Version or commit with its locked dependencies installed. From the checkout root, run the script below with env -u NODE_ENV sh repro.sh. It sets a private HOME, TMPDIR and T3CODE_HOME, then passes synthetic release notes through releaseNotesOfOfferedVersion and normalizeDesktopUpdateReleaseNotes from apps/desktop/src/updates/releaseNotes.ts, the two functions handleUpdateAvailable in apps/desktop/src/updates/DesktopUpdates.ts (lines 926-959) applies to an update-available event. It covers the four accepted note shapes (Markdown string, HTML string, string with a Full changelog summary, tagged array) with & and &unknownentity; controls. It does not start the desktop app. The script exits 0 when the controls are correct and every inherited name shows the substitution, so its assertions pin the current defect.

Reproduction script: repro.sh
#!/bin/sh
set -eu
checkout=$PWD
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
mkdir -p "$tmp/userhome" "$tmp/tmp" "$tmp/t3"
export HOME="$tmp/userhome" TMPDIR="$tmp/tmp" T3CODE_HOME="$tmp/t3"
cd "$checkout/apps/desktop"
env -u NODE_ENV node --input-type=module <<'JS'
import assert from 'node:assert/strict';
import { normalizeDesktopUpdateReleaseNotes, releaseNotesOfOfferedVersion } from './src/updates/releaseNotes.ts';
const render = (note) => normalizeDesktopUpdateReleaseNotes(
  releaseNotesOfOfferedVersion({ version: '1.0.0', releaseName: 'T3 Code v1.0.0', releaseNotes: note }),
  '1.0.0', 'latest',
).releaseNotes[0]?.items[0];
const shapes = {
  markdown: (text) => `- mentions ${text} here`,
  html: (text) => `<ul><li>mentions ${text} here</li></ul>`,
  summary: (text) => `- mentions ${text} here\n<details><summary>Full changelog</summary>ignored</details>`,
  tagged: (text) => [{ version: '1.0.0', note: `- mentions ${text} here` }],
};
const inherited = ['constructor', 'toString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString'];
for (const [shape, wrap] of Object.entries(shapes)) {
  // Controls: a supported entity decodes, an unsupported one stays literal.
  assert.equal(render(wrap('&amp;')), 'mentions & here');
  assert.equal(render(wrap('&unknownentity;')), 'mentions &unknownentity; here');
  for (const name of inherited) {
    const actual = render(wrap(`&${name};`));
    console.log(JSON.stringify({ shape, entity: name, actual }));
    // Pins the current defect: the inherited Object.prototype function's source text replaces the entity.
    assert.equal(actual, `mentions ${String(Object.prototype[name])} here`);
  }
}
// An HTML-escaped entity stays literal after one decode.
assert.equal(render('<ul><li>mentions &amp;constructor; here</li></ul>'), 'mentions &constructor; here');
console.log('controls passed; inherited-name substitution reproduced in all four input shapes');
JS
Expected behavior

HTML_ENTITY_REPLACEMENTS in apps/desktop/src/updates/releaseNotes.ts (lines 23-30) lists six supported names (amp, apos, gt, lt, nbsp, quot), and decodeHtmlEntity (lines 39-48) returns &${entity}; unchanged for any other non-numeric name. The &unknownentity; control follows that contract. &constructor; and the other inherited names are not supported entities, so each should stay literal: mentions &constructor; here.

Actual behavior

Summary. The desktop update release-note normalizer decodes six named HTML entities and is meant to leave any other named entity literal. An entity whose name matches an inherited Object.prototype property (&constructor;, &toString;, &valueOf;, &hasOwnProperty;, &isPrototypeOf;, &propertyIsEnumerable;, &toLocaleString;) is instead replaced by that function's source text, for example function Object() { [native code] }. This was reproduced with synthetic release-note input through the exported production functions; no published T3 Code release note was found that triggers it, so this is a latent cosmetic formatting defect rather than an observed incident.

Every inherited name in every input shape normalizes to the inherited function's source text, for example mentions function Object() { [native code] } here for &constructor; and mentions function toString() { [native code] } here for &toString;. The &amp; and &unknownentity; controls behave correctly, and the HTML-escaped &amp;constructor; correctly yields literal &constructor;. String.replace coerces the inherited function to text; the substitution does not call the function and does not show code execution. The sidebar release-notes popover (apps/web/src/components/sidebar/SidebarUpdateReleaseNotes.tsx, lines 120-136) renders the stored items as text, so the corrupted string would be displayed as text.

Reachability is conditional. The production updater reads T3 Code's own maintainer-controlled GitHub release feed, whose entries electron-updater passes as HTML. A read-only sample of eight stable release bodies (v0.1.2 through v0.1.9) and six recent nightlies, read as Markdown through the GitHub releases API, contained no alphabetic &name; entity at all. That sample is not the complete release history and does not inspect the rendered Atom HTML. If GitHub escapes & when it renders a release note to the feed, a note that mentions &constructor; reaches the decoder as &amp;constructor; and displays correctly; how the feed renders such text was not checked. No packaged desktop app or live update feed was exercised.

Code locator, as a breadcrumb rather than a prescribed fix: decodeHtmlEntity reads HTML_ENTITY_REPLACEMENTS[entity] on an ordinary object literal (line 40), so the lookup also finds properties inherited from Object.prototype, and line 41 returns the truthy function.

Evidence
  • Expected source: HTML_ENTITY_REPLACEMENTS and the literal fallback in decodeHtmlEntity, apps/desktop/src/updates/releaseNotes.ts lines 23-48, plus the &unknownentity; control in the reproduction.
  • Failure source: the reproduction output under Logs, produced by the exported production functions releaseNotesOfOfferedVersion and normalizeDesktopUpdateReleaseNotes.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The script above exited 0 from a clean checkout at the baseline. An independent verification run of an equivalent probe also reproduced it on the earlier upstream main commit named under Version or commit. The existing apps/desktop/src/updates/releaseNotes.test.ts passes (14 tests) and has no case for an inherited entity name.

Related

None of these covers this substitution: #32827 and #32832 concern stable/nightly release-note attribution, and #24358 concerns Full Changelog footer filtering.

Restoration check

With the script's input, each of &constructor;, &toString;, &valueOf;, &hasOwnProperty;, &isPrototypeOf;, &propertyIsEnumerable; and &toLocaleString; normalizes to mentions &<name>; here in all four shapes, matching the &unknownentity; control. (The script's inherited-name assertion encodes the current defect and will fail once it is fixed.) The &amp; control still yields mentions & here, the six supported names still decode, and &amp;constructor; still yields literal &constructor;.

Impact

Cosmetic issue

Version or commit

Commit d9805b2ca667be7caec8c9035950564ab96704c3 (desktop package version 0.1.9); also reproduced at upstream main 4bf5d53d93eedc45ca67d4042a33ca974a8ef567. releaseNotes.ts is byte-identical at the later main commit 4fd40e1a16e3800418b8f6a32932b68179098c8d, where the probe was not rerun.

Environment

macOS on arm64, Node v26.12.1. Exported production-function probe only; no packaged desktop app, browser or update installation was exercised.

Logs or stack traces

Excerpt of the script's output; each of the four shapes prints the same seven lines:

{"shape":"html","entity":"constructor","actual":"mentions function Object() { [native code] } here"}
{"shape":"html","entity":"toString","actual":"mentions function toString() { [native code] } here"}
{"shape":"html","entity":"valueOf","actual":"mentions function valueOf() { [native code] } here"}
{"shape":"html","entity":"hasOwnProperty","actual":"mentions function hasOwnProperty() { [native code] } here"}
{"shape":"html","entity":"isPrototypeOf","actual":"mentions function isPrototypeOf() { [native code] } here"}
{"shape":"html","entity":"propertyIsEnumerable","actual":"mentions function propertyIsEnumerable() { [native code] } here"}
{"shape":"html","entity":"toLocaleString","actual":"mentions function toLocaleString() { [native code] } here"}
controls passed; inherited-name substitution reproduced in all four input shapes
Screenshots, recordings, or supporting files

No response

Workaround

For maintainer-authored release notes, the tested HTML-escaped form &amp;constructor; preserves literal &constructor;. No published release was found that shows the substitution, so no end-user workaround is needed on current evidence.

主要言語
TypeScript
スター
24.8k
フォーク
6.4k
平均マージ
4時間 16分
マージ済み PR(30日)
352

環境構築

Codespaces で開く

このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

pingdotgg/t3code のほかの issue

pingdotgg/t3code の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。