Lift locale maps before ProjectWritableFields so a Journal-shaped save can persist the default host scalar and non-default locales through TranslationWriter without dropping nested JSON. Co-authored-by: Cursor <cursoragent@cursor.com>
47 lines
1.3 KiB
Go
47 lines
1.3 KiB
Go
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
|
|
}
|