콘텐츠로 이동

Artisan 보안 및 운영

브리지는 신뢰할 수 없는 입력이 포함될 수 있는 HTML을 두 개의 독립적인 네트워크 장벽과 엄격한 콘텐츠 정책 뒤에서 Chrome 안에 렌더링합니다. Chrome OS 샌드박스는 명시된 한계가 있는 별도의 선택적 제어 수단입니다. 이 페이지는 그 경계를 문서화하며, 해당 경계가 절대적이라고 주장하지 않습니다.

렌더링은 서버 측 요청 실행에 해당합니다. 애플리케이션이 HTML을 브라우저 엔진에 넘기면, 해당 엔진은 기본적으로 리소스를 가져올 수 있습니다. 신뢰할 수 없는 입력이 유발하는 아웃바운드 페치는 서버 측 요청 위조입니다. CWE-918은 이를 서버가 제공된 URL의 콘텐츠를 가져오면서 요청이 예상한 목적지로 향하는지 충분히 확인하지 않는 것으로 정의합니다. SSRF(CWE-918)는 CWE Top 25 약점입니다. OWASP ASVS는 서버 구성 요소의 아웃바운드 요청이 암묵적이지 않고 통제되어야 한다고 요구합니다. OWASP SSRF Prevention Cheat Sheet는 임의의 목적지에 대한 호출을 네트워크 계층에서 거부하는 것을 강력한 제어 수단으로 봅니다. 아래 기본 거부(deny-by-default) 네트워크 태세는 그 요구 사항에 대한 브리지의 대응입니다. NIST SP 800-53 SC-7은 브리지가 전송 계층에서 적용하는 것과 같은, 모두 거부하고 예외로 허용하는(deny-all-permit-by-exception) 경계 원칙을 기술합니다.

브리지에 전달된 HTML은 전적으로 프로세스 내부와 로컬 Chrome 인스턴스 안에서 처리됩니다. 브리지는 자체적으로 어떠한 아웃바운드 네트워크 호출도 하지 않으며 Chrome이 그러한 호출을 하지 못하도록 차단하므로(아래 네트워크 모델 참조), 입력 콘텐츠는 렌더러를 통해 호스트를 벗어나지 않습니다. 입력에 포함된 PII는 생성되는 PDF에 렌더링됩니다. 출력물도 입력과 동일한 상주성 제어 아래에서 다루십시오. 브리지는 입력이나 출력을 디스크에 보존하지 않습니다. 보존은 호출자의 책임입니다.

안전한 텔레메트리 및 로그 정제

섹션 제목: “안전한 텔레메트리 및 로그 정제”

ChromeHtmlRendererBrowserPool은 선택적 PSR-3 LoggerInterface를 받습니다. 브리지는 운영 메타데이터만 기록합니다. 입력 바이트 길이, 대상 너비와 높이, 출력 바이트 길이, 측정된 콘텐츠 높이, 구성된 바이너리 경로를 사용하는 브라우저 시작, 렌더링 횟수가 포함된 재시작 알림, 그리고 종료 이벤트입니다. 브리지는 HTML 콘텐츠, 렌더링된 바이트, 추출된 텍스트를 기록하지 않습니다. 이는 운영 이벤트를 기록하되 민감한 페이로드는 로그에서 제외하라는 NIST SP 800-92 지침과 일치합니다. 바이너리 경로는 기록됩니다. 이를 민감하지 않은 배포 메타데이터로 취급하십시오. 로그 호출 형태는 tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSizetests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath에서 검증합니다.

