diff --git a/CHANGELOG.md b/CHANGELOG.md index 47dd011dc6..bba0674a49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# 0.204.1 (2026-04-10) + +### CKB Node & Light Client + +- [CKB@v0.204.0](https://github.com/nervosnetwork/ckb/releases/tag/v0.204.0) was released on Dec. 15th, 2025. This version of CKB node is now bundled and preconfigured in Neuron. +- [CKB Light Client@v0.5.4](https://github.com/nervosnetwork/ckb-light-client/releases/tag/v0.5.4) was released on Jan. 2nd, 2026. This version of CKB Light Client is now bundled and preconfigured in Neuron + +### Assumed valid target + +Block before `0xa76ecc34238a30151211f63a09e6063ac7e7e760866b9be73b7560e3a95d3a50`(at height `18,298,596`) will be skipped in validation.(https://github.com/nervosnetwork/neuron/pull/3428) + +--- + +## Bug fixes + +- #3465: Harden release notes rendering and privileged window navigation.(@zhangyaning) + +**Full Changelog**: https://github.com/nervosnetwork/neuron/compare/v0.204.0...v0.204.1 + # 0.204.0 (2026-01-12) ### Caveat diff --git a/lerna.json b/lerna.json index 819f84c2ad..fda238f305 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.204.0", + "version": "0.204.1", "npmClient": "yarn", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/package.json b/package.json index 18914ec19b..54d4d80e83 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "neuron", "productName": "Neuron", "description": "CKB Neuron Wallet", - "version": "0.204.0", + "version": "0.204.1", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index 4aec85af4e..2bbf334c43 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -1,6 +1,6 @@ { "name": "neuron-ui", - "version": "0.204.0", + "version": "0.204.1", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/src/components/GeneralSetting/index.tsx b/packages/neuron-ui/src/components/GeneralSetting/index.tsx index 8f77e68c4d..08b3fe0498 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/index.tsx +++ b/packages/neuron-ui/src/components/GeneralSetting/index.tsx @@ -12,6 +12,7 @@ import { uniformTimeFormatter, bytesFormatter, clsx, wakeScreen, releaseWakeLock import Switch from 'widgets/Switch' import { keepScreenAwake } from 'services/localCache' import { LanguageSelect, UnLock } from 'widgets/Icons/icon' +import { sanitizeReleaseNotes } from 'utils/sanitizeReleaseNotes' import styles from './generalSetting.module.scss' import { useCheckUpdate, useUpdateDownloadStatus } from './hooks' import LockWindowDialog from './LockWindowDialog' @@ -67,7 +68,7 @@ const UpdateDownloadStatus = ({ if (available) { const releaseNotesHtml = () => { - return { __html: releaseNotes } + return { __html: sanitizeReleaseNotes(releaseNotes) } } /* eslint-disable react/no-danger */ diff --git a/packages/neuron-ui/src/tests/sanitizeReleaseNotes/index.test.ts b/packages/neuron-ui/src/tests/sanitizeReleaseNotes/index.test.ts new file mode 100644 index 0000000000..9e5d4c53f4 --- /dev/null +++ b/packages/neuron-ui/src/tests/sanitizeReleaseNotes/index.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest' +import { sanitizeReleaseNotes } from 'utils/sanitizeReleaseNotes' + +describe('sanitizeReleaseNotes', () => { + it('removes interactive markup and keeps basic formatting', () => { + const sanitized = sanitizeReleaseNotes(` +

safe

