package cabana import ( "bytes" "fmt" "regexp" "github.com/yuin/goldmark" ) var ( markdownEngine = goldmark.New() markdownUnsafeTag = regexp.MustCompile(`(?i)<(?:script|iframe|object|embed)\b`) markdownEventHandler = regexp.MustCompile(`(?i)\son[a-z]+\s*=`) markdownDangerousURL = regexp.MustCompile(`(?i)(?:javascript|vbscript|data):`) ) // RenderMarkdown converts source to HTML using the pinned goldmark engine // without html.WithUnsafe. Output that still contains script/iframe tags, // event handlers, or javascript/vbscript/data URLs is rejected, matching // postcard's mail HTML gate. func RenderMarkdown(src string) (string, error) { var buf bytes.Buffer if err := markdownEngine.Convert([]byte(src), &buf); err != nil { return "", fmt.Errorf("cabana: markdown: %w", err) } html := buf.String() if err := rejectUnsafeMarkdownHTML(html); err != nil { return "", err } return html, nil } func rejectUnsafeMarkdownHTML(html string) error { if markdownUnsafeTag.MatchString(html) { return fmt.Errorf("cabana: rendered HTML contains raw unsafe tags") } if markdownEventHandler.MatchString(html) { return fmt.Errorf("cabana: rendered HTML contains event handlers") } if markdownDangerousURL.MatchString(html) { return fmt.Errorf("cabana: rendered HTML contains a dangerous URL scheme") } return nil }