diff --git a/models/repo/repo.go b/models/repo/repo.go
index c4b215e074..db3709f1e8 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -584,9 +584,9 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
 	}, repo.Description)
 	if err != nil {
 		log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
-		return template.HTML(markup.Sanitize(repo.Description))
+		return template.HTML(markup.SanitizeDescription(repo.Description))
 	}
-	return template.HTML(markup.Sanitize(desc))
+	return template.HTML(markup.SanitizeDescription(desc))
 }
 
 // CloneLink represents different types of clone URLs of repository.
diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go
index 48c08831f1..992e85b989 100644
--- a/modules/markup/sanitizer.go
+++ b/modules/markup/sanitizer.go
@@ -18,9 +18,10 @@ import (
 // Sanitizer is a protection wrapper of *bluemonday.Policy which does not allow
 // any modification to the underlying policies once it's been created.
 type Sanitizer struct {
-	defaultPolicy    *bluemonday.Policy
-	rendererPolicies map[string]*bluemonday.Policy
-	init             sync.Once
+	defaultPolicy     *bluemonday.Policy
+	descriptionPolicy *bluemonday.Policy
+	rendererPolicies  map[string]*bluemonday.Policy
+	init              sync.Once
 }
 
 var (
@@ -41,6 +42,7 @@ func NewSanitizer() {
 func InitializeSanitizer() {
 	sanitizer.rendererPolicies = map[string]*bluemonday.Policy{}
 	sanitizer.defaultPolicy = createDefaultPolicy()
+	sanitizer.descriptionPolicy = createRepoDescriptionPolicy()
 
 	for name, renderer := range renderers {
 		sanitizerRules := renderer.SanitizerRules()
@@ -161,6 +163,27 @@ func createDefaultPolicy() *bluemonday.Policy {
 	return policy
 }
 
+// createRepoDescriptionPolicy returns a minimal more strict policy that is used for
+// repository descriptions.
+func createRepoDescriptionPolicy() *bluemonday.Policy {
+	policy := bluemonday.NewPolicy()
+
+	// Allow italics and bold.
+	policy.AllowElements("i", "b", "em", "strong")
+
+	// Allow code.
+	policy.AllowElements("code")
+
+	// Allow links
+	policy.AllowAttrs("href", "target", "rel").OnElements("a")
+
+	// Allow classes for emojis
+	policy.AllowAttrs("class").Matching(regexp.MustCompile(`^emoji$`)).OnElements("img", "span")
+	policy.AllowAttrs("aria-label").OnElements("span")
+
+	return policy
+}
+
 func addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitizerRule) {
 	for _, rule := range rules {
 		if rule.AllowDataURIImages {
@@ -176,6 +199,12 @@ func addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitize
 	}
 }
 
+// SanitizeDescription sanitizes the HTML generated for a repository description.
+func SanitizeDescription(s string) string {
+	NewSanitizer()
+	return sanitizer.descriptionPolicy.Sanitize(s)
+}
+
 // Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
 func Sanitize(s string) string {
 	NewSanitizer()
diff --git a/modules/markup/sanitizer_test.go b/modules/markup/sanitizer_test.go
index 0bc63ff0a7..b7b8792bd7 100644
--- a/modules/markup/sanitizer_test.go
+++ b/modules/markup/sanitizer_test.go
@@ -73,6 +73,28 @@ func Test_Sanitizer(t *testing.T) {
 	}
 }
 
+func TestDescriptionSanitizer(t *testing.T) {
+	NewSanitizer()
+
+	testCases := []string{
+		`<h1>Title</h1>`, `Title`,
+		`<img src='img.png' alt='image'>`, ``,
+		`<span class="emoji" aria-label="thumbs up">THUMBS UP</span>`, `<span class="emoji" aria-label="thumbs up">THUMBS UP</span>`,
+		`<span style="color: red">Hello World</span>`, `<span>Hello World</span>`,
+		`<br>`, ``,
+		`<a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>`, `<a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>`,
+		`<mark>Important!</mark>`, `Important!`,
+		`<details>Click me! <summary>Nothing to see here.</summary></details>`, `Click me! Nothing to see here.`,
+		`<input type="hidden">`, ``,
+		`<b>I</b> have a <i>strong</i> <strong>opinion</strong> about <em>this</em>.`, `<b>I</b> have a <i>strong</i> <strong>opinion</strong> about <em>this</em>.`,
+		`Provides alternative <code>wg(8)</code> tool`, `Provides alternative <code>wg(8)</code> tool`,
+	}
+
+	for i := 0; i < len(testCases); i += 2 {
+		assert.Equal(t, testCases[i+1], SanitizeDescription(testCases[i]))
+	}
+}
+
 func TestSanitizeNonEscape(t *testing.T) {
 	descStr := "<scrİpt>&lt;script&gt;alert(document.domain)&lt;/script&gt;</scrİpt>"