+ + + +
const ok = true
+ `) + + expect(sanitized).toContain('

safe

') + expect(sanitized).toContain('
const ok = true
') + expect(sanitized).not.toContain(' { + return nodes.flatMap(node => { + if (node.nodeType === Node.TEXT_NODE) { + return [doc.createTextNode(node.textContent ?? '')] + } + + if (node.nodeType !== Node.ELEMENT_NODE) { + return [] + } + + const element = node as HTMLElement + const tagName = element.tagName.toLowerCase() + + if (DROP_CONTENT_TAGS.has(tagName)) { + return [] + } + + const children = sanitizeNodes(doc, Array.from(element.childNodes)) + + if (!ALLOWED_TAGS.has(tagName)) { + return children + } + + const sanitizedElement = doc.createElement(tagName) + children.forEach(child => { + sanitizedElement.appendChild(child) + }) + return [sanitizedElement] + }) +} + +export const sanitizeReleaseNotes = (releaseNotes: string) => { + const template = document.createElement('template') + template.innerHTML = releaseNotes + + const container = document.createElement('div') + sanitizeNodes(document, Array.from(template.content.childNodes)).forEach(node => { + container.appendChild(node) + }) + return container.innerHTML +} + +export default sanitizeReleaseNotes diff --git a/packages/neuron-wallet/.env b/packages/neuron-wallet/.env index 7b5973f645..aa89e73dc4 100644 --- a/packages/neuron-wallet/.env +++ b/packages/neuron-wallet/.env @@ -121,6 +121,6 @@ MAINNET_MULTISIG_TXHASH=0x6888aa39ab30c570c2c30d9d5684d3769bf77265a7973211a3c087 TESTNET_MULTISIG_TXHASH=0x2eefdeb21f3a3edf697c28a52601b4419806ed60bb427420455cc29a090b26d5 # CKB NODE OPTIONS -CKB_NODE_ASSUME_VALID_TARGET='0xa76ecc34238a30151211f63a09e6063ac7e7e760866b9be73b7560e3a95d3a50' -CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER=18298596 -CKB_NODE_DATA_SIZE=135 +CKB_NODE_ASSUME_VALID_TARGET='0x510c9a883f0a4d0a66b26056a08274b6d5276a0a457e303b79f235bf2645239b' +CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER=19039735 +CKB_NODE_DATA_SIZE=139 diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 4dd89e35e0..395db8a8eb 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -3,7 +3,7 @@ "productName": "Neuron", "description": "CKB Neuron Wallet", "homepage": "https://www.nervos.org/", - "version": "0.204.0", + "version": "0.204.1", "private": true, "author": { "name": "Nervos Core Dev", @@ -92,7 +92,7 @@ "electron-builder": "24.13.3", "electron-devtools-installer": "3.2.1", "jest-when": "3.6.0", - "neuron-ui": "^0.204.0", + "neuron-ui": "^0.204.1", "typescript": "5.3.3" }, "resolutions": { diff --git a/packages/neuron-wallet/src/controllers/app/resolve-window-url.ts b/packages/neuron-wallet/src/controllers/app/resolve-window-url.ts new file mode 100644 index 0000000000..a81c4b52d1 --- /dev/null +++ b/packages/neuron-wallet/src/controllers/app/resolve-window-url.ts @@ -0,0 +1,21 @@ +import env from '../../env' + +export const resolveInternalWindowTarget = (url: string) => { + const normalizedUrl = url.trim() + + if (normalizedUrl.startsWith('#/')) { + return { + navigationUrl: normalizedUrl.replace(/^#/, ''), + windowUrl: `${env.mainURL}${normalizedUrl}`, + } + } + + if (normalizedUrl.startsWith('/')) { + return { + navigationUrl: normalizedUrl, + windowUrl: `${env.mainURL}#${normalizedUrl}`, + } + } + + return null +} diff --git a/packages/neuron-wallet/src/controllers/app/show-window.ts b/packages/neuron-wallet/src/controllers/app/show-window.ts index 6f436b3b67..42f34d230b 100644 --- a/packages/neuron-wallet/src/controllers/app/show-window.ts +++ b/packages/neuron-wallet/src/controllers/app/show-window.ts @@ -2,6 +2,7 @@ import { BrowserWindow } from 'electron' import path from 'path' import env from '../../env' import AppController from '.' +import { resolveInternalWindowTarget } from './resolve-window-url' const showWindow = ( url: string, @@ -9,10 +10,15 @@ const showWindow = ( options?: Electron.BrowserWindowConstructorOptions, channels?: string[], comparator: (win: BrowserWindow) => boolean = win => win.getTitle() === title -): BrowserWindow => { +): BrowserWindow | null => { + const target = resolveInternalWindowTarget(url) + if (!target) { + return null + } + const opened = BrowserWindow.getAllWindows().find(comparator) if (opened) { - opened.webContents.send('navigation', url.replace(/^#/, '')) + opened.webContents.send('navigation', target.navigationUrl) opened.focus() return opened } else { @@ -38,8 +44,7 @@ const showWindow = ( if (channels) { AppController.getInstance().registerChannels(win, channels) } - const fmtUrl = url.startsWith('http') || url.startsWith('file:') ? url : env.mainURL + url - win.loadURL(fmtUrl) + win.loadURL(target.windowUrl) win.on('ready-to-show', () => { win.setTitle(title) win.show() diff --git a/packages/neuron-wallet/tests/controllers/app/resolve-window-url.test.ts b/packages/neuron-wallet/tests/controllers/app/resolve-window-url.test.ts new file mode 100644 index 0000000000..a15e6ff484 --- /dev/null +++ b/packages/neuron-wallet/tests/controllers/app/resolve-window-url.test.ts @@ -0,0 +1,31 @@ +jest.mock('../../../src/env', () => ({ + __esModule: true, + default: { + mainURL: 'file:///app/index.html', + }, +})) + +import { resolveInternalWindowTarget } from '../../../src/controllers/app/resolve-window-url' + +describe('resolveInternalWindowTarget', () => { + it('accepts internal hash routes', () => { + expect(resolveInternalWindowTarget(' #/settings/general ')).toEqual({ + navigationUrl: '/settings/general', + windowUrl: 'file:///app/index.html#/settings/general', + }) + }) + + it('accepts internal slash routes', () => { + expect(resolveInternalWindowTarget('/settings/general')).toEqual({ + navigationUrl: '/settings/general', + windowUrl: 'file:///app/index.html#/settings/general', + }) + }) + + it.each(['https://attacker.example/poc', 'http://127.0.0.1:3000/#/settings', 'file:///tmp/poc.html'])( + 'rejects external target %s', + target => { + expect(resolveInternalWindowTarget(target)).toBeNull() + } + ) +}) diff --git a/yarn.lock b/yarn.lock index b7909cb87d..9498c4373a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15492,16 +15492,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -15610,14 +15601,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16647,7 +16631,21 @@ vite-plugin-svgr@4.3.0: "@svgr/core" "^8.1.0" "@svgr/plugin-jsx" "^8.1.0" -vite@6.3.6, "vite@^5.0.0 || ^6.0.0": +vite@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.4.2.tgz#a4e548ca3a90ca9f3724582cab35e1ba15efc6f2" + integrity sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ== + dependencies: + esbuild "^0.25.0" + fdir "^6.4.4" + picomatch "^4.0.2" + postcss "^8.5.3" + rollup "^4.34.9" + tinyglobby "^0.2.13" + optionalDependencies: + fsevents "~2.3.3" + +"vite@^5.0.0 || ^6.0.0": version "6.3.6" resolved "https://registry.yarnpkg.com/vite/-/vite-6.3.6.tgz#69a976b64930750d40219fbc68c5200874d315c1" integrity sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA== @@ -16930,16 +16928,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==