diff --git a/src/commands/backlinks.ts b/src/commands/backlinks.ts index b99ae9567..ec9fcb395 100644 --- a/src/commands/backlinks.ts +++ b/src/commands/backlinks.ts @@ -10,7 +10,7 @@ * gbrain check-backlinks fix --dry-run # preview fixes */ -import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'fs'; +import { readFileSync, writeFileSync, readdirSync, statSync, lstatSync, existsSync } from 'fs'; import { join, relative, basename } from 'path'; interface BacklinkGap { @@ -69,7 +69,7 @@ export function findBacklinkGaps(brainDir: string): BacklinkGap[] { for (const entry of readdirSync(dir)) { if (entry.startsWith('.')) continue; const full = join(dir, entry); - if (statSync(full).isDirectory()) { + if (lstatSync(full).isDirectory()) { walk(full); } else if (entry.endsWith('.md') && !entry.startsWith('_')) { const relPath = relative(brainDir, full); diff --git a/src/commands/files.ts b/src/commands/files.ts index 445bbcd4c..354b984bf 100644 --- a/src/commands/files.ts +++ b/src/commands/files.ts @@ -243,8 +243,6 @@ async function uploadRaw(args: string[]) { // Write pointer next to the page that references it pointerPath = `${pageSlug}/${filename}.redirect.yaml`; console.error(`Pointer: ${pointerPath}`); - // Note: the caller is responsible for writing the pointer file - // to the brain repo at the correct location } // Record in DB diff --git a/src/commands/lint.ts b/src/commands/lint.ts index 754bbf287..3a509066f 100644 --- a/src/commands/lint.ts +++ b/src/commands/lint.ts @@ -16,7 +16,7 @@ * gbrain lint # lint single file */ -import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'fs'; +import { readFileSync, writeFileSync, readdirSync, statSync, lstatSync, existsSync } from 'fs'; import { join, relative } from 'path'; export interface LintIssue { @@ -173,7 +173,7 @@ function collectPages(dir: string): string[] { for (const entry of readdirSync(d)) { if (entry.startsWith('.') || entry.startsWith('_')) continue; const full = join(d, entry); - if (statSync(full).isDirectory()) walk(full); + if (lstatSync(full).isDirectory()) walk(full); else if (entry.endsWith('.md')) pages.push(full); } } diff --git a/src/commands/publish.ts b/src/commands/publish.ts index 82514ad48..91e45f28d 100644 --- a/src/commands/publish.ts +++ b/src/commands/publish.ts @@ -275,11 +275,31 @@ export function generateHtml({ title, markdown, encrypted }: GenerateHtmlOptions window.__CT = ${JSON.stringify(encrypted.ciphertext)}; ` : ''; + // Sanitize markdown rendering to prevent XSS from embedded HTML in brain pages + const sanitizeScript = ` + function sanitizeHtml(html) { + const div = document.createElement('div'); + div.innerHTML = html; + div.querySelectorAll('script,iframe,object,embed,form').forEach(el => el.remove()); + div.querySelectorAll('*').forEach(el => { + for (const attr of [...el.attributes]) { + if (attr.name.startsWith('on') || attr.value.startsWith('javascript:')) { + el.removeAttribute(attr.name); + } + } + }); + return div.innerHTML; + } + `; + const contentScript = encrypted - ? `