fix(web): sandbox the speaker-notes presenter deck iframes (#5571)

This commit is contained in:
Ethan Guo
2026-07-15 12:19:46 +00:00
committed by GitHub
parent 8cc3a39907
commit 05cb03c8ab
2 changed files with 35 additions and 3 deletions
+3 -3
View File
@@ -244,15 +244,15 @@ export function buildSpeakerNotesPresenterHtml(options: {
<button type="button" id="reset"></button>
<div class="counter" id="counter"></div>
</div>
<div class="current"><iframe id="current" title="Current slide"></iframe></div>
<div class="current"><iframe id="current" title="Current slide" sandbox="allow-scripts"></iframe></div>
<div class="filmstrip">
<section id="previous-section">
<div class="thumb-label" id="previous-label"></div>
<div class="thumb-frame"><iframe id="previous" title="Previous slide"></iframe></div>
<div class="thumb-frame"><iframe id="previous" title="Previous slide" sandbox="allow-scripts"></iframe></div>
</section>
<section id="next-section">
<div class="thumb-label" id="next-label"></div>
<div class="thumb-frame"><iframe id="next" title="Next slide"></iframe></div>
<div class="thumb-frame"><iframe id="next" title="Next slide" sandbox="allow-scripts"></iframe></div>
</section>
</div>
</main>
@@ -372,4 +372,36 @@ describe('speaker notes HTML helpers', () => {
expect(html).toContain('window.resizeTo(nextWidth, nextHeight)');
expect(html).toContain("window.addEventListener('resize', scheduleMinimumWindowSize)");
});
it('sandboxes the presenter slide iframes to an opaque origin', () => {
const html = buildSpeakerNotesPresenterHtml({
previewHtml: '<section class="slide">One</section>',
title: 'Deck',
projectId: 'project-1',
fileName: 'deck.html',
notes: ['Intro'],
initialSlideIndex: 0,
slideCount: 1,
labels: {
title: 'Speaker notes',
edit: 'Edit',
save: 'Save notes',
pause: 'Pause',
resume: 'Resume',
reset: 'Reset',
previous: 'Previous',
next: 'Next',
empty: 'Empty',
slide: 'Slide {current} / {total}',
},
});
// The srcdoc loaded into each frame is the full, untrusted deck (scripts
// included); the frames must run it in an opaque origin so it cannot reach
// the app's storage or daemon.
for (const id of ['current', 'previous', 'next']) {
const tag = html.match(new RegExp(`<iframe id="${id}"[^>]*>`))?.[0] ?? '';
expect(tag).toContain('sandbox="allow-scripts"');
expect(tag).not.toContain('allow-same-origin');
}
});
});