Starbox is a pragmatic Go wrapper around the Starlark in Go project, making it easier to execute Starlark scripts, exchange data between Go and Starlark, and call functions across the Go-Starlark boundary. With a focus on simplicity and usability, Starbox aims to provide an enhanced experience for developers integrating Starlark scripting into their Go applications.
A host of powerful features are provided to supercharge your Starlark scripting experience:
- Streamlined Script Execution: Simplifies setting up and running Starlark scripts, offering a seamless interface for both script execution and interactive REPL sessions.
- Efficient Data Interchange: Enables robust and smooth data exchange between Go and Starlark, enhancing the interoperability and simplifying the integration process.
- Versatile Module Management: Extends Starlark's capabilities with a suite of built-in functions and the ability to load custom and override existing modules, covering functionalities from data processing to HTTP handling and file manipulation.
- Cross-Language Function Calls: Leverage the power of both languages by calling Go functions from Starlark and vice versa, creating powerful integrations.
- Integrated HTTP Context: Facilitates handling HTTP requests and responses within Starlark scripts, catering to web application development and server-side scripting.
- Collective Memory Sharing: Introduces a shared memory concept, enabling data sharing across different script executions and instances, fostering a more connected and dynamic scripting environment.
- Advanced Scripting Tools: Utilize features like REPL for interactive exploration and debugging, along with script caching for improved performance.
To include starbox in your Go project, use the following command:
go get github.com/1set/starboxHere's a quick example of how you can use Starbox:
import "github.com/1set/starbox"
// Define your box with global variables and modules
box := starbox.New("quick")
box.AddKeyValue("greet", func(name string) string {
return fmt.Sprintf("Hello, %s!", name)
})
box.AddNamedModules("random")
// Run a Starlark script
script := starbox.HereDoc(`
target = random.choice(["World", "Starlark", "Starbox"])
text = greet(target)
print("Starlark:", text)
print(__modules__)
`)
res, err := box.Run(script)
// Check for errors and results
if err != nil {
fmt.Println("Error executing script:", err)
return
}
fmt.Println("Go:", res["text"].(string))This may output:
[β|quick](15:50:27.677) Starlark: Hello, Starbox!
[β|quick](15:50:27.677) ["random"]
Go: Hello, Starbox!
Starbox is a host runtime: the Go host decides what an untrusted script may reach, and the script cannot widen that grant. The control surface has two distinct layers β Starbox implements the first; the second lives at the layer that constructs the modules.
New selects modules through a four-tier module set (EmptyModuleSet, SafeModuleSet, NetworkModuleSet, FullModuleSet); SafeModuleSet is an explicit, hand-curated allowlist that can reach neither the network nor the filesystem.
For stricter, host-declared control, construct the box with a Policy β a Go-side, default-deny allowlist the script can neither read nor mutate:
// Only "math" and "json" are loadable, no matter what the module set requests.
box := starbox.NewWithPolicy("sandboxed", starbox.Policy{
Modules: starbox.ModuleAllow{Names: []string{"math", "json"}},
})
box.SetModuleSet(starbox.FullModuleSet) // requested set is INTERSECTED with the policy- The zero
Policy{}permits nothing β strict default-deny by construction. ModuleAllow.Capabilitiesis opt-in capability widening: a non-zero tier (e.g.starlet.CapNetwork) permits every builtin whose capability set is a subset of it.- The gate covers every named-module path a script can load: builtin, custom (
AddModule*), dynamic, and script modules (AddModuleScript, matched by their registered.starname). - A withheld builtin surfaces a typed
ModuleWithheldError(matchable viaerrors.As). A policy-denied non-builtin (custom/dynamic/script) module is simply absent βload()fails as "not found", so the sandbox is not told a host-private module exists but is forbidden.
SetFSis an explicit exception. A host-mountedfs.FSis a raw filesystem grant with no module-name registry to match against, so it is not governed by the load gate. Under a restrictive policy, curate the mounted filesystem (or do not callSetFS).
Per-call filesystem / network / command / secret gating (what a loaded module is allowed to actually do) is out of scope for Starbox: it never imports the domain modules, which arrive as opaque loaders, so exec-gating belongs where those loaders are constructed (the host shell / CLI). Starbox ships the load gate only; it does not ship inert exec-grant fields that would be a fail-open footgun.
box.SetMaxExecutionSteps(1_000_000) // bound runaway loops a wall-clock timeout cannot stop
box.SetMaxOutputEntries(100) // cap the number of top-level result entriesA run that exceeds the step budget fails with a starlet.MaxStepsExceededError; one that produces too many result entries is withheld with an OutputLimitExceededError. Both are reachable via errors.As, and both are enforced on every run path (Run, RunFile, RunTimeout, RunInspect*, and the RunnerConfig.Execute() builder).
Learn what a script would see, and catch problems, without running it:
diags, _ := box.Check(script) // []Diagnostic: syntax + resolve errors as "file:line:col: msg"
surface, _ := box.DescribeSurface() // Surface: modules (name/origin/members) + globals (name/type)Both honor the active Policy β they report and accept exactly the modules a real Run would load, never a wider surface.
box.AddResultBuiltin("output") // script calls output(v) once per run to set its result
res, _ := box.Run(`output({"ok": True})`)
val, ok := box.GetResult() // the captured value; reset at the start of every run
con := box.EnableConsoleCapture() // funnel console output into a drainable buffer instead of stderr
box.Run(`print("hi")`)
for _, e := range con.Drain() { // []ConsoleEntry: Time, Level, Message, structured Fields
fmt.Println(e.Level, e.Message, e.Fields)
}print() becomes a LevelPrint entry; when the log module is loaded, log.* calls become leveled entries whose keyword arguments are preserved as structured Fields (never pre-rendered into the message). Drain returns the buffered entries and clears them, for a per-run drain.
Run failures carry typed, errors.As-matchable causes, and ClassifyRunError maps any run failure to a RunError{Kind} (Syntax, Compile, ModuleWithheld, MaxSteps, OutputLimit, Eval) for uniform host handling.
We welcome contributions to the Starbox project. If you encounter any issues or have suggestions for improvements, please feel free to open an issue or submit a pull request. Before undertaking any significant changes, please let us know by filing an issue or claiming an existing one to ensure there is no duplication of effort.
Starbox is licensed under the MIT License.
This project is inspired by and builds upon several open-source projects:
- Starlark in Go: The official Starlark interpreter in Go, created by Google.
- Starlight: A well-known Go wrapper and data conversion tool between Go and Starlark.
- Starlight Enhanced: A sophisticated fork of the original Starlight, with bug fixes and enhancement features.
- Starlib: A collection of third-party libraries for Starlark.
- Starlet: A Go wrapper that simplifies usage, offers data conversion, libraries and extensions for Starlark.
We thank the authors and contributors of these projects for their excellent works π