Builtin functions should not have access to StepsContext

Summary

Closes #437 (closed)

Builtin functions previously received *runner.StepsContext directly, giving them access to previously executed step results via StepResults(), RecordResult(), View().StepResults, and other internal state-management methods. This violates isolation — builtins should only see their own pre-expanded inputs, environment, and file paths.

This MR introduces a BuiltinContext interface with a standalone builtinContext struct that snapshots only the data builtins need. The struct holds copied values (not a pointer to StepsContext), so builtins cannot reach step results even via reflection.

Changes

  • New BuiltinContext interface (pkg/runner/builtin_context.go) — exposes 11 methods: input access, environment, working directories, I/O pipes, and output/export file paths
  • New builtinContext struct — holds snapshot copies (maps.Clone for inputs, env.Values() for environment, strings for paths, writer refs, LookupEnv function value)
  • StepFunc signature changed from func(ctx, *StepsContext) error to func(ctx, BuiltinContext) error
  • All 3 builtin Run functions updated (script, OCI build, OCI promote)
  • Shared getInputWithDefault helper extracted in steps_context.go to avoid duplicating input validation logic
  • View().OutputFile replaced with GetOutputFile() in build step

What builtins can no longer access

  • View() (exposes step results in interpolation context)
  • RecordResult() / StepResults() (previous step outputs)
  • AddGlobalEnv() / GlobalCtx() (global state mutation)
  • ExpandAndApplyEnv() / ReadOutputValues() / ReadExportedEnv() (internal lifecycle, handled by the Builtin wrapper)

Isolation guarantees

  • Compile-time: interface restricts the API surface
  • Reflection-proof: builtinContext holds copied data, not a *StepsContext pointer — steps map is unreachable
  • Expressions pre-expanded: inputs expanded in step.go, env expanded in builtin.go before NewBuiltinContext is called

Test plan

  • All existing unit and integration tests pass
  • New builtin_context_test.go with 23 subtests covering all interface methods, snapshot isolation, input copy semantics, env copy semantics, and live LookupEnv behavior
  • Verified builtins cannot call RecordResult, StepResults, View, AddGlobalEnv, or GlobalCtx (compile-time guarantee)
Edited by Georgi N. Georgiev | GitLab

Merge request reports

Loading