From b9c902b016f411a942c8707dd07d32b60bad087c Mon Sep 17 00:00:00 2001 From: simosmik Date: Wed, 11 Mar 2026 22:04:38 +0000 Subject: [PATCH] fix(security): disable executable gray-matter frontmatter in commands --- server/routes/commands.js | 8 ++++---- server/utils/commandParser.js | 4 ++-- server/utils/frontmatter.js | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 server/utils/frontmatter.js diff --git a/server/routes/commands.js b/server/routes/commands.js index 5446734..388a8f7 100644 --- a/server/routes/commands.js +++ b/server/routes/commands.js @@ -3,8 +3,8 @@ import { promises as fs } from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import os from 'os'; -import matter from 'gray-matter'; import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js'; +import { parseFrontmatter } from '../utils/frontmatter.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -38,7 +38,7 @@ async function scanCommandsDirectory(dir, baseDir, namespace) { // Parse markdown file for metadata try { const content = await fs.readFile(fullPath, 'utf8'); - const { data: frontmatter, content: commandContent } = matter(content); + const { data: frontmatter, content: commandContent } = parseFrontmatter(content); // Calculate relative path from baseDir for command name const relativePath = path.relative(baseDir, fullPath); @@ -475,7 +475,7 @@ router.post('/load', async (req, res) => { // Read and parse the command file const content = await fs.readFile(commandPath, 'utf8'); - const { data: metadata, content: commandContent } = matter(content); + const { data: metadata, content: commandContent } = parseFrontmatter(content); res.json({ path: commandPath, @@ -560,7 +560,7 @@ router.post('/execute', async (req, res) => { } } const content = await fs.readFile(commandPath, 'utf8'); - const { data: metadata, content: commandContent } = matter(content); + const { data: metadata, content: commandContent } = parseFrontmatter(content); // Basic argument replacement (will be enhanced in command parser utility) let processedContent = commandContent; diff --git a/server/utils/commandParser.js b/server/utils/commandParser.js index 11af5c7..56e3f70 100644 --- a/server/utils/commandParser.js +++ b/server/utils/commandParser.js @@ -1,9 +1,9 @@ -import matter from 'gray-matter'; import { promises as fs } from 'fs'; import path from 'path'; import { execFile } from 'child_process'; import { promisify } from 'util'; import { parse as parseShellCommand } from 'shell-quote'; +import { parseFrontmatter } from './frontmatter.js'; const execFileAsync = promisify(execFile); @@ -32,7 +32,7 @@ const BASH_COMMAND_ALLOWLIST = [ */ export function parseCommand(content) { try { - const parsed = matter(content); + const parsed = parseFrontmatter(content); return { data: parsed.data || {}, content: parsed.content || '', diff --git a/server/utils/frontmatter.js b/server/utils/frontmatter.js new file mode 100644 index 0000000..0a4b1eb --- /dev/null +++ b/server/utils/frontmatter.js @@ -0,0 +1,18 @@ +import matter from 'gray-matter'; + +const disabledFrontmatterEngine = () => ({}); + +const frontmatterOptions = { + language: 'yaml', + // Disable JS/JSON frontmatter parsing to avoid executable project content. + // Mirrors Gatsby's mitigation for gray-matter. + engines: { + js: disabledFrontmatterEngine, + javascript: disabledFrontmatterEngine, + json: disabledFrontmatterEngine + } +}; + +export function parseFrontmatter(content) { + return matter(content, frontmatterOptions); +}