From 508edf603c911454c056f18b3ea2b1015c55c410 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 7 May 2026 18:30:25 -0700 Subject: [PATCH 1/9] improvement(sandbox): upgrade pptx/docx/pdf bootstrap with image helpers, MIME guards, and 256 MB isolate limit --- apps/sim/lib/execution/isolated-vm-worker.cjs | 4 +- apps/sim/sandbox-tasks/docx-generate.ts | 49 ++++++++++++++- apps/sim/sandbox-tasks/pdf-generate.ts | 61 +++++++++++++++++-- apps/sim/sandbox-tasks/pptx-generate.ts | 44 ++++++++++++- 4 files changed, 149 insertions(+), 9 deletions(-) diff --git a/apps/sim/lib/execution/isolated-vm-worker.cjs b/apps/sim/lib/execution/isolated-vm-worker.cjs index 0bca38e4c2b..73d244de75f 100644 --- a/apps/sim/lib/execution/isolated-vm-worker.cjs +++ b/apps/sim/lib/execution/isolated-vm-worker.cjs @@ -183,7 +183,7 @@ async function executeCode(request, executionId) { const externalCopies = [] try { - isolate = new ivm.Isolate({ memoryLimit: 128 }) + isolate = new ivm.Isolate({ memoryLimit: 256 }) if (executionId !== undefined) activeIsolates.set(executionId, isolate) context = await isolate.createContext() const jail = context.global @@ -511,7 +511,7 @@ async function executeTask(request, executionId) { let tPhase = tStart try { - isolate = new ivm.Isolate({ memoryLimit: 128 }) + isolate = new ivm.Isolate({ memoryLimit: 256 }) if (executionId !== undefined) activeIsolates.set(executionId, isolate) context = await isolate.createContext() const jail = context.global diff --git a/apps/sim/sandbox-tasks/docx-generate.ts b/apps/sim/sandbox-tasks/docx-generate.ts index 04efa68abeb..98f2fb70100 100644 --- a/apps/sim/sandbox-tasks/docx-generate.ts +++ b/apps/sim/sandbox-tasks/docx-generate.ts @@ -15,10 +15,57 @@ export const docxGenerateTask = defineSandboxTask({ globalThis.addSection = (section) => { globalThis.__docxSections.push(section); }; - globalThis.getFileBase64 = async (fileId) => { + + // Page geometry constants (twips, 1 twip = 1/1440 inch) for US Letter + globalThis.PAGE_W = 12240; // 8.5" + globalThis.PAGE_H = 15840; // 11" + globalThis.MARGIN = 1440; // 1" margins + globalThis.CONTENT_W = 9360; // PAGE_W - 2 * MARGIN + + // 6 MB raw ≈ 8 MB base64; reject above this to avoid sandbox OOM. + const _MAX_IMG_B64 = 8 * 1024 * 1024; + + /** + * getFileBase64(fileId) — load a workspace file as a full data URI string. + * Returns the complete "data:image/png;base64,..." string. + * Use addImage() rather than passing this directly to ImageRun. + */ + globalThis.getFileBase64 = async function getFileBase64(fileId) { + if (!fileId || typeof fileId !== 'string') { + throw new Error('getFileBase64: fileId must be a non-empty string'); + } const res = await globalThis.__brokers.workspaceFile({ fileId }); + if (!res || !res.dataUri) { + throw new Error('getFileBase64: broker returned no data for file ' + fileId); + } + if (res.dataUri.length > _MAX_IMG_B64) { + throw new Error( + 'getFileBase64: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.' + ); + } return res.dataUri; }; + + /** + * addImage(fileId, opts) — fetch a workspace file and return a docx.ImageRun. + * Required opts: width, height (pixels or EMUs via transformation option). + * Example: + * new docx.Paragraph({ children: [await addImage('abc123', { width: 200, height: 100 })] }) + */ + globalThis.addImage = async function addImage(fileId, opts) { + const dataUri = await globalThis.getFileBase64(fileId); + const comma = dataUri.indexOf(','); + const header = comma !== -1 ? dataUri.slice(0, comma) : ''; + const base64 = comma !== -1 ? dataUri.slice(comma + 1) : dataUri; + const mime = header.split(';')[0].replace('data:', '') || 'image/png'; + const ext = mime.includes('png') ? 'png' : mime.includes('gif') ? 'gif' : mime.includes('bmp') ? 'bmp' : 'jpg'; + if (!globalThis.Buffer) throw new Error('addImage: Buffer polyfill missing — ensure docx bundle is loaded'); + return new globalThis.docx.ImageRun(Object.assign({ + data: globalThis.Buffer.from(base64, 'base64'), + transformation: { width: (opts && opts.width) || 200, height: (opts && opts.height) || 200 }, + type: ext, + }, opts || {})); + }; `, // JSZip's browser build doesn't support nodebuffer output, so we go through // base64 and decode back to bytes inside the isolate (avoids DataURL / Blob). diff --git a/apps/sim/sandbox-tasks/pdf-generate.ts b/apps/sim/sandbox-tasks/pdf-generate.ts index 4a6cbc26f25..7152131c830 100644 --- a/apps/sim/sandbox-tasks/pdf-generate.ts +++ b/apps/sim/sandbox-tasks/pdf-generate.ts @@ -12,20 +12,73 @@ export const pdfGenerateTask = defineSandboxTask({ if (!PDFLib) throw new Error('pdf-lib bundle not loaded'); globalThis.PDFLib = PDFLib; globalThis.pdf = await PDFLib.PDFDocument.create(); - globalThis.embedImage = async (dataUri) => { + + // Convenience shortcuts — avoids verbose PDFLib.rgb() / PDFLib.StandardFonts.Helvetica + globalThis.rgb = PDFLib.rgb; + globalThis.StandardFonts = PDFLib.StandardFonts; + + // Page-size constants in points (1pt = 1/72 inch) + globalThis.LETTER = [612, 792]; // 8.5" × 11" + globalThis.A4 = [595.28, 841.89]; // 210mm × 297mm + + // 6 MB raw ≈ 8 MB base64; reject above this to avoid sandbox OOM. + const _MAX_IMG_B64 = 8 * 1024 * 1024; + + /** + * embedImage(dataUri) — embed a data-URI image into the active PDF document. + * Dispatches to embedPng or embedJpg based on MIME type. + */ + globalThis.embedImage = async function embedImage(dataUri) { + if (!dataUri || typeof dataUri !== 'string') { + throw new Error('embedImage: dataUri must be a non-empty string'); + } + if (dataUri.length > _MAX_IMG_B64) { + throw new Error( + 'embedImage: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.' + ); + } const comma = dataUri.indexOf(','); + if (comma === -1) throw new Error('embedImage: invalid data URI (no comma separator)'); const header = dataUri.slice(0, comma); const base64 = dataUri.slice(comma + 1); const binary = globalThis.Buffer ? globalThis.Buffer.from(base64, 'base64') : null; if (!binary) throw new Error('Buffer polyfill missing'); const mime = header.split(';')[0].split(':')[1] || ''; - if (mime.includes('png')) return globalThis.pdf.embedPng(binary); - return globalThis.pdf.embedJpg(binary); + // image/jpg is non-standard but tolerated; the canonical MIME is image/jpeg + if (mime === 'image/png') return globalThis.pdf.embedPng(binary); + if (mime === 'image/jpeg' || mime === 'image/jpg') return globalThis.pdf.embedJpg(binary); + throw new Error('embedImage: only PNG and JPEG are supported (got ' + (mime || 'unknown — check data URI header') + ')'); }; - globalThis.getFileBase64 = async (fileId) => { + + /** + * getFileBase64(fileId) — load a workspace file as a data URI string. + */ + globalThis.getFileBase64 = async function getFileBase64(fileId) { + if (!fileId || typeof fileId !== 'string') { + throw new Error('getFileBase64: fileId must be a non-empty string'); + } const res = await globalThis.__brokers.workspaceFile({ fileId }); + if (!res || !res.dataUri) { + throw new Error('getFileBase64: broker returned no data for file ' + fileId); + } + if (res.dataUri.length > _MAX_IMG_B64) { + throw new Error( + 'getFileBase64: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.' + ); + } return res.dataUri; }; + + /** + * drawImage(page, fileId, opts) — fetch a workspace file and draw it on the given page. + * Required opts: x, y, width, height (points). + * Example: await drawImage(page, 'abc123', { x: 50, y: 700, width: 200, height: 100 }); + */ + globalThis.drawImage = async function drawImage(page, fileId, opts) { + const dataUri = await globalThis.getFileBase64(fileId); + const img = await globalThis.embedImage(dataUri); + page.drawImage(img, opts || {}); + }; `, finalize: ` const pdf = globalThis.pdf; diff --git a/apps/sim/sandbox-tasks/pptx-generate.ts b/apps/sim/sandbox-tasks/pptx-generate.ts index bca608791dd..01d9b1dd403 100644 --- a/apps/sim/sandbox-tasks/pptx-generate.ts +++ b/apps/sim/sandbox-tasks/pptx-generate.ts @@ -11,9 +11,49 @@ export const pptxGenerateTask = defineSandboxTask({ const PptxGenJS = globalThis.__bundles['pptxgenjs']; if (!PptxGenJS) throw new Error('pptxgenjs bundle not loaded'); globalThis.pptx = new PptxGenJS(); - globalThis.getFileBase64 = async (fileId) => { + globalThis.pptx.layout = 'LAYOUT_16x9'; + + // Slide geometry for LAYOUT_16x9 (inches) + globalThis.SLIDE_W = 10; + globalThis.SLIDE_H = 5.625; + globalThis.MARGIN = 0.5; + globalThis.CONTENT_W = 9; // SLIDE_W - 2 * MARGIN + globalThis.CONTENT_H = 3.8; // usable body height below a standard title row + + // ── Image helpers ────────────────────────────────────────────────────────── + // 6 MB raw ≈ 8 MB base64; reject above this to avoid sandbox OOM. + const _MAX_IMG_B64 = 8 * 1024 * 1024; + + /** + * getFileBase64(fileId) — load a workspace file as a data URI string. + * PptxGenJS data format: "image/png;base64," (no "data:" prefix). + * Use as: slide.addImage({ data: await getFileBase64(fileId), x, y, w, h }) + */ + globalThis.getFileBase64 = async function getFileBase64(fileId) { + if (!fileId || typeof fileId !== 'string') { + throw new Error('getFileBase64: fileId must be a non-empty string'); + } const res = await globalThis.__brokers.workspaceFile({ fileId }); - return res.dataUri; + if (!res || !res.dataUri) { + throw new Error('getFileBase64: broker returned no data for file ' + fileId); + } + if (res.dataUri.length > _MAX_IMG_B64) { + throw new Error( + 'getFileBase64: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.' + ); + } + // PptxGenJS expects "image/png;base64,..." — strip the leading "data:" if present + return res.dataUri.replace(/^data:/, ''); + }; + + /** + * addImage(slide, fileId, opts) — fetch a workspace file and embed it. + * Required opts: x, y, w, h (inches). + * Example: await addImage(slide, 'abc123', { x: 0.5, y: 1, w: 2, h: 1 }); + */ + globalThis.addImage = async function addImage(slide, fileId, opts) { + const data = await globalThis.getFileBase64(fileId); + slide.addImage(Object.assign({ data }, opts || {})); }; `, finalize: ` From 9e38939755880cde6bbf32ed2d85addbe03951c9 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 7 May 2026 18:38:37 -0700 Subject: [PATCH 2/9] fix(sandbox): strict MIME allowlist and nullish coalescing in docx addImage --- apps/sim/sandbox-tasks/docx-generate.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/sim/sandbox-tasks/docx-generate.ts b/apps/sim/sandbox-tasks/docx-generate.ts index 98f2fb70100..3572c815a02 100644 --- a/apps/sim/sandbox-tasks/docx-generate.ts +++ b/apps/sim/sandbox-tasks/docx-generate.ts @@ -58,11 +58,13 @@ export const docxGenerateTask = defineSandboxTask({ const header = comma !== -1 ? dataUri.slice(0, comma) : ''; const base64 = comma !== -1 ? dataUri.slice(comma + 1) : dataUri; const mime = header.split(';')[0].replace('data:', '') || 'image/png'; - const ext = mime.includes('png') ? 'png' : mime.includes('gif') ? 'gif' : mime.includes('bmp') ? 'bmp' : 'jpg'; + const extMap = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/jpg': 'jpg', 'image/gif': 'gif', 'image/bmp': 'bmp', 'image/svg+xml': 'svg' }; + const ext = extMap[mime]; + if (!ext) throw new Error('addImage: unsupported image type "' + mime + '". Use PNG, JPEG, GIF, BMP, or SVG.'); if (!globalThis.Buffer) throw new Error('addImage: Buffer polyfill missing — ensure docx bundle is loaded'); return new globalThis.docx.ImageRun(Object.assign({ data: globalThis.Buffer.from(base64, 'base64'), - transformation: { width: (opts && opts.width) || 200, height: (opts && opts.height) || 200 }, + transformation: { width: opts?.width ?? 200, height: opts?.height ?? 200 }, type: ext, }, opts || {})); }; From f3f1a45cbb5162fb7cecaf0f8d34c09d5e9ef15d Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 7 May 2026 18:39:28 -0700 Subject: [PATCH 3/9] fix(sandbox): validate required opts in pdf drawImage to prevent silent origin placement --- apps/sim/sandbox-tasks/pdf-generate.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/sim/sandbox-tasks/pdf-generate.ts b/apps/sim/sandbox-tasks/pdf-generate.ts index 7152131c830..d38ec2a6bb4 100644 --- a/apps/sim/sandbox-tasks/pdf-generate.ts +++ b/apps/sim/sandbox-tasks/pdf-generate.ts @@ -75,9 +75,12 @@ export const pdfGenerateTask = defineSandboxTask({ * Example: await drawImage(page, 'abc123', { x: 50, y: 700, width: 200, height: 100 }); */ globalThis.drawImage = async function drawImage(page, fileId, opts) { + if (!opts || opts.x == null || opts.y == null || opts.width == null || opts.height == null) { + throw new Error('drawImage: opts must include x, y, width, and height (in points)'); + } const dataUri = await globalThis.getFileBase64(fileId); const img = await globalThis.embedImage(dataUri); - page.drawImage(img, opts || {}); + page.drawImage(img, opts); }; `, finalize: ` From 86c6ba88b1b27a105f132a884ce3849bc532769e Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 7 May 2026 18:53:16 -0700 Subject: [PATCH 4/9] fix(sandbox): throw on malformed data URI in docx addImage --- apps/sim/sandbox-tasks/docx-generate.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/sim/sandbox-tasks/docx-generate.ts b/apps/sim/sandbox-tasks/docx-generate.ts index 3572c815a02..f6716c81e23 100644 --- a/apps/sim/sandbox-tasks/docx-generate.ts +++ b/apps/sim/sandbox-tasks/docx-generate.ts @@ -55,8 +55,9 @@ export const docxGenerateTask = defineSandboxTask({ globalThis.addImage = async function addImage(fileId, opts) { const dataUri = await globalThis.getFileBase64(fileId); const comma = dataUri.indexOf(','); - const header = comma !== -1 ? dataUri.slice(0, comma) : ''; - const base64 = comma !== -1 ? dataUri.slice(comma + 1) : dataUri; + if (comma === -1) throw new Error('addImage: invalid data URI (no comma separator)'); + const header = dataUri.slice(0, comma); + const base64 = dataUri.slice(comma + 1); const mime = header.split(';')[0].replace('data:', '') || 'image/png'; const extMap = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/jpg': 'jpg', 'image/gif': 'gif', 'image/bmp': 'bmp', 'image/svg+xml': 'svg' }; const ext = extMap[mime]; From 918954a51f044f5303919cec627e29b22158d91d Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 7 May 2026 18:57:30 -0700 Subject: [PATCH 5/9] fix(sandbox): prevent opts from clobbering computed ImageRun data/type/transformation --- apps/sim/sandbox-tasks/docx-generate.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/sim/sandbox-tasks/docx-generate.ts b/apps/sim/sandbox-tasks/docx-generate.ts index f6716c81e23..dfe446d7dd3 100644 --- a/apps/sim/sandbox-tasks/docx-generate.ts +++ b/apps/sim/sandbox-tasks/docx-generate.ts @@ -63,11 +63,12 @@ export const docxGenerateTask = defineSandboxTask({ const ext = extMap[mime]; if (!ext) throw new Error('addImage: unsupported image type "' + mime + '". Use PNG, JPEG, GIF, BMP, or SVG.'); if (!globalThis.Buffer) throw new Error('addImage: Buffer polyfill missing — ensure docx bundle is loaded'); - return new globalThis.docx.ImageRun(Object.assign({ + const { width, height, type: _t, data: _d, transformation: userTransform, ...passThrough } = opts || {}; + return new globalThis.docx.ImageRun(Object.assign(passThrough, { data: globalThis.Buffer.from(base64, 'base64'), - transformation: { width: opts?.width ?? 200, height: opts?.height ?? 200 }, type: ext, - }, opts || {})); + transformation: Object.assign({ width: width ?? 200, height: height ?? 200 }, userTransform || {}), + })); }; `, // JSZip's browser build doesn't support nodebuffer output, so we go through From 449ed273e947c968f388973f936e7ab646f0eec3 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 7 May 2026 19:05:52 -0700 Subject: [PATCH 6/9] fix(sandbox): prevent opts from clobbering fetched data in pptx addImage --- apps/sim/sandbox-tasks/pptx-generate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/sandbox-tasks/pptx-generate.ts b/apps/sim/sandbox-tasks/pptx-generate.ts index 01d9b1dd403..9a5a7dffb89 100644 --- a/apps/sim/sandbox-tasks/pptx-generate.ts +++ b/apps/sim/sandbox-tasks/pptx-generate.ts @@ -53,7 +53,7 @@ export const pptxGenerateTask = defineSandboxTask({ */ globalThis.addImage = async function addImage(slide, fileId, opts) { const data = await globalThis.getFileBase64(fileId); - slide.addImage(Object.assign({ data }, opts || {})); + slide.addImage(Object.assign({}, opts || {}, { data })); }; `, finalize: ` From 20c23416ff9d546d37af52a6cab10d1a6a8a54cf Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 7 May 2026 19:16:18 -0700 Subject: [PATCH 7/9] fix(sandbox): validate required opts in pptx addImage --- apps/sim/sandbox-tasks/pptx-generate.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/sim/sandbox-tasks/pptx-generate.ts b/apps/sim/sandbox-tasks/pptx-generate.ts index 9a5a7dffb89..986954da8d6 100644 --- a/apps/sim/sandbox-tasks/pptx-generate.ts +++ b/apps/sim/sandbox-tasks/pptx-generate.ts @@ -52,8 +52,11 @@ export const pptxGenerateTask = defineSandboxTask({ * Example: await addImage(slide, 'abc123', { x: 0.5, y: 1, w: 2, h: 1 }); */ globalThis.addImage = async function addImage(slide, fileId, opts) { + if (!opts || opts.x == null || opts.y == null || opts.w == null || opts.h == null) { + throw new Error('addImage: opts must include x, y, w, and h (in inches)'); + } const data = await globalThis.getFileBase64(fileId); - slide.addImage(Object.assign({}, opts || {}, { data })); + slide.addImage(Object.assign({}, opts, { data })); }; `, finalize: ` From 925d2baa562f3b502431f5561557e517e2f100d9 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 7 May 2026 19:26:49 -0700 Subject: [PATCH 8/9] fix(sandbox): remove silent image/png fallback in docx addImage MIME parsing --- apps/sim/sandbox-tasks/docx-generate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/sandbox-tasks/docx-generate.ts b/apps/sim/sandbox-tasks/docx-generate.ts index dfe446d7dd3..d689a8f9a02 100644 --- a/apps/sim/sandbox-tasks/docx-generate.ts +++ b/apps/sim/sandbox-tasks/docx-generate.ts @@ -58,7 +58,7 @@ export const docxGenerateTask = defineSandboxTask({ if (comma === -1) throw new Error('addImage: invalid data URI (no comma separator)'); const header = dataUri.slice(0, comma); const base64 = dataUri.slice(comma + 1); - const mime = header.split(';')[0].replace('data:', '') || 'image/png'; + const mime = header.split(';')[0].replace('data:', ''); const extMap = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/jpg': 'jpg', 'image/gif': 'gif', 'image/bmp': 'bmp', 'image/svg+xml': 'svg' }; const ext = extMap[mime]; if (!ext) throw new Error('addImage: unsupported image type "' + mime + '". Use PNG, JPEG, GIF, BMP, or SVG.'); From b7db38a5c700010de87aa8c2729fc2f7b8f05b0e Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 7 May 2026 19:46:35 -0700 Subject: [PATCH 9/9] fix(sandbox): consistency and cleanup pass on doc-gen tasks and worker - DOCX addImage: upfront width/height validation (matches PDF/PPTX pattern) - PDF embedImage: remove dead Buffer ternary; drop redundant size guard already enforced in getFileBase64 - isolated-vm-worker: add friendly MemoryLimitError branch in both execute paths so OOM produces a clear message instead of a raw V8 error --- apps/sim/lib/execution/isolated-vm-worker.cjs | 32 +++++++++++++++++++ apps/sim/sandbox-tasks/docx-generate.ts | 7 ++-- apps/sim/sandbox-tasks/pdf-generate.ts | 9 ++---- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/apps/sim/lib/execution/isolated-vm-worker.cjs b/apps/sim/lib/execution/isolated-vm-worker.cjs index 73d244de75f..aa23858e151 100644 --- a/apps/sim/lib/execution/isolated-vm-worker.cjs +++ b/apps/sim/lib/execution/isolated-vm-worker.cjs @@ -398,6 +398,21 @@ async function executeCode(request, executionId) { } } + if ( + err.message.includes('Array buffer allocation failed') || + err.message.includes('memory limit') + ) { + return { + result: null, + stdout, + error: { + message: + 'Execution exceeded memory limit (256 MB). Reduce image sizes or split the work into smaller batches.', + name: 'MemoryLimitError', + }, + } + } + return { result: null, stdout, @@ -937,6 +952,23 @@ async function executeTask(request, executionId) { timings, } } + + if ( + err.message?.includes('Array buffer allocation failed') || + err.message?.includes('memory limit') + ) { + return { + result: null, + stdout, + error: { + message: + 'Execution exceeded memory limit (256 MB). Reduce image sizes or split the work into smaller batches.', + name: 'MemoryLimitError', + }, + timings, + } + } + return { result: null, stdout, diff --git a/apps/sim/sandbox-tasks/docx-generate.ts b/apps/sim/sandbox-tasks/docx-generate.ts index d689a8f9a02..214b9f8f41f 100644 --- a/apps/sim/sandbox-tasks/docx-generate.ts +++ b/apps/sim/sandbox-tasks/docx-generate.ts @@ -53,6 +53,9 @@ export const docxGenerateTask = defineSandboxTask({ * new docx.Paragraph({ children: [await addImage('abc123', { width: 200, height: 100 })] }) */ globalThis.addImage = async function addImage(fileId, opts) { + if (!opts || opts.width == null || opts.height == null) { + throw new Error('addImage: opts must include width and height (in pixels)'); + } const dataUri = await globalThis.getFileBase64(fileId); const comma = dataUri.indexOf(','); if (comma === -1) throw new Error('addImage: invalid data URI (no comma separator)'); @@ -63,11 +66,11 @@ export const docxGenerateTask = defineSandboxTask({ const ext = extMap[mime]; if (!ext) throw new Error('addImage: unsupported image type "' + mime + '". Use PNG, JPEG, GIF, BMP, or SVG.'); if (!globalThis.Buffer) throw new Error('addImage: Buffer polyfill missing — ensure docx bundle is loaded'); - const { width, height, type: _t, data: _d, transformation: userTransform, ...passThrough } = opts || {}; + const { width, height, type: _t, data: _d, transformation: userTransform, ...passThrough } = opts; return new globalThis.docx.ImageRun(Object.assign(passThrough, { data: globalThis.Buffer.from(base64, 'base64'), type: ext, - transformation: Object.assign({ width: width ?? 200, height: height ?? 200 }, userTransform || {}), + transformation: Object.assign({ width, height }, userTransform || {}), })); }; `, diff --git a/apps/sim/sandbox-tasks/pdf-generate.ts b/apps/sim/sandbox-tasks/pdf-generate.ts index d38ec2a6bb4..a7f23e710f1 100644 --- a/apps/sim/sandbox-tasks/pdf-generate.ts +++ b/apps/sim/sandbox-tasks/pdf-generate.ts @@ -32,17 +32,12 @@ export const pdfGenerateTask = defineSandboxTask({ if (!dataUri || typeof dataUri !== 'string') { throw new Error('embedImage: dataUri must be a non-empty string'); } - if (dataUri.length > _MAX_IMG_B64) { - throw new Error( - 'embedImage: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.' - ); - } const comma = dataUri.indexOf(','); if (comma === -1) throw new Error('embedImage: invalid data URI (no comma separator)'); const header = dataUri.slice(0, comma); const base64 = dataUri.slice(comma + 1); - const binary = globalThis.Buffer ? globalThis.Buffer.from(base64, 'base64') : null; - if (!binary) throw new Error('Buffer polyfill missing'); + if (!globalThis.Buffer) throw new Error('embedImage: Buffer polyfill missing'); + const binary = globalThis.Buffer.from(base64, 'base64'); const mime = header.split(';')[0].split(':')[1] || ''; // image/jpg is non-standard but tolerated; the canonical MIME is image/jpeg if (mime === 'image/png') return globalThis.pdf.embedPng(binary);