From 0cca56984fcb861117c8aed723486ec399464e01 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Sat, 24 Oct 2026 04:39:12 -0400 Subject: [PATCH] fix(security): bound decoration steps, text-on-path elements, and rename expansions (#1257) --- crates/engine/src/rename.rs | 6 ++++++ crates/text/src/layout.rs | 2 +- crates/text/src/onpath.rs | 9 ++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/rename.rs b/crates/engine/src/rename.rs index 8d44f5ab..b4952475 100644 --- a/crates/engine/src/rename.rs +++ b/crates/engine/src/rename.rs @@ -81,6 +81,9 @@ impl BatchRename { out.push_str(name.get(last..m.start()).unwrap_or_default()); let (before, after) = (name.get(..m.start()).unwrap_or_default(), name.get(m.end()..).unwrap_or_default()); expand(&self.replace, &Ctx { matched: m.as_str(), before, after, groups: Some(&caps), up, down }, &mut out); + if out.len() >= MAX_NAME * 4 { + break; + } last = m.end(); } out.push_str(name.get(last..).unwrap_or_default()); @@ -110,6 +113,9 @@ impl BatchRename { fn expand(template: &str, c: &Ctx, out: &mut String) { let mut it = template.chars().peekable(); while let Some(ch) = it.next() { + if out.len() >= MAX_NAME * 4 { + break; + } if ch != '$' { out.push(ch); continue; diff --git a/crates/text/src/layout.rs b/crates/text/src/layout.rs index f844b571..54bee7e3 100644 --- a/crates/text/src/layout.rs +++ b/crates/text/src/layout.rs @@ -100,7 +100,7 @@ pub struct Decoration { } /// Most dots or wave samples drawn for one decoration. -const MAX_DECO_STEPS: usize = 20_000; +const MAX_DECO_STEPS: usize = 2_500; impl Decoration { /// The line's outline: a bar, a row of dots or a wave, `thickness` thick. diff --git a/crates/text/src/onpath.rs b/crates/text/src/onpath.rs index 2b506237..49378504 100644 --- a/crates/text/src/onpath.rs +++ b/crates/text/src/onpath.rs @@ -23,7 +23,14 @@ impl OnPath { /// Text on `tp` whose layout x = 0 sits `shift` before the start point and whose layout y /// `baseline` sits on the path. `None` for an empty or unmeasurable path. pub fn new(tp: &TextPath, shift: f64, baseline: f64) -> Option { - let mut line = polylines(&tp.path())?.into_iter().next()?; + let p = tp.path(); + if p.elements().len() > 20_000 { + return None; + } + let mut line = polylines(&p)?.into_iter().next()?; + if line.points.len() > 40_000 { + return None; + } // A subpath ending where it starts (an ellipse) goes round like a closed one. line.closed |= matches!((line.points.first(), line.points.last()), (Some(a), Some(b)) if (*a - *b).hypot() < 1e-6); Some(Self { origin: tp.start() * line.length() - shift, line, flip: tp.flip, baseline })