Vulnerability Overview
Vulnerable Code
The text the user types becomes content and is passed to this.edit(content)
this.editorEngine.overlay.clearUI();
this.editorEngine.overlay.state.addTextEditor(
adjustedRect,
this.originalContent,
el.styles?.computed ?? {},
(content: string) => {
this.edit(content);
},
() => {
this.end();
},
isComponent,
);
newContent is passed directly to the frame view’s remote method. https://github.com/onlook-dev/onlook/blob/8770e5460c30bbfa612cbe948a1632f9f93ba43e/apps/web/client/src/components/store/editor/text/index.ts#L87-L96
const res = await frameData.view.editText(
this.targetDomEl.domId,
newContent,
);
if (!res) {
throw new Error('Failed to edit text. No dom element returned');
}
await this.handleEditedText(res.domEl, newContent, frameData.view);
} catch (error) {
The Penpal bridge exposes editText so it can be invoked remotely. https://github.com/onlook-dev/onlook/blob/8770e5460c30bbfa612cbe948a1632f9f93ba43e/apps/web/client/src/app/project/[id]/_components/canvas/frame/view.tsx#L224-L226
startEditingText: promisifyMethod(penpalChild?.startEditingText),
editText: promisifyMethod(penpalChild?.editText),
stopEditingText: promisifyMethod(penpalChild?.stopEditingText),
Event handlers execute because the content is injected into innerHTML without any filtering. https://github.com/onlook-dev/onlook/blob/8770e5460c30bbfa612cbe948a1632f9f93ba43e/apps/web/preload/script/api/elements/text.ts#L91-L95
function updateTextContent(el: HTMLElement, content: string): void {
// Convert newlines to <br> tags in the DOM
const htmlContent = content.replace(/\\n/g, '<br>');
el.innerHTML = htmlContent;
}
PoC Description