scripts/check-phase15.sh --all refuses skip/no-tests/race/dirty PHP pin; the review closes T-15-01..15 and T-15-SC with executed TestNames. Co-authored-by: Cursor <cursoragent@cursor.com>
10 KiB
phase, slug, status, threats_total, threats_closed, threats_open, accepted_risks, asvs_level, block_on, created, verified, reviewer
| phase | slug | status | threats_total | threats_closed | threats_open | accepted_risks | asvs_level | block_on | created | verified | reviewer |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 15 | journal-plugin | verified | 16 | 16 | 0 | 0 | 1 | high | 2026-10-06 | 2026-10-06 | gsd-executor (15-04 Task 3, self-performed -- see Reviewer Note) |
Phase 15 — Security Review
Lean Journal plugin (
sm-journal-plugin) and the proof-host boot of user+translate+journal. Every Phase 15 threat locked in plans 01–04 is mapped below to executed, named Go evidence. Unmapped IDs would be a review gap, not an accepted risk; none exist.
Date: 2026-10-06
Scope: Plans 15-01 through 15-04; sm-journal-plugin; proof host
sm-grzybyfunkcjonalne-app; scripts/check-phase15.sh.
Repos grepped: sm-journal-plugin, summercms.go (excluding
.planning/ except this review), sm-grzybyfunkcjonalne-app.
Reviewer Note
15-04-PLAN.md Task 3 calls for an independent gsd-security-auditor
agent pass. This Cursor session has no dedicated security-auditor
subagent (same fallback as 14.2.1-04): the 15-04 executor performed the
review directly. Every high threat below is closed with source citations
and named tests re-executed during this review (2026-10-06 plugin
go test ./... -race and host go test ./... -race), not merely
inherited from earlier plans.
No external API integration: this phase ports a compiled plugin and local host contracts only. No external SaaS SDK this phase (Typesense stays behind a default-off gate; TestSearchGateOff recorded zero HTTP).
Verdict Summary
The register contains 16 total threats: 16 closed, 0 open, 0 accepted
risks. High findings block phase completion; all high rows are mitigate
with executed named tests. PHP pin SHA 02110eb1c0c3861370b0b9b47b209a0702ac5d88
is unchanged.
Trust Boundaries
| Boundary | Description | Data Crossing |
|---|---|---|
| anonymous GET → published posts | public /_journal/api/v1 |
published rows only; drafts 404 without data |
| backend JWT → writes / media | HS256 aud=backend |
title/content/files; never frontend audience |
| Fillable / API assigns → GORM | untrusted JSON | nest_*, redactor_id, user_id must not persist from maps |
| markdown → stored HTML | FormatHTML rejectUnsafe | script/iframe/event/js schemes |
| test fixture → production binary | process-local plugins | must not appear in host plugins.gen.go |
| gate → production claims | skipped containers / dirty PHP | named PASS + final marker |
Threat Register
| Threat ID | Category | Component | Severity | Disposition | Proof |
|---|---|---|---|---|---|
| T-15-01 | Spoofing | POST /_journal/api/v1/posts |
high | mitigate | journal_api_writes_test.go:TestJournalWriteUnauthenticated; frontend audience 401 |
| T-15-02 | Information Disclosure | GET posts/{slug} drafts | high | mitigate | TestJournal005DraftShow 404 without data; owner/access_other_posts 200 |
| T-15-03 | Tampering | POST /media/upload |
high | mitigate | TestJournal006MediaUpload 403 without access_posts; folder .. 422; /journal/ prefix |
| T-15-04 | Elevation of Privilege | Category/Tag/Post Fillable | high | mitigate | models/fillable_test.go:TestFillable; TestJournalAPIMassAssignRedactor |
| T-15-05 | Tampering | gormigrate DDL | high | mitigate | TestJournalTables; TestJournalMigrationsRollbackAndRemigrate; no AutoMigrate |
| T-15-06 | Tampering | MorphName | high | mitigate | TestTranslatable; TestPostTranslatableSmoke PHP class strings |
| T-15-07 | Elevation of Privilege | Posts admin | high | mitigate | TestPostsAdminForbidden 403 without access_posts; owner scope in Plan 02 |
| T-15-08 | Tampering | FormatHTML | high | mitigate | classes/format_html_test.go:TestFormatHTMLRejectsUnsafeHTML |
| T-15-09 | Elevation of Privilege | access_publish | high | mitigate | TestJournalWriteUnauthenticated publish 403; TestPostsAdminCreateSmoke/publish_without_access_publish |
| T-15-10 | Spoofing | write API tokens | high | mitigate | TestJournalWriteUnauthenticated / TestJournalWriteFrontendAudience reject aud=user |
| T-15-11 | Information Disclosure | Typesense sync | high | mitigate | search_test.go:TestSearchGateOff zero HTTP; unpublished ShouldBeSearchable false with gate flipped |
| T-15-12 | Denial of Service | X-Forwarded-For | medium | mitigate | plugin.go buckets use surf.ClientIP + TrustedProxies; TestJournalBuckets |
| T-15-13 | Tampering | error envelope | high | mitigate | TestJournalWriteUnauthenticated PHP {error} string, no cabana admin envelope |
| T-15-14 | Repudiation | phase gate | high | mitigate | scripts/check-phase15.sh detector refuses skip/no-tests/race; --self-test |
| T-15-15 | Information Disclosure | unpublished title prefix | medium | mitigate | TestJournalAPIShowNeighbors JSON title omits UnpublishedTitlePrefix |
| T-15-SC | Tampering | package installs | high | mitigate | plugin replace is only summercms => ../summercms.go; goldmark already in the graph; no new SaaS SDK |
Findings by Threat
T-15-01 — unauthenticated and frontend-audience writes
- Source:
controllers/api/auth.gorequireBackendPrincipal; PHP{error:"Authentication required"}. - Test evidence (re-run 2026-10-06):
TestJournalWriteUnauthenticatedPASS;TestJournalWriteFrontendAudiencePASS; featured-image POST/DELETE 401 inTestJournalFeaturedImageUnauthenticatedPASS. - Disposition: closed / mitigate.
T-15-02 — draft enumeration
- Source:
controllers/api/posts.goShow; 404 withoutdataunless owner oraccess_other_posts. - Test evidence (re-run 2026-10-06):
TestJournal005DraftShowPASS;TestJournalEndToEndanonymous draft 404 PASS. - Disposition: closed / mitigate.
T-15-03 — media traversal
- Source:
controllers/api/media.gofolder regex,..reject, forced/journal/prefix. - Test evidence (re-run 2026-10-06):
TestJournal006MediaUploadPASS;TestMediaObjectPathPASS. - Disposition: closed / mitigate.
T-15-04 — mass assignment
- Source: Tag/Category
Fillable; Post APIbuildNewPostfield-by-field (neverlagoon.Fillofredactor_id/user_id). - Test evidence (re-run 2026-10-06):
TestFillablePASS;TestJournalAPIMassAssignRedactorPASS. - Disposition: closed / mitigate.
T-15-05 — schema / AutoMigrate
- Source: gormigrate IDs
202610060001–007; production plugin has noAutoMigrate(. - Test evidence (re-run 2026-10-06):
TestJournalTablesPASS;TestJournalMigrationsRollbackAndRemigratePASS. Gate--forbiddenrefuses production AutoMigrate. - Disposition: closed / mitigate.
T-15-06 — MorphName
- Source: hard-coded
Golem15\Journal\Models\Post/Category/Tag. - Test evidence (re-run 2026-10-06):
TestTranslatablePASS;TestPostTranslatableSmokePASS. - Disposition: closed / mitigate.
T-15-07 — admin access_posts
- Source: Posts controller
RequiredPermissions; List/FormExtendQuery owner scope withoutaccess_other_posts. - Test evidence (re-run 2026-10-06):
TestPostsAdminForbiddenPASS (403 without grant). - Disposition: closed / mitigate.
T-15-08 — stored XSS in content_html
- Source:
classes/format_html.gogoldmark without unsafe HTML;rejectUnsafefor script/iframe/event/js/vbscript/data. - Test evidence (re-run 2026-10-06):
TestFormatHTMLRejectsUnsafeHTMLand subtests script/iframe/event/javascript/vbscript/data PASS. - Disposition: closed / mitigate.
T-15-09 — publish permission
- Source: Store/Update refuse
publishedwithoutgolem15.journal.access_publish; adminForbiddenError. - Test evidence (re-run 2026-10-06):
TestJournalWriteUnauthenticatedpublish 403 PASS;TestPostsAdminCreateSmoke/publish_without_access_publishPASS. - Disposition: closed / mitigate.
T-15-10 — frontend token on writes
- Source: backend JWT audience only; no Apparatus personal tokens.
- Test evidence (re-run 2026-10-06):
TestJournalWriteUnauthenticatedfrontend-audience POST 401 PASS. - Disposition: closed / mitigate.
T-15-11 — Typesense leak
- Source:
search_use_typesensedefault false;ShouldBeSearchablefalse when unpublished or gate off. - Test evidence (re-run 2026-10-06):
TestSearchGateOffPASS (zero HTTP; must not skip). - Disposition: closed / mitigate.
T-15-12 — rate-limit XFF
- Source:
Plugin.Bucketskeyssurf.ClientIPwithTrustedProxies. - Test evidence (re-run 2026-10-06):
TestJournalBucketsPASS. - Disposition: closed / mitigate.
T-15-13 — envelope mixup
- Source: journal
writeAPIErrorPHP{error}string; must not use cabana admin{error:{code}}on public API. - Test evidence (re-run 2026-10-06):
TestJournalWriteUnauthenticatedPASS (string error, nodata). - Disposition: closed / mitigate.
T-15-14 — gate repudiation
- Source:
scripts/check-phase15.shJSON detector. - Test evidence:
--self-test(fail/skip/zero/no-tests/race/missing-named) executed as the first--allstage. - Disposition: closed / mitigate.
T-15-15 — unpublished lock prefix
- Source: API serialize uses raw
Title;console.UnpublishedTitlePrefixis import-only. - Test evidence (re-run 2026-10-06):
TestJournalAPIShowNeighborsPASS. - Disposition: closed / mitigate.
T-15-SC — package installs
- Source: plugin
go.modreplace of summercms only; goldmark v1.8.6 already required for FormatHTML. - Test evidence:
--layoutreplace check; nogo getof a new SaaS SDK this plan. - Disposition: closed / mitigate.
Submodule provenance
Host gitlinks plugins/golem15/{user,translate,journal} are mode 160000.
TestBootUserTranslateJournal PASS (re-run 2026-10-06). --layout requires
the three production IDs and CORS _journal/api/*.
API-coverage declaration
No external SaaS SDK this phase. Typesense is optional and default-off;
TestSearchGateOff observed zero outbound HTTP.