Summary
PR #121 (closes #94) added a formatExitOutput helper that includes the WASM plugin's output bytes in the error message returned by engines/extism/evaluator/evaluator.go:execHelper when a function returns a non-zero exit code. The output is truncated at a hard-coded exitOutputMaxBytes = 1024 constant to keep error strings readable.
The cap is right for the default case but should be configurable — hosts may want a larger window for structured plugin payloads, or a smaller one in latency-sensitive logging contexts.
Proposal
Add a functional option that flows through the same option layer as WithLogHandler / WithStaticData:
eval, err := polyscript.New[polyscript.Extism](
polyscript.FromBytes(wasmBytes),
polyscript.WithEntryPoint("greet"),
polyscript.WithExitOutputMaxBytes[polyscript.Extism](8 * 1024),
)
Implementation sketch
engines/extism/evaluator/evaluator.go — add exitOutputMaxBytes int field to Evaluator. Default to the current exitOutputMaxBytes value (rename the constant to defaultExitOutputMaxBytes or fold into the struct's zero-value handling). Thread the value through exec() to execHelper()'s formatExitOutput call.
engines/extism/evaluator.New — accept the value (add a functional-options pattern to the evaluator if not present, or pass via constructor arg).
engines/extism.WithExitOutputMaxBytes — engine-level option on the config struct in engines/extism/new.go.
polyscript.WithExitOutputMaxBytes[E] — top-level generic option (or scope to Option[Extism] only since it's engine-specific).
- Update
polyscript.go:newExtism to pass the value through.
Behavior decisions
Pick before implementing:
- Zero value: keep the default (1024), or treat zero as "no cap"?
- Negative value: disable cap entirely (mirrors
HTTPOptions.MaxBodySize: -1), or reject as invalid?
Tests
- Add an option-level test in
engines/extism/evaluator/evaluator_test.go covering: default (cap at 1024), custom positive value (cap at N), zero (falls back to default), negative (no cap, full output).
- Extend
TestFormatExitOutput to take the cap as a parameter (helper signature changes from formatExitOutput(output []byte) string to formatExitOutput(output []byte, maxBytes int) string once the cap is configurable).
Alternatives considered
- Package-level
var + SetExitOutputMaxBytes(n int) setter. Smallest diff but introduces global mutable state, fights t.Parallel(), and doesn't compose with the rest of the option surface. Rejected.
context.Context value (WithExitOutputMaxBytes(ctx, n)). Per-call rather than per-evaluator. Useful if different requests want different caps but most hosts will set this once at construction. Defer unless a real use case shows up.
- Drop the cap entirely. Lets log infrastructure handle truncation. Simpler but a 10 MiB plugin payload then lives in the error string and any log aggregator without size limits inherits the bloat. Rejected.
Files
engines/extism/evaluator/evaluator.go (helper signature + Evaluator field)
engines/extism/evaluator/evaluator_test.go (new tests, refactor TestFormatExitOutput)
engines/extism/new.go (engine-level option, config field)
engines/extism/options.go (the engine option's godoc)
polyscript.go (top-level option, config field, newExtism plumbing)
polyscript_options_test.go (top-level test)
Depends on
Summary
PR #121 (closes #94) added a
formatExitOutputhelper that includes the WASM plugin's output bytes in the error message returned byengines/extism/evaluator/evaluator.go:execHelperwhen a function returns a non-zero exit code. The output is truncated at a hard-codedexitOutputMaxBytes = 1024constant to keep error strings readable.The cap is right for the default case but should be configurable — hosts may want a larger window for structured plugin payloads, or a smaller one in latency-sensitive logging contexts.
Proposal
Add a functional option that flows through the same option layer as
WithLogHandler/WithStaticData:Implementation sketch
engines/extism/evaluator/evaluator.go— addexitOutputMaxBytes intfield toEvaluator. Default to the currentexitOutputMaxBytesvalue (rename the constant todefaultExitOutputMaxBytesor fold into the struct's zero-value handling). Thread the value throughexec()toexecHelper()'sformatExitOutputcall.engines/extism/evaluator.New— accept the value (add a functional-options pattern to the evaluator if not present, or pass via constructor arg).engines/extism.WithExitOutputMaxBytes— engine-level option on theconfigstruct inengines/extism/new.go.polyscript.WithExitOutputMaxBytes[E]— top-level generic option (or scope toOption[Extism]only since it's engine-specific).polyscript.go:newExtismto pass the value through.Behavior decisions
Pick before implementing:
HTTPOptions.MaxBodySize: 0falls back toDefaultMaxBodySize(issue [security] Add MaxBodySize limit to HTTP loader (DoS via unbounded body) #98 / PR fix: cap HTTP-loaded script body size to prevent unbounded read DoS #107).HTTPOptions.MaxBodySize: -1), or reject as invalid?Tests
engines/extism/evaluator/evaluator_test.gocovering: default (cap at 1024), custom positive value (cap at N), zero (falls back to default), negative (no cap, full output).TestFormatExitOutputto take the cap as a parameter (helper signature changes fromformatExitOutput(output []byte) stringtoformatExitOutput(output []byte, maxBytes int) stringonce the cap is configurable).Alternatives considered
var+SetExitOutputMaxBytes(n int)setter. Smallest diff but introduces global mutable state, fightst.Parallel(), and doesn't compose with the rest of the option surface. Rejected.context.Contextvalue (WithExitOutputMaxBytes(ctx, n)). Per-call rather than per-evaluator. Useful if different requests want different caps but most hosts will set this once at construction. Defer unless a real use case shows up.Files
engines/extism/evaluator/evaluator.go(helper signature + Evaluator field)engines/extism/evaluator/evaluator_test.go(new tests, refactorTestFormatExitOutput)engines/extism/new.go(engine-level option, config field)engines/extism/options.go(the engine option's godoc)polyscript.go(top-level option, config field,newExtismplumbing)polyscript_options_test.go(top-level test)Depends on