03Portfolio
WireMark — bezpieczny język znaczników
Rozszerzony Markdown z bezpieczeństwem w rdzeniu: tokenizer, parser, sanitizer i renderer HTML w TypeScript bez zależności zewnętrznych, integracja z NestJS, edytor WYSIWYG i strona demo. Wszystko, czego nie ma na allowliście, nie przechodzi.
W liczbach
- Typy elementów w tokenizerze
- 24
- Zależności zewnętrzne rdzenia
- 0
- Limit wejścia (znaki)
- 100 000
Problem
Treści pisane przez użytkowników — komentarze, opisy, posty — chcą więcej niż zwykły tekst: nagłówków, list, kodu, wyróżnień. Każde takie pole to jednak furtka do XSS: skrypt w treści, link javascript:, atrybut onerror w obrazku albo niewidoczne znaki Unicode odwracające kierunek tekstu. Filtrowanie gotowego HTML-a po fakcie łatwo przeoczyć jeden przypadek.
Podejście
Zamiast czyścić HTML, WireMark w ogóle go nie przyjmuje: wejście przechodzi przez sanitizer (limit 100 000 znaków, normalizacja NFC, usuwanie znaków sterujących BiDi i bajtów zerowych), potem tokenizer rozpoznaje 24 typy elementów, a renderer buduje HTML wyłącznie z nich. Każdy tekst jest kodowany encjami (& < > " ' /), linki przechodzą przez listę dozwolonych protokołów (https, http, mailto) i opcjonalną listę domen, obrazki tylko przez https, a kolory odznak, typy calloutów, języki kodu i nazwy emoji — przez własne allowlisty. Parser zwraca obok HTML-a dziennik zdarzeń bezpieczeństwa i flagę podejrzanej treści, a metoda validate() sprawdza wejście przed zapisem. Do NestJS jest moduł z forRoot i forFeature, serwis, pipe i dekorator parametru.
Efekt
Powstał rdzeń biblioteki (ok. 970 linii TypeScript w czterech modułach, bez zewnętrznych zależności), integracja z NestJS oraz dwie strony w czystym HTML i JS: demo z edytorem i podglądem na żywo oraz edytor WYSIWYG z menu komend pod „/”, pływającym paskiem formatowania, konspektem i eksportem do HTML, WireMark albo czystego tekstu. W demo wstrzyknięty <script>, obrazek z onerror i link javascript: zostają wyświetlone jako tekst albo zablokowane, a pasek audytu pokazuje, co odrzucono.
Jak to działa
01
Najpierw sanitizer, potem składnia
Zanim cokolwiek zostanie sparsowane, wejście jest przycinane do limitu długości, normalizowane do NFC i czyszczone ze znaków sterujących kierunkiem tekstu, bajtów zerowych i mieszanych końców linii.
02
Allowlisty zamiast czarnych list
Link przechodzi tylko z protokołem https, http albo mailto (i opcjonalnie z dozwolonej domeny), obrazek tylko z https. Nieznany kolor odznaki zamienia się w szary, nieznany typ calloutu w domyślny, a język bloku kodu zostaje obcięty do liter, cyfr, myślnika i podkreślnika.
03
Wstrzyknięcie zostaje tekstem
Cały tekst jest kodowany encjami, więc <script> albo <img onerror> wyświetlają się jako znaki, a nie wykonują. Linki javascript: są odrzucane, a parser zapisuje każde takie zdarzenie w dzienniku audytu.

04
Rozszerzona składnia
Oprócz Markdownu: callouty z typem, odznaki z kolorem i wariantem, spoilery, zaznaczenie, indeks górny i dolny, emoji po nazwie z zamkniętej listy oraz bloki kodu z przyciskiem kopiowania.

05
Gotowe do NestJS
Moduł WireMark konfigurowany przez forRoot albo forFeature, serwis do ręcznego parsowania, pipe do pól żądania i dekorator parametru, który zwraca od razu bezpieczny HTML.