Skip to content

Commit

Permalink
fix(gatsby-plugin-mdx): don't allow JS frontmatter by default (#35830) (
Browse files Browse the repository at this point in the history
#35834)

Co-authored-by: Ty Hopp <hopp.ty.c@gmail.com>
Co-authored-by: Michal Piechowiak <misiek.piechowiak@gmail.com>
  • Loading branch information
3 people committed Jun 2, 2022
1 parent 36f21b0 commit ff94ed5
Show file tree
Hide file tree
Showing 29 changed files with 351 additions and 21 deletions.
1 change: 1 addition & 0 deletions e2e-tests/mdx-less-babel/.gitignore
Expand Up @@ -9,3 +9,4 @@ yarn-error.log

# Cypress output
cypress/videos/
cypress/screenshots/
@@ -0,0 +1 @@
Nothing here, do not remove
29 changes: 29 additions & 0 deletions e2e-tests/mdx-less-babel/cypress/integration/js-frontmatter.js
@@ -0,0 +1,29 @@
describe(`webpack loader`, () => {
it(`---js frontmatter should not parse by default`, () => {
cy.visit(`/js-frontmatter`).waitForRouteChange()

// Check frontmatter not parsed in page context
cy.get(`[data-cy="js-frontmatter"]`).invoke(`text`).should(`eq`, `disabled`)
})

it(`---javascript frontmatter should not parse by default`, () => {
cy.visit(`/javascript-frontmatter`).waitForRouteChange()

// Check frontmatter not parsed in page context
cy.get(`[data-cy="js-frontmatter"]`).invoke(`text`).should(`eq`, `disabled`)
})
})

describe(`data layer`, () => {
it(`---js or ---javascript frontmatter should not parse by default`, () => {
cy.visit(`/mdx-query-js-frontmatter/`).waitForRouteChange()
cy.contains(`I should not be parsed`).should("not.exist")
})
})

it(`---js and ---javascript frontmatter should not allow remote code execution`, () => {
cy.readFile(`cypress/fixtures/file-to-attempt-rce-on.txt`).should(
`eq`,
`Nothing here, do not remove`
)
})
12 changes: 12 additions & 0 deletions e2e-tests/mdx-less-babel/gatsby-node.js
@@ -0,0 +1,12 @@
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions

createTypes(`
type Mdx implements Node {
frontmatter: Frontmatter
}
type Frontmatter {
title: String
}
`)
}
15 changes: 8 additions & 7 deletions e2e-tests/mdx-less-babel/package.json
Expand Up @@ -6,9 +6,9 @@
"@mdx-js/mdx": "^1.6.6",
"@mdx-js/react": "^1.6.6",
"cypress": "^3.1.0",
"gatsby": "^2.0.118",
"gatsby-plugin-mdx": "^1.2.19",
"gatsby-source-filesystem": "^2.3.14",
"gatsby": "^3.0.0",
"gatsby-plugin-mdx": "^2.0.0",
"gatsby-source-filesystem": "^3.0.0",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"theme-ui": "^0.3.1"
Expand All @@ -18,10 +18,11 @@
],
"license": "MIT",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"clean": "gatsby clean",
"build": "cross-env CYPRESS_SUPPORT=y gatsby build",
"develop": "cross-env CYPRESS_SUPPORT=y gatsby develop",
"format": "prettier --write '**/*.js'",
"test": "cross-env CYPRESS_SUPPORT=y npm run build && npm run start-server-and-test",
"test": "npm run build && npm run start-server-and-test",
"start-server-and-test": "start-server-and-test serve http://localhost:9000 cy:run",
"serve": "gatsby serve",
"cy:open": "cypress open",
Expand All @@ -34,4 +35,4 @@
"prettier": "2.0.4",
"start-server-and-test": "^1.7.1"
}
}
}
16 changes: 16 additions & 0 deletions e2e-tests/mdx-less-babel/src/pages/javascript-frontmatter.mdx
@@ -0,0 +1,16 @@
---javascript
(() => {
require(`fs`).writeFileSync(`${process.cwd()}/cypress/fixtures/file-to-attempt-rce-on.txt`, (new Error('Helpful stack trace if this does execute. It should not execute.')).stack)
console.trace()
return {
title: `I should not be parsed`
}
})()

---

<h1>JS frontmatter engine is disabled by default</h1>

<span data-cy="js-frontmatter">
{props.pageContext.frontmatter?.title || `disabled`}
</span>
16 changes: 16 additions & 0 deletions e2e-tests/mdx-less-babel/src/pages/js-frontmatter.mdx
@@ -0,0 +1,16 @@
---js
(() => {
require(`fs`).writeFileSync(`${process.cwd()}/cypress/fixtures/file-to-attempt-rce-on.txt`, (new Error('Helpful stack trace if this does execute. It should not execute.')).stack)
console.trace()
return {
title: `I should not be parsed`
}
})()

---

<h1>JS frontmatter engine is disabled by default</h1>

<span data-cy="js-frontmatter">
{props.pageContext.frontmatter?.title || `disabled`}
</span>
30 changes: 30 additions & 0 deletions e2e-tests/mdx-less-babel/src/pages/mdx-query-js-frontmatter.js
@@ -0,0 +1,30 @@
import React from "react"
import { graphql } from "gatsby"

export default function PageRunningGraphqlResolversOnJSFrontmatterTestInputs({
data,
}) {
return <pre>{JSON.stringify(data.allMdx.nodes, null, 2)}</pre>
}

export const query = graphql`
{
allMdx(filter: { slug: { glob: "frontmatter-engine/*" } }) {
nodes {
frontmatter {
title
}
body
excerpt
tableOfContents
timeToRead
wordCount {
paragraphs
sentences
words
}
mdxAST
}
}
}
`
@@ -0,0 +1,16 @@
---javascript
(() => {
require(`fs`).writeFileSync(`${process.cwd()}/cypress/fixtures/file-to-attempt-rce-on.txt`, (new Error('Helpful stack trace if this does execute. It should not execute.')).stack)
console.trace()
return {
title: `I should not be parsed`
}
})()

---

<h1>JS frontmatter engine is disabled by default w</h1>

<span data-cy="js-frontmatter">
{props.pageContext.frontmatter?.title || `disabled`}
</span>
@@ -0,0 +1,16 @@
---js
(() => {
require(`fs`).writeFileSync(`${process.cwd()}/cypress/fixtures/file-to-attempt-rce-on.txt`, (new Error('Helpful stack trace if this does execute. It should not execute.')).stack)
console.trace()
return {
title: `I should not be parsed`
}
})()

---

<h1>JS frontmatter engine is disabled by default</h1>

<span data-cy="js-frontmatter">
{props.pageContext.frontmatter?.title || `disabled`}
</span>
1 change: 1 addition & 0 deletions e2e-tests/mdx/.gitignore
Expand Up @@ -9,3 +9,4 @@ yarn-error.log

# Cypress output
cypress/videos/
cypress/screenshots/
1 change: 1 addition & 0 deletions e2e-tests/mdx/cypress/fixtures/file-to-attempt-rce-on.txt
@@ -0,0 +1 @@
Nothing here, do not remove
29 changes: 29 additions & 0 deletions e2e-tests/mdx/cypress/integration/js-frontmatter.js
@@ -0,0 +1,29 @@
describe(`webpack loader`, () => {
it(`---js frontmatter should not parse by default`, () => {
cy.visit(`/js-frontmatter`).waitForRouteChange()

// Check frontmatter not parsed in page context
cy.get(`[data-cy="js-frontmatter"]`).invoke(`text`).should(`eq`, `disabled`)
})

it(`---javascript frontmatter should not parse by default`, () => {
cy.visit(`/javascript-frontmatter`).waitForRouteChange()

// Check frontmatter not parsed in page context
cy.get(`[data-cy="js-frontmatter"]`).invoke(`text`).should(`eq`, `disabled`)
})
})

describe(`data layer`, () => {
it(`---js or ---javascript frontmatter should not parse by default`, () => {
cy.visit(`/mdx-query-js-frontmatter/`).waitForRouteChange()
cy.contains(`I should not be parsed`).should("not.exist")
})
})

it(`---js and ---javascript frontmatter should not allow remote code execution`, () => {
cy.readFile(`cypress/fixtures/file-to-attempt-rce-on.txt`).should(
`eq`,
`Nothing here, do not remove`
)
})
2 changes: 1 addition & 1 deletion e2e-tests/mdx/gatsby-config.js
Expand Up @@ -37,7 +37,7 @@ module.exports = {
* See #26914 for more info.
*/
function remarkRequireFilePathPlugin() {
return function transformer(tree, file) {
return function transformer(_, file) {
if (!file.dirname) {
throw new Error("No directory name for this markdown file!")
}
Expand Down
13 changes: 13 additions & 0 deletions e2e-tests/mdx/gatsby-node.js
Expand Up @@ -16,3 +16,16 @@ exports.onPostBuild = async ({ graphql }) => {
{ spaces: 2 }
)
}

exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions

createTypes(`
type Mdx implements Node {
frontmatter: Frontmatter
}
type Frontmatter {
title: String
}
`)
}
1 change: 1 addition & 0 deletions e2e-tests/mdx/package.json
Expand Up @@ -19,6 +19,7 @@
],
"license": "MIT",
"scripts": {
"clean": "gatsby clean",
"build": "cross-env CYPRESS_SUPPORT=y gatsby build",
"develop": "cross-env CYPRESS_SUPPORT=y gatsby develop",
"format": "prettier --write '**/*.js'",
Expand Down
16 changes: 16 additions & 0 deletions e2e-tests/mdx/src/pages/javascript-frontmatter.mdx
@@ -0,0 +1,16 @@
---javascript
(() => {
require(`fs`).writeFileSync(`${process.cwd()}/cypress/fixtures/file-to-attempt-rce-on.txt`, (new Error('Helpful stack trace if this does execute. It should not execute.')).stack)
console.trace()
return {
title: `I should not be parsed`
}
})()

---

<h1>JS frontmatter engine is disabled by default</h1>

<span data-cy="js-frontmatter">
{props.pageContext.frontmatter?.title || `disabled`}
</span>
16 changes: 16 additions & 0 deletions e2e-tests/mdx/src/pages/js-frontmatter.mdx
@@ -0,0 +1,16 @@
---js
(() => {
require(`fs`).writeFileSync(`${process.cwd()}/cypress/fixtures/file-to-attempt-rce-on.txt`, (new Error('Helpful stack trace if this does execute. It should not execute.')).stack)
console.trace()
return {
title: `I should not be parsed`
}
})()

---

<h1>JS frontmatter engine is disabled by default</h1>

<span data-cy="js-frontmatter">
{props.pageContext.frontmatter?.title || `disabled`}
</span>
30 changes: 30 additions & 0 deletions e2e-tests/mdx/src/pages/mdx-query-js-frontmatter.js
@@ -0,0 +1,30 @@
import React from "react"
import { graphql } from "gatsby"

export default function PageRunningGraphqlResolversOnJSFrontmatterTestInputs({
data,
}) {
return <pre>{JSON.stringify(data.allMdx.nodes, null, 2)}</pre>
}

export const query = graphql`
{
allMdx(filter: { slug: { glob: "frontmatter-engine/*" } }) {
nodes {
frontmatter {
title
}
body
excerpt
tableOfContents
timeToRead
wordCount {
paragraphs
sentences
words
}
mdxAST
}
}
}
`
@@ -0,0 +1,16 @@
---javascript
(() => {
require(`fs`).writeFileSync(`${process.cwd()}/cypress/fixtures/file-to-attempt-rce-on.txt`, (new Error('Helpful stack trace if this does execute. It should not execute.')).stack)
console.trace()
return {
title: `I should not be parsed`
}
})()

---

<h1>JS frontmatter engine is disabled by default w</h1>

<span data-cy="js-frontmatter">
{props.pageContext.frontmatter?.title || `disabled`}
</span>
16 changes: 16 additions & 0 deletions e2e-tests/mdx/src/posts/frontmatter-engine/js-frontmatter.mdx
@@ -0,0 +1,16 @@
---js
(() => {
require(`fs`).writeFileSync(`${process.cwd()}/cypress/fixtures/file-to-attempt-rce-on.txt`, (new Error('Helpful stack trace if this does execute. It should not execute.')).stack)
console.trace()
return {
title: `I should not be parsed`
}
})()

---

<h1>JS frontmatter engine is disabled by default</h1>

<span data-cy="js-frontmatter">
{props.pageContext.frontmatter?.title || `disabled`}
</span>
5 changes: 5 additions & 0 deletions packages/gatsby-plugin-mdx/README.md
Expand Up @@ -129,6 +129,7 @@ scope, and more.
| [`mediaTypes`](#media-types) | `["text/markdown", "text/x-markdown"]` | Determine which media types are processed by MDX |
| [`shouldBlockNodeFromTransformation`](#shouldblocknodefromtransformation) | `(node) => false` | Disable MDX transformation for nodes where this function returns true |
| [`commonmark`](#commonmark) | `false` | Use CommonMark |
| [`JSFrontmatterEngine`](#jsfrontmatterengine) | `false` | Add support for JavaScript frontmatter engine |

#### Extensions

Expand Down Expand Up @@ -472,6 +473,10 @@ module.exports = {

MDX will be parsed using CommonMark.

#### JSFrontmatterEngine

Adds support for JavaScript frontmatter engine. Use with caution - see https://github.com/gatsbyjs/gatsby/security/advisories/GHSA-mj46-r4gr-5x83

### Components

MDX and `gatsby-plugin-mdx` use components for different things like rendering
Expand Down
3 changes: 3 additions & 0 deletions packages/gatsby-plugin-mdx/__tests__/gatsby-node.js
Expand Up @@ -17,6 +17,7 @@ describe(`pluginOptionsSchema`, () => {
`"mediaTypes[1]" must be a string`,
`"shouldBlockNodeFromTransformation" must have an arity lesser or equal to 1`,
`"root" must be a string`,
`"JSFrontmatterEngine" must be a boolean`,
]

const { errors } = await testPluginOptionsSchema(pluginOptionsSchema, {
Expand All @@ -29,6 +30,7 @@ describe(`pluginOptionsSchema`, () => {
mediaTypes: [1, 2],
shouldBlockNodeFromTransformation: (wrong, number) => null,
root: 1,
JSFrontmatterEngine: `this should be a boolean`,
})

expect(errors).toEqual(expectedErrors)
Expand Down Expand Up @@ -63,6 +65,7 @@ describe(`pluginOptionsSchema`, () => {
mediaTypes: [`text/markdown`, `text/x-markdown`, `custom-media/type`],
shouldBlockNodeFromTransformation: node => Boolean(node),
root: `james-holden`,
JSFrontmatterEngine: true,
})

expect(isValid).toBe(true)
Expand Down

0 comments on commit ff94ed5

Please sign in to comment.