브리지는 한 장벽이 우회되더라도 호스트가 노출되지 않도록 독립적인 장벽 두 개를 적용합니다.

  1. Content-Security-Policy. 모든 렌더링은 다음 항목을 담은 문서 안에서 ChromeSecurityPolicy::wrapHtml()로 감싸집니다.

    default-src 'none'; style-src 'unsafe-inline'; img-src data:;
    base-uri 'none'; form-action 'none'; frame-ancestors 'none';
    navigate-to 'none';

    default-src 'none'은 모든 리소스 출처를 거부합니다. img-src data:는 인라인 이미지만 허용합니다. navigate-to 'none'은 클라이언트 측 내비게이션을 차단합니다. style-src 'unsafe-inline'은 Chrome의 printToPDF가 인라인 스타일을 적용하는 데 필요한 유일한 완화 조치입니다. src/Artisan/ChromeSecurityPolicy.php에서 확인되며 ChromeSecurityPolicyTest::wrapHtmlIncludesNavigationCspDirectives에서 검증합니다.

  2. CDP 전송 차단. 콘텐츠를 로드하기 전에 ChromeHtmlRendererNetwork.enable을 실행한 다음 Network.setBlockedURLs를 패턴 ['*']로 실행하여, CSP와 무관하게 Chrome DevTools Protocol 전송 계층에서 모든 하위 리소스 URL을 차단합니다. src/Artisan/ChromeHtmlRenderer::blockAllNetworkRequests()에서 확인되며 ChromeHtmlRendererTest::renderAutoFitsHeightAndBlocksNetworkRequests에서 검증합니다(이 테스트는 정확한 CDP 메서드 순서와 ['urls' => ['*']] 매개변수를 확인합니다). 이는 OWASP SSRF 지침이 가장 강력한 제어 수단으로 권장하는 네트워크 계층 차단이며, NIST SP 800-53 SC-7과 일치하는 전송 수준의 전체 거부(deny-all)입니다.

그 결과, 입력에 들어 있는 원격 <img>, 스타일시트, 글꼴, 스크립트 또는 iframe URL은 로드되지 않습니다. 브리지는 도메인 허용 목록이나 사설 IP 필터를 구현하지 않는데, 그럴 필요가 없기 때문입니다. 브리지는 어떠한 아웃바운드 하위 리소스 페치도 전혀 허용하지 않습니다.

드리프트 참고: nextpdf/corewriteHtmlChrome() 독블록에는 Chrome이 “외부 리소스를 가져올 것”이라고 명시되어 있으며 “사설 IP 범위를 차단하고 허용 도메인을 제한”하도록 정책을 구성하라고 권고합니다. 이는 구성 가능한 허용 목록 모델을 기술한 것입니다. 출시된 Artisan ChromeSecurityPolicy는 허용 목록을 노출하지 않으며, 대신 모든 하위 리소스 요청을 무조건 차단합니다. 권위 있는 기준은 코어 독블록이 아니라 코드입니다. 이 드리프트는 코어 문서 팀을 위해 기록되었습니다.

ChromeSecurityPolicy::validate()는 Chrome에 연결하기 전에 실행되며 다음을 거부합니다.

검사 항목제한근거
HTML 크기> maxHtmlSize(기본값 5 MB)리소스 소진 제한(CWE Top 25 통제되지 않은 리소스 소비)
Base64 데이터 URI캡처 그룹 >= 13_000_000 바이트압축 폭탄 한계
<meta http-equiv="refresh">모두(대소문자 구분 없음, single/double)내부 엔드포인트로의 클라이언트 측 리디렉션, 즉 SSRF 내비게이션 벡터를 차단합니다

메타 리프레시 차단은 명시적인 SSRF 강화 조치입니다. 이것이 없으면 공격자의 HTML이 printToPDF 이전에 Chrome을 클라우드 메타데이터 엔드포인트로 리디렉션할 수 있습니다. 경계 동작은 ChromeSecurityPolicyTest 전반에서 검증됩니다(validateThrowsOnOversizedHtml, validateRejectsMetaRefreshRedirect, validateRejectsMetaRefreshCaseInsensitive, validateRejectsMetaRefreshWithSingleQuotes, validateRejectsOversizedBase64DataUri, validateRejectsBase64DataUriAtExactThreshold).

또한 ChromeSecurityPolicy::wrapHtml()은 스타일 블록에서 스크립트 컨텍스트로 벗어나는 것을 방지하기 위해 주입 전에 defaultCss에서 </style>를 제거합니다(ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss에서 검증됨).

Chrome 샌드박스 경계 — 명시적 기술

