mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-23 15:21:46 +00:00
Backend/frontend hardening & consistency cleanups (#1113)
* refactor(auth): session token validation and password-change consistency * refactor(journey): entry field allow-list and public share-link consistency * refactor(mcp): align tool authorization with the REST permission checks * chore: input validation and sanitisation touch-ups (uploads, pdf, maps, backup, csp)
This commit is contained in:
@@ -146,4 +146,20 @@ describe('downloadJourneyBookPDF', () => {
|
||||
expect(html).toContain('Journey Book');
|
||||
expect(html).toContain('The End');
|
||||
});
|
||||
|
||||
it('FE-COMP-JOURNEYPDF-007: sanitises HTML injected via an entry story and keeps the iframe script-free', async () => {
|
||||
const journey = buildJourney();
|
||||
journey.entries[0].story = 'Hello <script>alert(1)</script> <img src=x onerror="alert(2)"> world';
|
||||
await downloadJourneyBookPDF(journey);
|
||||
const iframe = getIframe()!;
|
||||
const html = iframe.srcdoc;
|
||||
|
||||
// The script tag, image beacon and event handler are stripped from the story.
|
||||
expect(html).not.toContain('<script');
|
||||
expect(html).not.toContain('onerror');
|
||||
expect(html).not.toContain('alert(2)');
|
||||
// Benign prose survives.
|
||||
expect(html).toContain('Hello');
|
||||
expect(html).toContain('world');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Journey Photo Book PDF — Polarsteps-inspired, magazine-density
|
||||
import { marked } from 'marked'
|
||||
import { sanitizeRichTextHtml } from '@trek/shared'
|
||||
import type { JourneyDetail, JourneyEntry, JourneyPhoto } from '../../store/journeyStore'
|
||||
|
||||
function esc(str: string | null | undefined): string {
|
||||
@@ -9,7 +10,9 @@ function esc(str: string | null | undefined): string {
|
||||
|
||||
function md(str: string | null | undefined): string {
|
||||
if (!str) return ''
|
||||
return marked.parse(str, { async: false, breaks: true }) as string
|
||||
// marked passes embedded raw HTML through by default, so sanitise the result
|
||||
// before it goes into the srcdoc iframe (keeps prose markup, drops scripts).
|
||||
return sanitizeRichTextHtml(marked.parse(str, { async: false, breaks: true }) as string)
|
||||
}
|
||||
|
||||
function abs(url: string | null | undefined): string {
|
||||
@@ -308,7 +311,9 @@ export async function downloadJourneyBookPDF(journey: JourneyDetail) {
|
||||
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.style.cssText = 'flex:1;width:100%;border:none;'
|
||||
iframe.sandbox = 'allow-same-origin allow-modals allow-scripts'
|
||||
// No script runs inside the document (print is triggered from the parent via
|
||||
// contentWindow.print()), so withhold allow-scripts to keep the sandbox tight.
|
||||
iframe.sandbox = 'allow-same-origin allow-modals'
|
||||
iframe.srcdoc = html
|
||||
|
||||
card.appendChild(header)
|
||||
|
||||
@@ -569,7 +569,9 @@ ${daysHtml}
|
||||
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.style.cssText = 'flex:1;width:100%;border:none;'
|
||||
iframe.sandbox = 'allow-same-origin allow-modals allow-scripts'
|
||||
// No script runs inside the document (print is parent-initiated), so withhold
|
||||
// allow-scripts to keep the sandbox tight.
|
||||
iframe.sandbox = 'allow-same-origin allow-modals'
|
||||
iframe.srcdoc = html
|
||||
|
||||
card.appendChild(header)
|
||||
|
||||
Reference in New Issue
Block a user