refactor: use iframes for gist embedding instead of gist-embed

The used library gist-embed relies on GitHub Gist's JSONP
endpoint which is a risk for XSS injection. By adding untrusted
content from GitHub into the DOM it also follows very bad
practises. Using the iframe embedding has the disadvantage of
not having the proper height for the frame auto-loaded, but
the security benefits are worth it.

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2025-04-08 00:03:48 +02:00
parent d2585fbd3b
commit 0636b5c20b
6 changed files with 8 additions and 23 deletions

View file

@ -10,18 +10,17 @@ const defaultDirectives = {
connectSrc: ['\'self\'', buildDomainOriginWithProtocol(config, 'ws')], connectSrc: ['\'self\'', buildDomainOriginWithProtocol(config, 'ws')],
fontSrc: ['\'self\''], fontSrc: ['\'self\''],
manifestSrc: ['\'self\''], manifestSrc: ['\'self\''],
frameSrc: ['\'self\'', 'https://player.vimeo.com', 'https://www.slideshare.net/slideshow/embed_code/key/', 'https://www.youtube.com'], frameSrc: ['\'self\'', 'https://player.vimeo.com', 'https://www.slideshare.net/slideshow/embed_code/key/', 'https://www.youtube.com', 'https://gist.github.com'],
imgSrc: ['*', 'data:'], // we allow using arbitrary images & explicit data for mermaid imgSrc: ['*', 'data:'], // we allow using arbitrary images & explicit data for mermaid
scriptSrc: [ scriptSrc: [
config.serverURL + '/build/', config.serverURL + '/build/',
config.serverURL + '/js/', config.serverURL + '/js/',
config.serverURL + '/config', config.serverURL + '/config',
'https://gist.github.com/',
'https://vimeo.com/api/oembed.json', 'https://vimeo.com/api/oembed.json',
'https://www.slideshare.net/api/oembed/2', 'https://www.slideshare.net/api/oembed/2',
'\'unsafe-inline\'' // this is ignored by browsers supporting nonces/hashes '\'unsafe-inline\'' // this is ignored by browsers supporting nonces/hashes
], ],
styleSrc: [config.serverURL + '/build/', config.serverURL + '/css/', '\'unsafe-inline\'', 'https://github.githubassets.com'], // unsafe-inline is required for some libs, plus used in views styleSrc: [config.serverURL + '/build/', config.serverURL + '/css/', '\'unsafe-inline\''], // unsafe-inline is required for some libs, plus used in views
objectSrc: ['*'], // Chrome PDF viewer treats PDFs as objects :/ objectSrc: ['*'], // Chrome PDF viewer treats PDFs as objects :/
formAction: ['\'self\''], formAction: ['\'self\''],
mediaSrc: ['*'] mediaSrc: ['*']

View file

@ -167,7 +167,6 @@
"file-saver": "2.0.5", "file-saver": "2.0.5",
"flowchart.js": "1.18.0", "flowchart.js": "1.18.0",
"fork-awesome": "1.2.0", "fork-awesome": "1.2.0",
"gist-embed": "2.6.0",
"globals": "16.0.0", "globals": "16.0.0",
"highlight.js": "10.7.3", "highlight.js": "10.7.3",
"html-webpack-plugin": "4.5.2", "html-webpack-plugin": "4.5.2",

View file

@ -63,6 +63,11 @@
-webkit-transition: opacity 0.2s; /* Safari */ -webkit-transition: opacity 0.2s; /* Safari */
transition: opacity 0.2s; transition: opacity 0.2s;
} }
iframe.github-gist-frame {
width: 100%;
border: none;
height: 32rem;
}
.slideshare .inner, .slideshare .inner,
.speakerdeck .inner { .speakerdeck .inner {

View file

@ -15,8 +15,6 @@ import markdownitContainer from 'markdown-it-container'
/* Defined regex markdown it plugins */ /* Defined regex markdown it plugins */
import Plugin from 'markdown-it-regexp' import Plugin from 'markdown-it-regexp'
import 'gist-embed'
require('prismjs/themes/prism.css') require('prismjs/themes/prism.css')
require('prismjs/components/prism-wiki') require('prismjs/components/prism-wiki')
require('prismjs/components/prism-haskell') require('prismjs/components/prism-haskell')
@ -304,10 +302,6 @@ export function finishView (view) {
} }
}) })
}) })
// gist
view.find('code[data-gist-id]').each((key, value) => {
if ($(value).children().length === 0) { $(value).gist(window.viewAjaxCallback) }
})
// sequence diagram // sequence diagram
const sequences = view.find('div.sequence-diagram.raw').removeClass('raw') const sequences = view.find('div.sequence-diagram.raw').removeClass('raw')
sequences.each((key, value) => { sequences.each((key, value) => {
@ -639,8 +633,6 @@ function generateCleanHTML (view) {
src.find('*[class=""]').removeAttr('class') src.find('*[class=""]').removeAttr('class')
eles.removeAttr('data-startline data-endline') eles.removeAttr('data-startline data-endline')
src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll') src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
// remove gist content
src.find('code[data-gist-id]').children().remove()
// disable todo list // disable todo list
src.find('input.task-list-item-checkbox').attr('disabled', '') src.find('input.task-list-item-checkbox').attr('disabled', '')
// replace emoji image path // replace emoji image path
@ -1156,8 +1148,7 @@ const gistPlugin = new Plugin(
(match, utils) => { (match, utils) => {
const gistid = match[1] const gistid = match[1]
const code = `<code data-gist-id="${gistid}"></code>` return `<iframe sandbox class="github-gist-frame" src="https://gist.github.com/${gistid}.pibb"></iframe>`
return code
} }
) )
// TOC // TOC

View file

@ -22,4 +22,3 @@ const $ = require('jquery')
window.jQuery = $ window.jQuery = $
window.$ = $ window.$ = $
require('bootstrap') require('bootstrap')
require('gist-embed/gist-embed.min')

View file

@ -1395,7 +1395,6 @@ __metadata:
flowchart.js: "npm:1.18.0" flowchart.js: "npm:1.18.0"
fork-awesome: "npm:1.2.0" fork-awesome: "npm:1.2.0"
formidable: "npm:2.1.2" formidable: "npm:2.1.2"
gist-embed: "npm:2.6.0"
globals: "npm:16.0.0" globals: "npm:16.0.0"
graceful-fs: "npm:4.2.11" graceful-fs: "npm:4.2.11"
helmet: "npm:8.1.0" helmet: "npm:8.1.0"
@ -7421,13 +7420,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"gist-embed@npm:2.6.0":
version: 2.6.0
resolution: "gist-embed@npm:2.6.0"
checksum: 10/6b9e1266e86f856a0ba64f27fe735748f1754b8a945b629ff5ab7705554a274d06ead0c177f503d4d94490851abb4caf533ef076c9d794ecc34c142491b4aff6
languageName: node
linkType: hard
"github-from-package@npm:0.0.0": "github-from-package@npm:0.0.0":
version: 0.0.0 version: 0.0.0
resolution: "github-from-package@npm:0.0.0" resolution: "github-from-package@npm:0.0.0"