fix(websub): validate callback URL (HTTPS), fix domain regex, enforce secret length

This commit is contained in:
Julien Herr
2026-05-21 23:01:20 +02:00
parent ee4b9d8fdc
commit 4165774667
2 changed files with 24 additions and 3 deletions
+22 -1
View File
@@ -33,6 +33,14 @@ hubRouter.post("/", async (c) => {
); );
} }
if (
typeof mode !== "string" ||
typeof topic !== "string" ||
typeof callbackUrl !== "string"
) {
return c.text("Bad Request: unexpected field types", 400);
}
if (mode !== "subscribe" && mode !== "unsubscribe") { if (mode !== "subscribe" && mode !== "unsubscribe") {
return c.text( return c.text(
"Bad Request: hub.mode must be subscribe or unsubscribe", "Bad Request: hub.mode must be subscribe or unsubscribe",
@@ -40,9 +48,19 @@ hubRouter.post("/", async (c) => {
); );
} }
let parsedCallback: URL;
try {
parsedCallback = new URL(callbackUrl);
} catch {
return c.text("Bad Request: hub.callback must be a valid URL", 400);
}
if (parsedCallback.protocol !== "https:") {
return c.text("Bad Request: hub.callback must use HTTPS", 400);
}
// Validate that topic matches a known RSS feed on this hub // Validate that topic matches a known RSS feed on this hub
const topicPattern = new RegExp( const topicPattern = new RegExp(
`^https://${env.DOMAIN.replace(".", "\\.")}/rss/([^/]+)$`, `^https://${env.DOMAIN.replaceAll(".", "\\.")}/rss/([^/]+)$`,
); );
const match = topic.match(topicPattern); const match = topic.match(topicPattern);
if (!match) { if (!match) {
@@ -54,6 +72,9 @@ hubRouter.post("/", async (c) => {
const feedId = match[1]; const feedId = match[1];
const secret = form.get("hub.secret") ?? undefined; const secret = form.get("hub.secret") ?? undefined;
if (secret && secret.length > 200) {
return c.text("Bad Request: hub.secret must be under 200 bytes", 400);
}
const rawLease = parseInt(form.get("hub.lease_seconds") ?? "", 10); const rawLease = parseInt(form.get("hub.lease_seconds") ?? "", 10);
const leaseSeconds = isNaN(rawLease) const leaseSeconds = isNaN(rawLease)
? DEFAULT_LEASE_SECONDS ? DEFAULT_LEASE_SECONDS
+2 -2
View File
@@ -1,11 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2021",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "node", "moduleResolution": "node",
"esModuleInterop": true, "esModuleInterop": true,
"strict": true, "strict": true,
"lib": ["ES2020"], "lib": ["ES2021"],
"types": ["@cloudflare/workers-types"], "types": ["@cloudflare/workers-types"],
"outDir": "dist", "outDir": "dist",
"noEmit": true, "noEmit": true,