fix(19.7): RB-IN-01 the My account URL check refuses control characters

Browsers strip tab and newline from URLs, so "/\t/host" passed the
SPA's same-origin check and resolved off-site. The check now refuses any
ASCII control character first, as Go's url.Parse does on the server, and
an absolute URL must parse with a host, so both sides apply one rule.
This commit is contained in:
2026-10-23 20:31:28 +02:00
parent a5ce43f211
commit 633d45082e
2 changed files with 29 additions and 5 deletions

View File

@@ -157,6 +157,21 @@ describe('My account contributions', () => {
expect(toasts.value.map((toast) => toast.text)).toEqual(['Done.'])
})
// RB-IN-01: browsers strip tab and newline, so these resolve off-site.
for (const url of ['/\t/evil.example/x', '/\n/evil.example', '/\r\n/evil.example', '/x\u0000', 'https://']) {
it(`a ${JSON.stringify(url)} URL answer renders no link and never opens a window`, async () => {
const opened = vi.spyOn(window, 'open').mockImplementation(() => null)
const { wrapper } = await openAccount({
[`POST ${OPEN}`]: envelope({ message: 'Done.', url }),
})
await wrapper.find('[data-account-contribution-action="open"]').trigger('click')
await flushPromises()
expect(opened).not.toHaveBeenCalled()
expect(wrapper.find('[data-account-contribution-url]').exists()).toBe(false)
expect(wrapper.html()).not.toContain('evil.example')
})
}
it('a section with error: true shows the section error text', async () => {
setBundle({ ...langFixture.data, ...strings, 'backend::lang.myaccount.section_error': { other: 'This section could not be loaded.' } }, 'en')
const { wrapper } = await openAccount({