51 Commits
Author SHA1 Message Date
Dylan M. TaylorandClaude Sonnet 5.5 a7910edd64 Review fixes: keep later CAD layouts, ordered-stop gradient sampling, skip full-stroke previews, cache shift line
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 17:19:59 +07:00
Dylan M. TaylorandClaude Opus 5.5 bc56851904 Security: bring Read into scope for the bounded link hash
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:19:59 +07:00
Dylan M. TaylorandClaude Opus 5.5 1febfd1d31 Security: render and PDF export bounds for symbol expansion, dotted guides, blending groups and pinned images
CodeRabbit findings #32, #38, #41, #44, #102:

- A frame (and a PDF export) draws symbol instances holding at most 16M objects together
  (`vectorcraft_doc::MAX_SYMBOL_NODES`): nested instances multiplied past the depth limits.
- A dotted guide whose control polygon would make more than 100,000 dashes is drawn solid.
- Groups blending with their backdrop, drawn twice each, are drawn isolated past 4 nested.
- An image is decoded only while it and the images paints hold fit twice the decode allowance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:19:59 +07:00
Dylan M. TaylorandClaude Opus 5.5 20bba9ebae Security: linear work for shortcut review, gradients, recovery names, include cycles, batch references, DOCX style ids, CAD layouts and long strokes
CodeRabbit findings #2, #16, #31, #103, #114, #124, #162, #179, #182, #183, #185:

- Shortcut import review: example names for each code in one pass over the rows.
- Gradients: a midpoint stop is mixed from its two stops; freeform recolor samples its
  increasing positions with one advancing search (`Gradient::sample_increasing_with`).
- Recovery: image entry names are looked up by address, and dead ones swept when the map
  doubles, not on every image.
- Script checker: include cycles from strongly connected components (iterative Tarjan), not
  a reachability row per file.
- Batch step references copy at most 256 MB of values for one step.
- DOCX: style ids over 256 bytes are ignored where defined and where used.
- DXF: layouts keep at most 4 MB of names; a hatch's loops count against the node limit.
- Freehand and Liquify: past 1000 samples a drag previews every n/1000 samples, strokes keep
  at most 100,000 samples, and the stroke kept is previewed in full before it is committed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:19:59 +07:00
Dylan M. TaylorandClaude Opus 5.5 03d2f0e83c Security: file authority for stored paths, linked files, placed documents, font folders, recovery areas and atomic saves
CodeRabbit findings #7, #72, #92, #94, #95, #99, #100, #140, #158, #174:

- Link search hashes at most the size it looked at (a growing file no longer matches).
- Resolving links reads, decodes and previews each linked file once, shared by every key.
- Placed documents read inside one another resolve links at most 8 deep, never one already
  being read.
