Przejdź do treści
Wszystkie projekty

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
01

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.

02

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.

03

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

  1. 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.

  2. 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.

  3. 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.

    Wstrzyknięcie zostaje tekstem
  4. 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.

    Rozszerzona składnia
  5. 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.

Napisz