03Portfolio
WireMark — a secure markup language
Extended Markdown with security built into the core: a tokenizer, parser, sanitizer and HTML renderer in TypeScript with no external dependencies, a NestJS integration, a WYSIWYG editor and a demo page. Anything not on the allowlist does not get through.
By the numbers
- Element types in the tokenizer
- 24
- External dependencies in the core
- 0
- Input limit (characters)
- 100 000
Problem
User-written content — comments, descriptions, posts — needs more than plain text: headings, lists, code, highlights. But every such field is an opening for XSS: a script in the content, a javascript: link, an onerror attribute on an image, or invisible Unicode characters that flip text direction. Filtering finished HTML after the fact makes it easy to miss one case.
Approach
Instead of cleaning HTML, WireMark does not accept it at all: input goes through the sanitizer (100,000-character limit, NFC normalisation, removal of BiDi control characters and null bytes), then the tokenizer recognises 24 element types and the renderer builds HTML from those alone. All text is entity-encoded (& < > " ' /), links pass through an allowlist of protocols (https, http, mailto) and an optional domain list, images are https-only, and badge colours, callout types, code languages and emoji names each have their own allowlist. Alongside the HTML, the parser returns a security audit log and a suspicious-content flag, and validate() checks input before it is stored. For NestJS there is a module with forRoot and forFeature, a service, a pipe and a parameter decorator.
Outcome
The result is the library core (about 970 lines of TypeScript in four modules, with no external dependencies), a NestJS integration and two pages in plain HTML and JS: a demo with a live editor and preview, and a WYSIWYG editor with a “/” command menu, a floating formatting toolbar, an outline and export to HTML, WireMark or plain text. In the demo, an injected <script>, an image with onerror and a javascript: link are shown as text or blocked, and the audit bar lists what was rejected.
How it works
01
Sanitizer first, syntax second
Before anything is parsed, input is capped at a length limit, normalised to NFC and stripped of text-direction control characters, null bytes and mixed line endings.
02
Allowlists instead of blocklists
A link only gets through with https, http or mailto (and optionally from an allowed domain), an image only over https. An unknown badge colour falls back to grey, an unknown callout type to the default, and a code-block language is trimmed to letters, digits, dash and underscore.
03
An injection stays text
All text is entity-encoded, so <script> or <img onerror> are displayed as characters rather than executed. javascript: links are rejected, and the parser records every such event in the audit log.

04
Extended syntax
Beyond Markdown: typed callouts, badges with colour and variant, spoilers, highlights, superscript and subscript, emoji by name from a closed list, and code blocks with a copy button.

05
Ready for NestJS
A WireMark module configured with forRoot or forFeature, a service for manual parsing, a pipe for request fields and a parameter decorator that returns safe HTML straight away.