diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..b89b87d --- /dev/null +++ b/.jules/sentinel.md @@ -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 `` 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. diff --git a/docs/app/sitemap.ts b/docs/app/sitemap.ts index bc266e9..3b8b6f0 100644 --- a/docs/app/sitemap.ts +++ b/docs/app/sitemap.ts @@ -16,7 +16,11 @@ async function getMdxFiles( baseDir: string, results: string[] = [], ): Promise { - 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) => { diff --git a/docs/content/reference/utilities.mdx b/docs/content/reference/utilities.mdx index 5a0b8c8..5d488e7 100644 --- a/docs/content/reference/utilities.mdx +++ b/docs/content/reference/utilities.mdx @@ -53,7 +53,9 @@ export default function Page() { return ( /> ); } diff --git a/scripts/init.ts b/scripts/init.ts index 6a8289f..c9281ab 100644 --- a/scripts/init.ts +++ b/scripts/init.ts @@ -106,7 +106,11 @@ async function main() { const results: string[] = []; const walk = async (currentDir: string): Promise => { 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[] = []; for (const dirent of list) { @@ -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 @@ -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