pptx: fix the review's first ten findings (safety and bugs) - #198
Conversation
The combined PowerPoint review ranked eleven "fix first" items; this
commit does 1-10. Per-request resource limits (11) are deliberately left
out.
Safety
- inline_markdown: the bold span's body could read every "*x" two ways
(lone star, or the opener of a nested italic), so an unclosed span was
exponential — a 67-character title held the GIL for 1.5 s, x2.6 per two
characters. The nested-italic unit now sits in the closer only. An
exhaustive comparison over all strings of up to 10 characters from
{*, a, space} differs from the old grammar in two degenerate inputs,
both now parsed the way the docs describe. Affects Word too.
- admin: with ADMIN_ENABLED and no password the session secret was a hash
of a constant in the source, and the gate admitted any signed session.
The gate is now locked in that state and the secret is random.
- pptx: merge spans were expanded into cell sets before the bounds check
(one 1000x1000 merge on a 2x2 table: ~110 MB). Bounds first, overlap by
rectangle.
- schema: the markdown-heading and blank-position regexes were quadratic
on whitespace runs (4 s / 1.6 s at 20k characters; the first runs on the
event loop). Rewritten linear; exhaustively equal on short inputs.
- Links in Word and PowerPoint become clickable only for http, https,
mailto and tel (inline_markdown.is_safe_link_target). Any other target
keeps its label and is reported once as link_refused.
Bugs
- Table fills wrote schema names ("dark1") into <a:schemeClr>, which only
takes "dk1"; the file could not be read back. Mapped via
scheme_color_val().
- data_labels wrote dLblPos="outEnd" on every chart type; now only bar,
column and pie, the types PowerPoint accepts it on.
- XML-invalid control characters in the footer, a section title or chart
data crashed the deck. Every caller string is cleaned once after
validation (control_chars_removed, info).
- NaN/Infinity in chart values, scatter points, table widths and blank
positions failed deep in the build; now rejected with the field's path.
- A non-ValueError inside a slide builder was re-raised as ValueError
without its traceback and reported as the caller's invalid input. It is
now a RuntimeError chained to the original and logged with exc_info.
Docs: tool descriptions (main.py), powerpoint-slides.md,
markdown-reference.md, SECURITY.md, admin-ui.md, deployment.md, and the
dev pages (powerpoint.md pipeline/invariants/tests, word.md codes,
shared-modules.md, architecture.md, admin-ui.md); AGENTS.md gains the
link rule. Every new test fails on the previous code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012SPJrnAL7ChjLJ78yMxPJm
|
@claude please review this PR for correctness bugs and security regressions. Where to look:
Also flag any behaviour change the PR description doesn't mention. Generated by Claude Code |
|
Claude finished @dvejsada's task in 5m 52s —— View job Review: correctness bugs and security regressions in the pptx safety/bugs fixes
Finding: the refused-link pre-scan silently undercounts refusals for
|
refused_link_targets() skipped every "" on the theory that it is an image. That holds only for a Word line that is nothing but the image; the inline renderers have no image branch, so everywhere else — any PowerPoint field, a Word paragraph with the image mid-line — they draw "!" plus a link. The renderer still refused an unsafe scheme, but the pre-scan never counted it, so no link_refused warning reached the caller. The shared scan now counts that shape. Word removes what really is not a link before scanning: whole-line images (IMAGE_PATTERN, as the block dispatcher matches them) and fenced code blocks (the _render_code_block close rule), in docx_tools.base_docx_tool._inline_text(). Docs: shared-modules.md (the pre-scan) and word.md (link_refused row). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012SPJrnAL7ChjLJ78yMxPJm
|
Fixed the refused-link undercount in b17c98a. Thanks for catching it.
On Generated by Claude Code |
Fixes items 1–10 of the "Fix first" list from the combined PowerPoint review. Item 11, per-request resource limits, is deliberately out of scope.
Safety
inline_markdown._BOLD, used by both Word and PowerPoint). Inside a bold span, every*xcould be read two ways: as a lone star, or as the opener of a nested italic. An unclosed span therefore had exponentially many parses to reject. A 67-character title held the GIL for 1.5 s, and every two more characters multiplied that by about 2.6.{*, a, space}. They differ in two degenerate inputs, and in both the new result matches the documented behaviour.ADMIN_ENABLEDset but noADMIN_PASSWORDorAPI_KEY, the cookie-signing secret was a hash of a constant in the source. The gate accepted any signed session, so a hand-signed cookie opened the admin UI. Now the gate is locked (make_before(locked=True)) and the secret is random per process.schema.py. The markdown heading regex took 4 s at 20k characters and runs on the event loop inside argument validation. The blank-slide position regex took 1.6 s. Both are rewritten to run in linear time and give the same results as before on all short inputs.http,https,mailtoandteltargets become clickable, checked byinline_markdown.is_safe_link_target. Any other link keeps its label as plain text and is reported once aslink_refused(warning).Bugs
dark1,dark2,light1orlight2wrote those names into<a:schemeClr>, but that attribute only acceptsdk1,dk2,lt1andlt2. The file could not be read back. The names are now mapped throughscheme_color_val().data_labels: truewrotedLblPos="outEnd"on all 11 chart types. It is now set only on bar, column and pie, where PowerPoint accepts it.control_chars_removed(info). A vertical tab or form feed becomes a line break.ValueError, without its traceback, and reported as "Invalid presentation input". Only aValueErrorstill takes that path. Anything else becomes aRuntimeErrorchained to the original and logged withexc_info.Behaviour changes
[site](example.com), is no longer clickable. Office resolved it as a file path relative to the document.Tests and docs
test_admin_no_password.py,test_link_schemes.py,test_pptx_control_chars.py,test_pptx_non_finite_numbers.py,test_pptx_internal_errors.py. Regression tests were added to the inline-markdown, table-formatting, schema, client-compat and blank-slide tests.ruffis clean.main.py.docs/powerpoint-slides.md,docs/markdown-reference.md,SECURITY.md,docs/admin-ui.md,docs/deployment.md.powerpoint.md(pipeline, invariants, tests),word.md,shared-modules.md,architecture.md,admin-ui.md.AGENTS.mdhas a new rule for links.Known follow-up
Five other unclosed-marker shapes are still quadratic, at about 2.5 s for 32k characters: italic with spaces, strikethrough, underline, highlight, and a link that is never closed. They grow with field length, so they belong with the per-request length limits (item 11).
🤖 Generated with Claude Code
https://claude.ai/code/session_012SPJrnAL7ChjLJ78yMxPJm
Generated by Claude Code