Three independent input-handling defects in platform/script/loader/, grouped by package. Each has a distinct fix.
1. NewFromDisk mangles or rejects paths containing #, %, or ?
platform/script/loader/fromDisk.go:45-57 round-trips the filesystem path through url.Parse, and GetReader (:94-96) opens l.sourceURL.Path. # becomes a fragment delimiter, % percent-escaping, ? a query — none valid in a filename.
Repro (confirmed):
/dir/file#1.risor → sourceURL.Path becomes /dir/file; GetReader → open /dir/file: no such file or directory.
/dir/100%zz.risor → NewFromDisk fails: invalid URL escape "%zz".
a%20b.risor → silently decoded to a b.risor.
Fix: store the cleaned path and open it directly (os.Open(l.path)). If a *url.URL is needed for GetSourceURL, build it as &url.URL{Scheme: "file", Path: path}; never derive the open path from a parsed URL.
2. NewFromHTTPWithOptions(url, nil) panics
platform/script/loader/fromHTTP.go:195 dereferences options (options.Timeout, options.InsecureSkipVerify, options.TLSConfig) with no nil check. A caller passing nil (a reasonable "use defaults") crashes instead of getting defaults or an error.
Repro (confirmed): invalid memory address or nil pointer dereference.
Fix: if options == nil { options = DefaultHTTPOptions() } at the top of the function.
3. InferLoader silently base64-decodes plain scripts that parse as base64
platform/script/loader/inference.go:80-83 passes the remaining string to NewFromStringBase64 (fromString.go:43-58), which decodes whenever the input parses as base64, with no signal that it was actually encoded. Any script whose trimmed text is coincidentally valid base64 (length a multiple of 4, base64 alphabet) is corrupted into binary.
Repro (confirmed): InferLoader("true") returns a loader whose content is the 3 bytes \xb6\xbb\x9e instead of the script text true.
Fix: default the string fallback to NewFromString; attempt base64 only behind an explicit opt-in or a stricter heuristic. At minimum, document that InferLoader is lossy for base64-shaped scripts.
Impact
Medium — each turns a reasonable input into a crash, a silent corruption, or an unexpected error. All confirmed by repro; none covered by existing tests.
Fix
Address each as above and add regression tests: a filename containing #/%/?, NewFromHTTPWithOptions(url, nil), and InferLoader on a base64-shaped plain script.
Three independent input-handling defects in
platform/script/loader/, grouped by package. Each has a distinct fix.1.
NewFromDiskmangles or rejects paths containing#,%, or?platform/script/loader/fromDisk.go:45-57round-trips the filesystem path throughurl.Parse, andGetReader(:94-96) opensl.sourceURL.Path.#becomes a fragment delimiter,%percent-escaping,?a query — none valid in a filename.Repro (confirmed):
/dir/file#1.risor→sourceURL.Pathbecomes/dir/file;GetReader→open /dir/file: no such file or directory./dir/100%zz.risor→NewFromDiskfails:invalid URL escape "%zz".a%20b.risor→ silently decoded toa b.risor.Fix: store the cleaned path and open it directly (
os.Open(l.path)). If a*url.URLis needed forGetSourceURL, build it as&url.URL{Scheme: "file", Path: path}; never derive the open path from a parsed URL.2.
NewFromHTTPWithOptions(url, nil)panicsplatform/script/loader/fromHTTP.go:195dereferencesoptions(options.Timeout,options.InsecureSkipVerify,options.TLSConfig) with no nil check. A caller passingnil(a reasonable "use defaults") crashes instead of getting defaults or an error.Repro (confirmed):
invalid memory address or nil pointer dereference.Fix:
if options == nil { options = DefaultHTTPOptions() }at the top of the function.3.
InferLoadersilently base64-decodes plain scripts that parse as base64platform/script/loader/inference.go:80-83passes the remaining string toNewFromStringBase64(fromString.go:43-58), which decodes whenever the input parses as base64, with no signal that it was actually encoded. Any script whose trimmed text is coincidentally valid base64 (length a multiple of 4, base64 alphabet) is corrupted into binary.Repro (confirmed):
InferLoader("true")returns a loader whose content is the 3 bytes\xb6\xbb\x9einstead of the script texttrue.Fix: default the string fallback to
NewFromString; attempt base64 only behind an explicit opt-in or a stricter heuristic. At minimum, document thatInferLoaderis lossy for base64-shaped scripts.Impact
Medium — each turns a reasonable input into a crash, a silent corruption, or an unexpected error. All confirmed by repro; none covered by existing tests.
Fix
Address each as above and add regression tests: a filename containing
#/%/?,NewFromHTTPWithOptions(url, nil), andInferLoaderon a base64-shaped plain script.