- A data set's image path and the placed-document rereader follow stored paths through
  `document_path` (no other computer's share).
- The Links panel looks at a linked file within the roots of the automation caller that
  opened the document.
- A fonts folder set by a confined caller is scanned without following links out of it.
- Recovery refuses an area folder that is a link or another account's.
- Atomic saves keep the replaced file's group and Linux access ACL, or make the replacement
  owner-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:19:59 +07:00
Dylan M. TaylorandClaude Opus 5.5 e2a870e602 Security: bounded flattening, plug-in budgets and caches, loaded textures, Revolve paint copies, color book names and ICC names
CodeRabbit findings #4, #34, #46, #51, #77, #101, #170, #171, #188:

- `vectorcraft_geom::flatten`: kurbo's flatten with each curve's tolerance raised so it makes
  at most ~4096 segments (non-finite curves become a line); the canvas outline and
  variable-width strokes use it.
- Plug-ins: a heavy filter's budget is the host's `Limits::heavy_fuel`/`heavy_wall_time_ms`,
  never more; a start function runs on one fuel slice; the effect cache holds at most ~1M
  anchors of inputs and results, and doesn't keep a result past a quarter of that.
- Loaded textures are cached by their PNG (base64 spacing doesn't matter), and live textures
  hold at most 512 MB together.
- Revolve draws the source path when the faces' paint copies would pass 256 MB.
- Color books: names with prefix and suffix take at most 64 MB.
- ICC: localized-name (`mluc`) records over 1 MB together are refused before moxcms copies them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:19:54 +07:00
Dylan M. TaylorandClaude Opus 5.5 b12ee928f3 Security: document model generation budgets for live art, clipping runs, placed documents, graph masks, path reading and native entries
CodeRabbit findings #58, #75, #82, #113, #159, #175, #187:

- Live evaluation (expansion, envelope mapping and everything inside) makes at most 4M
  objects (`MAX_GENERATED`); deep expansion counts what each live object makes, and envelope
  image pieces count before they are cut.
- Clipping runs copy at most 8M clip path elements together; past it runs are plain groups.
- Exports read at most 256 placed documents; the rest show their previews.
- Graph design and marker budgets count opacity-mask art (`Node::count_with_masks`).
- Native files read at most 32M anchors (4M on wasm) of paths; the web build allows 1 GiB of
  entries after the JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:19:16 +07:00
Dylan M. Taylor 50d976e1e6 style: keep paint.rs spot-colour chain under rustfmt's max width (#3940)
* style: wrap long spot-colour fallback chain to satisfy rustfmt

* fix(brush): replace negated partial-ord comparison (clippy 1.99)
2026-10-24 02:03:48 -04:00
Dylan M. Taylor 4c82749b08 Security: Affinity import and brush expansion bounds (#3887)
* Audit: Affinity import (spot colour chain, zero-width records, corner radii)

* Audit: Affinity layer names are cut at 1024 characters

* Audit: brush expansion bounds (masks, library parsing, flattening), pressure profile lookup

* Brush: reach a node's mask through its extras
2026-10-23 22:43:42 -04:00
Dylan M. Taylor 6c05c10fff chore(text): use is_some_and instead of unnecessary map_or (clippy) (#3929) 2026-10-23 22:39:51 -04:00
Dylan M. Taylor 76467a1a9b ci: move release workflow write permission to job level (SonarCloud S8233) (#3928) 2026-10-23 22:37:12 -04:00
Dylan M. Taylor d1288e3019 Security: text layout, shaping, font name and embedding bounds (#3880)
* Audit: text layout, shaping, fonts and embedding bounds

* Audit: text bitmap chunk walk without overflow

* Test: PNG metadata chunk walk

* Security: keep the word minimum in wrap regions; allow uncompressed iTXt text

A wrap shape can narrow the first baseline of a rectangular region:
place_at_least then relaxed the minimum to 0 (the region still counted as
"all lines equally wide") and picked the narrow span, breaking a word on
that baseline even though a lower one had room for it whole. Treat the
width as constant only when the region has no wrap shapes.

Also, PNG iTXt chunks may be stored uncompressed: only genuinely
compressed text (flag 1, conservatively anything that does not parse)
counts as compressed metadata now.
2026-10-23 21:53:51 -04:00
Dylan M. Taylor dad86afeee Security: private temp paths for the signing certificate and test output; web font licences stay inside the checkout (#3871)
* Audit: private temp paths for the signing certificate and testkit output; confine web font licences to the checkout

* testkit: refuse a pre-made private temp directory this user cannot write to
2026-10-23 21:52:17 -04:00
Dylan M. Taylor 2541fd8efd Audit: Image Trace bounds (mosaic duplicate tiles and short buffers, overflow-safe raster length, grouping and centerline work caps) (#3878) 2026-10-23 21:52:11 -04:00
Dylan M. Taylor 42f8b513a2 Fix clipboard read-policy bypass and lock-held file checks
- copied_file: check the read policy on the real path when it is
  valid UTF-8; skip non-UTF-8 paths while automation roots are
  active instead of checking a lossy name (path traversal via
  symlink, CodeRabbit #3872 thread 1).
- full_bytes_cached: collect matching cache candidates under the
  FULL lock, then run the read checks after releasing it, so
  filesystem walks no longer stall other FULL users (thread 2).
2026-10-24 05:16:20 +07:00
Dylan M. Taylor cd043f650b Tests: the recovery folder needs read access too; the Place queue check runs for any active tool 2026-10-24 05:16:20 +07:00
Dylan M. Taylor c99b77060d Test: the Place queue is checked against the read roots 2026-10-24 05:16:20 +07:00
Dylan M. Taylor e95e8418ed Audit: automation roots follow file.showPackage, the Place queue, headless file methods, clipboard files, placed-document workers and caches, folder searches and the recovery folder 2026-10-24 05:16:20 +07:00
Dylan M. Taylor 5020dc0585 PDF export: past the retry cap, one last run leaves out every image instead of failing 2026-10-24 04:49:07 +07:00
Dylan M. Taylor 52ceefe906 Audit: Targa and palette encoders check pixel buffer sizes 2026-10-24 04:49:07 +07:00
Dylan M. Taylor b59235e762 Test: sliding-window maximum 2026-10-24 04:49:07 +07:00
Dylan M. Taylor 4b8e69981e Audit: PDF import and export bounds 2026-10-24 04:49:07 +07:00
Dylan M. Taylor 9843d5f1ab Audit: export names, linked-file ids and MCP prompt arguments in one pass 2026-10-24 04:49:07 +07:00
Dylan M. Taylor e3223a419d Audit: Scribble rows counted, Outer Glow dilation in linear time, CSS export names in a set 2026-10-24 04:49:07 +07:00
Dylan M. Taylor 1bb904c147 Audit: Perspective Grid lines counted not summed; Liquify subdivision stays under its anchor ceiling 2026-10-24 04:13:37 +07:00
Dylan M. Taylor aa160b969b Audit: path edit work bounds (redundant points in one pass, simplify and join budgets) 2026-10-24 04:13:36 +07:00
Dylan M. Taylor f4a30c1a94 Audit: geometry length measurement refuses coordinates the integrator cannot handle 2026-10-24 04:13:36 +07:00
Dylan M. Taylor 98d11ad66b Audit: cap SVG entity expansion, filter layer stacks and export id/companion probing; share compound-path appearances; escape-proof font feature tags 2026-10-24 03:52:24 +07:00
Dylan M. Taylor c4fba30b9c ci(sonar): don't cancel superseded runs on main, so coverage is published (#3881) 2026-10-23 16:51:11 -04:00
Dylan M. Taylor c6e6c23b99 packaging: use [[ for bash conditional tests (SonarCloud S7688) (#3853)
SonarCloud flags every bare [ conditional in bash scripts under packaging/ as a reliability smell (rule S7688). Convert the flagged [ ... ] tests to [[ ... ]]: safer word-splitting and no glob surprises, same semantics for these string and file tests.
2026-10-23 13:21:42 -04:00
Dylan M. TaylorDylan M. Taylorcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
125df6d8ec G9: Preserve supplied passwords when confirming the Save PDF dialog (#3836)
Co-authored-by: Dylan M. Taylor <277927+dylanmtaylor@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-10-23 10:15:23 -04:00
Dylan M. Taylor f64db69b53 Use chrono instead of hand-rolled UTC conversion (#3387)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled Howard Hinnant civil/days-from-civil copies and the
leap-year/days-in-month tables with chrono as a workspace dependency:
- apps/vectorcraft/src/logging.rs: timestamp (log line time)
- crates/doc/src/metadata.rs: civil (Unix seconds -> [y,m,d,h,mi,s]) and iso8601
- crates/pdf/src/file_info.rs: unix (date -> seconds) and in_month, used by the
  PDF/XMP date parsers (pdf_date, xmp_date)
Output formats and parsing semantics are unchanged and pinned by the existing
tests (PDF D:... and XMP dates, offsets, leap-year rejection, year clamp 0-9999,
pre-epoch clamp). chrono uses only std, so wasm stays safe. AGENTS.md/CLAUDE.md
now say time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed

chrono's From<SystemTime> unwraps, so a clock past year 262143 panicked
inside the logger. timestamp now uses the fallible DateTime::from_timestamp
and reads such a clock (or a pre-epoch one) as the epoch instead. Years past
9999 are written with {:04} like before, because %Y would add a '+'. Tests
cover the out-of-range and 5-digit-year cases.

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* time: preserve old out-of-range date conversion (no epoch fallback)

`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any instant
past chrono's year range to 1970-01-01. Restore the old conversion domain with
a cycle-based helper that routes the calendar through chrono (the calendar
repeats every 400 years = 146_097 days, so month/day/time come from chrono and
only the year is carried in i128, keeping every i64 exact).

- apps/vectorcraft/src/logging.rs: `timestamp` via `utc_parts`; keep the
  pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch.
- crates/doc/src/metadata.rs: `civil` cycle-based, keeping its clamp EXACTLY
  as the old code had it: the input is clamped to 0000-01-01..9999-12-31 first
  and the year is clamped to 0..=9999 afterwards. (With the input clamp the old
  `unwrap_or_default()` was already unreachable; this drops it and makes the
  conversion explicit, with identical output.)

Pinned old outputs in tests:
  logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch),
          10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z
  metadata civil(i64::MIN)[0]=0, civil(i64::MAX)[0]=9999;
          iso8601(i64::MIN)=0000-01-01T00:00:00Z,
          iso8601(i64::MAX)=9999-12-31T23:59:59Z (year clamp, as before)

* Don't build a year-318857 SystemTime unchecked in the logging test

Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-21 19:36:32 -04:00
Dylan M. Taylor 2b8a11876a security: enforce HTTPS on curl downloads (SonarCloud S6506) (#3475)
Add --proto '=https' to curl invocations so redirects cannot downgrade to
plaintext HTTP. Resolves SonarCloud S6506 (shell/docker/githubactions).
2026-10-21 19:35:52 -04:00
Dylan M. Taylor 0f7156169b ci: add sonar-project.properties to use CI-based analysis
Presence of this file marks the project ineligible for SonarCloud Automatic
Analysis, resolving the CI-vs-automatic-analysis conflict.
2026-10-21 15:56:13 -04:00
Dylan M. Taylor a4c21343bb ci: add SonarCloud scan via shared reusable workflow
Calls storytold/craft-common-actions/.github/workflows/sonarcloud.yml@main
with secrets: inherit. Adds SonarCloud badge to README where missing and
removes the redundant sonar-project.properties (config now passed by the
reusable workflow).
2026-10-21 15:47:07 -04:00
Dylan M. Taylor cb0a7d9b31 feat(window): adopt shared craft-window for Linux decorations (depends on craft-libs#217) (#3366)
* feat(window): adopt shared craft-window for Linux decorations

Decide the window frame on Linux/BSD via the shared craft-window crate
(storytold/craft-libs) instead of VectorCraft's unconditional
`CUSTOM_TITLEBAR = !macos`, so the app matches the rest of the Craft
suite per desktop.

- Probe `craft_window::Choice::probe(Prefix::new("VECTORCRAFT"))` once at
  startup; custom titlebar is on when the frame is `Frame::App(_)` and the
  user has not opted into the system title bar.
- Preserve the `systemTitleBar` preference: opting in forces the native
  (desktop) frame back on, as before.
- Keep macOS and Windows behavior exactly as today (decided by
  `CUSTOM_TITLEBAR`, not the probe): macOS integrated title strip,
  Windows app-drawn caption. craft-window returns `Frame::Desktop` for
  both; VectorCraft's overdraw is its own choice.
- The only behavioral change: KDE/Wayland now keeps KWin's native
  decorations (Frame::Desktop) instead of always drawing a custom
  titlebar; GNOME still draws its own (Frame::App).
- `VECTORCRAFT_TITLEBAR` override and the `systemTitleBar` pref both still
  work (the env var is craft-window's override for the `VECTORCRAFT` prefix).

NOT MERGEABLE YET: craft-window is open PR storytold/craft-libs#217; the
dep points at the PR branch and must be repointed to a merged `rev`
before this can merge.

* fix(window): native frame when X11 is forced; pin craft-window to merged rev

- When Preferences > Linux Display Server > X11 forces the window onto
  XWayland, treat the frame as the desktop's (native X11 decorations)
  rather than letting a GNOME/Wayland probe pick Frame::App and draw our
  own title bar on what is really an X11 window. An explicit
  VECTORCRAFT_TITLEBAR override still wins (checked via Choice::overridden).
- Repoint craft-window from the (now-merged) feature branch to the squash
  commit on storytold/craft-libs (craft-libs#217); regenerate Cargo.lock.
- The WAYLAND_SOCKET concern raised in review is handled in the merged
  craft-window crate itself (the probe skips when WAYLAND_SOCKET is set).

Verified: cargo build + test -p vectorcraft (131 tests) pass against the
merged dep; windows7 (--no-default-features) still compiles; rustfmt clean.

* fix(window): only force native frame when X11 libs are actually present

Follow-up to review: the XWayland title-bar override now also checks
xwayland::x11_libraries_found(), matching the launch path's own condition.
If X11 is requested but the libraries are missing, startup stays on
Wayland, so the frame decision must keep the probe's app-drawn result
rather than flipping to native. Verified: tests pass, rustfmt clean.
2026-10-21 07:51:10 -04:00
Dylan M. Taylor bc1ae08252 Merge pull request #3224 from Matoo125/fix/emf-pattern-tile-budget
EMF export: pattern tiles stop at 250,000 in all
2026-10-20 12:17:30 -04:00
Dylan M. Taylor cdc4ecc67e Merge pull request #3223 from Matoo125/fix/nested-repeat-budget
Repeat: nested Repeats stop at 250,000 instances in all
2026-10-20 12:17:16 -04:00
Dylan M. Taylor 0c42504069 Merge pull request #3194 from Matoo125/fix/mesh-remove-lines-malformed
Gradient meshes: deleting mesh lines leaves a malformed mesh as it is
2026-10-20 12:17:01 -04:00
Dylan M. Taylor 31eafe3f8d Merge pull request #3193 from Matoo125/fix/mesh-point-count-overflow
Gradient meshes: a grid whose point count overflows is malformed
2026-10-20 12:16:47 -04:00
Dylan M. Taylor ae663f53aa Merge pull request #2923 from dylanmtaylor/docs/add-sonar-coderabbit-badges
docs: add SonarCloud and CodeRabbit badges to README
2026-10-20 11:31:29 -04:00
Dylan M. Taylor 923a3ddc09 docs: put SonarCloud and CodeRabbit badges on their own line 2026-10-18 14:47:47 -04:00
Dylan M. Taylor a466625c89 ci: configure Dependabot for Cargo and GitHub Actions
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
2026-10-18 12:31:54 -06:00
Dylan M. Taylor 0d2eeed995 docs: add SonarCloud and CodeRabbit badges and standardize to flat-square 2026-10-18 11:05:48 -04:00
Dylan M. Taylor fe16f852db docs: clarify readiness audience and tracked features baseline 2026-10-18 08:28:33 -04:00
Dylan M. Taylor 090659b78b docs: drop references to competing commercial software 2026-10-18 07:52:58 -04:00
Dylan M. Taylor e9edc49977 ci: switch to shared nix workflow from craft-common-actions 2026-10-16 14:57:18 +07:00
Dylan M. Taylor f9092edfe3 ci: standardize FreeBSD workflow on shared FreeBSD 15.1 action 2026-10-16 14:57:17 +07:00
Dylan M. Taylor f08f3619e3 ci: refactor workflows to use storytold/craft-common-actions and add nix check 2026-10-15 21:38:50 -04:00
Dylan M. Taylor c28a9b5dd2 ci: upgrade actions to Node 24 to fix deprecation warnings 2026-10-11 21:42:27 -04:00
Dylan M. Taylor 4f0809953d chore: bump github actions to fix Node 20 deprecation warnings 2026-10-09 20:04:42 -04:00