fix: drop executable grab urls

This commit is contained in:
Neil 2026-05-30 13:23:50 -07:00 committed by GitHub
parent 2812c79011
commit e24c2cf532
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 49 additions and 0 deletions

View File

@ -122,4 +122,30 @@ describe('clampGrabPayload', () => {
expect(payload?.target.reactComponents?.length).toBeLessThanOrEqual(512)
expect(payload?.target.sourceFile).toBe('src/Button.tsx:12:4')
})
it('drops executable and embedded URL schemes from page and attribute URLs', () => {
const payload = clampGrabPayload(
makeRawPayload({
page: {
...(makeRawPayload().page as Record<string, unknown>),
sanitizedUrl: 'javascript:alert(1)'
},
target: {
...(makeRawPayload().target as Record<string, unknown>),
attributes: {
href: 'javascript:alert(1)',
src: 'data:text/html,<script>alert(1)</script>',
action: 'vbscript:msgbox(1)',
title: 'Safe label'
}
}
})
)
expect(payload?.page.sanitizedUrl).toBe('')
expect(payload?.target.attributes.href).toBe('')
expect(payload?.target.attributes.src).toBe('')
expect(payload?.target.attributes.action).toBe('')
expect(payload?.target.attributes.title).toBe('Safe label')
})
})

View File

@ -5,6 +5,8 @@ import {
GRAB_SECRET_PATTERNS
} from '../../shared/browser-grab-types'
const SAFE_GRAB_URL_PROTOCOLS = new Set(['http:', 'https:', 'file:'])
/**
* Re-validate and clamp all string, array, and budget fields in a grab payload
* before forwarding to the renderer. This is the main-side safety net: even if
@ -66,6 +68,12 @@ export function clampGrabPayload(raw: unknown): BrowserGrabPayload | null {
}
try {
const url = new URL(str)
if (url.protocol === 'about:') {
return url.toString() === 'about:blank' ? 'about:blank' : ''
}
if (!SAFE_GRAB_URL_PROTOCOLS.has(url.protocol)) {
return ''
}
url.search = ''
url.hash = ''
return url.toString()

View File

@ -121,6 +121,13 @@ describe('buildGuestOverlayScript', () => {
expect(script).toContain("return '';")
})
it('arm script rejects executable and embedded URL schemes', () => {
const script = buildGuestOverlayScript('arm')
expect(script).toContain('SAFE_URL_PROTOCOLS')
expect(script).toContain('!SAFE_URL_PROTOCOLS.has(u.protocol)')
})
it('arm script slices text nodes before normalizing bounded text', () => {
const script = buildGuestOverlayScript('arm')

View File

@ -93,6 +93,8 @@ const ARM_SCRIPT = `(function() {
'secret', 'password', 'passwd'
];
var SAFE_URL_PROTOCOLS = new Set(['http:', 'https:', 'file:']);
var STYLE_PROPS = [
'display', 'position', 'width', 'height', 'margin', 'padding',
'color', 'backgroundColor', 'border', 'borderRadius', 'fontFamily',
@ -118,6 +120,12 @@ const ARM_SCRIPT = `(function() {
function sanitizeUrl(url) {
try {
var u = new URL(url);
if (u.protocol === 'about:') {
return u.toString() === 'about:blank' ? 'about:blank' : '';
}
if (!SAFE_URL_PROTOCOLS.has(u.protocol)) {
return '';
}
u.search = '';
u.hash = '';
return u.toString();