
DOMPurify v3.4.15
DOMPurify - HTML, MathML 및 SVG를 위한 DOM 전용, 초고속, 매우 관대한 XSS 새니타이저. DOMPurify는 안전한 기본 설정으로 작동하지만, 다양한 구성 옵션과 훅을 제공합니다. 데모:
DOMPurify
DOMPurify는 HTML, MathML, SVG를 위한 DOM 전용의 초고속이며 매우 관대한 XSS 새니타이저입니다.
사용법도 매우 간단하고 시작하기도 쉽습니다. DOMPurify는 2014년 2월에 시작되었으며, 그동안 버전 v3.4.15에 도달했습니다.
DOMPurify는 JavaScript로 실행되며 모든 최신 브라우저(Safari (10+), Opera (15+), Edge, Firefox, Chrome - 그리고 Blink, Gecko, WebKit을 사용하는 거의 모든 것)에서 작동합니다. MSIE나 기타 레거시 브라우저에서도 깨지지 않습니다. 그냥 아무것도 하지 않을 뿐입니다.
참고로 DOMPurify v2.5.9가 MSIE를 지원하는 최신 버전입니다. MSIE와 호환되는 중요한 보안 업데이트가 필요하면 2.x 브랜치를 사용하세요.
당사의 자동화된 테스트는 매 푸시마다 현재 엔진(Chromium, Firefox, WebKit을 Ubuntu, macOS, Windows 전반에서)의 9가지 브라우저/OS 조합을 커버하며, 별도의 매트릭스가 구형 엔진 스냅샷(대략 Chromium 110, Firefox 108, WebKit 16.4까지, 약 3년 전)에서도 스위트를 재실행하여 오래된 브라우저에서의 회귀도 잡아냅니다. 또한 jsdom에서 DOMPurify와 함께 Node.js v20, v22, v24, v25, v26을 실행합니다. 더 오래된 Node 버전도 작동하는 것으로 알려져 있지만, 뭐... 보장은 없습니다.
DOMPurify는 웹 공격과 XSS에 대한 방대한 배경을 가진 보안 전문가들이 작성했습니다. 걱정 마세요. 자세한 내용은 보안 목표 및 위협 모델도 읽어보시기 바랍니다. 꼭 읽어주세요. 정말로요. 그리고 잔혹한 세부 사항을 즐기신다면, 공격 클래스 및 우회 이력 페이지에서 DOMPurify가 방어하는 파서 변이, 네임스페이스, 클로버링, 템플릿 트릭을 정리해 두었습니다.
DOMPurify 프로젝트는 HTML Sanitizer API의 탄생에 영감을 주었으며, 이는 이미 많은 브라우저에서 제공되고 있습니다. 동일한 기능이 이제 WHATWG HTML 명세에서 직접 표준화되고 있습니다.
목차
- 무엇을 하나요?
- 어떻게 사용하나요?
- 데모가 있나요?
- 보안 버그를 발견하면 어떻게 하나요?
- 정화 샘플 좀 보여주시겠어요?
- 무엇이 지원되나요?
- Internet Explorer 같은 레거시 브라우저는요?
- DOMPurify와 Trusted Types는요?
- DOMPurify를 구성할 수 있나요?
- 영구 구성
- 훅
- 제거된 구성
- 지속적 통합
- 보안 메일링 리스트
- 누가 기여했나요?
무엇을 하나요?
DOMPurify는 HTML을 정화하고 XSS 공격을 방지합니다. 예를 들어 지저분한 HTML로 가득 찬 문자열을 DOMPurify에 넣으면 깨끗한 HTML이 담긴 문자열을 반환합니다(다르게 구성하지 않은 경우). DOMPurify는 위험한 HTML을 포함하는 모든 것을 제거하여 XSS 공격과 기타 위험한 것들을 방지합니다. 또한 굉장히 빠릅니다. 브라우저가 제공하는 기술을 사용하여 XSS 필터로 만들어냅니다. 브라우저가 빠를수록 DOMPurify도 빨라집니다.
어떻게 사용하나요?
쉽습니다. 웹사이트에 DOMPurify를 포함하기만 하면 됩니다.
압축되지 않은 버전 사용하기 (소스 맵 사용 가능)```html
### 축소되고 테스트된 프로덕션 버전 사용하기 (소스 맵 제공)```html
<script type="text/javascript" src="dist/purify.min.js"></script>
그런 다음 다음 코드를 실행하여 문자열을 정제할 수 있습니다:```js const clean = DOMPurify.sanitize(dirty);
또는 Angular 같은 것으로 작업하는 것을 좋아한다면 이것도 좋습니다:```js
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize('<b>hello there</b>');
결과 HTML은 innerHTML을 사용하여 DOM 요소에 쓰거나 document.write()를 사용하여 DOM에 쓸 수 있습니다. 이는 전적으로 사용자에게 달려 있습니다.
기본적으로 HTML, SVG 그리고 MathML을 허용한다는 점에 유의하세요. HTML만 필요한 경우(매우 일반적인 사용 사례일 수 있음) 다음과 같이 쉽게 설정할 수 있습니다:```js
const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });
### 함정이 있을 가능성이 있나요?
음, 주의하세요. HTML을 _먼저_ 정화한 다음 _나중에_ 수정하면 **정화 효과를 무효화**하기 쉽습니다. 정화된 마크업을 정화 _이후에_ 다른 라이브러리에 전달한다면, 그 라이브러리가 자체적으로 HTML을 함부로 다루지 않는지 반드시 확인하세요. 안전한 사용법과 두 번 생각해봐야 할 태그/속성에 대해서는 [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model)을, 후처리와 마크업 컨텍스트 변경이 왜 정화를 무력화하는지에 대해서는 [Attack Classes & Bypass History](https://github.com/cure53/DOMPurify/wiki/Attack-Classes-&-Bypass-History)를 참고하세요.
### 알겠습니다, 이해됐으니 넘어가죠
마크업을 정화한 후에는 `DOMPurify.removed` 속성을 살펴보고 어떤 요소와 속성이 제거되었는지 확인할 수도 있습니다. 이 속성을 **보안에 중요한 결정을 내리는 데 사용하지 마세요**. 이는 단지 호기심 많은 사람들을 위한 작은 도우미일 뿐입니다.
### 서버에서 DOMPurify 실행하기
DOMPurify는 기술적으로 Node.js와 함께 서버 측에서도 작동합니다. 우리의 지원은 [Node.js 릴리스 주기](https://nodejs.org/en/about/previous-releases)를 따르기 위해 노력합니다.
서버에서 DOMPurify를 실행하려면 DOM이 존재해야 하는데, 이는 아마 놀라운 일이 아닐 것입니다. 일반적으로 [jsdom](https://github.com/jsdom/jsdom)이 선택되는 도구이며, 우리는 최신 버전의 _jsdom_을 사용할 것을 **강력히 권장**합니다.
왜일까요? 구버전의 _jsdom_은 DOMPurify가 100% 올바르게 작동하더라도 XSS를 초래하는 방식으로 버그가 있는 것으로 알려져 있기 때문입니다. 예를 들어 _jsdom v19.0.0_에는 **알려진 공격 벡터**가 있으며 이는 _jsdom v20.0.0_에서 수정되었습니다. 따라서 _jsdom_을 최신 상태로 유지할 것을 정말로 권장합니다.
또한 [happy-dom](https://github.com/capricorn86/happy-dom)과 같은 도구가 존재하지만 현재로서는 **안전한 것으로 간주되지 않는다**는 점을 유의하세요. DOMPurify를 _happy-dom_과 결합하는 것은 현재 권장되지 않으며 XSS로 이어질 가능성이 높습니다. 선택한 서버 측 DOM이 왜 신뢰할 수 있는 컴퓨팅 기반의 일부인지에 대한 배경은 [Attack Classes & Bypass History](https://github.com/cure53/DOMPurify/wiki/Attack-Classes-&-Bypass-History)를 참고하세요.
그 외에는 서버에서 DOMPurify를 사용해도 괜찮습니다. 아마도요. 이는 정말로 _jsdom_ 또는 서버 측에서 사용하는 DOM에 달려 있습니다. 이를 감수할 수 있다면, 다음과 같이 작동하게 할 수 있습니다:```bash
npm install dompurify
npm install jsdom
_jsdom_의 경우(최신 버전을 사용하세요), 다음이면 충분합니다:```js const createDOMPurify = require('dompurify'); const { JSDOM } = require('jsdom');
const window = new JSDOM('').window; const DOMPurify = createDOMPurify(window); const clean = DOMPurify.sanitize('hello there');
또는 import로 작업하는 것을 선호한다면, 이렇게 할 수도 있습니다:```js
import { JSDOM } from 'jsdom';
import DOMPurify from 'dompurify';
const window = new JSDOM('').window;
const purify = DOMPurify(window);
const clean = purify.sanitize('<b>hello there</b>');
특정 환경에서 작동시키는 데 문제가 있다면, 많은 사람들이 겪을 수 있는 문제를 해결해 주는 훌륭한 isomorphic-dompurify 프로젝트를 참고해 보세요.```bash npm install isomorphic-dompurify
## 주요 기능
- **다중 소스 수집**: GitHub, GitLab, 로컬 디렉터리, ZIP 아카이브, 개별 파일
- **지능형 분석**: 언어 감지, 프레임워크 식별, 의존성 추출
- **보안 스캐닝**: 시크릿 탐지, SAST 규칙, 의존성 취약점 검사
- **유연한 보고**: JSON, Markdown, HTML, SARIF 형식
- **CI/CD 통합**: GitHub Actions, GitLab CI, Jenkins, pre-commit 훅
- **확장 가능한 아키텍처**: 플러그인 시스템, 사용자 정의 규칙, API 액세스
## 설치
### 사전 요구 사항
- Python 3.9 이상
- Git (저장소 소스용)
- 선택 사항: Docker (컨테이너화된 배포용)
### pip 사용
```bash
pip install codescan
소스에서 설치
git clone https://github.com/example/codescan.git
cd codescan
pip install -e .
Docker 사용
docker pull example/codescan:latest
docker run --rm -v $(pwd):/workspace example/codescan scan /workspace
빠른 시작
기본 스캔
# 로컬 디렉터리 스캔
codescan scan ./my-project
# GitHub 저장소 스캔
codescan scan https://github.com/example/repo
# 특정 브랜치 스캔
codescan scan https://github.com/example/repo --branch develop
구성 파일
프로젝트 루트에 codescan.yaml 파일을 생성하세요:
version: "1.0"
sources:
- type: local
path: ./src
- type: github
url: https://github.com/example/repo
branch: main
analyzers:
- language-detection
- dependency-extraction
- secret-detection
- sast
reporters:
- type: json
output: reports/results.json
- type: sarif
output: reports/results.sarif
exclude:
- "**/node_modules/**"
- "**/.git/**"
- "**/vendor/**"
CLI 옵션
사용법: codescan [OPTIONS] COMMAND [ARGS]...
옵션:
--config PATH 구성 파일 경로
--verbose 상세 출력 활성화
--quiet 콘솔 출력 억제
--version 버전 표시 후 종료
--help 이 메시지 표시 후 종료
명령어:
scan 소스 코드 스캔
analyze 기존 스캔 결과 분석
report 보고서 생성
rules 스캔 규칙 관리
config 구성 관리
사용 예제
여러 소스 스캔
codescan scan \
--source ./local-project \
--source https://github.com/example/repo \
--source https://gitlab.com/example/project \
--output results.json
특정 분석기 실행
codescan scan ./project \
--analyzers secret-detection,sast \
--severity high,critical
CI/CD 통합
# .github/workflows/codescan.yml
name: CodeScan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install CodeScan
run: pip install codescan
- name: Run scan
run: codescan scan . --output results.sarif --format sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
구성 참조
소스 구성
| 필드 | 유형 | 설명 | 필수 |
|---|---|---|---|
type | string | 소스 유형 (local, github, gitlab, zip) | 예 |
path | string | 로컬 소스의 경로 | 조건부 |
url | string | 원격 소스의 URL | 조건부 |
branch | string | 스캔할 브랜치 | 아니요 |
token | string | 인증 토큰 | 아니요 |
depth | integer | 클론 깊이 | 아니요 |
분석기 구성
| 분석기 | 설명 | 기본값 |
|---|---|---|
language-detection | 프로그래밍 언어 식별 | 활성화됨 |
dependency-extraction | 의존성 추출 | 활성화됨 |
secret-detection | 하드코딩된 시크릿 탐지 | 활성화됨 |
sast | 정적 애플리케이션 보안 테스팅 | 활성화됨 |
license-check | 라이선스 준수 확인 | 비활성화됨 |
complexity | 코드 복잡도 측정 | 비활성화됨 |
보고자 구성
| 보고자 | 출력 형식 | 사용 사례 |
|---|---|---|
json | JSON | 프로그래밍 방식 처리 |
markdown | Markdown | 문서화 |
html | HTML | 대화형 보고서 |
sarif | SARIF | CI/CD 통합 |
csv | CSV | 스프레드시트 내보내기 |
보안 규칙
시크릿 탐지
CodeScan은 다음을 포함한 일반적인 시크릿 유형을 탐지합니다:
- API 키 (AWS, GCP, Azure)
- 데이터베이스 자격 증명
- 개인 키 및 인증서
- OAuth 토큰
- JWT 시크릿
- 암호화 키
SAST 규칙
내장된 SAST 규칙은 다음을 포함합니다:
- SQL 인젝션
- 크로스 사이트 스크립팅 (XSS)
- 명령 인젝션
- 경로 순회
- 안전하지 않은 역직렬화
- 하드코딩된 자격 증명
사용자 정의 규칙
rules/ 디렉터리에 사용자 정의 규칙을 생성하세요:
# rules/custom-rule.yaml
id: CUSTOM-001
name: 사용자 정의 보안 규칙
description: 잠재적인 보안 문제를 탐지합니다
severity: high
category: security
patterns:
- type: regex
pattern: 'dangerous_function\s*\('
languages: [python, javascript]
remediation: |
안전한 대안을 사용하세요.
자세한 내용은 보안 가이드를 참조하세요.
API 참조
Python API
from codescan import Scanner, Config
# 스캐너 초기화
config = Config.from_file("codescan.yaml")
scanner = Scanner(config)
# 스캔 실행
results = scanner.scan()
# 결과 처리
for finding in results.findings:
print(f"{finding.severity}: {finding.message}")
print(f" 위치: {finding.location}")
print(f" 규칙: {finding.rule_id}")
REST API
# 스캔 시작
curl -X POST http://localhost:8080/api/v1/scans \
-H "Content-Type: application/json" \
-d '{"source": "./project", "analyzers": ["sast", "secret-detection"]}'
# 스캔 상태 확인
curl http://localhost:8080/api/v1/scans/{scan_id}
# 결과 가져오기
curl http://localhost:8080/api/v1/scans/{scan_id}/results
문제 해결
일반적인 문제
스캔 시간 초과
대규모 저장소의 경우 시간 초과를 늘리세요:
codescan scan ./large-repo --timeout 3600
메모리 문제
병렬 처리를 줄이세요:
codescan scan ./project --workers 2
권한 오류
파일 권한을 확인하세요:
chmod -R +r ./project
디버그 모드
상세 로깅을 활성화하세요:
codescan scan ./project --verbose --log-level debug
기여
기여를 환영합니다! 자세한 내용은 CONTRIBUTING.md를 참조하세요.
개발 환경 설정
git clone https://github.com/example/codescan.git
cd codescan
python -m venv venv
source venv/bin/activate
pip install -r requirements-dev.txt
pre-commit install
테스트 실행
pytest tests/ -v --cov=codescan
라이선스
이 프로젝트는 MIT 라이선스에 따라 라이선스가 부여됩니다. 자세한 내용은 LICENSE 파일을 참조하세요.
감사의 글
- Semgrep - 영감을 준 SAST 엔진
- TruffleHog - 시크릿 탐지 참조
- Syft - 의존성 추출 참조
지원
const clean = DOMPurify.sanitize('hello');
## 데모가 있나요?
물론 데모가 있습니다! [DOMPurify 사용해 보기](https://cure53.de/purify)
## 보안 버그를 발견하면 어떻게 해야 하나요?
우선, 즉시 [이메일](mailto:[email protected])로 연락해 주시면 수정 작업을 진행할 수 있습니다. [PGP 키](https://keyserver.ubuntu.com/pks/lookup?op=vindex&search=0xC26C858090F70ADA)
또한, 버그 바운티 대상이 될 가능성이 높습니다! [Fastmail](https://www.fastmail.com/)의 훌륭한 분들이 자사 서비스에 DOMPurify를 사용하고 있으며, 우리 라이브러리를 버그 바운티 범위에 추가했습니다. 따라서 DOMPurify를 우회하거나 약화시키는 방법을 발견하셨다면, 그들의 웹사이트와 [버그 바운티 정보](https://www.fastmail.com/about/bugbounty/)도 확인해 보시기 바랍니다.
## 정화 샘플 몇 가지 보여주실 수 있나요?
정화된 마크업은 어떻게 생겼나요? [데모](https://cure53.de/purify)에서 다양한 악성 요소들을 확인할 수 있습니다. 하지만 더 작은 예시 몇 가지도 보여드리겠습니다!```js
DOMPurify.sanitize(''); // becomes <img src="https://raw.githubusercontent.com/cure53/dompurify/main/x">
DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // becomes <svg><g></g></svg>
DOMPurify.sanitize('<p>abcdef</p>'); // becomes <p>abc</p>
DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math><mi></mi></math>
DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // becomes <table><tbody><tr><td>HELLO</td></tr></tbody></table>
DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><li><a href="//google.com">click</a></li></ul>
이것들은 맛보기에 불과합니다. 이 샘플들이 나온 공격 클래스의 전체 분류 - mutation XSS, namespace confusion, DOM clobbering, rawtext breakouts 등 - 은 Attack Classes & Bypass History를 참조하세요.
무엇이 지원되나요?
DOMPurify는 현재 HTML5, SVG, MathML을 지원합니다. DOMPurify는 기본적으로 CSS, HTML 사용자 정의 데이터 속성을 허용합니다. DOMPurify는 또한 Shadow DOM을 지원하며 - DOM 템플릿을 재귀적으로 정화합니다. DOMPurify는 또한 jQuery $() 및 elm.html() API와 함께 사용하기 위해 HTML을 정화할 수 있으며 알려진 문제가 없습니다. 기본적으로 허용되는 요소와 속성의 정확한 집합은 Default TAGs & ATTRIBUTEs allow-list & blocklist 위키 페이지를 참조하세요.
Internet Explorer와 같은 레거시 브라우저는 어떻게 되나요?
DOMPurify는 아무것도 하지 않습니다. 단순히 입력한 문자열을 그대로 반환합니다. DOMPurify는 isSupported라는 속성을 노출하여 작업을 수행할 수 있는지 여부를 알려주므로, 자체적인 백업 계획을 세울 수 있습니다.
DOMPurify와 Trusted Types는 어떻게 되나요?
버전 1.0.9에서 Trusted Types API (MDN)에 대한 지원이 DOMPurify에 추가되었습니다. 버전 2.0.0에서는 이와 관련된 DOMPurify의 동작을 제어하는 구성 플래그가 추가되었습니다.
DOMPurify.sanitize가 Trusted Types API를 사용할 수 있는 환경에서 사용되고 RETURN_TRUSTED_TYPE이 true로 설정된 경우, 문자열 대신 TrustedHTML 값을 반환하려고 시도합니다 (RETURN_DOM 및 RETURN_DOM_FRAGMENT 구성 옵션의 동작은 변경되지 않습니다).
DOMPurify를 사용하여 trustedTypes에서 정책을 생성하려면 RETURN_TRUSTED_TYPE: false가 필요합니다. createHTML은 TrustedHTML이 아닌 일반 문자열을 기대하기 때문입니다. 아래 예제에서 이를 확인할 수 있습니다.```js
window.trustedTypes.createPolicy('default', {
createHTML: (to_escape) =>
DOMPurify.sanitize(to_escape, { RETURN_TRUSTED_TYPE: false }),
});
`TRUSTED_TYPES_POLICY`가 제공되지 않으면 DOMPurify는 `dompurify`라는 자체 내부 Trusted Types 정책을 생성하려고 시도합니다. 페이지에서 이미 자체 정책과 함께 엄격한 CSP(예: `trusted-types my-organization`)를 정의하여 `dompurify`라는 정책을 허용하지 않는 경우, 이 시도는 브라우저에 의해 차단되고 `TrustedTypes policy dompurify could not be created.` 경고와 함께 CSP 위반이 기록됩니다.
DOMPurify가 내부 폴백 정책을 생성하지 않도록 하려면 `TRUSTED_TYPES_POLICY: null`을 전달하세요. 이는 자체 정책의 `createHTML` 내부에서 `DOMPurify.sanitize`를 호출할 때 올바른 선택이며, CSP의 `trusted-types` 허용 목록에 `dompurify`를 추가할 필요가 없다는 것을 의미합니다.```js
window.trustedTypes.createPolicy('my-organization', {
createHTML: (input) =>
DOMPurify.sanitize(input, { TRUSTED_TYPES_POLICY: null }),
});
자체 래핑 정책을 DOMPurify의 TRUSTED_TYPES_POLICY로 다시 전달하지 마십시오 (예를 들어 DOMPurify.setConfig({ TRUSTED_TYPES_POLICY: myPolicy })를 통해). 해당 정책의 createHTML이 이미 DOMPurify.sanitize를 호출하고 있는 경우에는 더욱 그렇습니다. 이는 정의상 순환 구조입니다 - 정제(sanitize)가 정책을 호출하고, 그 정책이 다시 DOMPurify를 호출하여 정제하는 구조이기 때문입니다 - 그리고 DOMPurify는 무한 재귀를 방지하기 위해 설명적인 TypeError를 발생시킵니다. 자체 정책이 DOMPurify를 호출해야 하며, DOMPurify가 자체 정책을 호출하도록 구성되어서는 안 됩니다.
이 default 정책 패턴을 전체 페이지에 자동으로 적용하고 싶다면 - 즉, 레거시 코드, 서드파티 위젯, 그리고 쉽게 찾거나 재작성할 수 없는 수천 개의 innerHTML 할당을 포함한 모든 HTML 싱크가 정제되도록 하려면 - DOMFortify를 살펴보십시오. 이는 DOMPurify를 기반으로 하는 바로 그런 Trusted Types default 정책을 설치하고 스크립트 싱크(eval, script.src, ...)를 즉시 거부합니다. 이는 의도적으로 별도의 프로젝트입니다: DOMPurify는 집중된 정제기(sanitizer)로 남아 있고, DOMFortify는 의도적으로 DOMPurify의 범위를 벗어나는 문서 전반의 적용 계층을 처리합니다.
DOMPurify를 구성할 수 있나요?
예. 포함된 기본 구성 값들은 이미 상당히 좋습니다 - 하지만 물론 재정의할 수 있습니다. /demos 폴더를 확인하여 DOMPurify를 커스터마이즈하는 방법에 대한 다양한 예제를 살펴보십시오.
허용 목록(ADD_TAGS, ADD_ATTR, CUSTOM_ELEMENT_HANDLING, …)을 넓히거나 기본값을 완화하기 전에, 두 번 생각해야 할 태그와 속성들을 훑어보는 것이 좋습니다 - 몇몇은 예상치 못한 방식으로 위험합니다.
일반 설정```js
// strip {{ ... }}, ${ ... } and <% ... %> to make output safe for template systems // be careful please, this mode is not recommended for production usage. // allowing template parsing in user-controlled HTML is not advised at all. // only use this mode if there is really no alternative. const clean = DOMPurify.sanitize(dirty, { SAFE_FOR_TEMPLATES: true });
// change how e.g. comments containing risky HTML characters are treated.
// be very careful, this setting should only be set to false if you really only handle
// HTML and nothing else, no SVG, MathML or the like.
// Otherwise, changing from true to false will lead to XSS in this or some other way.
const clean = DOMPurify.sanitize(dirty, { SAFE_FOR_XML: false });
### 허용 목록과 차단 목록 제어```js
// allow only <b> elements, very strict
const clean = DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ['b'] });
// allow only <b> and <q> with style attributes
const clean = DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'q'],
ALLOWED_ATTR: ['style'],
});
// allow all safe HTML elements but neither SVG nor MathML
// note that the USE_PROFILES setting will override the ALLOWED_TAGS setting
// so don't use them together
const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });
// allow all safe SVG elements and SVG Filters, no HTML or MathML
const clean = DOMPurify.sanitize(dirty, {
USE_PROFILES: { svg: true, svgFilters: true },
});
// allow all safe MathML elements and SVG, but no SVG Filters
const clean = DOMPurify.sanitize(dirty, {
USE_PROFILES: { mathMl: true, svg: true },
});
// change the default namespace from HTML to something different
const clean = DOMPurify.sanitize(dirty, {
NAMESPACE: 'http://www.w3.org/2000/svg',
});
// leave all safe HTML as it is and add <style> elements to block-list
const clean = DOMPurify.sanitize(dirty, { FORBID_TAGS: ['style'] });
// leave all safe HTML as it is and add style attributes to block-list
const clean = DOMPurify.sanitize(dirty, { FORBID_ATTR: ['style'] });
// extend the existing array of allowed tags and add <my-tag> to allow-list
const clean = DOMPurify.sanitize(dirty, { ADD_TAGS: ['my-tag'] });
// extend the existing array of allowed attributes and add my-attr to allow-list
const clean = DOMPurify.sanitize(dirty, { ADD_ATTR: ['my-attr'] });
// use functions to control which additional tags and attributes are allowed
const allowlist = {
one: ['attribute-one'],
two: ['attribute-two'],
};
const clean = DOMPurify.sanitize(
'<one attribute-one="1" attribute-two="2"></one><two attribute-one="1" attribute-two="2"></two>',
{
ADD_TAGS: (tagName) => {
return Object.keys(allowlist).includes(tagName);
},
ADD_ATTR: (attributeName, tagName) => {
return allowlist[tagName]?.includes(attributeName) || false;
},
}
); // <one attribute-one="1"></one><two attribute-two="2"></two>
// prohibit ARIA attributes, leave other safe HTML as is (default is true)
const clean = DOMPurify.sanitize(dirty, { ALLOW_ARIA_ATTR: false });
// prohibit HTML5 data attributes, leave other safe HTML as is (default is true)
const clean = DOMPurify.sanitize(dirty, { ALLOW_DATA_ATTR: false });
Custom Elements와 관련된 동작 제어```js
// DOMPurify allows to define rules for Custom Elements. When using the CUSTOM_ELEMENT_HANDLING // literal, it is possible to define exactly what elements you wish to allow (by default, none are allowed). // // The same goes for their attributes. By default, the built-in or configured allow.list is used. // // You can use a RegExp literal to specify what is allowed or a predicate, examples for both can be seen below. // When using a predicate function for attributeNameCheck, it can optionally receive the tagName as a second parameter // for more granular control over which attributes are allowed for specific elements. // The default values are very restrictive to prevent accidental XSS bypasses. Handle with great care!
const clean = DOMPurify.sanitize( '
', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: null, // no custom elements are allowed attributeNameCheck: null, // default / standard attribute allow-list is used allowCustomizedBuiltInElements: false, // no customized built-ins allowed }, } ); //const clean = DOMPurify.sanitize( '
', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: /^foo-/, // allow all tags starting with "foo-" attributeNameCheck: /baz/, // allow all attributes containing "baz" allowCustomizedBuiltInElements: true, // customized built-ins are allowed }, } ); //const clean = DOMPurify.sanitize( '
', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: (tagName) => tagName.match(/^foo-/), // allow all tags starting with "foo-" attributeNameCheck: (attr) => attr.match(/baz/), // allow all containing "baz" allowCustomizedBuiltInElements: true, // allow customized built-ins }, } ); //// Example with attributeNameCheck receiving tagName as a second parameter const clean = DOMPurify.sanitize( '', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: (tagName) => tagName.match(/^element-(one|two)$/), attributeNameCheck: (attr, tagName) => { if (tagName === 'element-one') { return ['attribute-one'].includes(attr); } else if (tagName === 'element-two') { return ['attribute-two'].includes(attr); } else { return false; } }, allowCustomizedBuiltInElements: false, }, } ); //
### URI 값과 관련된 동작 제어```js
// extend the existing array of elements that can use Data URIs
const clean = DOMPurify.sanitize(dirty, { ADD_DATA_URI_TAGS: ['a', 'area'] });
// extend the existing array of elements that are safe for URI-like values (be careful, XSS risk)
const clean = DOMPurify.sanitize(dirty, { ADD_URI_SAFE_ATTR: ['my-attr'] });
허용되는 속성 값 제어```js
// allow external protocol handlers in URL attributes (default is false, be careful, XSS risk) // by default only http, https, ftp, ftps, tel, mailto, callto, sms, cid, xmpp and matrix are allowed. const clean = DOMPurify.sanitize(dirty, { ALLOW_UNKNOWN_PROTOCOLS: true });
// allow specific protocol handlers in URL attributes via regex (default is false, be careful, XSS risk) // by default only (protocol-)relative URLs, http, https, ftp, ftps, tel, mailto, callto, sms, cid, xmpp and matrix are allowed. // Default RegExp: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i; const clean = DOMPurify.sanitize(dirty, { ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i, });
### 반환 타입에 영향 주기```js
// return a DOM HTMLBodyElement instead of an HTML string (default is false)
const clean = DOMPurify.sanitize(dirty, { RETURN_DOM: true });
// return a DOM DocumentFragment instead of an HTML string (default is false)
const clean = DOMPurify.sanitize(dirty, { RETURN_DOM_FRAGMENT: true });
// use the RETURN_TRUSTED_TYPE flag to turn on Trusted Types support if available
const clean = DOMPurify.sanitize(dirty, { RETURN_TRUSTED_TYPE: true }); // will return a TrustedHTML object instead of a string if possible
// use a provided Trusted Types policy
const clean = DOMPurify.sanitize(dirty, {
// supplied policy must define createHTML and createScriptURL
TRUSTED_TYPES_POLICY: trustedTypes.createPolicy('dompurify', {
createHTML(s) {
return s;
},
createScriptURL(s) {
return s;
},
}),
});
// opt out of DOMPurify's internal `dompurify` Trusted Types policy entirely
// (useful when your CSP `trusted-types` allowlist does not include `dompurify`)
const clean = DOMPurify.sanitize(dirty, { TRUSTED_TYPES_POLICY: null });
정제 방식을 조정하세요```js
// return entire document including tags (default is false) const clean = DOMPurify.sanitize(dirty, { WHOLE_DOCUMENT: true });
// disable DOM Clobbering protection on output (default is true, handle with care, minor XSS risks here) const clean = DOMPurify.sanitize(dirty, { SANITIZE_DOM: false });
// enforce strict DOM Clobbering protection via namespace isolation (default is false)
// when enabled, isolates the namespace of named properties (i.e., id and name attributes)
// from JS variables by prefixing them with the string user-content-
const clean = DOMPurify.sanitize(dirty, { SANITIZE_NAMED_PROPS: true });
// keep an element's content when the element is removed (default is true) const clean = DOMPurify.sanitize(dirty, { KEEP_CONTENT: false });
// glue elements like style, script or others to document.body and prevent unintuitive browser behavior in several edge-cases (default is false) const clean = DOMPurify.sanitize(dirty, { FORCE_BODY: true });
// remove all elements under
// extend the default FORBID_CONTENTS list to also remove elements under
// change the parser type so sanitized data is treated as XML and not as HTML, which is the default const clean = DOMPurify.sanitize(dirty, { PARSER_MEDIA_TYPE: 'application/xhtml+xml', });
### 우리가 정제하는 곳에 영향력을 행사합니다```js
// use the IN_PLACE mode to sanitize a node "in place", which is much faster depending on how you use DOMPurify
const dirty = document.createElement('a');
dirty.setAttribute('href', 'javascript:alert(1)');
const clean = DOMPurify.sanitize(dirty, { IN_PLACE: true }); // see https://github.com/cure53/DOMPurify/issues/288 for more info
여기에 DOMPurify를 필요에 맞게 실행, 사용자 정의 및 구성하는 방법을 보여주는 더 많은 예제가 있습니다.
영구 구성
동일한 구성을 DOMPurify.sanitize에 반복적으로 전달하는 대신 DOMPurify.setConfig 메서드를 사용할 수 있습니다. 구성은 다음 DOMPurify.setConfig 호출까지, 또는 재설정을 위해 DOMPurify.clearConfig를 호출할 때까지 유지됩니다. 활성 구성은 하나만 존재하므로, 한 번 설정되면 DOMPurify.sanitize에 전달된 모든 추가 구성 매개변수는 무시됩니다.
후크
DOMPurify는 DOMPurify.addHook 메서드를 사용하여 다음 후크 중 하나에 하나 이상의 함수를 연결함으로써 기능을 확장할 수 있습니다:
beforeSanitizeElementsuponSanitizeElement('s' 없음 - 모든 요소에 대해 호출됨)afterSanitizeElementsbeforeSanitizeAttributesuponSanitizeAttributeafterSanitizeAttributesbeforeSanitizeShadowDOMuponSanitizeShadowNodeafterSanitizeShadowDOM
필요한 경우 현재 처리 중인 DOM 노드, 검증된 노드 및 속성 데이터가 담긴 리터럴, 그리고 DOMPurify 구성을 콜백에 전달합니다. API를 멋지게 사용하는 방법을 보려면 MentalJS 후크 데모를 확인하세요.
예제:```js DOMPurify.addHook( 'uponSanitizeAttribute', function (currentNode, hookEvent, config) { // Do something with the current node // You can also mutate hookEvent for current node (i.e. set hookEvent.forceKeepAttr = true) // For other than 'uponSanitizeAttribute' hook types hookEvent equals to null } );
### 훅에서 `sanitize()`를 호출하는 것에 관한 참고 사항
**`DOMPurify.sanitize()`는 재진입이 불가능합니다.** 훅 내부에서, 또는 `CUSTOM_ELEMENT_HANDLING.tagNameCheck`나 `attributeNameCheck`와 같은 구성 콜백에서 호출하지 마십시오. 이러한 콜백은 활성 새니타이저 패스의 _중간_에 실행됩니다.
중첩된 `sanitize()` 호출은 전달받은 구성을 다시 읽으며, 그 과정에서 **외부 패스가 여전히 사용 중인 구성을 대체합니다**. 그런 다음 외부 문서의 나머지 부분은 여러분의 구성이 아닌 중첩 호출의 구성에 따라 새니타이즈됩니다. 중첩 호출은 일반적으로 기본 구성으로 실행되므로, 엄격한 `ALLOWED_TAGS` 허용 목록이 문서 중간에서 오류나 경고 없이 조용히 기본 허용 목록으로 다시 넓어질 수 있습니다.
중첩된 마크업, 예를 들어 속성 값 안에 포함된 HTML 조각을 새니타이즈해야 하는 경우 두 가지 안전한 방법이 있습니다. 호출마다 구성을 전달하는 대신 [`DOMPurify.setConfig`](#persistent-configuration)로 구성을 한 번 설정하십시오. 영구 구성은 중첩 호출에서도 공유되어 전체 패스 동안 유지됩니다. 또는 훅에서 조각들을 수집한 뒤 외부 호출이 반환된 _후에_ 별도의 `sanitize()` 호출로 새니타이즈하십시오.
## 제거된 구성
| 옵션 | 도입 버전 | 비고 |
| --------------- | ----- | ------------------------ |
| SAFE_FOR_JQUERY | 2.1.0 | 대체가 필요하지 않습니다. |
## 지속적 통합
현재 GitHub Actions와 Playwright를 함께 사용하고 있습니다. 이를 통해 모든 커밋에서 관련 최신 브라우저에서 모든 것이 작동하는지 확인할 수 있으며, 별도의 예약 및 병합 시 워크플로가 구형 엔진 스냅샷에서도 스위트를 다시 실행하여 구형 브라우저에서의 손상도 잡아냅니다. 빌드 로그는 여기에서 확인하십시오: https://github.com/cure53/DOMPurify/actions
`npm run test`를 실행하여 로컬 테스트를 추가로 실행할 수 있습니다.
모든 관련 커밋은 추가 보안을 위해 `0x24BB6BF4` 키로 서명됩니다 (2016년 4월 8일부터).
### 개발 및 기여
#### 설치 (`npm i`)
공식적으로 `npm`을 지원합니다. GitHub Actions 워크플로는 `npm`을 사용하여 의존성을 설치하도록 구성되어 있습니다. 더 이상 사용되지 않는 버전의 `npm`을 사용할 경우, 설치된 의존성의 버전을 완전히 보장할 수 없으며, 이는 예상치 못한 문제로 이어질 수 있습니다.
#### 스크립트
코드 일관성을 보장하기 위해 pre-commit 워크플로의 일부로 `xo`를 통한 ESLint를 사용합니다. 또한 소스 및 Markdown 포맷팅을 위해 [Prettier](https://github.com/prettier/prettier)를 사용하며, `/dist` 자산은 `rollup`을 통해 빌드됩니다.
다음은 npm 스크립트입니다:
- `npm run dev` - 소스 변경 사항을 감시하면서 압축되지 않은 UMD 번들을 빌드합니다
- `npm run test` - 소스를 린트하고, jsdom을 통해 테스트를 실행하며, Playwright를 통해 Chromium에서 브라우저 테스트를 실행합니다
- `npm run test:jsdom` - jsdom을 통한 테스트만 실행합니다
- `npm run test:happydom` - happy-dom을 통해 스위트를 실행합니다 (지원되지 않는 환경이며, 호환성 보장이 아닌 견고성 검사로 유지됩니다)
- `npm run test:browser` - Playwright를 통한 테스트만 실행합니다
- `npm run test:browser:legacy` - 구형 브라우저 엔진에서 스위트를 실행합니다 (`PW_MODULE`을 고정된 구형 Playwright 설치로 지정하십시오; `.github/workflows/legacy-browsers.yml` 참조)
- `npm run test:ci` - jsdom 및 Playwright에 대한 CI 테스트 흐름을 실행합니다
- `npm run test:fuzz` - `sanitize()`와 CONFIG를 다루는 소규모 퍼저를 실행합니다
- `npm run bench` - 빌드된 `dist/purify.cjs`에 대해 jsdom 마이크로 벤치마크를 실행합니다 (먼저 빌드하십시오; `--json` 및 `--compare a.json b.json`은 브랜치 간 A/B 실행을 지원합니다 - 결과는 방향성 지표이며, 사용자 대상 주장은 실제 브라우저에서 확인하십시오)
- `npm run coverage` - 계측된 번들을 빌드하고, jsdom 스위트를 실행하며, 로컬 HTML 라인/브랜치 커버리지 보고서를 `coverage/index.html`에 작성합니다 (jsdom 범위에 한정되며, CI에서는 실행되지 않습니다)
- `npm run build:cov` - 계측된 커버리지 번들만 빌드합니다
- `npm run lint` - xo를 통한 ESLint로 소스를 린트합니다
- `npm run format` - Prettier로 JavaScript/TypeScript 및 Markdown 소스를 포맷합니다
- `npm run format:js` - JavaScript/TypeScript 소스만 포맷합니다
- `npm run format:md` - Markdown 파일만 포맷합니다
- `npm run build` - 타입 선언과 배포 번들을 빌드한 다음, 생성된 타입을 수정하고 정리합니다
- `npm run build:types` - TypeScript 선언 파일만 생성합니다
- `npm run build:rollup` - 모든 Rollup 번들을 빌드합니다
- `npm run build:umd` - 압축되지 않은 UMD 번들만 빌드합니다
- `npm run build:umd:min` - 압축된 UMD 번들만 빌드합니다
- `npm run build:es` - ES 모듈 번들만 빌드합니다
- `npm run build:cjs` - CommonJS 번들만 빌드합니다
- `npm run build:fix-types` - 생성된 타입 파일을 후처리합니다
- `npm run build:cleanup` - 임시로 생성된 타입 출력을 정리합니다
- `npm run verify-typescript` - TypeScript 검증 스크립트를 실행합니다
- `npm run commit-amend-build` - 빌드 출력을 수정하기 위한 유지관리자 도우미 스크립트를 실행합니다
참고: 모든 실행 스크립트는 `npm run <script>`를 통해 트리거됩니다.
더 많은 npm 스크립트가 있지만, 이들은 주로 CI와 통합하기 위한 것이거나, 예를 들어 모든 커밋마다 빌드 배포 파일을 수정하기 위한 "비공개" 용도입니다.
## 보안 메일링 리스트
DOMPurify의 **보안상 중요한** 릴리스가 게시될 때마다 알림을 보내는 메일링 리스트를 운영하고 있습니다. 즉, 누군가 우회 방법을 발견하여 릴리스로 수정한 경우(우회가 발견되면 항상 그렇습니다) 해당 리스트로 메일이 발송됩니다. 이는 일반적으로 우회에 대해 알게 된 후 몇 분 또는 몇 시간 이내에 이루어집니다. 리스트는 여기에서 구독할 수 있습니다:
[https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security](https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security)
기능 릴리스는 이 리스트에 공지되지 않습니다.
## 누가 기여했나요?
많은 사람들이 DOMPurify가 오늘날의 모습이 되도록 도왔으며, 그들은 인정받을 자격이 있습니다!
[gnyselcuk](https://github.com/gnyselcuk), [leechristensen](https://github.com/leechristensen),[offset](https://github.com/offset), [Bankde](https://github.com/Bankde), [lukewarlow](https://github.com/lukewarlow), [DEMON1A](https://github.com/DEMON1A), [fg0x0](https://github.com/fg0x0), [kodareef5](https://github.com/kodareef5), [DavidOliver](https://github.com/DavidOliver), [1Jesper1](https://github.com/1Jesper1), [bencalif](https://github.com/bencalif), [trace37labs](https://github.com/trace37labs), [eddieran](https://github.com/eddieran), [christos-eth](https://github.com/christos-eth), [researchatfluidattacks](https://github.com/researchatfluidattacks), [frevadiscor](https://github.com/frevadiscor), [Rotzbua](https://github.com/Rotzbua), [binhpv](https://github.com/binhpv), [MariusRumpf](https://github.com/MariusRumpf), [prasadrajandran](https://github.com/prasadrajandran), [Cybozu 💛💸](https://github.com/cybozu), [hata6502 💸](https://github.com/hata6502), [openclaw 💸](https://github.com/openclaw), [intra-mart-dh 💸](https://github.com/intra-mart-dh), [nelstrom ❤️](https://github.com/nelstrom), [hash_kitten ❤️](https://twitter.com/hash_kitten), [kevin_mizu ❤️](https://twitter.com/kevin_mizu), [icesfont ❤️](https://github.com/icesfont), [reduckted ❤️](https://github.com/reduckted), [dcramer 💸](https://github.com/dcramer), [JGraph 💸](https://github.com/jgraph), [baekilda 💸](https://github.com/baekilda), [Healthchecks 💸](https://github.com/healthchecks), [Sentry 💸](https://github.com/getsentry), [jarrodldavis 💸](https://github.com/jarrodldavis), [CynegeticIO](https://github.com/CynegeticIO), [ssi02014 ❤️](https://github.com/ssi02014), [GrantGryczan](https://github.com/GrantGryczan), [Lowdefy](https://twitter.com/lowdefy), [granlem](https://twitter.com/MaximeVeit), [oreoshake](https://github.com/oreoshake), [tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [is2ei](https://github.com/is2ei), [SoheilKhodayari](https://github.com/SoheilKhodayari), [franktopel](https://github.com/franktopel), [NateScarlet](https://github.com/NateScarlet), [neilj](https://github.com/neilj), [fhemberger](https://github.com/fhemberger), [Joris-van-der-Wel](https://github.com/Joris-van-der-Wel), [ydaniv](https://github.com/ydaniv), [terjanq](https://twitter.com/terjanq), [filedescriptor](https://github.com/filedescriptor), [ConradIrwin](https://github.com/ConradIrwin), [gibson042](https://github.com/gibson042), [choumx](https://github.com/choumx), [0xSobky](https://github.com/0xSobky), [styfle](https://github.com/styfle), [koto](https://github.com/koto), [tlau88](https://github.com/tlau88), [strugee](https://github.com/strugee), [oparoz](https://github.com/oparoz), [mathiasbynens](https://github.com/mathiasbynens), [edg2s](https://github.com/edg2s), [dnkolegov](https://github.com/dnkolegov), [dhardtke](https://github.com/dhardtke), [wirehead](https://github.com/wirehead), [thorn0](https://github.com/thorn0), [styu](https://github.com/styu), [mozfreddyb ❤️](https://github.com/mozfreddyb), [mikesamuel](https://github.com/mikesamuel), [jorangreef](https://github.com/jorangreef), [jimmyhchan](https://github.com/jimmyhchan), [jameydeorio](https://github.com/jameydeorio), [jameskraus](https://github.com/jameskraus), [hyderali](https://github.com/hyderali), [hansottowirtz](https://github.com/hansottowirtz), [hackvertor](https://github.com/hackvertor), [freddyb](https://github.com/freddyb), [flavorjones](https://github.com/flavorjones), [djfarrelly](https://github.com/djfarrelly), [devd](https://github.com/devd), [camerondunford](https://github.com/camerondunford), [buu700](https://github.com/buu700), [buildog](https://github.com/buildog), [alabiaga](https://github.com/alabiaga), [Vector919](https://github.com/Vector919), [Robbert](https://github.com/Robbert), [GreLI](https://github.com/GreLI), [FuzzySockets](https://github.com/FuzzySockets), [ArtemBernatskyy](https://github.com/ArtemBernatskyy), [@garethheyes](https://twitter.com/garethheyes), [@shafigullin](https://twitter.com/shafigullin), [@mmrupp](https://twitter.com/mmrupp), [@irsdl](https://twitter.com/irsdl),[ShikariSenpai](https://github.com/ShikariSenpai), [ansjdnakjdnajkd](https://github.com/ansjdnakjdnajkd), [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro), [@CmdEngineer\_](https://twitter.com/CmdEngineer_), [@avr4mit](https://twitter.com/avr4mit), [davecardwell](https://github.com/davecardwell), [Develop-KIM](https://github.com/Develop-KIM), [asamuzaK](https://github.com/asamuzaK), [fishjojo1 ❤️](https://github.com/fishjojo1), [Rikuxx0](https://github.com/Rikuxx0), [donmccurdy](https://github.com/donmccurdy), [hhk-png](https://github.com/hhk-png), [elrion018](https://github.com/elrion018), [michalnieruchalski-tiugo](https://github.com/michalnieruchalski-tiugo), [reey](https://github.com/reey), [KanhaKanhaiya](https://github.com/KanhaKanhaiya), [odaysec](https://github.com/odaysec), [Akokonunes](https://github.com/Akokonunes), [alirezarouhbakhsh](https://github.com/alirezarouhbakhsh), [Jaybhade](https://github.com/Jaybhade) 그리고 특히 [@securitymb ❤️](https://twitter.com/securitymb) & [@masatokinugawa ❤️](https://twitter.com/masatokinugawa)