diff --git a/internal/engine/actions/alert/security_advisory/security_advisory.go b/internal/engine/actions/alert/security_advisory/security_advisory.go index 0a221baf60..5e9b1e24da 100644 --- a/internal/engine/actions/alert/security_advisory/security_advisory.go +++ b/internal/engine/actions/alert/security_advisory/security_advisory.go @@ -22,8 +22,8 @@ import ( "encoding/json" "errors" "fmt" + htmltemplate "html/template" "strings" - "text/template" "github.com/google/go-github/v53/github" "github.com/rs/zerolog" @@ -95,9 +95,9 @@ type Alert struct { actionType interfaces.ActionType cli provifv1.GitHub saCfg *pb.RuleType_Definition_Alert_AlertTypeSA - summaryTmpl *template.Template - descriptionTmpl *template.Template - descriptionNoRemTmpl *template.Template + summaryTmpl *htmltemplate.Template + descriptionTmpl *htmltemplate.Template + descriptionNoRemTmpl *htmltemplate.Template } type paramsSA struct { @@ -133,17 +133,17 @@ func NewSecurityAdvisoryAlert( return nil, fmt.Errorf("action type cannot be empty") } // Parse the templates for summary and description - sumT, err := template.New(tmplSummaryName).Parse(tmplSummary) + sumT, err := htmltemplate.New(tmplSummaryName).Option("missingkey=error").Parse(tmplSummary) if err != nil { return nil, fmt.Errorf("cannot parse summary template: %w", err) } descriptionTmplNoRemStr := strings.Join([]string{tmplPart1Top, tmplPart2MiddleNoRem, tmplPart3Bottom}, "\n") - descNoRemT, err := template.New(tmplDescriptionNameNoRem).Parse(descriptionTmplNoRemStr) + descNoRemT, err := htmltemplate.New(tmplDescriptionNameNoRem).Option("missingkey=error").Parse(descriptionTmplNoRemStr) if err != nil { return nil, fmt.Errorf("cannot parse description template: %w", err) } descriptionTmplStr := strings.Join([]string{tmplPart1Top, tmplPart2MiddleRem, tmplPart3Bottom}, "\n") - descT, err := template.New(tmplDescriptionNameRem).Parse(descriptionTmplStr) + descT, err := htmltemplate.New(tmplDescriptionNameRem).Option("missingkey=error").Parse(descriptionTmplStr) if err != nil { return nil, fmt.Errorf("cannot parse description template: %w", err) } diff --git a/internal/engine/actions/remediate/gh_branch_protect/gh_branch_protect.go b/internal/engine/actions/remediate/gh_branch_protect/gh_branch_protect.go index 26159ff93a..ff31e195e7 100644 --- a/internal/engine/actions/remediate/gh_branch_protect/gh_branch_protect.go +++ b/internal/engine/actions/remediate/gh_branch_protect/gh_branch_protect.go @@ -59,7 +59,7 @@ func NewGhBranchProtectRemediator( return nil, fmt.Errorf("action type cannot be empty") } - patchTemplate, err := util.ParseNewTemplate(&ghp.Patch, "patch") + patchTemplate, err := util.ParseNewTextTemplate(&ghp.Patch, "patch") if err != nil { return nil, fmt.Errorf("cannot parse patch template: %w", err) } diff --git a/internal/engine/actions/remediate/pull_request/pull_request.go b/internal/engine/actions/remediate/pull_request/pull_request.go index 141335b295..032a59d8a9 100644 --- a/internal/engine/actions/remediate/pull_request/pull_request.go +++ b/internal/engine/actions/remediate/pull_request/pull_request.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + htmltemplate "html/template" "log" "net/http" "os" @@ -81,8 +82,8 @@ type Remediator struct { cli provifv1.GitHub actionType interfaces.ActionType - titleTemplate *template.Template - bodyTemplate *template.Template + titleTemplate *htmltemplate.Template + bodyTemplate *htmltemplate.Template entries []prEntry } @@ -97,12 +98,12 @@ func NewPullRequestRemediate( return nil, fmt.Errorf("pull request remediation config is invalid: %w", err) } - titleTmpl, err := util.ParseNewTemplate(&prCfg.Title, "title") + titleTmpl, err := util.ParseNewHtmlTemplate(&prCfg.Title, "title") if err != nil { return nil, fmt.Errorf("cannot parse title template: %w", err) } - bodyTmpl, err := util.ParseNewTemplate(&prCfg.Body, "body") + bodyTmpl, err := util.ParseNewHtmlTemplate(&prCfg.Body, "body") if err != nil { return nil, fmt.Errorf("cannot parse body template: %w", err) } @@ -132,7 +133,7 @@ func prConfigToEntries(prCfg *pb.RuleType_Definition_Remediate_PullRequestRemedi for i := range prCfg.Contents { cnt := prCfg.Contents[i] - contentTemplate, err := util.ParseNewTemplate(&cnt.Content, fmt.Sprintf("Content[%d]", i)) + contentTemplate, err := util.ParseNewTextTemplate(&cnt.Content, fmt.Sprintf("Content[%d]", i)) if err != nil { return nil, fmt.Errorf("cannot parse content template (index %d): %w", i, err) } @@ -233,7 +234,7 @@ func (r *Remediator) Do( } func dryRun(title, body string, entries []prEntry) { - tmpl, err := template.New(dryRunTemplateName).Parse(dryRunTmpl) + tmpl, err := template.New(dryRunTemplateName).Option("missingkey=error").Parse(dryRunTmpl) if err != nil { log.Fatalf("Error parsing template: %v", err) } @@ -422,7 +423,7 @@ func (r *Remediator) contentSha1() (string, error) { } func (r *Remediator) prMagicComment() (string, error) { - tmpl, err := template.New(prMagicTemplateName).Parse(prBodyMagicTemplate) + tmpl, err := template.New(prMagicTemplateName).Option("missingkey=error").Parse(prBodyMagicTemplate) if err != nil { return "", err } @@ -467,7 +468,7 @@ func (r *Remediator) getPrBodyText(tmplParams *PrTemplateParams) (string, string } func createReviewBody(prText, magicComment string) (string, error) { - tmpl, err := template.New(prTemplateName).Parse(prBodyTmplStr) + tmpl, err := template.New(prTemplateName).Option("missingkey=error").Parse(prBodyTmplStr) if err != nil { return "", err } diff --git a/internal/engine/actions/remediate/rest/rest.go b/internal/engine/actions/remediate/rest/rest.go index 5b12b8502d..ec6eb6bb35 100644 --- a/internal/engine/actions/remediate/rest/rest.go +++ b/internal/engine/actions/remediate/rest/rest.go @@ -59,14 +59,14 @@ func NewRestRemediate(actionType interfaces.ActionType, restCfg *pb.RestType, return nil, fmt.Errorf("action type cannot be empty") } - endpointTmpl, err := util.ParseNewTemplate(&restCfg.Endpoint, "endpoint") + endpointTmpl, err := util.ParseNewTextTemplate(&restCfg.Endpoint, "endpoint") if err != nil { return nil, fmt.Errorf("cannot parse endpoint template: %w", err) } var bodyTmpl *template.Template if restCfg.Body != nil { - bodyTmpl, err = util.ParseNewTemplate(restCfg.Body, "body") + bodyTmpl, err = util.ParseNewTextTemplate(restCfg.Body, "body") if err != nil { return nil, fmt.Errorf("cannot parse body template: %w", err) } diff --git a/internal/engine/eval/trusty/actions.go b/internal/engine/eval/trusty/actions.go index 7d2b02725c..de226f739c 100644 --- a/internal/engine/eval/trusty/actions.go +++ b/internal/engine/eval/trusty/actions.go @@ -19,8 +19,8 @@ import ( "bytes" "context" "fmt" + htmltemplate "html/template" "strings" - "text/template" "github.com/stacklok/minder/internal/constants" pb "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1" @@ -71,8 +71,8 @@ type summaryPrHandler struct { trustyUrl string trackedAlternatives []dependencyAlternatives - headerTmpl *template.Template - rowsTmpl *template.Template + headerTmpl *htmltemplate.Template + rowsTmpl *htmltemplate.Template } func (sph *summaryPrHandler) trackAlternatives( @@ -149,11 +149,11 @@ func newSummaryPrHandler( cli provifv1.GitHub, trustyUrl string, ) (*summaryPrHandler, error) { - headerTmpl, err := template.New(tableHeaderTmplName).Parse(tableTemplateHeader) + headerTmpl, err := htmltemplate.New(tableHeaderTmplName).Parse(tableTemplateHeader) if err != nil { return nil, fmt.Errorf("could not parse dependency template: %w", err) } - rowsTmpl, err := template.New(tableRowsTmplName).Parse(tableTemplateRow) + rowsTmpl, err := htmltemplate.New(tableRowsTmplName).Parse(tableTemplateRow) if err != nil { return nil, fmt.Errorf("could not parse vulnerability template: %w", err) } diff --git a/internal/engine/eval/vulncheck/review.go b/internal/engine/eval/vulncheck/review.go index 649f8b13fc..08fa104ef2 100644 --- a/internal/engine/eval/vulncheck/review.go +++ b/internal/engine/eval/vulncheck/review.go @@ -19,6 +19,7 @@ import ( "bytes" "context" "fmt" + htmltemplate "html/template" "io" "strings" "text/template" @@ -62,7 +63,7 @@ type reviewTemplateData struct { func createReviewBody(reviewText string) (string, error) { // Create and parse the template - tmpl, err := template.New(reviewTemplateName).Parse(reviewTmplStr) + tmpl, err := template.New(reviewTemplateName).Option("missingkey=error").Parse(reviewTmplStr) if err != nil { return "", err } @@ -433,8 +434,8 @@ type summaryPrHandler struct { logger zerolog.Logger trackedDeps []dependencyVulnerabilities - headerTmpl *template.Template - rowsTmpl *template.Template + headerTmpl *htmltemplate.Template + rowsTmpl *htmltemplate.Template } const ( @@ -548,11 +549,11 @@ func newSummaryPrHandler( Str("repo-name", pr.RepoName). Logger() - headerTmpl, err := template.New(tableVulnerabilitiesHeaderName).Parse(tableVulnerabilitiesHeader) + headerTmpl, err := htmltemplate.New(tableVulnerabilitiesHeaderName).Parse(tableVulnerabilitiesHeader) if err != nil { return nil, fmt.Errorf("could not parse dependency template: %w", err) } - rowsTmpl, err := template.New(tableVulnerabilitiesRowsName).Parse(tableVulnerabilitiesRows) + rowsTmpl, err := htmltemplate.New(tableVulnerabilitiesRowsName).Parse(tableVulnerabilitiesRows) if err != nil { return nil, fmt.Errorf("could not parse vulnerability template: %w", err) } diff --git a/internal/engine/ingester/rest/rest.go b/internal/engine/ingester/rest/rest.go index 33db86f88b..2b76af87b4 100644 --- a/internal/engine/ingester/rest/rest.go +++ b/internal/engine/ingester/rest/rest.go @@ -68,7 +68,7 @@ func NewRestRuleDataIngest( return nil, fmt.Errorf("missing endpoint") } - tmpl, err := util.ParseNewTemplate(&restCfg.Endpoint, "endpoint") + tmpl, err := util.ParseNewTextTemplate(&restCfg.Endpoint, "endpoint") if err != nil { return nil, fmt.Errorf("cannot parse endpoint template: %w", err) } diff --git a/internal/util/rest.go b/internal/util/rest.go index 9cffb98082..e7f9b0e959 100644 --- a/internal/util/rest.go +++ b/internal/util/rest.go @@ -19,6 +19,7 @@ package util import ( "bytes" "fmt" + htmltemplate "html/template" "net/url" "strings" "text/template" @@ -34,13 +35,28 @@ func HttpMethodFromString(inMeth, dfl string) string { return method } -// ParseNewTemplate parses a named template from a string, ensuring it is not empty -func ParseNewTemplate(tmpl *string, name string) (*template.Template, error) { +// ParseNewTextTemplate parses a named template from a string, ensuring it is not empty +func ParseNewTextTemplate(tmpl *string, name string) (*template.Template, error) { if tmpl == nil || len(*tmpl) == 0 { return nil, fmt.Errorf("missing template") } - t := template.New(name) + t := template.New(name).Option("missingkey=error") + t, err := t.Parse(*tmpl) + if err != nil { + return nil, fmt.Errorf("cannot parse template: %w", err) + } + + return t, nil +} + +// ParseNewHtmlTemplate parses a named template from a string, ensuring it is not empty +func ParseNewHtmlTemplate(tmpl *string, name string) (*htmltemplate.Template, error) { + if tmpl == nil || len(*tmpl) == 0 { + return nil, fmt.Errorf("missing template") + } + + t := htmltemplate.New(name).Option("missingkey=error") t, err := t.Parse(*tmpl) if err != nil { return nil, fmt.Errorf("cannot parse template: %w", err) @@ -59,7 +75,7 @@ func GenerateCurlCommand(method, apiBaseURL, endpoint, body string) (string, err {{.URL}} \ -d '{{.Body}}'` - tmpl, err := template.New("curlCmd").Parse(tmplStr) + tmpl, err := template.New("curlCmd").Option("missingkey=error").Parse(tmplStr) if err != nil { return "", err }