콘텐츠로 이동

계약 / 보안 정책

보안 정책 도메인은 기본 거부 방식으로 동작하는 세 가지 계약을 제공합니다. CryptoPolicyInterface는 알고리즘과 키 선택을 통제하고, HtmlSecurityPolicyInterface는 HTML 기능 표면을 제한하며, ExternalResourcePolicyInterface는 원격 리소스 로딩을 관리합니다. 이들은 모두 계약이므로, 배포 환경에서는 포크 없이 더 엄격한 정책을 제공할 수 있습니다.

Terminal window
composer require nextpdf/core:^3

CryptoPolicyInterface는 암호화 게이트 역할을 합니다. Core는 모든 서명, 암호화 또는 해싱 단계 전에 이를 조회합니다. 이 검사는 해시, 서명 OID, 암호(cipher), 키 강도를 다룹니다. 또한 이 계약은 감사 로깅을 위해 최소 해시와 정책 이름을 보고합니다. 이를 통해 FIPS 140-3 또는 eIDAS와 같은 규칙 세트를 적용할 수 있습니다. 서명 및 암호화 코드는 변경되지 않습니다. 정책이 설정되지 않은 경우, 모든 알고리즘이 허용됩니다. 규제 대상 배포 환경에서는 명시적 정책을 설정해야 합니다.

HtmlSecurityPolicyInterface는 HTML 파싱 계층에서 동작합니다. 이 정책은 콘텐츠가 어떤 렌더러에도 도달하기 전에 실행됩니다. 태그, 속성, CSS 속성 또는 URL 스킴의 허용 여부를 알려주며, 입력 크기와 중첩 깊이도 제한합니다. 또한 크기 제한과 CSP 헤더를 설정하는 렌더러별 전송 정책(Chrome, Cloudflare, Gotenberg)과 함께 동작합니다. HTML 정책은 파싱 계층의 공격 표면을 축소합니다. 제거된 태그는 레이아웃에 도달하지 않습니다. 따라서 주입된 요소는 출력을 변경할 수 없습니다. 정책이 설정되지 않은 경우, 기본값은 전체 기능 세트를 허용합니다.

ExternalResourcePolicyInterface는 HTML 파이프라인이 외부 글꼴, 스타일시트 또는 이미지를 가져올 수 있는지를 알려줍니다. 또한 각 가져오기에 대한 제한을 설정합니다. 기본 태세는 모두 거부입니다. 모든 옵션은 명시적으로 켜기 전까지 꺼져 있습니다. 이 계약은 최소 권한 원칙을 따릅니다. 신뢰할 수 없는 HTML은 공격자 URL을 가리킬 수 있습니다. 이 정책은 @font-face 가져오기를 스킴, 크기, 글리프 수로 통제합니다. @import는 스킴, 깊이, 전체 크기로 통제합니다. background-image는 스킴 목록과 정확히 일치하는 도메인 허용 목록으로 통제합니다. data-URI 크기도 제한합니다. 또한 SVG 외부 참조를 통제합니다. 이 계약은 프로덕션에서 이를 항상 거부해야 한다고 명시합니다. 허용하면 요청 위조와 스크립트 주입이 가능해집니다. 개방형 URL 가져오기는 서버 측 요청 위조 경로입니다. OWASP Top 10 2025에 따르면, URL을 변경하여 접근 제어를 우회할 수 있습니다. 구성 요소는 안전한 링크를 통해 공식 출처에서만 가져와야 합니다.

유형종류주요 멤버안정성도입 버전
CryptoPolicyInterfaceinterfaceisHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName()안정1.9.0
HtmlSecurityPolicyInterfaceinterfaceisTagAllowed(), isAttributeAllowed(), isCssPropertyAllowed(), isUrlSchemeAllowed(), getMaxInputSize(), getMaxNestingDepth(), getName()안정3.1.0
ExternalResourcePolicyInterfaceinterfaceisFontFaceAllowed(), getAllowedFontSchemes(), getMaxFontFileSize(), getMaxFontGlyphs(), isImportAllowed(), getMaxImportDepth(), isBackgroundImageAllowed(), getAllowedImageDomains(), getMaxDataUrlSize(), isSvgExternalReferenceAllowed()안정4.0.0

ExternalResourcePolicyInterface는 타입이 지정된 한계를 반환합니다. 크기는 positive-int, 가져오기 깊이는 int<1, 100>, 스킴 및 도메인 목록은 list<non-empty-string>입니다. 기본 구현은 모든 기능을 거부합니다.

examples/contracts/security-policy-quickstart.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\HtmlSecurityPolicyInterface;
/**
* Decide whether a tag survives the policy.
*
* @param HtmlSecurityPolicyInterface $policy A core or custom policy.
*/
function tagSurvives(HtmlSecurityPolicyInterface $policy, string $tag): bool
{
return $policy->isTagAllowed($tag);
}

이 함수는 계약에 의존합니다. 제한적인 정책과 기본 정책 모두 이 계약을 충족합니다.

examples/contracts/security-policy-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\CryptoPolicyInterface;
use NextPDF\Contracts\ExternalResourcePolicyInterface;
use NextPDF\Contracts\HtmlSecurityPolicyInterface;
use Psr\Log\LoggerInterface;
final readonly class UntrustedHtmlGate
{
public function __construct(
private HtmlSecurityPolicyInterface $htmlPolicy,
private ExternalResourcePolicyInterface $resourcePolicy,
private CryptoPolicyInterface $cryptoPolicy,
private LoggerInterface $logger,
) {}
/**
* Reject input that exceeds the configured limits before rendering.
*
* @param string $html Untrusted HTML markup.
*/
public function assertAcceptable(string $html): void
{
$maxInput = $this->htmlPolicy->getMaxInputSize();
if ($maxInput > 0 && \strlen($html) > $maxInput) {
$this->logger->warning('HTML rejected: input over limit', [
'policy' => $this->htmlPolicy->getName(),
'limit' => $maxInput,
]);
throw new \LengthException('HTML input exceeds policy limit.');
}
if ($this->resourcePolicy->isSvgExternalReferenceAllowed()) {
$this->logger->error('Unsafe policy: SVG external references enabled.');
throw new \LogicException('SVG external references must be denied in production.');
}
}
}

