test(14.2.1-06): land remaining named tests and phase gate

WR-02 proofs and T-14.2.1-19..23 must fail the phase closed if hydration or child locale validation is removed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-10-06 16:28:29 +02:00
parent 4e37d877f1
commit ef03016314
4 changed files with 86 additions and 10 deletions

View File

@@ -2,26 +2,26 @@
phase: 14.2.1 phase: 14.2.1
slug: translate-plugin slug: translate-plugin
status: verified status: verified
threats_total: 19 threats_total: 24
threats_closed: 19 threats_closed: 24
threats_open: 0 threats_open: 0
accepted_risks: 0 accepted_risks: 0
asvs_level: 1 asvs_level: 1
created: 2026-10-06 created: 2026-10-06
verified: 2026-10-06 verified: 2026-10-06
reviewer: gsd-executor (14.2.1-04 Task 3, self-performed -- see Reviewer Note) reviewer: gsd-executor (14.2.1-04 Task 3 and 14.2.1-06 Task 3, self-performed -- see Reviewer Note)
--- ---
# Phase 14.2.1 — Security Review # Phase 14.2.1 — Security Review
> Lean Translate plugin (`sm-translate-plugin`), cabana `markdown`/`mltext`/`mlmarkdown`, > Lean Translate plugin (`sm-translate-plugin`), cabana `markdown`/`mltext`/`mlmarkdown`,
> surf `LocaleResolver`, and the proof-host boot of user+translate. Every > surf `LocaleResolver`, and the proof-host boot of user+translate. Every
> T-14.2.1 threat locked in plans 01–04 is mapped below to executed, named Go > T-14.2.1 threat locked in plans 01–04 and 06 is mapped below to executed, named Go
> or Vitest evidence. Unmapped IDs would be a review gap, not an accepted > or Vitest evidence. Unmapped IDs would be a review gap, not an accepted
> risk; none exist. > risk; none exist.
**Date:** 2026-10-06 **Date:** 2026-10-06
**Scope:** Plans 14.2.1-01 through 14.2.1-04; `sm-translate-plugin`; **Scope:** Plans 14.2.1-01 through 14.2.1-06; `sm-translate-plugin`;
`summercms.go` modules `cabana` and `surf` plus admin SPA ML/markdown `summercms.go` modules `cabana` and `surf` plus admin SPA ML/markdown
controls; proof host `sm-grzybyfunkcjonalne-app`. controls; proof host `sm-grzybyfunkcjonalne-app`.
**Repos grepped:** `sm-translate-plugin`, `summercms.go` (excluding **Repos grepped:** `sm-translate-plugin`, `summercms.go` (excluding
@@ -32,8 +32,9 @@ controls; proof host `sm-grzybyfunkcjonalne-app`.
14.2.1-04-PLAN.md Task 3 calls for an independent `gsd-security-auditor` 14.2.1-04-PLAN.md Task 3 calls for an independent `gsd-security-auditor`
agent pass. This Cursor session has no dedicated security-auditor subagent agent pass. This Cursor session has no dedicated security-auditor subagent
(same fallback as Phase 08): the 14.2.1-04 executor performed the review (same fallback as Phase 08): the 14.2.1-04 executor performed the review
directly. Every high threat below is closed with source citations and named directly, and 14.2.1-06 Task 3 appended CR-01/WR-02 rows the same way. Every
tests **re-executed during this review** (2026-10-06), not merely inherited high threat below is closed with source citations and named tests
**re-executed during this review** (2026-10-06), not merely inherited
from earlier plans. The executor checkpoint return states this plainly. from earlier plans. The executor checkpoint return states this plainly.
No external API integration: this phase ports a compiled plugin and local No external API integration: this phase ports a compiled plugin and local
@@ -43,7 +44,7 @@ framework/host contracts only.
## Verdict Summary ## Verdict Summary
The register contains **19 total threats: 19 closed, 0 open, 0 accepted The register contains **24 total threats: 24 closed, 0 open, 0 accepted
risks**. High findings block phase completion; all high rows are mitigate risks**. High findings block phase completion; all high rows are mitigate
with executed named tests. PHP pin SHA `725d547ec839f02b5fdc0f0a6faaed601a414d50` with executed named tests. PHP pin SHA `725d547ec839f02b5fdc0f0a6faaed601a414d50`
is unchanged. is unchanged.
@@ -85,6 +86,11 @@ is unchanged.
| T-14.2.1-16 | Tampering | migration rollback | medium | mitigate | `TestTranslateMigrationsRollbackAndRemigrate` dedicated Postgres | | T-14.2.1-16 | Tampering | migration rollback | medium | mitigate | `TestTranslateMigrationsRollbackAndRemigrate` dedicated Postgres |
| T-14.2.1-17 | Tampering | generated admin artifacts | medium | mitigate | `TestMLOpenAPIConformance`; `scripts/check-admin-openapi.sh --check`; `scripts/check-admin-dist.sh` | | T-14.2.1-17 | Tampering | generated admin artifacts | medium | mitigate | `TestMLOpenAPIConformance`; `scripts/check-admin-openapi.sh --check`; `scripts/check-admin-dist.sh` |
| T-14.2.1-18 | Elevation of Privilege | test fixture | high | mitigate | `TestTranslateEndToEndFixtureAbsentFromProduction`; host `plugins.gen.go` has no `acme.fixture` | | T-14.2.1-18 | Elevation of Privilege | test fixture | high | mitigate | `TestTranslateEndToEndFixtureAbsentFromProduction`; host `plugins.gen.go` has no `acme.fixture` |
| T-14.2.1-19 | Information Disclosure | hydrateMLRecord / ShowChild | high | mitigate | `modules/cabana/field_ml.go` hydrates only declared ML fields on Show/save and child record payloads; `TestMLHydration`; `TestRelationChildMLNestedSave` |
| T-14.2.1-20 | Information Disclosure | FormMeta.EnabledLocales | medium | mitigate | `modules/cabana/http.go` copies locales only on protect()'d form/relation schemas; `admin/tests/form/formState.test.ts` seeds en+pl; `admin/tests/form/MLFields.test.ts` create-empty |
| T-14.2.1-21 | Tampering | FormView.adopt | medium | mitigate | `mergeMLValue` applies only to ML fields; `admin/tests/form/formState.test.ts` GET-string merge; `admin/tests/form/MLFields.test.ts` sibling locales |
| T-14.2.1-22 | Tampering | fillChild / CreateChild / UpdateChild | high | mitigate | `liftMLValues` before `projectOperation`; `TestRelationChildMLNestedSave` (de 422, no row) |
| T-14.2.1-23 | Elevation of Privilege | RelationService.writer | high | mitigate | `http.go` `relations()` Lookup on existing protect()'d child routes; apply after loadParent/loadChild and child PK; `TestRelationChildMLNestedSave` |
| T-14.2.1-SC | Tampering | package installs | high | mitigate | no `go get` / npm install this plan; plugin `replace` of `sm-user-plugin` is a sibling checkout; framework `go.mod`/`admin/package-lock.json` unchanged | | T-14.2.1-SC | Tampering | package installs | high | mitigate | no `go get` / npm install this plan; plugin `replace` of `sm-user-plugin` is a sibling checkout; framework `go.mod`/`admin/package-lock.json` unchanged |
--- ---
@@ -199,6 +205,36 @@ is unchanged.
- **Test evidence (re-run 2026-10-06):** `TestTranslateEndToEndFixtureAbsentFromProduction` matched by `TestTranslateEndToEnd*` in the plugin `./...` run PASS; host `plugins.gen.go` lists only `golem15.user` and `golem15.translate`. - **Test evidence (re-run 2026-10-06):** `TestTranslateEndToEndFixtureAbsentFromProduction` matched by `TestTranslateEndToEnd*` in the plugin `./...` run PASS; host `plugins.gen.go` lists only `golem15.user` and `golem15.translate`.
- **Disposition:** closed / mitigate. - **Disposition:** closed / mitigate.
### T-14.2.1-19 — ML hydration disclosure
- **Source:** `modules/cabana/field_ml.go` `hydrateMLRecord` walks declared `mltext`/`mlmarkdown` fields only; list projection never calls it; `TranslatedExact` skips D-11 so missing pl stays empty. ShowChild uses the same helper after `loadParent`/`loadChild`.
- **Test evidence (re-run 2026-10-06):** `TestMLHydration` PASS (english-only pl is `""`); `TestRelationChildMLNestedSave` PASS (create/update/show hydrated maps).
- **Disposition:** closed / mitigate.
### T-14.2.1-20 — enabled locale list on schemas
- **Source:** `modules/cabana/http.go` `enabledContentLocales` after Lookup, copied onto protect()'d form and relation schema meta; `FormMeta.EnabledLocales` is `json:"enabledLocales,omitempty"` so list Meta omits it.
- **Test evidence (re-run 2026-10-06):** `admin/tests/form/formState.test.ts` `seeds every enabled locale as an empty string`; `admin/tests/form/MLFields.test.ts` create-empty selectors list en+pl.
- **Disposition:** closed / mitigate.
### T-14.2.1-21 — adopt merge of GET scalars
- **Source:** `admin/src/components/form/formState.ts` `mergeMLValue` overlays a host string onto the seed for ML fields only; password fields still clear.
- **Test evidence (re-run 2026-10-06):** `admin/tests/form/formState.test.ts` `merges a GET host string onto the seed without dropping sibling locales`; `admin/tests/form/MLFields.test.ts` GET-string cases for mltext and mlmarkdown.
- **Disposition:** closed / mitigate.
### T-14.2.1-22 — relation-child nested ML tampering
- **Source:** `modules/cabana/relation_child.go` `CreateChild`/`UpdateChild` call `liftMLValues` on the child form before `fillChild`/`projectOperation`; undeclared locale, non-string, missing default, and nil writer with a nested map are 422 before Fill.
- **Test evidence (re-run 2026-10-06):** `TestRelationChildMLNestedSave` PASS (create+update persist en/pl; locale `de` is 422 and inserts no row).
- **Disposition:** closed / mitigate.
### T-14.2.1-23 — RelationService writer privilege
- **Source:** `modules/cabana/http.go` `relations()` Lookup of `TranslationWriter` matches `crud()`; no new HTTP route; `applyMLTranslations` runs after `loadParent`/`loadChild` and after the child row has a primary key.
- **Test evidence (re-run 2026-10-06):** `TestRelationChildMLNestedSave` PASS; `recordingWriter.WriteTranslated` refuses a zero PK.
- **Disposition:** closed / mitigate.
### T-14.2.1-SC — package installs ### T-14.2.1-SC — package installs
- **Source:** no new module versions beyond existing stack pins; plugin `replace` of `sm-user-plugin` points at the host submodule checkout; `gocloud.dev v0.46.0` is the already-decided blob pin. Framework `go.mod` / `admin/package-lock.json` were not changed in this phase. - **Source:** no new module versions beyond existing stack pins; plugin `replace` of `sm-user-plugin` points at the host submodule checkout; `gocloud.dev v0.46.0` is the already-decided blob pin. Framework `go.mod` / `admin/package-lock.json` were not changed in this phase.

View File

@@ -61,6 +61,35 @@ describe('ML field registry and nested save body', () => {
}) })
}) })
it('sends every enabled locale including empty pl', () => {
const fields = [field('mltext'), field('mlmarkdown', 'body')]
const payload = editablePayload(fields, {
title: { en: 'Hello', pl: '' },
body: { en: '# Hi', pl: '' },
})
expect(payload).toEqual({
title: { en: 'Hello', pl: '' },
body: { en: '# Hi', pl: '' },
})
})
it('exposes one locale selector per ML field listing enabled locales', () => {
const title = mlMount(MLTextField, {
field: field('mltext'),
modelValue: { en: '', pl: '' },
controlId: 'f-title',
})
const body = mlMount(MLMarkdownField, {
field: field('mlmarkdown', 'body'),
modelValue: { en: '', pl: '' },
controlId: 'f-body',
})
expect(title.findAll('[data-ml-locale]')).toHaveLength(1)
expect(body.findAll('[data-ml-locale]')).toHaveLength(1)
expect(title.findAll('[data-ml-locale] option').map((node) => node.text())).toEqual(['en', 'pl'])
expect(body.findAll('[data-ml-locale] option').map((node) => node.text())).toEqual(['en', 'pl'])
})
it('edits the active locale and copies from another', async () => { it('edits the active locale and copies from another', async () => {
const wrapper = mlMount(MLTextField, { const wrapper = mlMount(MLTextField, {
field: field('mltext'), field: field('mltext'),

View File

@@ -1,8 +1,10 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { enableAutoUnmount, mount } from '@vue/test-utils' import { enableAutoUnmount, mount } from '@vue/test-utils'
import { ref } from 'vue'
import type { FormField } from '../../src/api/types' import type { FormField } from '../../src/api/types'
import MarkdownField from '../../src/components/form/fields/MarkdownField.vue' import MarkdownField from '../../src/components/form/fields/MarkdownField.vue'
import MLMarkdownField from '../../src/components/form/fields/MLMarkdownField.vue' import MLMarkdownField from '../../src/components/form/fields/MLMarkdownField.vue'
import { FORM_ENABLED_LOCALES } from '../../src/components/form/formContext'
import { resetState } from '../helpers' import { resetState } from '../helpers'
function field(type = 'markdown', name = 'body'): FormField { function field(type = 'markdown', name = 'body'): FormField {
@@ -64,6 +66,11 @@ describe('MarkdownField composition and sinks', () => {
controlId: 'f-body', controlId: 'f-body',
}, },
attachTo: document.body, attachTo: document.body,
global: {
provide: {
[FORM_ENABLED_LOCALES as symbol]: ref(['en', 'pl']),
},
},
}) })
expect(wrapper.findComponent(MarkdownField).exists()).toBe(true) expect(wrapper.findComponent(MarkdownField).exists()).toBe(true)
await wrapper.find('[data-ml-locale]').setValue('pl') await wrapper.find('[data-ml-locale]').setValue('pl')

View File

@@ -41,6 +41,8 @@ FRAMEWORK_REQUIRE=(
TestML TestML
TestMLFieldTypes TestMLFieldTypes
TestMLNestedSave TestMLNestedSave
TestMLHydration
TestRelationChildMLNestedSave
TestMarkdownRejectsUnsafeHTML TestMarkdownRejectsUnsafeHTML
TestMLOpenAPIConformance TestMLOpenAPIConformance
) )
@@ -48,13 +50,15 @@ HOST_REQUIRE=(TestBootUserTranslate)
HIGH_THREATS=( HIGH_THREATS=(
T-14.2.1-01 T-14.2.1-02 T-14.2.1-04 T-14.2.1-05 T-14.2.1-06 T-14.2.1-01 T-14.2.1-02 T-14.2.1-04 T-14.2.1-05 T-14.2.1-06
T-14.2.1-07 T-14.2.1-09 T-14.2.1-10 T-14.2.1-11 T-14.2.1-12 T-14.2.1-07 T-14.2.1-09 T-14.2.1-10 T-14.2.1-11 T-14.2.1-12
T-14.2.1-14 T-14.2.1-15 T-14.2.1-18 T-14.2.1-SC T-14.2.1-14 T-14.2.1-15 T-14.2.1-18 T-14.2.1-19 T-14.2.1-22
T-14.2.1-23 T-14.2.1-SC
) )
ALL_THREATS=( ALL_THREATS=(
T-14.2.1-01 T-14.2.1-02 T-14.2.1-03 T-14.2.1-04 T-14.2.1-05 T-14.2.1-01 T-14.2.1-02 T-14.2.1-03 T-14.2.1-04 T-14.2.1-05
T-14.2.1-06 T-14.2.1-07 T-14.2.1-08 T-14.2.1-09 T-14.2.1-10 T-14.2.1-06 T-14.2.1-07 T-14.2.1-08 T-14.2.1-09 T-14.2.1-10
T-14.2.1-11 T-14.2.1-12 T-14.2.1-13 T-14.2.1-14 T-14.2.1-15 T-14.2.1-11 T-14.2.1-12 T-14.2.1-13 T-14.2.1-14 T-14.2.1-15
T-14.2.1-16 T-14.2.1-17 T-14.2.1-18 T-14.2.1-SC T-14.2.1-16 T-14.2.1-17 T-14.2.1-18 T-14.2.1-19 T-14.2.1-20
T-14.2.1-21 T-14.2.1-22 T-14.2.1-23 T-14.2.1-SC
) )
usage() { usage() {