섹션 제목: “Chrome 샌드박스 경계 — 명시적 기술”

Chrome OS 샌드박스는 위의 네트워크 장벽과는 별도의 제어 수단이며, 브리지는 이 샌드박스를 보장하지 않습니다.

  • 기본적으로 noSandboxfalse이므로 Chrome은 자체 샌드박스를 활성화한 채로 실행됩니다. 브리지는 샌드박스를 구현하지 않으며, 호스트 커널 지원에 의존하는 Chrome 바이너리의 샌드박스에 의존합니다.
  • 옵션 noSandbox: true를 설정하면 Chrome이 --no-sandbox로 실행됩니다. 이 설정은 Chrome 프로세스 격리 샌드박스를 제거합니다. 샌드박스를 초기화할 수 없는 컨테이너를 위해 제공되지만, 격리를 실질적으로 약화합니다. 렌더러 침해는 더 이상 Chrome 샌드박스로 봉쇄되지 않습니다.
  • 브리지의 네트워크 장벽(CSP + CDP 차단)은 샌드박스 활성화 여부와 관계없이 유효하지만, 프로세스 격리를 대체하지는 않습니다. OWASP ASVS 최소 권한 지침이 적용됩니다. Chrome을 루트가 아닌 사용자로, 제약된 컨테이너 안에서 실행하고, noSandbox는 불가피한 경우에만 사용하며, --no-sandbox 배포는 입력에 대한 더 높은 신뢰 요건으로 취급하십시오.

이 문서는 브리지가 “기본적으로 안전(secure by default)“하다거나 “변조 불가능(tamper-proof)“하다거나, 샌드박스를 비활성화하는 것이 안전하다고 주장하지 않습니다. 이 문서는 존재하는 제어 수단과 그 제어가 멈추는 지점을 기술합니다. 샌드박스 지원이 가능한 컨테이너를 프로비저닝하는 방법은 /integrations/artisan/chrome-renderer-setup/ 페이지에서 다룹니다.

다음은 src/Artisan/Exception/ 및 render/transport 코드에 열거된 항목입니다.

조건표면화 형태출처
chrome-php/chrome 라이브러리 부재ChromeNotAvailableException(설치 명령 포함)BrowserPool::getBrowser()
HTML이 maxHtmlSize를 초과함RuntimeException(메시지 “exceeds maximum allowed size”)ChromeSecurityPolicy::validate()
크기를 초과한 base64 데이터 URIRuntimeException(메시지 “oversized base64 data URI”)ChromeSecurityPolicy::validate()
금지된 메타 리프레시RuntimeException(메시지 “forbidden meta refresh redirect”)ChromeSecurityPolicy::validate()
Chrome 시작 / 타임아웃 / 충돌ChromeRenderException(원인을 래핑)ChromeHtmlRenderer::render()
Chrome이 빈 PDF를 반환함ChromeRenderException(메시지 “returned empty data”)ChromeHtmlRenderer::render()
페이지에 콘텐츠 스트림이 없음PdfParseExceptionPageImporter::import()

렌더링 내부에서 발생한 ChromeRenderException은 변경 없이 다시 던져집니다. 그 외의 모든 Throwable은 이전 예외를 보존하면서 ChromeRenderException으로 래핑됩니다(ChromeHtmlRendererTest::renderRethrowsChromeRenderExceptionWithoutWrapping::renderWrapsUnexpectedThrowablesWithChromeRenderException에서 검증됨). Chrome 페이지는 실패 시에도 항상 finally 블록에서 닫힙니다.

  • 입력 크기: maxHtmlSize(기본값 5 MB)와 13 MB base64 데이터 URI 상한.
  • 시간: renderTimeout 초가 콘텐츠 로드와 CDP 동기 호출을 모두 제한합니다. CDP 제어 명령은 고정 5초 타임아웃을 사용합니다.
  • 프로세스: BrowserPool은 메모리 증가를 제한하기 위해 100회 렌더링마다 Chrome을 재시작하고, close() / 소멸 시 프로세스를 닫습니다.

