diff --git a/.symfony/recipe/nowo-tech/html-to-word-bundle/1.0/config/packages/nowo_html_to_word.yaml b/.symfony/recipe/nowo-tech/html-to-word-bundle/1.0/config/packages/nowo_html_to_word.yaml index 7d29042..8487611 100644 --- a/.symfony/recipe/nowo-tech/html-to-word-bundle/1.0/config/packages/nowo_html_to_word.yaml +++ b/.symfony/recipe/nowo-tech/html-to-word-bundle/1.0/config/packages/nowo_html_to_word.yaml @@ -3,3 +3,15 @@ nowo_html_to_word: profiles: default: strict_mode: false + images: + # Keep remote HTTP(S) image fetching off by default (SSRF surface). + resolve_remote: false + # When enabling resolve_remote, set a tight remote_host_allowlist (empty deny-all). + # remote_host_allowlist: ['cdn.example.com'] + +when@prod: + nowo_html_to_word: + profiles: + default: + images: + resolve_remote: false diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 84dbf37..7a5ccfc 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -49,7 +49,7 @@ It does **not** expose HTTP routes by itself; embedding apps choose authorizatio ### SSRF via remote images - **Risk**: When `images.resolve_remote` is true, `file_get_contents()` may fetch attacker-controlled URLs from the server network. -- **Mitigation**: Keep remote resolution **disabled** in sensitive deployments; terminate outbound traffic at the network layer; prefer base64 or controlled CDN URLs at the application layer. HTTP timeouts are limited (10s) in `ImageResolver`. +- **Mitigation**: Keep remote resolution **disabled** (recipe default and `when@prod: resolve_remote: false`). `RemoteImageHostPolicy` **denies** remote hosts when `remote_host_allowlist` is empty — configure an explicit allowlist before enabling remote. Terminate outbound traffic at the network layer; prefer base64 or controlled CDN URLs at the application layer. HTTP timeouts are limited (10s) in `ImageResolver`. ### Path traversal / local file read via `` @@ -110,4 +110,4 @@ Record confirmation in the release PR or tag notes. | Grade | Pass (conditional) | | Risk | Medium | | Method | Cursor security-review / campaign static pass (`src/`, Flex recipe, demo, SECURITY docs) | -| Open residuals | No Critical/High. **Accepted Medium:** SSRF surface if `images.resolve_remote: true` without a tight `remote_host_allowlist` and network egress controls; empty allowlist is permissive when remote is enabled. Prefer default `resolve_remote: false`. App-owned: HTML size limits, path validation for local ``, avoid logging full HTML or credentialed remote URLs. | +| Open residuals | No Critical/High. **Accepted Medium:** SSRF surface if `images.resolve_remote: true` without a tight `remote_host_allowlist` and network egress controls. An **empty** `remote_host_allowlist` is **deny-all** (`RemoteImageHostPolicy`); configure an explicit allowlist when enabling remote. Flex recipe keeps `resolve_remote: false` (including `when@prod`). App-owned: HTML size limits, path validation for local ``, avoid logging full HTML or credentialed remote URLs. | diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index 345a79f..5fc98ad 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -57,7 +57,7 @@ No breaking application code changes are required. - **Consumers:** `composer update nowo-tech/html-to-word-bundle` and clear cache as usual. - **Optional:** set `images.remote_timeout` (seconds, default `10`) under a profile’s `images` section if you need a different remote download timeout — see [CONFIGURATION.md](CONFIGURATION.md). -- **Security:** when `images.resolve_remote` is enabled, keep a tight `remote_host_allowlist` (empty allowlist remains permissive) — see [SECURITY.md](SECURITY.md). +- **Security:** when `images.resolve_remote` is enabled, set an explicit `remote_host_allowlist` (empty allowlist is deny-all via `RemoteImageHostPolicy`) — see [SECURITY.md](SECURITY.md). - **Maintainers:** coverage gate is now **100%** lines; demo FrankenPHP image uses PHP **8.5** with optional `FRANKENPHP_MODE`. ## 1.1.3 → 1.1.4