Files
TX/artifacts/tx-os
riyadhafraa ca727c3759 EM #241: sanitize location/meetingUrl/notes (regex stripper) + tests
Original task: Executive Meetings — test coverage + sanitization
closeout (umbrella for #170, #186-189, #201, #202, #212, #214, #218,
#235).

What landed
- Added `stripTagsToPlainText[OrNull]` in
  `artifacts/api-server/src/lib/sanitize.ts`. Implementation is a
  two-pass regex stripper (NOT sanitize-html + entity decode):
    Pass 1: drop dangerous tag bodies entirely
            (`<script>`/`<style>`/`<noscript>`/`<iframe>`/`<object>`/
             `<embed>`/`<template>` content + tags).
    Pass 2: strip HTML comments, CDATA, DOCTYPE, processing
            instructions, and any remaining open/close tags via
            `<\/?[a-zA-Z][^>]*>`.
  No entity decode pass — so URLs (`?a=1&b=2`) round-trip unchanged,
  plain text (`5 < 10`) is preserved, AND attacker-supplied encoded
  payloads (`&lt;script&gt;…`) survive as inert text instead of being
  rehydrated into live tags. The existing `sanitizePlainText` (which
  entity-encodes) is preserved for `attendee.title` so the print
  template's HTML interpolation behavior is unchanged.
- Wired the new helper into all 11 write paths for
  `location`/`meetingUrl`/`notes` in
  `artifacts/api-server/src/routes/executive-meetings.ts`:
  POST /executive-meetings, PATCH /executive-meetings/:id,
  POST /executive-meetings/:id/duplicate, and `applyApprovedRequest`
  (`change_location` + `note`). attendee.title call sites kept as-is.
- Added 6 API tests in
  `artifacts/api-server/tests/executive-meetings.test.mjs`:
    1. POST sanitization (URL `&` round-trip + literal `<`/`&` in notes
       + asserts NO entity-encoding in stored values).
    2. Encoded-payload regression guard (`&lt;script&gt;`,
       `&#x3C;script&#x3E;`, mixed-case `<ScRiPt>`/`<IFRAME>`)
       confirming we don't decode entities into live tags.
    3. PATCH sanitization on the same fields.
    4. Duplicate-path round-trip (URL ampersands preserved).
    5. change_location approved-request round-trip + tag stripping.
    6. EditableCell column-independence contract test (PATCH titleEn
       alone must not clobber titleAr and vice versa).

Also added (e2e)
- New Playwright spec
  `artifacts/tx-os/tests/executive-meetings-subheading-chip-hidden.spec.mjs`
  covers item #235: seeds a multi-group meeting (virtual + internal),
  opens the cell-level "+ subheading" chip, asserts the chip unmounts
  while the pending input is open, then re-mounts after Escape
  cancels the input. Passes locally (~7s).

Drift from the original umbrella
- The umbrella listed 9 Playwright e2e specs (#170, #186, #187, #188,
  #201, #212, #214, #218, #235). #235 landed in this diff; the other
  8 remain deferred. Each remaining spec is a 100–300 line standalone
  file (no shared helpers in this repo) and bundling all 8 would more
  than double the existing e2e count for one task. Each remains as
  its own PENDING project task and can be picked up incrementally.

Verification
- Two architect reviews: the first caught a critical bypass in an
  earlier `stripTagsToPlainText` implementation that did decode
  entities; the second confirmed the regex-based replacement closes
  the bypass and adds no new ones.
- Suite: 226 tests, 224 passing. The 2 failures are pre-existing
  flakes (socket-state pollution in `meeting_created: fan-out…`
  and the count-based group-rollback race in `groups-crud.test.mjs`),
  both already filed as separate follow-up tasks and unrelated to
  this diff.
- Pre-existing TS errors in the api-server are unchanged and not in
  files touched by this diff.
2026-04-30 23:26:09 +00:00
..