23447 Commits

Author SHA1 Message Date
Andrey Antukh
87c51090b1 Merge remote-tracking branch 'origin/staging' into develop 2026-08-26 20:14:51 +02:00
Andrey Antukh
6e173a02fb 📚 Split backend testing memory and link from testing skill
Extract the backend Testing section from backend/core into a dedicated
backend/testing memory, following the pattern of common, frontend, and
exporter. Update the testing skill and root testing memory to point at
the new location, and add exporter/testing to the skill's required
reading list.

AI-assisted-by: deepseek-v4-flash
2026-08-26 18:11:40 +00:00
Andrey Antukh
4be749d45f Add minor improvements to scripts/gh.py 2026-08-26 19:19:22 +02:00
Pablo Alba
e1a2d0b932
🐛 Fix nitrate sso failure message (#11214) 2026-08-26 17:24:59 +02:00
Andrey Antukh
33e39bc7ed 🔧 Fix backend format check script in CI
The backend format check was using 'check-fmt' instead of 'check-fmt:clj',
causing CI to always fail on the fmt step.

Closes #11358

AI-assisted-by: longcat-2.0
2026-08-26 14:08:41 +02:00
Andrey Antukh
33f13f9bfd 🐛 Fix test formatting for get-team-invitation-token
AI-assisted-by: longcat-2.0
2026-08-26 14:08:41 +02:00
Andrey Antukh
4adfa5d2f2 🐛 Require edition permissions for get-team-invitation-token
The handler previously allowed any team member (including viewers) to
generate invitation tokens. Now requires at least edition-level
permissions (can-edit, admin, or owner).

Closes #11358

AI-assisted-by: longcat-2.0
2026-08-26 14:08:41 +02:00
Andrey Antukh
7079d33ae1
🐛 Enforce ownership check on tempfile bucket access (#11270)
* 🐛 Enforce ownership check on tempfile bucket access

The upload-tempfile RPC stores profile-id with tempfile objects, but
objects-handler never verified the requester was the owner. Any
authenticated user who knew the UUID could access the tempfile.

Add ownership check: tempfile bucket now requires the request's
profile-id to match the stored profile-id. Returns 404 on mismatch
(not 403) to avoid leaking object existence.

Legacy tempfiles without stored profile-id remain accessible to any
authenticated user for backward compatibility.

Closes #11269

AI-assisted-by: qwen3.7-plus

* ♻️ Extract tempfile-bucket constant and fix docstring indentation

Extract the 'tempfile' bucket string literal into a named constant
(sto/tempfile-bucket) to prevent typos and make future bucket renames
trivial. Updated 9 occurrences across 7 files.

Also fixed minor docstring indentation inconsistency in authenticated?
function.

AI-assisted-by: qwen3.7-plus

* ♻️ Refactor process-bucket! and authenticated? helpers

Replace case with cond in process-bucket! to properly resolve
sto/tempfile-bucket var from another namespace (case does not
evaluate qualified vars at compile time).

Redefine authenticated? in terms of request-profile-id to remove
duplicated lookup logic.

Closes #11269

AI-assisted-by: mimo-v2.5-pro
2026-08-26 13:05:36 +02:00
Andrey Antukh
b33213787e
🐛 Add accumulated storage byte quota for media uploads (#11038)
* 🐛 Add accumulated storage byte quota for media uploads

Add media-storage-bytes-per-team quote to prevent persistent DoS via
repeated uploads. The quota sums storage_object sizes from both
file_media_object (media + thumbnails) and team_font_variant
(otf/ttf/woff1/woff2). Default limit is 20 GiB per team, configurable
via PENPOT_QUOTES_MEDIA_STORAGE_BYTES_PER_TEAM.

The check is invoked in upload-file-media-object before processing,
looking up the team-id via file -> project -> team_id join.

AI-assisted-by: mimo-v2.5-pro

* 🐛 Fix deduplicated storage overcounting in media-storage-bytes-per-team quote

The SQL query sql:get-media-storage-bytes-per-team used UNION ALL
across six SELECT branches that each produce a so_id reference.
When deduplication causes multiple file_media_object or
team_font_variant rows to point at the same storage_object, UNION ALL
counts that objects size once per reference — inflating "used bytes"
and causing false :max-quote-reached rejections.

Change all five UNION ALL to UNION so that duplicate so_id values are
collapsed before the JOIN storage_object / SUM(so.size).

Add a test (media-storage-bytes-quote-deduped) that creates one
storage_object referenced by two file_media_object rows and asserts
the computed usage reflects the deduplicated physical size, not 2x.

AI-assisted-by: mimo-v2.5-pro
2026-08-26 08:33:20 +02:00
Alejandro Alonso
7419bc7007
🐛 Evict multi-scale tile cache on shape edits (#11337)
those textures across zoom for progressive previews, and invalidate
by old∪new document coverage so rotate/move edits do not leave
stale fragments on zoom-out.
2026-08-26 08:25:23 +02:00
María Valderrama
5e1ced03ea
🐛 Fix missing warning when moving a team (#11357) 2026-08-26 08:08:40 +02:00
Andrey Antukh
d655aa9c63 Merge remote-tracking branch 'origin/staging' into develop 2026-08-25 20:46:47 +02:00
Luis de Dios
44dfc04300
🐛 Fix font selector dropdown takes noticeably long to open when changing font (#11073)
* 🐛 Fix font selector dropdown takes noticeably long to open when changing font

* ♻️ Refactor detach-preview-sprite! to use atomic swap

Use idiomatic atomic swap! update instead of non-atomic
read-then-write pattern. The new implementation computes
the decremented refs inside swap! and only removes the
node when the result reaches zero.

AI-assisted-by: mimo-v2.5-pro

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-08-25 16:55:34 +02:00
Andrey Antukh
85a68ea3b2 🐛 Normalize toast HTML prop to boolean
Ensure toast components always receive a boolean `is-html` prop so nil or
truthy notification values do not violate the Rumext schema.

AI-assisted-by: gpt-5.6-luna
2026-08-25 13:47:37 +00:00
0xTHAC0
0a3352927d
🐛 Hide register link in viewer login modal when registration is disabled (#11199)
The viewer's login-register modal always showed the Register link
regardless of the disable-registration server flag. The main login
page already gates this link on (contains? cf/flags :registration);
apply the same guard to the viewer modal to make the flag consistent
across all entry points.
2026-08-25 15:10:09 +02:00
Andrey Antukh
b79680eeb7
🐛 Fix asset 404 for unauthenticated share-link viewers (#11342)
PR #11036 added a per-request permission check to the file-media asset
endpoints (/assets/by-file-media-id/:id and the /thumbnail variant) using
bfc/get-file-permissions. Anonymous share-link viewers were then rejected
because they have neither a session nor an access token, the asset URL
carries no share context, and the 2-arg get-file-permissions short-circuits
to nil when profile-id is nil.

Make the asset endpoints share-link aware, mirroring how get-view-only-bundle
already authorizes the same scenario: read the share-id from the query
string, validate it as a UUID, and call the 3-arg perms/get-file-read-permissions
(which chains the existing 2-arg bfc lookup, the bfc share-link fallback,
and the Nitrate org-owner fallback). On the frontend, extend
cf/resolve-file-media with an optional share-id arg and pass it from the
WASM viewer render path using the share-id already present in
[:viewer-local :share-id]. Non-viewer call sites (workspace, clipboard,
code-gen) keep the original URL shape because the new arg defaults to nil.

Closes #11338

AI-assisted-by: minimax-m3
2026-08-25 13:10:55 +02:00
Alejandro Alonso
57c0e81616
Present viewport before interest and clamp paint to atlas (#11313)
Present visible tiles via ViewportReady so zoom settle turns sharp
without waiting on the interest ring, and paint at atlas slot size so
DPR 2 does not rasterize 1024 only to downscale into 512 slots.
2026-08-25 12:56:07 +02:00
Andrey Antukh
81c3b3cd56
📎 Update copyright name on file header (#11346) 2026-08-25 11:55:10 +02:00
Andrey Antukh
a83a5e2eff Merge remote-tracking branch 'origin/staging' into develop 2026-08-25 09:41:31 +02:00
Luis de Dios
c31236947c
🐛 Fix skipped-tokens warning details show raw HTML tags instead of a bulleted list (#11325) 2026-08-25 09:37:54 +02:00
Andrey Antukh
52573be074
🐛 Enforce share-link page scope in get-page and get-file-fragment RPC commands (#11284)
The get-page RPC command did not validate that the requested page-id was within the share-link's authorized :pages set, allowing share-link holders to read out-of-scope pages. The get-file-fragment command had the same issue.

This fix adds page scope validation to get-page, rejecting requests for pages not in the share-link's :pages set with a :not-found error. For get-file-fragment, share-link access is denied entirely as fragments lack direct page-id mapping.

The fix aligns these commands with the existing correct behavior in get-view-only-bundle, which already filters pages based on share-link scope.

Closes #11281

AI-assisted-by: qwen3.7-plus
2026-08-25 09:37:42 +02:00
María Valderrama
316b58bc75
🐛 Fix team dropdown not refreshing after organization deletion (#11336) 2026-08-24 20:02:59 +02:00
María Valderrama
e93c419785
🐛 Fix wrong copy on disabled invite button tooltip (#11311) 2026-08-24 15:25:31 +02:00
Pablo Alba
7732c93541
💄 Update nitrate activation code link (#11330) 2026-08-24 14:04:22 +02:00
Andrey Antukh
64a52d6b04 Merge remote-tracking branch 'origin/staging' into develop 2026-08-23 21:01:31 +02:00
Alejandro Alonso
dd6b521bc7
🐛 Fix WASM text selection copy to Windows apps (#11305)
Write text/html alongside text/plain on copy/cut so Windows apps that
prefer CF_HTML do not paste the empty contenteditable newline.
2026-08-21 15:15:40 +02:00
Andrey Antukh
47d599fe34
Persist binfile manifest and emit workspace audit events (#11106) (#11138)
Persist binfile manifest metadata in file_data on import so file
statistics are available at open-workspace time. Emit a new
open-workspace-file audit event enriched with file statistics:
page count, shape count, component count, linked libraries,
design tokens, and whether the file is a shared library.

Closes #11106

AI-assisted-by: mimo-v2.5-pro
2026-08-21 14:37:21 +02:00
Alejandro Alonso
8aefa2ddfd
🐛 Freeze viewport gestures during WASM page transition (#11301)
Pan/zoom via render_from_cache while the tile atlas is still empty
left a blank workspace under the page-transition blur. Ignore
set-view-box / view-interaction-start until tiles-complete, block
pointer events on the viewport SVG, and flush any deferred local
viewport sync when the overlay ends.
2026-08-21 14:19:50 +02:00
Andrey Antukh
0cacf9bd99 ♻️ Rename code-review-and-quality skill to code-review
Rename skill directory and update all references to follow the
same naming pattern as plan-review.

Simplify review.md command from 138 to 25 lines — remove redundant
content that duplicated what the skills already define. The command
now acts as a thin router; the skills own the methodology.

AI-assisted-by: mimo-v2.5-pro
2026-08-21 12:11:11 +00:00
Andrey Antukh
7c85837290
🐛 Fix session invalidation on logout to prevent token replay (#11317)
Logout only cleared the auth-token cookie but never deleted the
server-side row because delete-fn read ::id which wrap-authz no longer
sets since 363b4e3778. Make delete-fn delete via ::session/:id attached
by wrap-authz so replayed tokens are rejected (CWE-613, GHSA-mj9f-5cwq-7p3q).

Add regression tests covering invalidation, idempotency and isolation
of other sessions. Fix verified with Red→Green TDD and full backend
suite (677 tests).

Closes #11316

AI-assisted-by: muse-spark-1.2-contributor
2026-08-21 12:55:24 +02:00
Andrey Antukh
dd4a163217
🐛 Remove internal error details from HTTP error responses (#11288)
* 🚑 Remove internal error details from HTTP error responses

PostgreSQL exceptions, I/O exceptions, and unhandled errors were
leaking raw database messages (table names, constraint names,
SQLSTATE codes), filesystem paths, and internal exception details
to API clients via :hint, :state, and :path response fields.

Remove these fields from server-error responses while keeping
full error context in server-side logs for operators.

Closes #11287

AI-assisted-by: mimo-v2.5-pro

* 🚑 Strip internal fields and map PG errors to safe messages

Complete the security fix for GHSA-r8wx-23q6-w3gf by addressing
the incomplete redaction found in code review.

Add strip-internal-fields helper to dissoc :hint, :state, :path,
and :context from error response data in three handlers that
previously passed raw ex-data through to clients:

- handle-error :internal
- handle-exception :default (else branch)
- handle-error :assertion (else branch)

Add pgsql-state->message to map PostgreSQL SQLSTATE codes to safe,
client-facing messages (e.g. 23505 → "A conflicting entry already
exists") instead of returning raw PG error text. Include :message
in all PSQLException response branches.

Add regression tests asserting :hint, :state, :path, :context are
absent from responses for :internal and unhandled ex-info errors.

Closes #11287

AI-assisted-by: mimo-v2.5-pro

* 🚑 Keep :hint in error protocol, fix unsafe sources

Refine the security fix based on code review feedback.

Keep :hint as part of the error protocol — it is essential for
controlled error communication. Remove it from strip-internal-fields
(which now only strips :state, :path, :context).

Fix the actual sources of unsafe :hint values:

- http/middleware.clj: replace (ex-message cause) with safe static
  strings for IllegalArgumentException, RequestTooBigException, and
  EOFException. These :validation errors return ex-data verbatim
  to clients, so raw exception messages were leaking internals.

- PSQLException handler: use :hint instead of :message for the
  SQLSTATE-mapped messages, staying consistent with the error
  protocol.

Update tests to assert :hint is present (with safe static values)
in :internal and unhandled ex-info responses, and absent only from
bare RuntimeException and IOException responses.

Closes #11287

AI-assisted-by: mimo-v2.5-pro
2026-08-21 12:43:23 +02:00
Andrey Antukh
9fa07e7468 ⬆️ Update opencode on devenv dockerfile 2026-08-21 12:18:33 +02:00
Andrey Antukh
6d4a6f6a9a Merge remote-tracking branch 'origin/staging' into develop 2026-08-21 12:13:35 +02:00
Andrey Antukh
77971740e6 Merge remote-tracking branch 'origin/main' into staging 2026-08-21 12:13:22 +02:00
Alejandro Alonso
2318866f8d
🎉 Add repair functions for variant validation errors (#10768) (#11309)
* 🎉 Add repair functions for variant validation errors

* 📚 Fix copyright notice

Co-authored-by: Andrés Moya <andres.moya@kaleidos.net>
2.17.2-RC2
2026-08-21 11:17:25 +02:00
Alejandro Alonso
4cb9f951d2 🐛 Skip atlas writes during pan/zoom fast mode
During view gestures, fast mode renders tiles without shadows or
blur. Writing those tiles into the doc/tile atlas left shadowless
patches when render_from_cache overlayed them on the scaled
preview. Keep the last HQ atlas tiles until the post-gesture
full-quality render completes.
2026-08-21 11:17:16 +02:00
Alejandro Alonso
5dab689a6e 🐛 Pack tile atlas and clamp HiDPI surfaces under GPU limits
HQ tiles are 512px and the atlas stays at 4096² (64 full-size
slots). Browser zoom plus a forced ?dpr= can need more visible
tiles than that, and a framebuffer larger than the GPU allows.

Pack interest tiles into smaller atlas cells, blit at 512 then
scale, and inset Linear samples so seams do not bleed. Clamp the
canvas backing store and DPR together, wrap Skia at the real
drawingBuffer size, and wait one frame after DPR changes so CSS
client size and overlays stay aligned.
2026-08-21 11:17:16 +02:00
Alejandro Alonso
689d506788 Render eligible frame drop shadows via direct geometry path
Add a direct container-geometry path for eligible frames: inline blur
when the kernel fits the tile margin, otherwise a cached filter-surface
pass reused across tiles via DropShadowFilterCache on both the direct
and slow render_shape paths.

Move frame shadow logic into shadows.rs. Fix nested/clipped frame
shadows by deferring parent clip to composite time, apply negative
spread via inset, and allow rotated/transformed frames on the direct
path. Skip descendant extrect walks for clipped frames when only
nested drop shadows matter, and skip child silhouettes when the
container fill already covers shadow descendants.
2026-08-21 11:17:16 +02:00
David Barragán Merino
ca72213cbb 👷 Change the runner's label to a more descriptive one 2026-08-20 21:58:35 +02:00
Belén Albeza
f29a94058a
🐛 Fix not quitting v3 editor with Esc + Undo transactions (#11293)
* 🐛 Fix Esc key not quitting editor v3

* 🐛 Fix undo transactions being split in editor v3
2026-08-20 14:45:43 +02:00
María Valderrama
9f6878d118
🐛 Fix disabled invitation explanatory title (#11295) 2026-08-20 13:19:35 +02:00
Alejandro Alonso
2dcf1a8a0a Merge remote-tracking branch 'origin/staging' into develop 2026-08-20 09:15:03 +02:00
Andrey Antukh
209aea8365
🐛 Add proper ownership check on managing/deleting shared link on a file (#11290)
* 🐛 Add ownership check to share-link deletion

The delete-share-link RPC command only verified file-level edit
permission but did not check if the caller owned the share-link.
This allowed any file editor to delete share-links created by
other users, disrupting collaborative workflows.

The fix adds an ownership check that allows deletion only by:
- The share-link creator (owner-id matches profile-id)
- File admins (is-admin permission)
- File owners (is-owner permission)

Implemented using TDD:
- RED: Test demonstrates IDOR vulnerability (editor can delete)
- GREEN: Ownership check prevents unauthorized deletion
- All existing tests continue to pass

Closes #11289

AI-assisted-by: qwen3.7-plus

* 🐛 Add test coverage for share-link deletion escape hatches

Address code review feedback for PR #11290:

- Add test for editor deleting their own share-link
- Add test for admin deleting editor's share-link
- Add test for owner deleting editor's share-link
- Remove redundant :is-owner check (already included in :is-admin)
- Add clarifying comment about :is-admin including :is-owner

Closes #11289

AI-assisted-by: qwen3.7-plus
2026-08-19 18:26:35 +02:00
Andrey Antukh
c200a4d777
🐛 Fix HTML escaping in notification pill detail section (#11275)
The notification pill component now properly respects the `is-html`
flag when rendering the detail section, matching the behavior of the
children section. Token import error messages now escape HTML
characters in user-provided values like token names and type names
before displaying them in notifications.

AI-assisted-by: qwen3.7-plus
2026-08-19 18:21:55 +02:00
Elena Torró
ed588d4500
Disable ReduceOpsTaskSplitting Skia flag (#11280) 2026-08-19 17:27:50 +02:00
Elena Torró
a91c796b0e
🐛 Fix missing zip export on tempfile types (#11292) 2026-08-19 16:19:15 +02:00
Marina López
c378ec9218
🐛 Avoid swallowing fatal errors in organization sso telemetry (#11279) 2026-08-19 14:23:40 +02:00
Andrey Antukh
4da6499197 🐛 Fix linear gradients in SVG text exports (#11272)
* 🐛 Use gradient type instead of export type in SVG renderer

data->gradient-def was comparing the render `type` parameter (:svg,
:png, :pdf) against "linear" to decide between linearGradient and
radialGradient elements. Since the export type is never "linear",
the comparison always fell through to radialGradient, causing all
linear gradients to be exported as radial in SVG output.

Read the gradient type from the data map instead:
(get-in data ["gradient" "type"])

Closes #5972

* 🐛 Add SVG gradient export regression test

Extract SVG gradient definition generation from the renderer so it can
be tested directly. Add exporter test build wiring and cover both
linear and radial gradient output.

AI-assisted-by: gpt-5.6-luna

*  Standardize exporter testing workflow

Align exporter scripts with the frontend testing pattern. Add a
dedicated GitHub Actions workflow and document the canonical exporter
commands in Serena memories.

AI-assisted-by: gpt-5.6-luna

*  Add focused exporter test execution

Mirror frontend test-runner behavior for focused namespaces and test
vars. Support --focus, --log-level, and --help, and document the
commands.

AI-assisted-by: gpt-5.6-luna

* 🐛 Replace shell exec with execFile in exporter

Replace child_process.exec with execFile to eliminate shell
interpretation. Add hex color validation in exporter and frontend
to reject malformed input before command construction.

This fixes GHSA-4f36-m4hj-cv86 (CVSS 9.9 Critical), an authenticated
OS command injection vulnerability where malicious fill-color values
could execute arbitrary commands in the exporter container.

Defense in depth:
- Layer 1: execFile passes arguments directly without shell parsing
- Layer 2: Exporter validates colors with strict hex regex
- Layer 3: Frontend filters invalid colors before DOM emission

All three independent reporters' attack vectors are addressed:
- Quote breakout (lyhtheori)
- Command substitution (B1gN0Se)
- Path traversal (KimiSecurityTeam)

AI-assisted-by: qwen3.7-plus

* 🐛 Use existing hex-color-string? and fix test path mismatch

Address code review feedback:

- Replace duplicated hex-color-rx and valid-hex-color? with existing
  hex-color-string? from app.common.types.color
- Fix RCE test to use marker path in payload instead of hardcoded /tmp/pwned

AI-assisted-by: qwen3.7-plus

---------

Co-authored-by: Sumit Ridhal <sridhal@redhat.com>
2.17.2-RC1
2026-08-19 13:57:14 +02:00
Andrey Antukh
aa3bc1ae98 🐛 Fix linear gradients in SVG text exports (#11272)
* 🐛 Use gradient type instead of export type in SVG renderer

data->gradient-def was comparing the render `type` parameter (:svg,
:png, :pdf) against "linear" to decide between linearGradient and
radialGradient elements. Since the export type is never "linear",
the comparison always fell through to radialGradient, causing all
linear gradients to be exported as radial in SVG output.

Read the gradient type from the data map instead:
(get-in data ["gradient" "type"])

Closes #5972

* 🐛 Add SVG gradient export regression test

Extract SVG gradient definition generation from the renderer so it can
be tested directly. Add exporter test build wiring and cover both
linear and radial gradient output.

AI-assisted-by: gpt-5.6-luna

*  Standardize exporter testing workflow

Align exporter scripts with the frontend testing pattern. Add a
dedicated GitHub Actions workflow and document the canonical exporter
commands in Serena memories.

AI-assisted-by: gpt-5.6-luna

*  Add focused exporter test execution

Mirror frontend test-runner behavior for focused namespaces and test
vars. Support --focus, --log-level, and --help, and document the
commands.

AI-assisted-by: gpt-5.6-luna

* 🐛 Replace shell exec with execFile in exporter

Replace child_process.exec with execFile to eliminate shell
interpretation. Add hex color validation in exporter and frontend
to reject malformed input before command construction.

This fixes GHSA-4f36-m4hj-cv86 (CVSS 9.9 Critical), an authenticated
OS command injection vulnerability where malicious fill-color values
could execute arbitrary commands in the exporter container.

Defense in depth:
- Layer 1: execFile passes arguments directly without shell parsing
- Layer 2: Exporter validates colors with strict hex regex
- Layer 3: Frontend filters invalid colors before DOM emission

All three independent reporters' attack vectors are addressed:
- Quote breakout (lyhtheori)
- Command substitution (B1gN0Se)
- Path traversal (KimiSecurityTeam)

AI-assisted-by: qwen3.7-plus

* 🐛 Use existing hex-color-string? and fix test path mismatch

Address code review feedback:

- Replace duplicated hex-color-rx and valid-hex-color? with existing
  hex-color-string? from app.common.types.color
- Fix RCE test to use marker path in payload instead of hardcoded /tmp/pwned

AI-assisted-by: qwen3.7-plus

---------

Co-authored-by: Sumit Ridhal <sridhal@redhat.com>
2026-08-19 13:53:40 +02:00
Andrey Antukh
60d87a6342
🐛 Fix linear gradients in SVG text exports (#11272)
* 🐛 Use gradient type instead of export type in SVG renderer

data->gradient-def was comparing the render `type` parameter (:svg,
:png, :pdf) against "linear" to decide between linearGradient and
radialGradient elements. Since the export type is never "linear",
the comparison always fell through to radialGradient, causing all
linear gradients to be exported as radial in SVG output.

Read the gradient type from the data map instead:
(get-in data ["gradient" "type"])

Closes #5972

* 🐛 Add SVG gradient export regression test

Extract SVG gradient definition generation from the renderer so it can
be tested directly. Add exporter test build wiring and cover both
linear and radial gradient output.

AI-assisted-by: gpt-5.6-luna

*  Standardize exporter testing workflow

Align exporter scripts with the frontend testing pattern. Add a
dedicated GitHub Actions workflow and document the canonical exporter
commands in Serena memories.

AI-assisted-by: gpt-5.6-luna

*  Add focused exporter test execution

Mirror frontend test-runner behavior for focused namespaces and test
vars. Support --focus, --log-level, and --help, and document the
commands.

AI-assisted-by: gpt-5.6-luna

* 🐛 Replace shell exec with execFile in exporter

Replace child_process.exec with execFile to eliminate shell
interpretation. Add hex color validation in exporter and frontend
to reject malformed input before command construction.

This fixes GHSA-4f36-m4hj-cv86 (CVSS 9.9 Critical), an authenticated
OS command injection vulnerability where malicious fill-color values
could execute arbitrary commands in the exporter container.

Defense in depth:
- Layer 1: execFile passes arguments directly without shell parsing
- Layer 2: Exporter validates colors with strict hex regex
- Layer 3: Frontend filters invalid colors before DOM emission

All three independent reporters' attack vectors are addressed:
- Quote breakout (lyhtheori)
- Command substitution (B1gN0Se)
- Path traversal (KimiSecurityTeam)

AI-assisted-by: qwen3.7-plus

* 🐛 Use existing hex-color-string? and fix test path mismatch

Address code review feedback:

- Replace duplicated hex-color-rx and valid-hex-color? with existing
  hex-color-string? from app.common.types.color
- Fix RCE test to use marker path in payload instead of hardcoded /tmp/pwned

AI-assisted-by: qwen3.7-plus

---------

Co-authored-by: Sumit Ridhal <sridhal@redhat.com>
2026-08-19 13:29:04 +02:00