Skip to content

Feat/threads discussion forum jsonld#2962

Merged
eldadfux merged 3 commits intomainfrom
feat/threads-discussion-forum-jsonld
May 4, 2026
Merged

Feat/threads discussion forum jsonld#2962
eldadfux merged 3 commits intomainfrom
feat/threads-discussion-forum-jsonld

Conversation

@eldadfux
Copy link
Copy Markdown
Member

@eldadfux eldadfux commented May 4, 2026

What does this PR do?

(Provide a description of what this PR does.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

eldadfux and others added 3 commits May 4, 2026 18:53
Implements Google Search structured data for /threads/[id] per
discussion-forum guidelines: WebPage with mainEntity DiscussionForumPosting
(author, datePublished, text, headline, url) and Comment entities for replies.

Co-authored-by: Cursor <cursoragent@cursor.com>
Modified the MessageCard component to show the TL;DR section only when it is available and not empty, enhancing the user experience by providing relevant information when applicable.
@appwrite
Copy link
Copy Markdown

appwrite Bot commented May 4, 2026

Appwrite Website

Project ID: 69d7efb00023389e8d27

Sites (1)
Site Status Logs Preview QR
 website
69d7f2670014e24571ca
Failed Failed View Logs Preview URL QR Code

Website (appwrite/website)

Project ID: 684969cb000a2f6c0a02

Sites (1)
Site Status Logs Preview QR
 website
68496a17000f03d62013
Processing Processing View Logs Preview URL QR Code


Tip

Custom domains work with both CNAME for subdomains and NS records for apex domains

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 4, 2026

Greptile Summary

This PR adds DiscussionForumPosting JSON-LD structured data to thread pages, generated server-side and injected via {@html} in <svelte:head>. It also tightens the TL;DR render guard to skip empty strings.

  • P1 – XSS: JSON.stringify does not HTML-encode </>, so a Discord message containing </script> will break out of the injected script tag. Unicode-escape <, >, and & in createDiscussionForumPageSchema before returning.

Confidence Score: 3/5

Not safe to merge until the JSON-LD payload is HTML-escaped to prevent </script> injection from user-generated thread content.

A P1 security finding (XSS via unescaped </script> in user-supplied data) caps the score; two additional P2 quality issues lower it slightly below the P1 ceiling.

src/lib/utils/metadata.ts — the createDiscussionForumPageSchema return value must HTML-escape <, >, and & before the JSON is embedded in a script tag.

Security Review

  • XSS – src/lib/utils/metadata.ts (createDiscussionForumPageSchema): User-generated content (thread title, message text, author name) is embedded directly into a <script type="application/ld+json"> tag via JSON.stringify, which does not HTML-encode < or >. A crafted </script> sequence in any field would close the script tag prematurely, allowing arbitrary HTML/script injection for all visitors of that thread page.

Important Files Changed

Filename Overview
src/lib/utils/metadata.ts Adds createDiscussionForumPageSchema for JSON-LD generation; user-supplied content is not HTML-escaped before injection into a <script> tag, creating a XSS vector via </script> in thread/message data.
src/routes/threads/[id]/+page.server.ts Server load function correctly constructs the canonical URL and maps thread/message fields into the schema helper; no issues beyond what the helper itself exposes.
src/routes/threads/[id]/+page.svelte Injects the JSON-LD block via {@html} inside <svelte:head>; also fixes TL;DR guard to skip empty strings. Safe as long as the payload from metadata.ts is properly escaped.

Reviews (1): Last reviewed commit: "format" | Re-trigger Greptile

Comment thread src/lib/utils/metadata.ts
mainEntity
};

return JSON.stringify(graph);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security XSS via unescaped </script> in user-generated JSON-LD

JSON.stringify does not HTML-encode < or >, so if any user-supplied field (thread title, message text, or author name) contains the literal string </script>, it will appear verbatim in the serialized JSON and prematurely close the surrounding <script type="application/ld+json"> tag. An attacker on Discord could craft a message body to inject arbitrary script into every visitor's browser. The standard fix is to Unicode-escape the three dangerous characters before returning:

return JSON.stringify(graph)
    .replace(/</g, '\\u003c')
    .replace(/>/g, '\\u003e')
    .replace(/&/g, '\\u0026');

Comment thread src/lib/utils/metadata.ts
Comment on lines +173 to +176
function nonEmptyText(value: string, fallback: string): string {
const t = value.trim();
return t.length > 0 ? value : fallback;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 nonEmptyText returns untrimmed value, inconsistently

The helper trims value to decide whether it is empty, but then returns the original (untrimmed) value when non-empty. Returning t (the already-trimmed copy) is more consistent and avoids passing leading/trailing whitespace into JSON-LD fields.

Suggested change
function nonEmptyText(value: string, fallback: string): string {
const t = value.trim();
return t.length > 0 ? value : fallback;
}
function nonEmptyText(value: string, fallback: string): string {
const t = value.trim();
return t.length > 0 ? t : fallback;
}

Comment thread src/lib/utils/metadata.ts
Comment on lines +168 to +171
function toIso8601DateTime(value: string): string {
const d = new Date(value);
return Number.isNaN(d.getTime()) ? new Date().toISOString() : d.toISOString();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Silent date fallback hides bad data

toIso8601DateTime silently substitutes the current server render time when given an unparseable date string, producing a plausible-looking ISO timestamp that masks a data quality issue upstream and can mislead Google Search Console.

Suggested change
function toIso8601DateTime(value: string): string {
const d = new Date(value);
return Number.isNaN(d.getTime()) ? new Date().toISOString() : d.toISOString();
}
function toIso8601DateTime(value: string): string {
const d = new Date(value);
if (Number.isNaN(d.getTime())) {
console.warn(`[metadata] Invalid date string: "${value}"`);
return '';
}
return d.toISOString();
}

@eldadfux eldadfux merged commit c0e7af0 into main May 4, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant