Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gatsby-transformer-remark): Disallow JS frontmatter by default (#37244) #37298

Merged
merged 1 commit into from
Dec 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 85 additions & 56 deletions packages/gatsby-transformer-remark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,46 @@ Parses Markdown files using [remark](http://remark.js.org/).

## Install

`npm install gatsby-transformer-remark`

## How to use

```javascript
// In your gatsby-config.js
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
// Footnotes mode (default: true)
footnotes: true,
// GitHub Flavored Markdown mode (default: true)
gfm: true,
// Plugins configs
plugins: [],
Install the plugin to your site:

```shell
npm install gatsby-transformer-remark
```

Add it to your `gatsby-config`:

```js:title=gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {},
},
},
],
],
}
```

## Options

```js:title=gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
// Footnotes mode (default: true)
footnotes: true,
// GitHub Flavored Markdown mode (default: true)
gfm: true,
// Add your gatsby-remark-* plugins here
plugins: [],
// Enable JS for https://github.com/jonschlinkert/gray-matter#optionsengines (default: false)
// It's not advised to set this to "true" and this option will likely be removed in the future
jsFrontmatterEngine: false,
},
},
],
}
```

The following parts of `options` enable the `remark-footnotes` and `remark-gfm`
Expand All @@ -31,10 +52,30 @@ plugins:
- `options.footnotes`
- `options.gfm`

A full explanation of how to use markdown in Gatsby can be found here:
[Adding Markdown Pages](https://www.gatsbyjs.com/docs/how-to/routing/adding-markdown-pages/)
A full explanation of how to use markdown in Gatsby can be found here: [Adding Markdown Pages](https://www.gatsbyjs.com/docs/how-to/routing/adding-markdown-pages/)

There are many `gatsby-remark-*` plugins which you can install to customize how Markdown is processed. Check out the [source code for using-remark](https://github.com/gatsbyjs/gatsby/tree/master/examples/using-remark) as an example.

### `gray-matter` options

`gatsby-transformer-remark` uses [gray-matter](https://github.com/jonschlinkert/gray-matter) to parse Markdown frontmatter, so you can specify any of the options mentioned [in its README](https://github.com/jonschlinkert/gray-matter#options) in the `options` key of the plugin.

There are many Gatsby Remark plugins which you can install to customize how Markdown is processed. Many of them are demoed at https://using-remark.gatsbyjs.org/. See also the [source code for using-remark](https://github.com/gatsbyjs/gatsby/tree/master/examples/using-remark).
**Example: Excerpts**

If you don't want to use `pruneLength` for excerpts but a custom separator, you can specify an `excerpt_separator`:

```js:title=gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
excerpt_separator: `<!-- end -->`
}
},
],
}
```

## Parsing algorithm

Expand Down Expand Up @@ -120,19 +161,20 @@ By default, `absolute` is set to `false`, generating a relative path. If you'd l

To pass default options to the plugin generating the `tableOfContents`, configure it in `gatsby-config.js` as shown below. The options shown below are the defaults used by the plugin.

```javascript
// In your gatsby-config.js
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
tableOfContents: {
heading: null,
maxDepth: 6,
```js:title=gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
tableOfContents: {
heading: null,
maxDepth: 6,
},
},
},
},
]
],
}
```

### Excerpts
Expand Down Expand Up @@ -198,23 +240,6 @@ You can also get excerpts in Markdown format.
}
```

## `gray-matter` options

`gatsby-transformer-remark` uses [gray-matter](https://github.com/jonschlinkert/gray-matter) to parse Markdown frontmatter, so you can specify any of the options mentioned [here](https://github.com/jonschlinkert/gray-matter#options) in the `gatsby-config.js` file.

### Example: Excerpts

If you don't want to use `pruneLength` for excerpts but a custom separator, you can specify an `excerpt_separator` in the `gatsby-config.js` file:

```javascript
{
"resolve": `gatsby-transformer-remark`,
"options": {
"excerpt_separator": `<!-- end -->`
}
}
```

Any file that does not have the given `excerpt_separator` will fall back to the default pruning method.

## Troubleshooting
Expand All @@ -237,14 +262,18 @@ If that is the case, you can set `truncate` option on `excerpt` field, like:

If your Markdown file contains HTML, `excerpt` will not return a value.

In that case, you can set an `excerpt_separator` in the `gatsby-config.js` file:
In that case, you can set an `excerpt_separator` in the `gatsby-config`:

```javascript
{
"resolve": `gatsby-transformer-remark`,
"options": {
"excerpt_separator": `<!-- endexcerpt -->`
}
```js:title=gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
excerpt_separator: `<!-- endexcerpt -->`
},
},
],
}
```

Expand Down
45 changes: 28 additions & 17 deletions packages/gatsby-transformer-remark/src/__tests__/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,43 @@ describe(`gatsby-node.js`, () => {
`"footnotes" must be a boolean`,
`"gfm" must be a boolean`,
`"plugins" must be an array`,
`"jsFrontmatterEngine" must be a boolean`,
]

const { errors } = await testPluginOptionsSchema(pluginOptionsSchema, {
footnotes: `this should be a boolean`,
gfm: `this should be a boolean`,
plugins: `this should be an array`,
})
const { errors, isValid } = await testPluginOptionsSchema(
pluginOptionsSchema,
{
footnotes: `this should be a boolean`,
gfm: `this should be a boolean`,
plugins: `this should be an array`,
jsFrontmatterEngine: `this should be a boolean`,
}
)

expect(isValid).toBe(false)
expect(errors).toEqual(expectedErrors)
})

it(`should validate the schema`, async () => {
const { isValid } = await testPluginOptionsSchema(pluginOptionsSchema, {
footnotes: false,
gfm: false,
plugins: [
`gatsby-remark-copy-linked-files`,
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 756,
const { isValid, errors } = await testPluginOptionsSchema(
pluginOptionsSchema,
{
footnotes: false,
gfm: false,
plugins: [
`gatsby-remark-copy-linked-files`,
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 756,
},
},
},
],
})
],
jsFrontmatterEngine: true,
}
)

expect(isValid).toBe(true)
expect(errors).toEqual([])
})
})
41 changes: 41 additions & 0 deletions packages/gatsby-transformer-remark/src/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ exports.unstable_shouldOnCreateNode = unstable_shouldOnCreateNode
exports.createSchemaCustomization = require(`./create-schema-customization`)
exports.setFieldsOnGraphQLNodeType = require(`./extend-node-type`)

// Dedupe warning
let warnedAboutJSFrontmatterEngine = false

exports.pluginOptionsSchema = function ({ Joi }) {
return Joi.object({
footnotes: Joi.boolean().description(
Expand All @@ -21,5 +24,43 @@ exports.pluginOptionsSchema = function ({ Joi }) {
plugins: Joi.subPlugins().description(
`A list of remark plugins. See also: https://github.com/gatsbyjs/gatsby/tree/master/examples/using-remark for examples`
),
// TODO(v6): Remove and disallow any custom engines (including JS)
jsFrontmatterEngine: Joi.boolean()
.default(false)
.description(
`Enable JS for https://github.com/jonschlinkert/gray-matter#optionsengines`
),
}).custom(value => {
const { jsFrontmatterEngine, engines = {} } = value || {}

if (jsFrontmatterEngine) {
// show this warning only once in main process
if (!process.env.GATSBY_WORKER_ID) {
console.warn(
`JS frontmatter engine is enabled in gatsby-transformer-remark (via jsFrontmatterEngine: true). This can cause a security risk, see https://github.com/gatsbyjs/gatsby/security/advisories/GHSA-7ch4-rr99-cqcw. If you are not relying on this feature we strongly suggest disabling it via the "jsFrontmatterEngine: false" plugin option. If you rely on this feature make sure to properly secure or sanitize your content source.`
)
}
return value
}

const js = () => {
if (!warnedAboutJSFrontmatterEngine) {
console.warn(
`You have frontmatter declared with "---js" or "---javascript" that is not parsed by default to mitigate a security risk (see https://github.com/gatsbyjs/gatsby/security/advisories/GHSA-7ch4-rr99-cqcw). If you require this feature it can be enabled by setting "jsFrontmatterEngine: true" in the plugin options of gatsby-transformer-remark.`
)
warnedAboutJSFrontmatterEngine = true
}
// we still have to return a frontmatter, so we just stub it with empty object
return {}
}

return {
...value,
engines: {
...engines,
js,
javascript: js,
},
}
})
}