go-gitea/gitea · critical

Markup sanitizer rule regexp must start with ^ and end with

Error message

Markup sanitizer rule regexp must start with ^ and end with $ to be strict

What it means

A start-time panic in Gitea's custom markup sanitizer. When app.ini defines [markup.sanitizer.*] rules with both ELEMENT and REGEXP, the code enforces that the regexp is anchored: it must begin with ^ and end with $. This strictness stops attacker-supplied markup from smuggling partial matches past the allow-list (e.g., a regexp like https?://evil\.com matching inside a longer attribute value), so a non-anchored rule aborts startup deliberately.

Source

Thrown at modules/markup/sanitizer_custom.go:23

import (
	"regexp"
	"strings"

	"gitea.dev/modules/setting"

	"github.com/microcosm-cc/bluemonday"
)

func (st *Sanitizer) addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitizerRule) {
	for _, rule := range rules {
		if rule.AllowDataURIImages {
			policy.AllowDataURIImages()
		}
		if rule.Element != "" {
			if rule.Regexp != "" {
				if !strings.HasPrefix(rule.Regexp, "^") || !strings.HasSuffix(rule.Regexp, "$") {
					panic("Markup sanitizer rule regexp must start with ^ and end with $ to be strict")
				}
				policy.AllowAttrs(rule.AllowAttr).Matching(regexp.MustCompile(rule.Regexp)).OnElements(rule.Element)
			} else {
				policy.AllowAttrs(rule.AllowAttr).OnElements(rule.Element)
			}
		}
	}
}

View on GitHub (pinned to 43ace7cc8a)

Solutions

  1. Edit app.ini and anchor every sanitizer REGEXP: wrap the pattern as ^...$ (e.g. REGEXP = ^https?://example\.com/.*$)
  2. Restart Gitea and confirm startup succeeds
  3. Validate all other [markup.sanitizer.*] sections for the same problem in one pass
  4. After it boots, test the sanitizer with a crafted issue/comment containing the element to confirm the rule matches as intended

Example fix

; before
[markup.sanitizer.example]
ELEMENT = a
ALLOW_ATTR = href
REGEXP = https?://example\.com/.*

; after
[markup.sanitizer.example]
ELEMENT = a
ALLOW_ATTR = href
REGEXP = ^https?://example\.com/.*$
Defensive patterns

Strategy: validation

Validate before calling

// Validate app.ini sanitizer rules before boot (config linter / pre-flight check)
func validateSanitizerRegexp(rules []setting.MarkupSanitizerRule) error {
	for _, r := range rules {
		if r.Element != "" && r.Regexp != "" {
			if !strings.HasPrefix(r.Regexp, "^") || !strings.HasSuffix(r.Regexp, "$") {
				return fmt.Errorf("markup.sanitizer rule for %q: REGEXP %q must start with ^ and end with $", r.Element, r.Regexp)
			}
			if _, err := regexp.Compile(r.Regexp); err != nil {
				return fmt.Errorf("markup.sanitizer rule for %q: invalid REGEXP: %w", r.Element, err)
			}
	}
	return nil
}

Type guard

// Go: guard before applying a rule
func isStrictRegexp(s string) bool {
	return strings.HasPrefix(s, "^") && strings.HasSuffix(s, "$")
}

Try / catch

// Panics happen at process start; catch them in the supervisor/deployment, not in code:
// run `gitea doctor` / a config-dry-run in the container entrypoint before exec'ing the server
// so a bad app.ini fails fast with a clear message instead of a crash loop.

Prevention

When it happens

Trigger: Any [markup.sanitizer.<name>] section in app.ini with ELEMENT set and a REGEXP that does not start with ^ or does not end with $: e.g. REGEXP = ^https?:// (missing trailing $), REGEXP = .* (no anchors), or trailing whitespace/newline after the $ in the quoted value.

Common situations: Copying a sanitizer example from an outdated blog/docs where anchors were not required; upgrading Gitea to a version that introduced the anchor enforcement against configs that previously worked; hand-editing app.ini and introducing a typo or missing anchor; trailing spaces after $ inside the value.

Related errors


AI-assisted analysis of go-gitea/gitea@43ace7cc8a (2026-08-15). Data as JSON: /api/errors/094ae3763d2f8780. Report an issue: GitHub.