diff --git a/e2e/src/api/specs/shared-link.e2e-spec.ts b/e2e/src/api/specs/shared-link.e2e-spec.ts
index 3918429e4e..154f190f53 100644
--- a/e2e/src/api/specs/shared-link.e2e-spec.ts
+++ b/e2e/src/api/specs/shared-link.e2e-spec.ts
@@ -95,7 +95,7 @@ describe('/shared-links', () => {
       expect(resp.status).toBe(200);
       expect(resp.header['content-type']).toContain('text/html');
       expect(resp.text).toContain(
-        `<meta name="description" content="${metadataAlbum.assets.length} shared photos & videos" />`,
+        `<meta name="description" content="${metadataAlbum.assets.length} shared photos &amp; videos" />`,
       );
     });
 
@@ -103,14 +103,14 @@ describe('/shared-links', () => {
       const resp = await request(shareUrl).get(`/${linkWithAlbum.key}`);
       expect(resp.status).toBe(200);
       expect(resp.header['content-type']).toContain('text/html');
-      expect(resp.text).toContain(`<meta name="description" content="0 shared photos & videos" />`);
+      expect(resp.text).toContain(`<meta name="description" content="0 shared photos &amp; videos" />`);
     });
 
     it('should have correct asset count in meta tag for shared asset', async () => {
       const resp = await request(shareUrl).get(`/${linkWithAssets.key}`);
       expect(resp.status).toBe(200);
       expect(resp.header['content-type']).toContain('text/html');
-      expect(resp.text).toContain(`<meta name="description" content="1 shared photos & videos" />`);
+      expect(resp.text).toContain(`<meta name="description" content="1 shared photos &amp; videos" />`);
     });
 
     it('should have fqdn og:image meta tag for shared asset', async () => {
diff --git a/server/package-lock.json b/server/package-lock.json
index 8a2c0c9f25..80de8b37ff 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -59,6 +59,7 @@
         "reflect-metadata": "^0.2.0",
         "rxjs": "^7.8.1",
         "sanitize-filename": "^1.6.3",
+        "sanitize-html": "^2.14.0",
         "semver": "^7.6.2",
         "sharp": "^0.33.0",
         "sirv": "^3.0.0",
@@ -91,6 +92,7 @@
         "@types/picomatch": "^3.0.0",
         "@types/pngjs": "^6.0.5",
         "@types/react": "^19.0.0",
+        "@types/sanitize-html": "^2.13.0",
         "@types/semver": "^7.5.8",
         "@types/supertest": "^6.0.0",
         "@types/ua-parser-js": "^0.7.36",
@@ -6042,6 +6044,16 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/sanitize-html": {
+      "version": "2.13.0",
+      "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.13.0.tgz",
+      "integrity": "sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "htmlparser2": "^8.0.0"
+      }
+    },
     "node_modules/@types/semver": {
       "version": "7.5.8",
       "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
@@ -8931,7 +8943,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
       "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=10"
@@ -10747,6 +10758,15 @@
         "node": ">=0.12.0"
       }
     },
+    "node_modules/is-plain-object": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+      "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/is-promise": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
@@ -12394,6 +12414,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/parse-srcset": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
+      "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
+      "license": "MIT"
+    },
     "node_modules/parse5": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
@@ -14072,6 +14098,20 @@
         "truncate-utf8-bytes": "^1.0.0"
       }
     },
+    "node_modules/sanitize-html": {
+      "version": "2.14.0",
+      "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.14.0.tgz",
+      "integrity": "sha512-CafX+IUPxZshXqqRaG9ZClSlfPVjSxI0td7n07hk8QO2oO+9JDnlcL8iM8TWeOXOIBFgIOx6zioTzM53AOMn3g==",
+      "license": "MIT",
+      "dependencies": {
+        "deepmerge": "^4.2.2",
+        "escape-string-regexp": "^4.0.0",
+        "htmlparser2": "^8.0.0",
+        "is-plain-object": "^5.0.0",
+        "parse-srcset": "^1.0.2",
+        "postcss": "^8.3.11"
+      }
+    },
     "node_modules/scheduler": {
       "version": "0.25.0",
       "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
diff --git a/server/package.json b/server/package.json
index 06c23f3a1e..2d3356bb2c 100644
--- a/server/package.json
+++ b/server/package.json
@@ -85,6 +85,7 @@
     "reflect-metadata": "^0.2.0",
     "rxjs": "^7.8.1",
     "sanitize-filename": "^1.6.3",
+    "sanitize-html": "^2.14.0",
     "semver": "^7.6.2",
     "sharp": "^0.33.0",
     "sirv": "^3.0.0",
@@ -117,6 +118,7 @@
     "@types/picomatch": "^3.0.0",
     "@types/pngjs": "^6.0.5",
     "@types/react": "^19.0.0",
+    "@types/sanitize-html": "^2.13.0",
     "@types/semver": "^7.5.8",
     "@types/supertest": "^6.0.0",
     "@types/ua-parser-js": "^0.7.36",
diff --git a/server/src/services/api.service.ts b/server/src/services/api.service.ts
index a18f863f99..71fb36b4f2 100644
--- a/server/src/services/api.service.ts
+++ b/server/src/services/api.service.ts
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
 import { Cron, CronExpression, Interval } from '@nestjs/schedule';
 import { NextFunction, Request, Response } from 'express';
 import { readFileSync } from 'node:fs';
+import sanitizeHtml from 'sanitize-html';
 import { ONE_HOUR } from 'src/constants';
 import { ConfigRepository } from 'src/repositories/config.repository';
 import { LoggingRepository } from 'src/repositories/logging.repository';
@@ -12,21 +13,25 @@ import { VersionService } from 'src/services/version.service';
 import { OpenGraphTags } from 'src/utils/misc';
 
 const render = (index: string, meta: OpenGraphTags) => {
+  const [title, description, imageUrl] = [meta.title, meta.description, meta.imageUrl].map((item) =>
+    item ? sanitizeHtml(item, { allowedTags: [] }) : '',
+  );
+
   const tags = `
-    <meta name="description" content="${meta.description}" />
+    <meta name="description" content="${description}" />
 
     <!-- Facebook Meta Tags -->
     <meta property="og:type" content="website" />
-    <meta property="og:title" content="${meta.title}" />
-    <meta property="og:description" content="${meta.description}" />
-    ${meta.imageUrl ? `<meta property="og:image" content="${meta.imageUrl}" />` : ''}
+    <meta property="og:title" content="${title}" />
+    <meta property="og:description" content="${description}" />
+    ${imageUrl ? `<meta property="og:image" content="${imageUrl}" />` : ''}
 
     <!-- Twitter Meta Tags -->
     <meta name="twitter:card" content="summary_large_image" />
-    <meta name="twitter:title" content="${meta.title}" />
-    <meta name="twitter:description" content="${meta.description}" />
+    <meta name="twitter:title" content="${title}" />
+    <meta name="twitter:description" content="${description}" />
 
-    ${meta.imageUrl ? `<meta name="twitter:image" content="${meta.imageUrl}" />` : ''}`;
+    ${imageUrl ? `<meta name="twitter:image" content="${imageUrl}" />` : ''}`;
 
   return index.replace('<!-- metadata:tags -->', tags);
 };