Skip to content
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
13 changes: 13 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Review owners. See https://docs.github.com/articles/about-code-owners
# Teams are the ones granted access to this repository in OpenVTC/governance.

* @OpenVTC/openvtc-maintainers

# Wiki pages are drafted and refreshed by an LLM agent from upstream sources
# (README, "How This Wiki Is Maintained"), so a page is untrusted input: raw
# HTML written into it is parsed and published to the live site. Content
# changes need a human on them.
/content/ @OpenVTC/openvtc-maintainers @OpenVTC/openvtc-admins

# Build, deploy and repository automation.
/.github/ @OpenVTC/openvtc-admins
28 changes: 28 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CI

on:
push:
branches:
- v4
pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
- name: Install dependencies
run: npm ci
# Includes quartz/util/sanitize.test.ts, which builds the fixture pages in
# quartz/util/fixtures/sanitize and fails if any of their payloads reach the
# emitted HTML.
- name: Run tests
run: npm test
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
public
node_modules
.quartz-cache
# Deliberately malformed markup that must reach the parser exactly as written.
quartz/util/fixtures
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"rehype-mathjax": "^7.1.0",
"rehype-pretty-code": "^0.14.5",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"rehype-slug": "^6.0.0",
"remark": "^15.0.1",
"remark-breaks": "^4.0.0",
Expand Down
7 changes: 6 additions & 1 deletion quartz/plugins/transformers/ofm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
import { Element, Literal, Root as HtmlRoot } from "hast"
import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
import rehypeRaw from "rehype-raw"
import rehypeSanitize from "rehype-sanitize"
import { sanitizeSchema } from "../../util/sanitize"
import { SKIP, visit } from "unist-util-visit"
import path from "path"
import { splitAnchor } from "../../util/path"
Expand Down Expand Up @@ -541,7 +543,10 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
return plugins
},
htmlPlugins() {
const plugins: PluggableList = [rehypeRaw]
// rehypeRaw parses the raw HTML that remarkRehype passed through verbatim, so everything
// an author (or the LLM that maintains the content) wrote becomes real markup here.
// Sanitize it immediately afterwards: every later plugin only ever sees vetted markup.
const plugins: PluggableList = [rehypeRaw, [rehypeSanitize, sanitizeSchema]]

if (opts.parseBlockReferences) {
plugins.push(() => {
Expand Down
122 changes: 122 additions & 0 deletions quartz/util/fixtures/sanitize/features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
title: Feature fixture
tags:
- fixture
- fixture/nested
---

# Heading one

Intro with a [[target]] link, an [[target|aliased link]], a [[target#Section A|header link]],
a [[second#^blk1|block link]], an [external link](https://example.com/path?q=1), a
[relative link](./target), a [mailto](mailto:a@example.com) and an #inline-tag.

Arrows -> and => and <- here. ==highlighted text== and `inline code` and **bold** _em_ ~~del~~.

H~2~O is not GFM; <sub>sub</sub> <sup>sup</sup> <kbd>Ctrl</kbd> <abbr title="x">abbr</abbr>.

## Callouts

> [!note]
> A plain note.

> [!warning] Custom title with **bold** and [[target]]
> Body line one.
>
> Body paragraph two.

> [!tip]- Collapsed tip
> Hidden body.

> [!faq]+ Expanded question
> Body.

> [!my-custom-type|meta] Custom type
> Body.

> A normal quote.

## Embeds

![[target]]

![[second#^blk1]]

![[picture.png|Alt text|100x50]]

![[picture.png]]

![[doc.pdf]]

![[clip.mp4]]

![[song.mp3]]

![video](movie.webm)

![yt](https://www.youtube.com/watch?v=dQw4w9WgXcQ)

![image](https://example.com/image.png "title")

## Code

```ts title="example.ts" {2}
const a = 1
const b = "two"
function f(x: number) {
return x * 2
}
```

```
plain block
```

```mermaid
graph TD
A[Start] --> B{Is it?}
B -->|Yes| C[OK]
```

## Math

Inline $e^{i\pi} + 1 = 0$ math.

$$
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
$$

## Footnotes

Text with a footnote[^1] and another[^note].

[^1]: The first footnote.
[^note]: A named footnote with [[target]].

## Tables and lists

| Left | Center | Right |
| :--- | :----: | ----: |
| a | [[target]] | c |

- [ ] open task
- [x] done task
- nested
1. one
2. two

<details>
<summary>Summary text</summary>

Details body.

</details>

<div align="center">centered div</div>

<img src="https://example.com/x.png" alt="raw img" width="20">

---

Term with line break
next line.
47 changes: 47 additions & 0 deletions quartz/util/fixtures/sanitize/payloads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
title: "Payloads <img src=x onerror=alert(1)>"
description: '"><script>xss-marker</script><img src=x onerror=alert(2)>'
tags:
- sanitize-fixture
---

Inline image: <img src=x onerror=alert(3)>

<script>alert("xss-marker")</script>

<style>/* xss-marker */ body { display: none }</style>

[markdown link](javascript:alert(4))

<a href="javascript:alert(5)">raw link</a>

<a href="&#106;avascript:alert(6)">entity-encoded link</a>

<iframe srcdoc="<script>alert(7)</script>"></iframe>

<iframe src="javascript:alert(8)"></iframe>

<svg><g onload="alert(9)"></g></svg>

<form action="javascript:alert(10)"><button formaction="javascript:alert(11)">submit</button></form>

<object data="javascript:alert(12)"></object>

<embed src="javascript:alert(13)">

<meta http-equiv="refresh" content="0;url=javascript:alert(14)">

<base href="javascript:alert(15)//">

<details open ontoggle="alert(16)"><summary>summary</summary></details>

<video src=x onerror=alert(17)></video>

<div onclick="alert(18)">clickable</div>

==<img src=x onerror=alert(19)>==

![[target|x" onmouseover="alert(20)]]

> [!note] <img src=x onerror=alert(21)>
> Callout body.
9 changes: 9 additions & 0 deletions quartz/util/fixtures/sanitize/second.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: Second page
---

A paragraph with a block reference. ^blk1

## Section B

Section B body.
5 changes: 5 additions & 0 deletions quartz/util/fixtures/sanitize/tags/sanitize-fixture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Sanitize fixture tag
---

Tag page body <img src=x onerror=alert(22)> <script>alert("xss-marker")</script>
12 changes: 12 additions & 0 deletions quartz/util/fixtures/sanitize/target.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: Target page
---

Target intro paragraph.

## Section A

Section A body.

> [!info] Info in target
> Content.
Loading