High severity8.8NVD Advisory· Published Dec 18, 2025· Updated Apr 10, 2026
CVE-2025-68278
CVE-2025-68278
Description
Tina is a headless content management system. In tinacms prior to version 3.1.1, tinacms uses the gray-matter package in an insecure way allowing attackers that can control the content of the processed markdown files, e.g., blog posts, to execute arbitrary code. tinacms version 3.1.1, @tinacms/cli version 2.0.4, and @tinacms/graphql version 2.0.3 contain a fix for the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
tinacmsnpm | < 3.1.1 | 3.1.1 |
@tinacms/clinpm | < 2.0.4 | 2.0.4 |
@tinacms/graphqlnpm | < 2.0.3 | 2.0.3 |
Affected products
3Patches
1fa7c27abef96fix: Prevent JavaScript and CoffeeScript execution in frontmatter (#6268)
3 files changed · +353 −0
.changeset/five-plums-tie.md+11 −0 added@@ -0,0 +1,11 @@ +--- +"@tinacms/graphql": patch +--- + +fix: Security patch to prevent arbitrary code execution in markdown frontmatter + +This fixes a security vulnerability where the `gray-matter` package executes JavaScript code by default when processing markdown files with `---js`, `---javascript`, or `---coffee` frontmatter delimiters. Attackers who could control markdown file content could exploit this to execute arbitrary code on the server. + +The fix disables JavaScript and CoffeeScript engines in frontmatter parsing. Valid frontmatter formats (YAML, TOML, JSON) continue to work normally, including storing JSX/HTML as string data. + +**Breaking Change:** If your content files use `---js`, `---javascript`, or `---coffee` frontmatter delimiters, they will now throw an error. You should migrate these files to use YAML, TOML, or JSON frontmatter instead \ No newline at end of file
packages/@tinacms/graphql/src/database/util.test.ts+292 −0 added@@ -0,0 +1,292 @@ +import { describe, it, expect } from 'vitest'; +import { parseFile, stringifyFile } from './util'; + +describe('gray-matter security', () => { + describe('parseFile', () => { + it('should reject JavaScript frontmatter to prevent code execution', () => { + const maliciousContent = `---js +{ + "title": "Pawned" + console.log(require("fs").readFileSync("/etc/passwd").toString()) +} +--- +# Blog Post + +Content here`; + + expect(() => { + parseFile(maliciousContent, '.md', (yup) => yup.object({})); + }).toThrow( + 'JavaScript execution in frontmatter is not allowed for security reasons' + ); + }); + + it('should reject javascript (alternate syntax) frontmatter', () => { + const maliciousContent = `---javascript +{ + "title": "Pawned" + console.log(require("fs").readFileSync("/etc/passwd").toString()) +} +--- +# Blog Post + +Content here`; + + expect(() => { + parseFile(maliciousContent, '.md', (yup) => yup.object({})); + }).toThrow( + 'JavaScript execution in frontmatter is not allowed for security reasons' + ); + }); + + it('should reject CoffeeScript frontmatter to prevent code execution', () => { + const maliciousContent = `---coffee +title: "Pawned" + console.log(require("fs").readFileSync("/etc/passwd").toString()) +--- +# Blog Post + +Content here`; + + expect(() => { + parseFile(maliciousContent, '.md', (yup) => yup.object({})); + }).toThrow( + 'CoffeeScript execution in frontmatter is not allowed for security reasons' + ); + }); + + it('should reject CoffeeScript (full name) frontmatter to prevent code execution', () => { + const maliciousContent = `---coffeescript +title: "Pawned" + console.log(require("fs").readFileSync("/etc/passwd").toString()) +--- +# Blog Post + +Content here`; + + expect(() => { + parseFile(maliciousContent, '.md', (yup) => yup.object({})); + }).toThrow( + 'CoffeeScript execution in frontmatter is not allowed for security reasons' + ); + }); + + it('should successfully parse YAML frontmatter (default safe format)', () => { + const safeContent = `--- +title: Safe Title +author: Test Author +--- +# Blog Post + +Content here`; + + const result = parseFile(safeContent, '.md', (yup) => yup.object({})); + + expect(result).toEqual({ + title: 'Safe Title', + author: 'Test Author', + $_body: '# Blog Post\n\nContent here', + }); + }); + + it('should successfully parse TOML frontmatter', () => { + const tomlContent = `+++ +title = "TOML Title" +author = "Test Author" ++++ +# Blog Post + +Content here`; + + const result = parseFile(tomlContent, '.md', (yup) => yup.object({}), { + frontmatterFormat: 'toml', + frontmatterDelimiters: '+++', + }); + + expect(result).toEqual({ + title: 'TOML Title', + author: 'Test Author', + $_body: '# Blog Post\n\nContent here', + }); + }); + + it('should successfully parse JSON frontmatter', () => { + const jsonContent = `--- +{ + "title": "JSON Title", + "author": "Test Author" +} +--- +# Blog Post + +Content here`; + + const result = parseFile(jsonContent, '.md', (yup) => yup.object({}), { + frontmatterFormat: 'json', + }); + + expect(result).toEqual({ + title: 'JSON Title', + author: 'Test Author', + $_body: '# Blog Post\n\nContent here', + }); + }); + + it('should successfully parse YAML frontmatter with JSX components as data', () => { + // JSX components stored as string data in YAML should work fine + // This is different from executing JavaScript code + const jsxContent = `--- +title: Blog with Hero Component +hero: + component: '<Hero title="Welcome" subtitle="To our site" />' + props: + variant: primary + showImage: true +customComponent: '<CustomSection><p>Content here</p></CustomSection>' +--- +# Blog Post + +Content with components stored in frontmatter`; + + const result = parseFile(jsxContent, '.md', (yup) => yup.object({})); + + expect(result).toEqual({ + title: 'Blog with Hero Component', + hero: { + component: '<Hero title="Welcome" subtitle="To our site" />', + props: { + variant: 'primary', + showImage: true, + }, + }, + customComponent: '<CustomSection><p>Content here</p></CustomSection>', + $_body: '# Blog Post\n\nContent with components stored in frontmatter', + }); + + // Verify the JSX is stored as a string, not executed + expect(typeof result.hero.component).toBe('string'); + expect(typeof result.customComponent).toBe('string'); + }); + }); + + describe('stringifyFile', () => { + it('should not allow stringify with js engine', () => { + const content = { + title: 'Test', + _relativePath: 'test.md', + _id: 'test.md', + _template: 'post', + _collection: 'posts', + $_body: 'Content', + }; + + expect(() => { + // @ts-ignore - testing invalid configuration + stringifyFile(content, '.md', false, { + frontmatterFormat: 'js' as any, + }); + }).toThrow( + 'JavaScript execution in frontmatter is not allowed for security reasons' + ); + }); + + it('should not allow stringify with javascript engine', () => { + const content = { + title: 'Test', + _relativePath: 'test.md', + _id: 'test.md', + _template: 'post', + _collection: 'posts', + $_body: 'Content', + }; + + expect(() => { + // @ts-ignore - testing invalid configuration + stringifyFile(content, '.md', false, { + frontmatterFormat: 'javascript' as any, + }); + }).toThrow( + 'JavaScript execution in frontmatter is not allowed for security reasons' + ); + }); + + it('should not allow stringify with coffee engine', () => { + const content = { + title: 'Test', + _relativePath: 'test.md', + _id: 'test.md', + _template: 'post', + _collection: 'posts', + $_body: 'Content', + }; + + expect(() => { + // @ts-ignore - testing invalid configuration + stringifyFile(content, '.md', false, { + frontmatterFormat: 'coffee' as any, + }); + }).toThrow( + 'CoffeeScript execution in frontmatter is not allowed for security reasons' + ); + }); + + it('should not allow stringify with coffeescript engine', () => { + const content = { + title: 'Test', + _relativePath: 'test.md', + _id: 'test.md', + _template: 'post', + _collection: 'posts', + $_body: 'Content', + }; + + expect(() => { + // @ts-ignore - testing invalid configuration + stringifyFile(content, '.md', false, { + frontmatterFormat: 'coffeescript' as any, + }); + }).toThrow( + 'CoffeeScript execution in frontmatter is not allowed for security reasons' + ); + }); + + it('should successfully stringify with YAML (default)', () => { + const content = { + title: 'Test Title', + author: 'Test Author', + _relativePath: 'test.md', + _id: 'test.md', + _template: 'post', + _collection: 'posts', + $_body: 'Content here', + }; + + const result = stringifyFile(content, '.md', false); + + expect(result).toContain('title: Test Title'); + expect(result).toContain('author: Test Author'); + expect(result).toContain('Content here'); + expect(result).not.toContain('_relativePath'); + expect(result).not.toContain('_id'); + expect(result).not.toContain('_template'); + expect(result).not.toContain('_collection'); + }); + + it('should successfully stringify with TOML', () => { + const content = { + title: 'Test Title', + author: 'Test Author', + _relativePath: 'test.md', + _id: 'test.md', + _template: 'post', + _collection: 'posts', + $_body: 'Content here', + }; + + const result = stringifyFile(content, '.md', false, { + frontmatterFormat: 'toml', + frontmatterDelimiters: '+++', + }); + + expect(result).toContain('title = "Test Title"'); + expect(result).toContain('author = "Test Author"'); + expect(result).toContain('Content here'); + }); + }); +});
packages/@tinacms/graphql/src/database/util.ts+50 −0 modified@@ -23,6 +23,56 @@ const matterEngines = { parse: (val) => toml.parse(val), stringify: (val) => toml.stringify(val), }, + // Disable JavaScript and CoffeeScript execution to prevent code execution vulnerability (CVE) + // gray-matter executes JS/Coffee code by default, which is a security risk + js: { + parse: () => { + throw new Error( + 'JavaScript execution in frontmatter is not allowed for security reasons' + ); + }, + stringify: () => { + throw new Error( + 'JavaScript execution in frontmatter is not allowed for security reasons' + ); + }, + }, + javascript: { + parse: () => { + throw new Error( + 'JavaScript execution in frontmatter is not allowed for security reasons' + ); + }, + stringify: () => { + throw new Error( + 'JavaScript execution in frontmatter is not allowed for security reasons' + ); + }, + }, + coffee: { + parse: () => { + throw new Error( + 'CoffeeScript execution in frontmatter is not allowed for security reasons' + ); + }, + stringify: () => { + throw new Error( + 'CoffeeScript execution in frontmatter is not allowed for security reasons' + ); + }, + }, + coffeescript: { + parse: () => { + throw new Error( + 'CoffeeScript execution in frontmatter is not allowed for security reasons' + ); + }, + stringify: () => { + throw new Error( + 'CoffeeScript execution in frontmatter is not allowed for security reasons' + ); + }, + }, }; export const stringifyFile = (
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/tinacms/tinacms/commit/fa7c27abef968e3f3a3e7d564f282bc566087569nvdPatchWEB
- github.com/tinacms/tinacms/security/advisories/GHSA-529f-9qwm-9628nvdExploitVendor AdvisoryWEB
- github.com/advisories/GHSA-529f-9qwm-9628ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-68278ghsaADVISORY
News mentions
0No linked articles in our index yet.