fix: swap gray-matter engine from js-yaml to yaml package

Closes CVE-2023-44270 (quadratic-DoS via crafted YAML merge-key aliases).
js-yaml@3.x (pinned by gray-matter@4) is no longer used for frontmatter
parsing. The 'yaml' package (already a dependency) is used instead.

Behavioral: dates parsed as strings (YAML 1.2), single-letter keys
preserved as-is, merge keys not resolved. No production code affected.
This commit is contained in:
Mauricio Wolff
2026-06-23 20:31:32 +02:00
parent 4911f04f79
commit fde15eb1f2
2 changed files with 23 additions and 6 deletions
+2 -1
View File
@@ -18,7 +18,8 @@ This is a test note with frontmatter.`;
expect(result.frontmatter.title).toBe("Test Note");
expect(result.frontmatter.tags).toEqual(["test", "example"]);
expect(result.frontmatter.created).toEqual(new Date("2023-01-01"));
// YAML 1.2 (yaml package) keeps dates as strings; js-yaml@3 parsed them as Date objects.
expect(result.frontmatter.created).toBe("2023-01-01");
expect(result.content.trim()).toBe("# Test Note\n\nThis is a test note with frontmatter.");
});
+21 -5
View File
@@ -1,7 +1,23 @@
import matter from 'gray-matter';
import { parseDocument } from 'yaml';
import { parse, stringify, parseDocument } from 'yaml';
import type { ParsedNote, FrontmatterValidationResult } from './types.js';
// Use the 'yaml' package as gray-matter's engine instead of js-yaml.
// js-yaml@3.x (pinned by gray-matter@4) has known quadratic-DoS via
// crafted YAML merge-key aliases (CVE-2023-44270). The 'yaml' package
// is already a dependency (used in preserveStringify) and is not affected.
const yamlEngine = {
// YAML 1.2 schema (default). Dates stay as strings, but this avoids the
// yaml-1.1 bug where single-letter keys like 'y'/'n' become booleans.
// Merge keys (<<) are not resolved, which is fine for Obsidian frontmatter.
parse: (str: string) => parse(str),
stringify: (data: any) => stringify(data),
};
function withYamlEngine(options: Record<string, any> = {}): Record<string, any> {
return { ...options, engines: { yaml: yamlEngine } };
}
/**
* Parse a frontmatter value that may be a JSON string (LLM clients sometimes
* pass frontmatter as a serialized JSON string instead of an object).
@@ -31,7 +47,7 @@ export function parseFrontmatter(value: any): Record<string, any> | undefined {
export class FrontmatterHandler {
parse(content: string): ParsedNote {
try {
const parsed = matter(content);
const parsed = matter(content, withYamlEngine());
return {
frontmatter: parsed.data,
content: parsed.content,
@@ -56,7 +72,7 @@ export class FrontmatterHandler {
return content;
}
return matter.stringify(content, frontmatterData);
return matter.stringify(content, frontmatterData, withYamlEngine());
} catch (error) {
throw new Error(`Failed to stringify frontmatter: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
@@ -71,7 +87,7 @@ export class FrontmatterHandler {
try {
// Test if the frontmatter can be serialized to valid YAML using gray-matter
matter.stringify('', frontmatterData);
matter.stringify('', frontmatterData, withYamlEngine());
} catch (error) {
result.isValid = false;
result.errors.push(`Invalid YAML structure: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -141,7 +157,7 @@ export class FrontmatterHandler {
if (!updates || Object.keys(updates).length === 0) {
return content;
}
return matter.stringify(content, updates);
return matter.stringify(content, updates, withYamlEngine());
}
const doc = parseDocument(rawMatter.trimStart());