diff --git a/syft/format/template/encoder.go b/syft/format/template/encoder.go index e387601c3..08ed33a65 100644 --- a/syft/format/template/encoder.go +++ b/syft/format/template/encoder.go @@ -33,7 +33,16 @@ type encoder struct { func NewFormatEncoder(cfg EncoderConfig) (sbom.FormatEncoder, error) { // TODO: revisit this... should no template file be an error or simply render an empty result? or render the json output? // Note: do not check for the existence of the template file here, as the default encoder cannot provide one. + // start from the hermetic (repeatable) sprig map, which omits functions that reach into the + // environment or network (env, expandenv, getHostByName), then re-expose just the date/time + // helpers requested in issue #2372. Allowlisting here keeps the user-template trust boundary + // safe even if sprig adds new non-hermetic functions in the future. f := sprig.HermeticTxtFuncMap() + full := sprig.TxtFuncMap() + for _, name := range []string{"now", "date", "dateInZone", "dateModify", "date_in_zone", "date_modify", "htmlDate", "htmlDateInZone"} { + f[name] = full[name] + } + f["getLastIndex"] = func(collection any) int { if v := reflect.ValueOf(collection); v.Kind() == reflect.Slice { return v.Len() - 1 diff --git a/syft/format/template/encoder_test.go b/syft/format/template/encoder_test.go index 4418b20af..dc487730d 100644 --- a/syft/format/template/encoder_test.go +++ b/syft/format/template/encoder_test.go @@ -82,6 +82,27 @@ func TestFormatWithOptionAndHasField(t *testing.T) { ) } +func TestFuncMap_ExposesDateFunctions_ExcludesEnvAndNetwork(t *testing.T) { + enc, err := NewFormatEncoder(DefaultEncoderConfig()) + require.NoError(t, err) + + e, ok := enc.(encoder) + require.True(t, ok) + + // date/time functions (the reason for issue #2372) should be available + for _, name := range []string{"now", "date", "dateInZone", "dateModify", "unixEpoch"} { + _, exists := e.funcMap[name] + assert.Truef(t, exists, "expected date function %q to be available", name) + } + + // functions that reach into the environment or network must remain excluded. + // rand*/uuidv4 are also kept out to preserve hermetic (repeatable) output. + for _, name := range []string{"env", "expandenv", "getHostByName", "randAlphaNum", "uuidv4"} { + _, exists := e.funcMap[name] + assert.Falsef(t, exists, "expected non-hermetic function %q to be excluded", name) + } +} + func TestFormatWithoutOptions(t *testing.T) { f, err := NewFormatEncoder(DefaultEncoderConfig()) require.NoError(t, err)