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