Skip to content

chantier: September 2026 audit remediation — 53 findings, one PR each #220

Description

@LeadcodeDev

Tracking issue for the September 2026 audit remediation chantier.

An exhaustive multi-agent audit of the five workspace crates produced 88 raw findings. Each was submitted to two independent adversarial reviewers — one on technical correctness, one on repository convention and severity proportionality — with instructions to refute by default. Half were rejected. 46 survive after deduplication.

Full report: https://claude.ai/code/artifact/c5c97b94-ba8d-41c0-9683-9f9123da342b

Ground rules for this chantier

  • Every finding gets its own branch, its own regression test, and its own pull request.
  • The regression test is written before the fix and must fail without it.
  • All pull requests target chantier/audit-2026-09, never main.
  • Frozen contract: ConversionContext::for_viewport(w, h) (crates/rustmotion-core/src/css/taffy_bridge.rs) is the single constructor the validator and the renderer share. Do not reintroduce ConversionContext::default() on a path that knows the real viewport.
  • Frozen contract: the Painter trait signature does not change. The CSS cascade reaches painters by applying inherit_from to the component's own style before paint, not by threading a new parameter through 60 components.

Findings

Workstream A — Animation & peinture (core)

  • RM-01 [High/correctness] The opacity/filter save_layer bounds clip the node's own outset box-shadow and any descendant ink outside its border-box — crates/rustmotion-core/src/engine/paint_pass.rs:390
  • RM-09 [Medium/correctness] Underdamped spring step response uses the wrong sine argument — every spring starts with a jolt instead of from rest — crates/rustmotion-core/src/engine/animator.rs:504
  • RM-10 [Medium/correctness] Linear-gradient angle is rotated 180 degrees from the CSS convention it claims to implement — crates/rustmotion-core/src/engine/paint_pass.rs:1262
  • RM-37 [Low/quality] spring_settle_time re-runs a 2k–20k-sample scan on every spring_value call in the per-frame path — crates/rustmotion-core/src/engine/animator.rs:470

Workstream B — Validateur géométrique

  • RM-05 [Medium/coherence] Geometry validator lays out with the hardcoded 1920×1080 default ConversionContext, not the scenario's real viewport — crates/rustmotion/src/cli/commands/geometry.rs:180
  • RM-15 [Medium/correctness] Static transform lengths resolved with font-size 16 and max(w,h) instead of the node's font-size and per-axis sizes — crates/rustmotion/src/cli/commands/geometry.rs:477
  • RM-16 [Medium/correctness] --fix rewrites the author's file with machine-absolute asset paths — crates/rustmotion/src/cli/commands/validate.rs:146
  • RM-31 [Low/correctness] unwrappable_text_overflow is measured against the border box while the painter is handed the content box — crates/rustmotion/src/cli/commands/geometry.rs:785
  • RM-32 [Low/correctness] white-space: nowrap silences the height-overflow check entirely — crates/rustmotion/src/cli/commands/geometry.rs:844
  • RM-33 [Low/correctness] auto_scroll_disabled_overflow compares a terminal's content-box natural height against its border-box layout height — crates/rustmotion/src/cli/commands/geometry.rs:963
  • RM-40 [Low/quality] component_kind is duplicated verbatim in geometry.rs instead of reusing the canonical helper — crates/rustmotion/src/cli/commands/geometry.rs:1158

Workstream C — Encodage, audio & annulation

  • RM-17 [Medium/correctness] Render failure leaves a valid-but-truncated MP4 at the user's output path; ffmpeg is never killed — crates/rustmotion/src/encode/video/ffmpeg.rs:620
  • RM-18 [Medium/correctness] Infinite loop + unbounded allocation in build_atempo_filter when playback_rate <= 0 — crates/rustmotion/src/encode/video_audio.rs:221
  • RM-34 [Low/correctness] decode_audio_file discards the decoded SignalSpec and trusts container-header / hardcoded fallbacks for rate and channels — crates/rustmotion/src/encode/audio.rs:67
  • RM-35 [Low/correctness] A failure at frame 300 of 900 leaves a truncated, playable MP4/GIF at the output path — crates/rustmotion/src/encode/video/ffmpeg.rs:601
  • RM-44 [Low/security] Predictable temp paths in the world-writable temp dir: symlink overwrite and audio-cache poisoning — crates/rustmotion/src/encode/video/ffmpeg.rs:452

Workstream D — Médias, caches & CLI de rendu

  • RM-20 [Medium/quality] GIF decode is a cache stampede inside the rayon batch: N workers each decode the whole GIF into RAM simultaneously — crates/rustmotion-components/src/gif.rs:176
  • RM-21 [Medium/quality] preextract_video_frames buffers a whole video's raw RGBA in memory twice, then caches it forever — crates/rustmotion/src/engine/preload.rs:238
  • RM-23 [Medium/security] GIF decoder allocates width×height×4 per frame from an unvalidated header, with no dimension or frame-count cap — crates/rustmotion-components/src/gif.rs:129
  • RM-25 [Low/coherence] --watch incremental renders never clear the asset caches, so edited assets are stale and memory only grows — crates/rustmotion/src/cli/commands/render.rs:452
  • RM-30 [Low/correctness] rustmotion batch --name-template mangles every non-ASCII character in the template into mojibake — crates/rustmotion/src/cli/commands/batch.rs:74

Workstream E — Fonds animés & scène

  • RM-19 [Medium/correctness] Heropattern scroll wrap uses the pattern's width as the period on BOTH axes, snapping 41 of the 87 patterns every wrap — crates/rustmotion/src/engine/render/background.rs:806
  • RM-22 [Medium/quality] Heropattern tile is rasterized at 1x and then magnified by a nearest-neighbour shader, so scale produces a blocky pattern instead of a crisp vector — crates/rustmotion/src/engine/render/background.rs:605
  • RM-36 [Low/correctness] Two paint paths: paint_decorative_fullscreen bypasses the box tree and has drifted from the dispatcher it mirrors — crates/rustmotion/src/engine/render/scene.rs:595
  • RM-45 [Low/security] Scenario-supplied heropattern colour is spliced unescaped into generated SVG source parsed by usvg with the filesystem-reading default href resolver — crates/rustmotion/src/engine/render/background.rs:595

Workstream F — Crate rustmotion-html

  • RM-03 [Medium/coherence] and read a hardcoded attribute allowlist; every other attribute is dropped with no error, and --strict-attrs cannot see it — crates/rustmotion-html/src/scene.rs:20
  • RM-04 [Medium/coherence] CSS shorthand values transpile to strings the core length parser cannot read, silently resolving to 0px — crates/rustmotion-html/src/style.rs:46
  • RM-11 [Medium/correctness] Text-tag children bypass every guard: <script>/<style> source gets painted, //<rm-*> vanish silently — crates/rustmotion-html/src/element.rs:95
  • RM-12 [Medium/correctness] Studio HTML write-back silently deletes everything outside in the author's file — crates/rustmotion-html/src/lib.rs:289

Workstream G — Studio (Dioxus)

  • RM-13 [Medium/correctness] Debounced write replays a stale whole-document snapshot, silently clobbering the agent's on-disk edit — crates/rustmotion-studio/src/editor/inspector.rs:2119
  • RM-14 [Medium/correctness] HTML scenarios: every coalesced edit but the last is lost on disk while the preview keeps showing them all — crates/rustmotion-studio/src/editor/inspector.rs:2166
  • RM-28 [Low/correctness] render_frame_deep converts a render panic into an empty JPEG that is cached and served as HTTP 200, defeating the failure ledger — crates/rustmotion-studio/src/editor/frames.rs:42
  • RM-29 [Low/correctness] Undo/redo does not cancel the pending debounced write; Cmd+Z inside the 250 ms window is silently reverted and memory/disk diverge — crates/rustmotion-studio/src/scenario/history.rs:147
  • RM-38 [Low/quality] rustmotion-studio has no integration tests at all, and inspector.rs — the entire file-write pipeline — has exactly one test, on a pure layout helper — crates/rustmotion-studio/src/editor/inspector.rs:2404
  • RM-39 [Low/quality] Every optimistic edit re-mixes the whole audio track on a fresh, uncancellable thread — crates/rustmotion-studio/src/editor/playback.rs:115

Workstream H — Entrée non fiable : expand, include, assets réseau

  • RM-41 [Low/security] No timeout on any HTTP call — ureq 3.x defaults every timeout to None, so a hung host hangs the render forever — crates/rustmotion-core/src/engine/renderer/assets.rs:141
  • RM-42 [Low/security] Scenario-supplied src is handed verbatim to ffmpeg -i with no protocol allowlist — crates/rustmotion-core/src/engine/renderer/assets.rs:222
  • RM-43 [Low/security] for-each expansion has a depth ceiling but no node budget — nesting is multiplicative — crates/rustmotion-core/src/expand.rs:157
  • RM-46 [Low/security] Untrusted scenario can drive arbitrary outbound HTTP from include — no allowlist, no opt-out — crates/rustmotion/src/include.rs:220

Workstream I — Composants & cascade CSS

  • RM-06 [Medium/correctness] box_builder default sizes resolve font-size context-free — a relative unit collapses marquee/list to a 0px-high box — crates/rustmotion-components/src/box_builder.rs:1716
  • RM-07 [Medium/correctness] The CSS cascade is computed but no painter can ever read it — inherit_from is dead for rendering — crates/rustmotion-components/src/legacy_dispatch.rs:68
  • RM-08 [Medium/correctness] TableIntrinsic measures content-fitted column widths; the painter splits the width evenly — the doc comment claims they match — crates/rustmotion-components/src/table.rs:116

Workstream J — Layout & CSS core

  • RM-26 [Low/correctness] em on layout properties resolves against a constant 16px, never the element's font-size — crates/rustmotion-core/src/css/taffy_bridge.rs:292
  • RM-27 [Low/correctness] measure_fn gets border-box known and content-box available, forwarded verbatim to IntrinsicMeasure — crates/rustmotion-core/src/engine/layout_pass.rs:97

Workstream K — Documentation, schéma & CI

  • RM-02 [Medium/coherence] README.md's whole Architecture section describes a codebase that no longer exists — and HEAD just made it the crates.io landing page for all four published crates — README.md:2034
  • RM-24 [Low/coherence] Shipped skill doc still tells generators that 23 components render nothing — contradicted by box_builder and its own test — crates/rustmotion/skills/rules/card-flex-layout.md:46

Workstream K (supplement) — established by direct verification

  • RM-47 [Low/documentation] README and the published crate description both say "51 components"; the Component enum has 60 — README.md + crates/rustmotion-components/Cargo.toml
  • RM-48 [Medium/supply-chain] CI has no dependency-vulnerability gate; 12 RUSTSEC advisories went undetected — .github/workflows/ci.yaml
  • RM-49 [Low/supply-chain] GitHub Actions are pinned by mutable tag, including on the workflow that holds CARGO_REGISTRY_TOKEN — .github/workflows/publish.yaml
  • RM-50 [Medium/correctness] VideoConfig.codec parses and does nothing: "codec": "prores" silently renders H.264 — crates/rustmotion-core/src/schema/scenario.rs
  • RM-51 [Low/correctness] MotionBlurConfig.intensity and VariableDefinition.var_type are inert — crates/rustmotion-core/src/schema/video.rs
  • RM-52 [Medium/quality] No guard stops a new schema field from being inert — crates/rustmotion-core/src/schema/
  • RM-53 [Low/documentation] There is no Limitations section anywhere; every non-goal is discovered by reading source — docs/non-goals.md

Severity breakdown

High Medium Low Total
1 25 27 53

Known overlap

Issues #155, #157 and #162 describe defects this chantier also fixes. They were deliberately left out of scope of this tracking issue and will need closing by hand once the chantier lands.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions