diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts
index fdc994233c..cbab8ac374 100644
--- a/server/api/registry/badge/[type]/[...pkg].get.ts
+++ b/server/api/registry/badge/[type]/[...pkg].get.ts
@@ -87,6 +87,14 @@ function measureDefaultTextWidth(text: string): number {
return Math.max(MIN_BADGE_TEXT_WIDTH, Math.round(text.length * CHAR_WIDTH) + BADGE_PADDING_X * 2)
}
+function escapeXML(str: string): string {
+ return str
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+}
+
function measureShieldsTextLength(text: string): number {
const measuredWidth = measureTextWidth(text, SHIELDS_FONT_SHORTHAND)
@@ -108,9 +116,11 @@ function renderDefaultBadgeSvg(params: {
const rightWidth = measureDefaultTextWidth(finalValue)
const totalWidth = leftWidth + rightWidth
const height = 20
+ const escapedLabel = escapeXML(finalLabel)
+ const escapedValue = escapeXML(finalValue)
return `
-
`.trim()
diff --git a/shared/schemas/social.ts b/shared/schemas/social.ts
index f0c018e11d..1e3305941b 100644
--- a/shared/schemas/social.ts
+++ b/shared/schemas/social.ts
@@ -13,7 +13,19 @@ export type PackageLikeBody = v.InferOutput
// TODO: add 'avatar'
export const ProfileEditBodySchema = v.object({
displayName: v.pipe(v.string(), v.maxLength(640)),
- website: v.optional(v.union([v.literal(''), v.pipe(v.string(), v.url())])),
+ website: v.optional(
+ v.union([
+ v.literal(''),
+ v.pipe(
+ v.string(),
+ v.url(),
+ v.check(
+ url => url.startsWith('https://') || url.startsWith('http://'),
+ 'Website must use http or https',
+ ),
+ ),
+ ]),
+ ),
description: v.optional(v.pipe(v.string(), v.maxLength(2560))),
})