Тесты: покрыть инъекцию trackerHead в ShareSeoController (+ no-op ветка аудита trackerHead) #100

Closed
opened 2026-06-21 04:10:51 +03:00 by Ghost · 0 comments

Контекст

Code review изменений по sandbox HTML-embed + админский tracker (диапазон 81823fce^..HEAD) выявил пробел в тестовом покрытии новой security-релевантной логики. Сам код корректен и проверен — это задача именно на регрессионные тесты, не на исправление поведения.


1. Инъекция trackerHead в ShareSeoController не покрыта тестами (основное)

apps/server/src/core/share/share-seo.controller.ts — единственное место, где админский HTML/JS-сниппет вставляется дословно (без экранирования) в <head> публичной share-страницы. Ни один спек не ссылается на ShareSeoController (grep по *.spec.ts / *.e2e-spec.ts — пусто); единственный тест с trackerHead (workspace-html-embed.spec.ts) покрывает только путь сохранения настройки, а не инъекции.

Без тестов остаются 4 ветки:

  1. trackerHead задан + в шаблоне есть </head> → сниппет вставляется перед </head>.
  2. trackerHead задан, но </head> в index-HTML не найден → logger.warn + пропуск (HTML без изменений).
  3. trackerHead пустой / только пробелы / отсутствует → пропуск (typeof === 'string' && trim().length > 0).
  4. Корректность function-replacer () => ...: именно функция-replacer защищает от того, что $&, $`, $', $$ внутри сниппета будут истолкованы String.prototype.replace как шаблоны подстановки и исказят тег. Это самый тонкий инвариант — он молча сломается, если кто-то «упростит» replacer обратно в строковый аргумент.

Предлагаемый тест

Добавить apps/server/src/core/share/share-seo.controller.spec.ts:

  • застабить fs.readFileSync минимальным index-HTML с </head> и маркером мета-тегов;
  • замокать shareService / workspaceRepo / environmentService;
  • перехватить res.send и проверить:
    • сниппет вставлен непосредственно перед </head>, когда trackerHead задан;
    • сниппет с $& / $$ попадает в вывод дословно (этот кейс упадёт на строковом replacer'е — он и фиксирует инвариант);
    • при отсутствии </head> вызывается logger.warn, а HTML не меняется;
    • нет вставки для пустого / пробельного / undefined trackerHead.

2. (Минор) No-op ветка аудита trackerHead в workspace.service.ts

apps/server/src/core/workspace/services/workspace.service.ts (~528-540): при prev === trackerHead (повторное сохранение того же значения) updateSetting вызывается, но before/after.trackerHead не должны по��адать в аудит. Покрыт только путь с изменением значения; no-op-ветка не проверена (та же форма у соседнего htmlEmbed). Влияние — только шум в аудите.

Предлагаемый тест

Кейс с одинаковыми settingsBefore и входным значением: проверить, что updateSetting вызван, а изменение по trackerHead в аудит-payload не попало.


Источник: code-review-orchestrator, диапазон 81823fce^..HEAD.

## Контекст Code review изменений по sandbox HTML-embed + админский tracker (диапазон `81823fce^..HEAD`) выявил пробел в тестовом покрытии новой security-релевантной логики. Сам код корректен и проверен — это задача именно на **регрессионные тесты**, не на исправление поведения. --- ## 1. Инъекция `trackerHead` в `ShareSeoController` не покрыта тестами (основное) `apps/server/src/core/share/share-seo.controller.ts` — единственное место, где админский HTML/JS-сниппет вставляется **дословно (без экранирования)** в `<head>` публичной share-страницы. Ни один спек не ссылается на `ShareSeoController` (`grep` по `*.spec.ts` / `*.e2e-spec.ts` — пусто); единственный тест с `trackerHead` (`workspace-html-embed.spec.ts`) покрывает только путь *сохранения* настройки, а не *инъекции*. Без тестов остаются 4 ветки: 1. `trackerHead` задан + в шаблоне есть `</head>` → сниппет вставляется перед `</head>`. 2. `trackerHead` задан, но `</head>` в index-HTML не найден → `logger.warn` + пропуск (HTML без изменений). 3. `trackerHead` пустой / только пробелы / отсутствует → пропуск (`typeof === 'string' && trim().length > 0`). 4. **Корректность function-replacer** `() => ...`: именно функция-replacer защищает от того, что `$&`, `` $` ``, `$'`, `$$` внутри сниппета будут истолкованы `String.prototype.replace` как шаблоны подстановки и исказят тег. Это самый тонкий инвариант — он молча сломается, если кто-то «упростит» replacer обратно в строковый аргумент. ### Предлагаемый тест Добавить `apps/server/src/core/share/share-seo.controller.spec.ts`: - застабить `fs.readFileSync` минимальным index-HTML с `</head>` и маркером мета-тегов; - замокать `shareService` / `workspaceRepo` / `environmentService`; - перехватить `res.send` и проверить: - сниппет вставлен непосредственно перед `</head>`, когда `trackerHead` задан; - сниппет с `$&` / `$$` попадает в вывод **дословно** (этот кейс упадёт на строковом replacer'е — он и фиксирует инвариант); - при отсутствии `</head>` вызывается `logger.warn`, а HTML не меняется; - нет вставки для пустого / пробельного / `undefined` `trackerHead`. --- ## 2. (Минор) No-op ветка аудита `trackerHead` в `workspace.service.ts` `apps/server/src/core/workspace/services/workspace.service.ts` (~528-540): при `prev === trackerHead` (повторное сохранение того же значения) `updateSetting` вызывается, но `before/after.trackerHead` **не должны** по��адать в аудит. Покрыт только путь с изменением значения; no-op-ветка не проверена (та же форма у соседнего `htmlEmbed`). Влияние — только шум в аудите. ### Предлагаемый тест Кейс с одинаковыми `settingsBefore` и входным значением: проверить, что `updateSetting` вызван, а изменение по `trackerHead` в аудит-payload **не** попало. --- _Источник: code-review-orchestrator, диапазон `81823fce^..HEAD`._
Ghost closed this issue 2026-06-21 14:10:34 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#100