Markdown on shithub
shithub renders user-authored markdown — issue bodies, PR descriptions, comments, READMEs — through one canonical pipeline. This page documents what's supported and what's deliberately not.
Supported
CommonMark + GFM
The full CommonMark spec plus the curated GFM additions:
- Headings (
# Titlethrough###### h6) with auto-generated anchor IDs (<h1 id="title">). - Paragraphs, soft line breaks (rendered as
<br>in comment-style contexts; preserved as whitespace in READMEs). - Bullet, numbered, and task lists (
- [x]/- [ ]). - Block quotes, fenced + indented code blocks.
- Inline
code, bold, italic,strikethrough. - Tables (GFM pipe syntax).
- Autolinks (
https://example.combecomes a link automatically).
Code blocks with syntax highlighting
Fenced code with a language tag turns on Chroma highlighting:
```go
fmt.Println("hello")
```
Languages we recognize: every language Chroma supports (~250).
Unknown languages render as plain <pre><code> with no
highlighting.
shithub-specific inline patterns
| You write | We render |
|---|---|
@alice |
Link to /alice if the user exists |
#42 |
Link to issue/PR #42 in the current repo, if visible |
alice/proj#42 |
Cross-repo issue/PR link, if visible to you |
abc1234 |
Commit link in the current repo (7+ hex chars) |
:rocket: / :+1: |
Emoji from a curated set (~150 shortcodes) |
These patterns do not match inside code blocks or inline code —
`#42` stays literal.
If a reference can't be resolved (the issue doesn't exist, the user doesn't exist, the cross-repo target isn't visible to you), we render the text as-is. No broken links, no "deleted" labels, no existence leaks.
Safe HTML (allowlisted)
These tags pass through unchanged:
<details>/<summary>(collapsible sections)<kbd>(keyboard markers)<sup>,<sub>(superscript / subscript)- README presentation attributes GitHub commonly allows:
align="left|center|right"on paragraphs / headings / divs, andwidth/heighton images. - Standard text formatting tags Goldmark emits (em, strong, code, pre, blockquote, ul, ol, li, table family).
Not supported
We deliberately do not match GitHub's looser markdown surface:
| Feature | Why |
|---|---|
| Raw HTML beyond allowlist | XSS prevention. Anything outside the list is stripped. |
data: URIs |
Avoids tracking pixels and decompression bombs. |
javascript: URLs |
Always XSS. |
<script>, <style>, <iframe>, <object>, <embed>, <base>, <meta> |
XSS / unwanted side effects. |
Inline event handlers (onclick, onerror, etc.) |
XSS. |
| Math (KaTeX) | Post-MVP. |
| Mermaid diagrams | Post-MVP. |
| GFM Footnotes | Deferred — file an issue if you want them. |
For inline images, repo-relative paths work via the /raw/ route:
. External-host images are also
allowed; remote tracking pixels are inherent to that — we don't
proxy.
Newline handling
There are two render modes, picked per surface:
- Comment / issue / PR body: newlines render as
<br>(matches GitHub's UI). You can write a paragraph by leaving a blank line. - README and other structured docs: standard CommonMark newline rules (paragraphs separated by blank lines, soft newlines join words).
Cache + version
Rendered HTML is cached on the source row alongside a pipeline version. Bumping the renderer (a sanitizer-policy change, a new extension, a major Goldmark/bluemonday upgrade with output drift) re-renders comments lazily on next read — we never run a "re-render every comment" batch.
Contributing
Markdown changes go through internal/markdown/. The boundary is
enforced: importing goldmark or bluemonday outside that
package fails CI (scripts/lint-markdown-boundary.sh).
If a new XSS vector lands in the wild, add a fixture to
internal/markdown/markdown_test.go::TestRender_HostileInputs and
fix the policy.
View source
| 1 | # Markdown on shithub |
| 2 | |
| 3 | shithub renders user-authored markdown — issue bodies, PR |
| 4 | descriptions, comments, READMEs — through one canonical pipeline. |
| 5 | This page documents what's supported and what's deliberately not. |
| 6 | |
| 7 | ## Supported |
| 8 | |
| 9 | ### CommonMark + GFM |
| 10 | |
| 11 | The full CommonMark spec plus the curated GFM additions: |
| 12 | |
| 13 | - Headings (`# Title` through `###### h6`) with auto-generated |
| 14 | anchor IDs (`<h1 id="title">`). |
| 15 | - Paragraphs, soft line breaks (rendered as `<br>` in |
| 16 | comment-style contexts; preserved as whitespace in READMEs). |
| 17 | - Bullet, numbered, and **task lists** (`- [x]` / `- [ ]`). |
| 18 | - Block quotes, fenced + indented code blocks. |
| 19 | - Inline `code`, **bold**, *italic*, ~~strikethrough~~. |
| 20 | - Tables (GFM pipe syntax). |
| 21 | - Autolinks (`https://example.com` becomes a link automatically). |
| 22 | |
| 23 | ### Code blocks with syntax highlighting |
| 24 | |
| 25 | Fenced code with a language tag turns on Chroma highlighting: |
| 26 | |
| 27 | ```` |
| 28 | ```go |
| 29 | fmt.Println("hello") |
| 30 | ``` |
| 31 | ```` |
| 32 | |
| 33 | Languages we recognize: every language Chroma supports (~250). |
| 34 | Unknown languages render as plain `<pre><code>` with no |
| 35 | highlighting. |
| 36 | |
| 37 | ### shithub-specific inline patterns |
| 38 | |
| 39 | | You write | We render | |
| 40 | | --------------------- | ---------------------------------------------------- | |
| 41 | | `@alice` | Link to `/alice` if the user exists | |
| 42 | | `#42` | Link to issue/PR #42 in the current repo, if visible | |
| 43 | | `alice/proj#42` | Cross-repo issue/PR link, if visible to you | |
| 44 | | `abc1234` | Commit link in the current repo (7+ hex chars) | |
| 45 | | `:rocket:` / `:+1:` | Emoji from a curated set (~150 shortcodes) | |
| 46 | |
| 47 | These patterns *do not match inside code blocks or inline code* — |
| 48 | `` `#42` `` stays literal. |
| 49 | |
| 50 | If a reference can't be resolved (the issue doesn't exist, the |
| 51 | user doesn't exist, the cross-repo target isn't visible to you), |
| 52 | we render the text as-is. No broken links, no "deleted" labels, |
| 53 | no existence leaks. |
| 54 | |
| 55 | ### Safe HTML (allowlisted) |
| 56 | |
| 57 | These tags pass through unchanged: |
| 58 | |
| 59 | - `<details>` / `<summary>` (collapsible sections) |
| 60 | - `<kbd>` (keyboard markers) |
| 61 | - `<sup>`, `<sub>` (superscript / subscript) |
| 62 | - README presentation attributes GitHub commonly allows: |
| 63 | `align="left|center|right"` on paragraphs / headings / divs, |
| 64 | and `width` / `height` on images. |
| 65 | - Standard text formatting tags Goldmark emits (em, strong, code, |
| 66 | pre, blockquote, ul, ol, li, table family). |
| 67 | |
| 68 | ## Not supported |
| 69 | |
| 70 | We deliberately do **not** match GitHub's looser markdown surface: |
| 71 | |
| 72 | | Feature | Why | |
| 73 | | ------------------------ | --------------------------------------------------- | |
| 74 | | Raw HTML beyond allowlist | XSS prevention. Anything outside the list is stripped. | |
| 75 | | `data:` URIs | Avoids tracking pixels and decompression bombs. | |
| 76 | | `javascript:` URLs | Always XSS. | |
| 77 | | `<script>`, `<style>`, `<iframe>`, `<object>`, `<embed>`, `<base>`, `<meta>` | XSS / unwanted side effects. | |
| 78 | | Inline event handlers (`onclick`, `onerror`, etc.) | XSS. | |
| 79 | | Math (KaTeX) | Post-MVP. | |
| 80 | | Mermaid diagrams | Post-MVP. | |
| 81 | | GFM Footnotes | Deferred — file an issue if you want them. | |
| 82 | |
| 83 | For inline images, repo-relative paths work via the `/raw/` route: |
| 84 | ``. External-host images are also |
| 85 | allowed; remote tracking pixels are inherent to that — we don't |
| 86 | proxy. |
| 87 | |
| 88 | ## Newline handling |
| 89 | |
| 90 | There are two render modes, picked per surface: |
| 91 | |
| 92 | - **Comment / issue / PR body**: newlines render as `<br>` |
| 93 | (matches GitHub's UI). You can write a paragraph by leaving a |
| 94 | blank line. |
| 95 | - **README and other structured docs**: standard CommonMark |
| 96 | newline rules (paragraphs separated by blank lines, soft |
| 97 | newlines join words). |
| 98 | |
| 99 | ## Cache + version |
| 100 | |
| 101 | Rendered HTML is cached on the source row alongside a pipeline |
| 102 | version. Bumping the renderer (a sanitizer-policy change, a new |
| 103 | extension, a major Goldmark/bluemonday upgrade with output drift) |
| 104 | re-renders comments lazily on next read — we never run a "re-render |
| 105 | every comment" batch. |
| 106 | |
| 107 | ## Contributing |
| 108 | |
| 109 | Markdown changes go through `internal/markdown/`. The boundary is |
| 110 | enforced: importing `goldmark` or `bluemonday` outside that |
| 111 | package fails CI (`scripts/lint-markdown-boundary.sh`). |
| 112 | |
| 113 | If a new XSS vector lands in the wild, add a fixture to |
| 114 | `internal/markdown/markdown_test.go::TestRender_HostileInputs` and |
| 115 | fix the policy. |