fix(classes): sanitize content_html with bluemonday instead of regex checks
- FormatHTML sanitizes the goldmark output with a bluemonday UGC policy extended only with what goldmark and the imported posts and categories use; unsafe markup is stripped, as Winter's Html::clean strips it - HTML the policy keeps whole is stored byte for byte (token comparison) - CheckHTML keeps its signature and 422 contract, refusing HTML the policy would change - the three regexes are gone: the CR-01 bypasses are neutralized and "Data:" or "metadata:" text is no longer refused (WR-01)
This commit is contained in:
@@ -151,3 +151,44 @@ func TestPostsAdminResaveKeepsHTML(t *testing.T) {
|
||||
env.expect(t, http.StatusOK, http.MethodPut, adminAPI(fmt.Sprintf("%s/%d", adminPostsPath, created.ID)), token, body)
|
||||
assertHTML("re-save")
|
||||
}
|
||||
|
||||
// TestPostsAdminSaveSanitizesHTML pins CR-01 and WR-01 on the admin write
|
||||
// path: unsafe markup in content is stripped from content_html, as Winter's
|
||||
// Html::clean strips it, instead of refusing the save, and a "Data:" label is
|
||||
// ordinary text. content itself is stored as submitted.
|
||||
func TestPostsAdminSaveSanitizesHTML(t *testing.T) {
|
||||
env := newPostsEnv(t)
|
||||
token := env.admin(t,
|
||||
controllers.PermissionAccessPosts,
|
||||
controllers.PermissionAccessPublish,
|
||||
controllers.PermissionAccessOtherPosts,
|
||||
)
|
||||
const content = "<p>Data: 12 maja 2024</p>\n" +
|
||||
"<p><img src=\"https://cdn.example.test/a.jpg\" alt=\"a\"onerror=\"alert(1)\"></p>\n" +
|
||||
"<p><a href=\"javascript:alert(1)\">x</a></p>\n" +
|
||||
"<base href=\"https://evil.test/\">"
|
||||
rec := env.expect(t, http.StatusCreated, http.MethodPost, adminAPI(adminPostsPath), token, map[string]any{
|
||||
"title": map[string]any{"en": "Sanitized", "pl": "Oczyszczony"},
|
||||
"slug": map[string]any{"en": "sanitized-html", "pl": "oczyszczony-html"},
|
||||
"content": map[string]any{"en": content, "pl": content},
|
||||
})
|
||||
var created struct {
|
||||
ID uint `json:"id"`
|
||||
}
|
||||
if err := json.Unmarshal(adminDecode(t, rec).Data, &created); err != nil || created.ID == 0 {
|
||||
t.Fatalf("create data %s: %v", rec.Body.String(), err)
|
||||
}
|
||||
var stored models.Post
|
||||
if err := env.gdb.First(&stored, created.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const want = "<p>Data: 12 maja 2024</p>\n" +
|
||||
"<p><img src=\"https://cdn.example.test/a.jpg\" alt=\"a\"></p>\n" +
|
||||
"<p>x</p>"
|
||||
if got := strings.TrimSpace(stored.ContentHTML); got != want {
|
||||
t.Fatalf("content_html = %q, want %q", got, want)
|
||||
}
|
||||
if stored.Content != content {
|
||||
t.Fatalf("content = %q, want it stored as submitted", stored.Content)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user