이것들은 제한이지 할당량이 아닙니다. 신뢰할 수 없는 입력에 노출되는 모든 경로에는 CWE Top 25 리소스 소비 지침에 따라 호스트 수준 리소스 제한(cgroup, ulimit, 요청 예산)이 여전히 권장됩니다.

PSR-3 로거를 주입해 다음을 캡처하십시오. 렌더링 시작(크기, 너비, 높이), 렌더링 완료(출력 크기, 콘텐츠 높이), 브라우저 시작(바이너리 경로), 브라우저 재시작(렌더링 횟수), 브라우저 종료(렌더링 횟수). 방출되는 이벤트는 이것들뿐이며, 페이로드 콘텐츠는 포함하지 않습니다. 이를 지연 시간 SLO 및 재시작률 알림에 사용하십시오.

주장참조clause_id(조항 ID)reference_id(참조 ID)
서버 구성 요소의 아웃바운드 요청은 통제되어야 함OWASP ASVS 5.0§ (SSRF/아웃바운드 제어)
SSRF = 서버가 목적지를 검증하지 않고 제공된 URL 을 가져옴CWE Top 25 2025(CWE-918) 약점 목록cwe_top25_2025#x28.x2.p2
SSRF(CWE-918)는 CWE Top 25 약점임CWE Top 25 2025 약점 목록cwe_top25_2025#x1.p73
통제되지 않은 리소스 소비는 CWE Top 25 약점임CWE Top 25 2025(CWE-400) 약점 목록cwe_top25_2025#x19.x2.p2
기본 거부 경계 보호(예외로 허용)NIST SP 800-53 Rev 5 SC-7 표준SC-7
임의의 목적지에 대한 호출을 네트워크 계층에서 거부하는 것이 강력한 SSRF 제어임OWASP Cheat Sheet Series(SSRF Prevention §Network layer) 가이드owasp_cheatsheet_series#x132.x2
URL 을 가져오는 구성 요소를 SSRF 로부터 보호OWASP Cheat Sheet Series 가이드§ (SSRF 방지, URL 페치 도구)
신뢰할 수 없는 콘텐츠 렌더링 격리, 최소 권한OWASP ASVS 5.0§ (샌드박스 / 최소 권한)
운영 이벤트를 기록; 페이로드는 로그에서 제외NIST SP 800-92§ (로그 콘텐츠 지침)

인용은 NextPDF 컴플라이언스 엔진을 통해 검색되었습니다(코퍼스 매니페스트 1d05b7c4…d790b6). 조항 텍스트는 인용 없이 의역되었습니다.

위협제어잔여 위험
원격 하위 리소스를 통한 SSRFCSP default-src 'none' + CDP setBlockedURLs('*')두 장벽을 모두 우회하는 Chrome 엔진 버그(심층 방어는 위험을 낮추지만 제거하지는 않음)
메타 리프레시 내비게이션을 통한 SSRFChrome 이전 검증이 해당 태그를 거부함패턴과 일치하지 않는 새로운 내비게이션 벡터
리소스 소진입력 크기 + base64 상한 + 타임아웃 + 100회 렌더링 재시작호스트별 할당량 없음. cgroup/ulimit과 병행하십시오
렌더러 프로세스 침해Chrome 샌드박스 활성화 시noSandbox: true는 이 제어를 완전히 제거함
스타일 이탈 / 주입</style>defaultCss에서 제거. CSP 가 스크립트를 차단함향후 제거되지 않은 벡터를 통한 주입

브리지는 어떠한 암호화 연산도 수행하지 않습니다. 브리지는 Chrome을 통해 PDF 바이트를 생성하고 이를 임베드합니다. 서명, 암호화, FIPS 모드 동작은 코어/Premium의 관심사이며 Artisan의 영향을 받지 않습니다.

  • 구성: /integrations/artisan/configuration/
  • Chrome 렌더러 설정: /integrations/artisan/chrome-renderer-setup/
  • 문제 해결: /integrations/artisan/troubleshooting/
  • 프로덕션 사용: /integrations/artisan/production-usage/
  • 개요: /integrations/artisan/overview/