Make processStringSync's throw behaviour opt-in.

This commit is contained in:
Sam Rose 2024-10-04 11:30:10 +01:00
parent 2e4607edb6
commit 4fb870e449
No known key found for this signature in database
6 changed files with 30 additions and 29 deletions

View File

@ -145,10 +145,16 @@
const debouncedEval = Utils.debounce((expression, context, snippets) => { const debouncedEval = Utils.debounce((expression, context, snippets) => {
try { try {
expressionError = null expressionError = null
expressionResult = processStringSync(expression || "", { expressionResult = processStringSync(
expression || "",
{
...context, ...context,
snippets, snippets,
}) },
{
noThrow: false,
}
)
} catch (err) { } catch (err) {
expressionResult = null expressionResult = null
expressionError = err expressionError = err

View File

@ -41,11 +41,11 @@ describe("jsRunner (using isolated-vm)", () => {
}) })
it("should prevent sandbox escape", async () => { it("should prevent sandbox escape", async () => {
await expect( expect(
processJS(`return this.constructor.constructor("return process.env")()`) await processJS(
).rejects.toThrow( `return this.constructor.constructor("return process.env")()`
"error while running user-supplied JavaScript: ReferenceError: process is not defined"
) )
).toEqual("ReferenceError: process is not defined")
}) })
describe("helpers", () => { describe("helpers", () => {

View File

@ -1,5 +1,5 @@
import { AutoFieldDefaultNames } from "../../constants" import { AutoFieldDefaultNames } from "../../constants"
import { processStringSync, UserScriptError } from "@budibase/string-templates" import { processStringSync } from "@budibase/string-templates"
import { import {
AutoColumnFieldMetadata, AutoColumnFieldMetadata,
FieldSchema, FieldSchema,
@ -81,14 +81,7 @@ export async function processFormulas<T extends Row | Row[]>(
...row, ...row,
[column]: tracer.trace("processStringSync", {}, span => { [column]: tracer.trace("processStringSync", {}, span => {
span?.addTags({ table_id: table._id, column, static: isStatic }) span?.addTags({ table_id: table._id, column, static: isStatic })
try {
return processStringSync(formula, context) return processStringSync(formula, context)
} catch (err: any) {
if (err.code === UserScriptError.code) {
return err.userScriptError.toString()
}
throw err
}
}), }),
} }
} }

View File

@ -95,6 +95,8 @@ export function processJS(handlebars: string, context: any) {
} catch (error: any) { } catch (error: any) {
onErrorLog && onErrorLog(error) onErrorLog && onErrorLog(error)
const { noThrow = true } = context.__opts || {}
// The error handling below is quite messy, because it has fallen to // The error handling below is quite messy, because it has fallen to
// string-templates to handle a variety of types of error specific to usages // string-templates to handle a variety of types of error specific to usages
// above it in the stack. It would be nice some day to refactor this to // above it in the stack. It would be nice some day to refactor this to
@ -123,12 +125,18 @@ export function processJS(handlebars: string, context: any) {
// This is to catch the error that happens if a user-supplied JS script // This is to catch the error that happens if a user-supplied JS script
// throws for reasons introduced by the user. // throws for reasons introduced by the user.
if (error.code === UserScriptError.code) { if (error.code === UserScriptError.code) {
if (noThrow) {
return error.userScriptError.toString()
}
throw error throw error
} }
if (error.name === "SyntaxError") { if (error.name === "SyntaxError") {
if (noThrow) {
return error.toString() return error.toString()
} }
throw error
}
return "Error while executing JS" return "Error while executing JS"
} }

View File

@ -123,7 +123,7 @@ function createTemplate(
export async function processObject<T extends Record<string, any>>( export async function processObject<T extends Record<string, any>>(
object: T, object: T,
context: object, context: object,
opts?: { noHelpers?: boolean; escapeNewlines?: boolean; onlyFound?: boolean } opts?: ProcessOptions
): Promise<T> { ): Promise<T> {
testObject(object) testObject(object)
@ -173,17 +173,13 @@ export async function processString(
export function processObjectSync( export function processObjectSync(
object: { [x: string]: any }, object: { [x: string]: any },
context: any, context: any,
opts: any opts?: ProcessOptions
): object | Array<any> { ): object | Array<any> {
testObject(object) testObject(object)
for (let key of Object.keys(object || {})) { for (let key of Object.keys(object || {})) {
let val = object[key] let val = object[key]
if (typeof val === "string") { if (typeof val === "string") {
try {
object[key] = processStringSync(object[key], context, opts) object[key] = processStringSync(object[key], context, opts)
} catch (error: any) {
object[key] = error.toString()
}
} else if (typeof val === "object") { } else if (typeof val === "object") {
object[key] = processObjectSync(object[key], context, opts) object[key] = processObjectSync(object[key], context, opts)
} }
@ -235,9 +231,6 @@ export function processStringSync(
return process(string) return process(string)
} }
} catch (err: any) { } catch (err: any) {
if (err.code === UserScriptError.code) {
throw err
}
return input return input
} }
} }

View File

@ -3,6 +3,7 @@ export interface ProcessOptions {
noEscaping?: boolean noEscaping?: boolean
noHelpers?: boolean noHelpers?: boolean
noFinalise?: boolean noFinalise?: boolean
noThrow?: boolean
escapeNewlines?: boolean escapeNewlines?: boolean
onlyFound?: boolean onlyFound?: boolean
disabledHelpers?: string[] disabledHelpers?: string[]