diff --git a/CHANGELOG.md b/CHANGELOG.md
index 59b7094..0f9dece 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
- 2026-06-08 (post-refactor, pre-merge) — **Two MEDIUM security regressions fixed** in the 7-column refactor (`3a9d9d3`), flagged by `security-guidance@claude-code-plugins`:
1. **CSV formula injection inside the multi-line `Exécution` cell**: `_csv_safe` only checks `cell[0]`. With `executed_at` non-null, the cell starts with a safe date digit, but inner lines (commands, execution_result) starting with `=`/`+`/`-`/`@` evaded defense. Fix: `_format_execution_csv()` applies `_csv_safe` per user-controlled component BEFORE the multi-line concat. Outer `_csv_safe` on the assembled cell retained as belt-and-braces.
2. **Stored XSS in Markdown table cells**: the new GFM table allows inline HTML (we use it for `
`). A `sim.commands = ""` would be rendered raw by MD viewers that interpret inline HTML (Notion, Obsidian, GitHub preview). Fix: `_cell()` now calls `html.escape()` on each value BEFORE the pipe-escape and `\n` → `
` substitution — mirrors the `_render_engagement_html` PDF defense. The `
` we insert ourselves stays unescaped (it's not user-controlled). 2 dedicated regression tests added.
+- 2026-06-09 (post-merge-review) — PDF export: A4 landscape orientation (user feedback post-merge-review). `@page { size: A4 landscape; }` added to `_CSS`; `font-size` reduced to 11px and `table-layout: fixed; word-break: break-word` added to prevent 7-column overflow on narrower portrait layout.
---
diff --git a/backend/app/services/export.py b/backend/app/services/export.py
index b0e830d..0b51e57 100644
--- a/backend/app/services/export.py
+++ b/backend/app/services/export.py
@@ -198,11 +198,12 @@ def render_engagement_csv(
# ---------------------------------------------------------------------------
_CSS = """
-body { font-family: sans-serif; font-size: 13px; color: #1a1a1a; margin: 40px; }
-h1 { font-size: 22px; border-bottom: 2px solid #333; padding-bottom: 6px; }
-h2 { font-size: 17px; margin-top: 32px; color: #333; }
-table { border-collapse: collapse; width: 100%; margin-bottom: 12px; }
-th, td { border: 1px solid #ccc; padding: 4px 8px; text-align: left; vertical-align: top; white-space: pre-wrap; }
+@page { size: A4 landscape; margin: 20mm; }
+body { font-family: sans-serif; font-size: 11px; color: #1a1a1a; margin: 0; }
+h1 { font-size: 20px; border-bottom: 2px solid #333; padding-bottom: 6px; }
+h2 { font-size: 15px; margin-top: 32px; color: #333; }
+table { border-collapse: collapse; width: 100%; margin-bottom: 12px; table-layout: fixed; }
+th, td { border: 1px solid #ccc; padding: 3px 6px; text-align: left; vertical-align: top; white-space: pre-wrap; word-break: break-word; }
th { background: #e0e0e0; }
.meta { color: #555; margin-bottom: 16px; }
"""
diff --git a/backend/tests/test_export_render.py b/backend/tests/test_export_render.py
index 6d6b8a6..2bf2474 100644
--- a/backend/tests/test_export_render.py
+++ b/backend/tests/test_export_render.py
@@ -291,6 +291,15 @@ def test_render_engagement_pdf_contains_simulation_table(app) -> None:
assert header in html, f"Expected French header '{header}' in HTML"
+def test_render_engagement_html_has_landscape_page_rule(app) -> None:
+ from backend.app.services.export import _render_engagement_html
+
+ with app.app_context():
+ eng = _make_engagement()
+ html = _render_engagement_html(eng, [])
+ assert "landscape" in html, "HTML must include A4 landscape @page rule for PDF output"
+
+
# ---------------------------------------------------------------------------
# Defense-in-depth: filename header injection
# ---------------------------------------------------------------------------