Pull the error object out of isolated-vm when a user script throws an error.

This commit is contained in:
Sam Rose 2024-10-03 09:42:08 +01:00
parent b9a975694f
commit b004ef5448
No known key found for this signature in database
3 changed files with 25 additions and 2 deletions

View File

@ -44,7 +44,7 @@ describe("jsRunner (using isolated-vm)", () => {
const output = await processJS( const output = await processJS(
`return this.constructor.constructor("return process.env")()` `return this.constructor.constructor("return process.env")()`
) )
expect(output).toBe("Error while executing JS") expect(output).toBe("ReferenceError: process is not defined")
}) })
describe("helpers", () => { describe("helpers", () => {

View File

@ -17,6 +17,15 @@ class ExecutionTimeoutError extends Error {
} }
} }
class UserScriptError extends Error {
constructor(readonly userScriptError: Error) {
super(
`error while running user-supplied JavaScript: ${userScriptError.message}`,
{ cause: userScriptError }
)
}
}
export class IsolatedVM implements VM { export class IsolatedVM implements VM {
private isolate: ivm.Isolate private isolate: ivm.Isolate
private vm: ivm.Context private vm: ivm.Context
@ -29,6 +38,7 @@ export class IsolatedVM implements VM {
private readonly resultKey = "results" private readonly resultKey = "results"
private runResultKey: string private runResultKey: string
private runErrorKey: string
constructor({ constructor({
memoryLimit, memoryLimit,
@ -47,6 +57,7 @@ export class IsolatedVM implements VM {
this.jail.setSync("global", this.jail.derefInto()) this.jail.setSync("global", this.jail.derefInto())
this.runResultKey = crypto.randomUUID() this.runResultKey = crypto.randomUUID()
this.runErrorKey = crypto.randomUUID()
this.addToContext({ this.addToContext({
[this.resultKey]: { [this.runResultKey]: "" }, [this.resultKey]: { [this.runResultKey]: "" },
}) })
@ -216,7 +227,13 @@ export class IsolatedVM implements VM {
} }
} }
code = `results['${this.runResultKey}']=${this.codeWrapper(code)}` code = `
try {
results['${this.runResultKey}']=${this.codeWrapper(code)}
} catch (e) {
results['${this.runErrorKey}']=e
}
`
const script = this.isolate.compileScriptSync(code) const script = this.isolate.compileScriptSync(code)
@ -227,6 +244,9 @@ export class IsolatedVM implements VM {
// We can't rely on the script run result as it will not work for non-transferable values // We can't rely on the script run result as it will not work for non-transferable values
const result = this.getFromContext(this.resultKey) const result = this.getFromContext(this.resultKey)
if (result[this.runErrorKey]) {
throw new UserScriptError(result[this.runErrorKey])
}
return result[this.runResultKey] return result[this.runResultKey]
} }

View File

@ -100,6 +100,9 @@ export function processJS(handlebars: string, context: any) {
if (error.name === "ExecutionTimeoutError") { if (error.name === "ExecutionTimeoutError") {
return "Request JS execution limit hit" return "Request JS execution limit hit"
} }
if ("userScriptError" in error) {
return error.userScriptError.toString()
}
return "Error while executing JS" return "Error while executing JS"
} }
} }