-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Bug Description
JsRuntime::register_handler() in lib.rs L146-L149 uses a raw .contains("export") check to decide whether to auto-append export { handler };:
let handler_script = if !handler_script.contains("export") {
format!("{}\nexport {{ handler }};", handler_script)
} else {
handler_script
};This is a plain substring match against the entire script source, including string literals, comments, and variable names. If the handler script contains the word "export" anywhere — even inside a string, a comment, or an identifier like exportData — the auto-export is skipped, and the subsequent module.get("handler") call on line 166 fails because the module has no handler export.
Reproduction
// This handler WORKS — no "export" anywhere
const script_ok = `
function handler(event) {
return { result: "hello" };
}
`;
// This handler FAILS — "export" appears in a string literal
const script_broken = `
function handler(event) {
const xml = '<config mode="export">value</config>';
return { result: xml };
}
`;
// This handler FAILS — "export" appears in a comment
const script_comment = `
function handler(event) {
// TODO: export this data to CSV
return { result: 42 };
}
`;
// This handler FAILS — "export" appears in a variable name
const script_ident = `
function handler(event) {
const exportPath = "/tmp/out.csv";
return { result: exportPath };
}
`;In all three broken cases, the runtime sees "export" in the source, assumes the developer has already written an ES export statement, skips auto-appending export { handler };, and the handler module has no exports → runtime error.
Impact
This silently breaks handler registration for any script whose content (not just its module structure) happens to contain the substring "export". The failure mode is confusing — the error comes from module.get("handler") failing, not from anything obviously related to exports.
For downstream consumers that generate handler scripts programmatically (e.g. wrapping LLM-generated code inside a function handler(event) { ... } body), this is especially problematic because the script content is arbitrary and frequently contains strings with "export" in them.
Suggested Fix
Since register_handler is specifically documented as taking "a JavaScript module that exports a function named handler", the safest fix is to always append the export and let QuickJS handle duplicates:
// Option A: Always append — QuickJS will ignore a duplicate export
// of the same binding if one already exists in the source.
let handler_script = format!("{}\nexport {{ handler }};", handler_script);If QuickJS rejects duplicate exports, an alternative is to use a more precise heuristic:
// Option B: Regex that matches actual export syntax, not substrings
let has_export = regex::Regex::new(r"\bexport\s+(default\s+|async\s+)?(function|class|const|let|var|\{|\*)")
.unwrap()
.is_match(&handler_script);
let handler_script = if !has_export {
format!("{}\nexport {{ handler }};", handler_script)
} else {
handler_script
};Environment
Crate: hyperlight-js-runtime
File: lib.rs, register_handler() method, line 146
Hyperlight version: 0.12.0