이 게이트는 파이프라인이 실행되기 전에 입력 제한을 적용하고 안전하지 않은 리소스 정책을 거부합니다. 감사 목적으로 정책 이름을 로깅하고 특정 예외를 발생시킵니다.

  • CryptoPolicyInterface는 정책이 설정되지 않은 경우 모든 알고리즘을 허용합니다. 개방형 기본값은 개발 편의를 위한 것이며, 프로덕션용 보안 태세가 아닙니다. 규제 대상 배포 환경에서는 명시적 정책을 설정하십시오.
  • HtmlSecurityPolicyInterface::getMaxInputSize()는 무제한을 의미하는 0을 반환합니다. 0은 “모두 거부”가 아니라 “정책 제한 없음”으로 취급하고, 전송 계층 제한도 함께 적용하십시오.
  • ExternalResourcePolicyInterface는 기본적으로 모두 거부입니다. 스킴 목록을 설정하지 않고 @font-face 또는 background-image를 활성화하면 요청 위조 표면이 열립니다. 기능을 활성화할 때는 허용 목록을 설정하십시오.
  • getAllowedImageDomains()의 도메인 허용 목록이 비어 있으면 배경 이미지가 활성화된 후 모든 도메인이 허용됨을 의미합니다. 빈 목록은 거부가 아닙니다. 명시적 도메인을 제공하십시오.
  • isSvgExternalReferenceAllowed()는 프로덕션에서 false를 반환해야 합니다. 이 계약은 그 요구 사항을 문서화합니다. true를 반환하는 정책은 구성 선택이 아니라 결함입니다.

정책 검사는 술어 호출입니다. 즉, O(1)이며 입력 크기에 비례하는 비용이 없습니다. 파싱 중에는 이 정책을 태그마다, 속성마다, CSS 속성마다, URL마다 조회합니다. 병적으로 구성된 문서는 호출 횟수를 배가시킵니다. 각 호출은 상수 시간을 유지합니다. 벽시계 시간 1500 ms와 최대 64 MB의 performance_budget는 정책 평가보다 파싱과 렌더링에 좌우됩니다. 입력 크기와 중첩 깊이 제한은 파서 자체의 비용을 제한하기 위해 존재합니다. 엄격한 정책은 과도하게 크거나 깊게 중첩된 문서를 레이아웃 전에 거부함으로써 최악의 경우 성능을 개선합니다.

이 계약들은 엔진의 방어 경계이므로 위협 모델이 명시적입니다. 알고리즘 다운그레이드는 CryptoPolicyInterface에 의해 완화되며, 이 인터페이스는 모든 연산 전에 약한 해시와 짧은 키를 차단합니다. 교차 사이트 스크립팅에서 PDF로 이어지는 공격과 콘텐츠 주입은 HtmlSecurityPolicyInterface에 의해 완화되며, 이 인터페이스는 렌더러가 실행되기 전에 파싱 계층에서 허용되지 않은 태그, 속성, CSS를 제거합니다. 서버 측 요청 위조, 압축 해제 폭탄, 누적 크기 폭탄은 ExternalResourcePolicyInterface에 의해 완화되며, 이 인터페이스는 기본적으로 모두 거부하고 모든 가져오기를 스킴, 크기, 깊이, 도메인으로 제한합니다. 리소스 고갈은 입력 크기, 중첩 깊이, 글꼴 글리프, 가져오기 깊이 제한에 의해 완화됩니다. 각 정책이 계약으로 정의되어 있으므로, 배포 환경은 엔진을 포크하지 않고도 경계를 강화하며, 정책 이름은 감사 로깅을 위해 노출됩니다. 모든 HTML, 모든 URL, 모든 글꼴 및 이미지 바이트를 적대적인 것으로 취급하십시오. 이 페이지는 계약이 암호화 정책을 관장하기 때문에 export_control_class: legal-review-required로 표시되어 있습니다. 본문은 모든 규범적 출처를 의역하며, 어떤 텍스트도 직접 인용하지 않습니다.

주장표준조항증거
제약 없는 URL 처리는 URL을 수정하여 접근 제어를 우회할 수 있게 하며, 외부 리소스 정책은 모두 거부 기본값과 정확히 일치하는 도메인 허용 목록으로 이를 완화합니다.OWASP Top 10 2025 표준A01
외부 구성 요소는 안전한 링크를 통해 공식 출처에서만 획득해야 하며, 이 정책은 스킴 허용 목록을 통해 이를 적용합니다.OWASP Top 10 2025 표준소프트웨어 공급망

두 항목은 모두 OWASP 지침을 의역한 것입니다. OWASP 자료는 조항으로 참조되며, 엔진은 해당 텍스트를 재현하지 않습니다.

Core는 세 가지 정책 계약을 정의하고 동결하며, 개발용으로는 허용적인 기본값을, 모두 거부 리소스 정책에는 엄격한 기본값을 제공합니다. Enterprise 에디션은 CryptoPolicyInterface 뒤에 FIPS 140-3 프로필을 제공하므로, 규제 대상 배포 환경은 서명 또는 암호화 코드를 변경하지 않고도 검증된 알고리즘 태세를 확보합니다. 계약 표면은 모든 에디션에서 동일합니다. 차이점은 배포 환경이 주입하는 정책 구현입니다.