Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2025-01-01 - Avoid `dangerouslySetInnerHTML`
**Vulnerability:** XSS risk from `dangerouslySetInnerHTML` via untrusted JSON-LD.
**Learning:** React 19 provides built-in mechanisms to safely render JSON objects inside script tags directly like `<script>{JSON_LD_STRING}</script>` instead of relying on `dangerouslySetInnerHTML`.
**Prevention:** Avoid `dangerouslySetInnerHTML` unless explicitly needed and audited, always sanitize user inputs, and leverage built-in React 19 safety features where applicable.
6 changes: 5 additions & 1 deletion docs/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ async function getMdxFiles(
baseDir: string,
results: string[] = [],
): Promise<string[]> {
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
const resolvedDir = path.resolve(dir);
if (!resolvedDir.startsWith(path.resolve(baseDir))) {
throw new Error("Path traversal detected");
}
const entries = await fs.promises.readdir(resolvedDir, { withFileTypes: true });

await Promise.all(
entries.map(async (entry) => {
Expand Down
4 changes: 3 additions & 1 deletion docs/content/reference/utilities.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ export default function Page() {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: safeJsonLdStringify(jsonLd) }}
>
{safeJsonLdStringify(jsonLd)}
</script>
/>
);
}
Expand Down
14 changes: 11 additions & 3 deletions scripts/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ async function main() {
const results: string[] = [];
const walk = async (currentDir: string): Promise<void> => {
try {
const list = await fs.promises.readdir(currentDir, { withFileTypes: true });
const resolvedCurrentDir = path.resolve(currentDir);
if (!resolvedCurrentDir.startsWith(path.resolve(dir))) {
throw new Error("Path traversal detected");
}
const list = await fs.promises.readdir(resolvedCurrentDir, { withFileTypes: true });
const tasks: Promise<void>[] = [];

for (const dirent of list) {
Expand Down Expand Up @@ -137,7 +141,11 @@ async function main() {
const filePath = path.join(process.cwd(), file);
try {
await fs.promises.access(filePath);
let content = await fs.promises.readFile(filePath, 'utf8');
const resolvedFilePath = path.resolve(filePath);
if (!resolvedFilePath.startsWith(process.cwd())) {
throw new Error("Path traversal detected");
}
let content = await fs.promises.readFile(resolvedFilePath, 'utf8');

// Order matters for replacements
// 1. GitHub full URLs
Expand Down Expand Up @@ -174,7 +182,7 @@ async function main() {
// 6. General "cur8d" replacement (Brand name)
content = content.replace(/cur8d/g, name);

await fs.promises.writeFile(filePath, content, 'utf8');
await fs.promises.writeFile(resolvedFilePath, content, 'utf8');
console.log(`βœ… Updated ${file}`);
} catch {
// File does not exist, skip
Expand Down