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