docs(phase-19.1): add security threat verification
This commit is contained in:
96
.planning/phases/19.1-chat-satellites/19.1-SECURITY.md
Normal file
96
.planning/phases/19.1-chat-satellites/19.1-SECURITY.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
phase: "19.1"
|
||||
slug: "chat-satellites"
|
||||
status: verified
|
||||
# threats_open = count of OPEN threats at or above workflow.security_block_on severity (the blocking gate)
|
||||
threats_open: 0
|
||||
asvs_level: 1
|
||||
created: "2026-10-18"
|
||||
---
|
||||
|
||||
# Phase 19.1 — Security
|
||||
|
||||
> Per-phase security contract: threat register, accepted risks, and audit trail.
|
||||
|
||||
Implementation lives in the sibling repos `sm-userfriends-plugin`, `sm-forum-plugin`, `sm-chatbots-plugin`, `sm-herald-plugin`, `sm-chatvideo-plugin`; framework pieces are `modules/breakwater`, `modules/fetchguard` and `scripts/check-phase19.1.sh`. Paths below are relative to the meta repo.
|
||||
|
||||
---
|
||||
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description | Data Crossing |
|
||||
|----------|-------------|---------------|
|
||||
| JWT actor → `/_userfriends/api/v1` | untrusted body and path ids | friendship and group ids |
|
||||
| `user.Deleting` → userfriends GDPR cascade | deleted user must not leave owned groups orphaned | group ownership, member and ban rows |
|
||||
| JWT actor → `/_forum/api/v1` | untrusted markdown and context ids | post content (HTML sanitizer boundary) |
|
||||
| `ForumAccessResolver` / `BotAuthResolver` | host-supplied allow; default deny-all | access decisions |
|
||||
| JWT actor → chatbots `POST /dispatch` | untrusted input string; invoker never from body | command input |
|
||||
| Client → herald subscription | user-supplied push endpoint URL | URL (SSRF) |
|
||||
| Herald send job → push service | outbound HTTPS | push payloads |
|
||||
| Operator → `vapid-generate` | VAPID private key | secret (stdout only) |
|
||||
| JWT actor → `/_chatvideo/api/v1` | conversation id, join_code | session and call-link ids |
|
||||
| Token mint → client | LiveKit HS256 join token is a capability grant | signed token |
|
||||
| Authorizer → realtime subscribe | client-chosen `video:session:{id}` | channel name |
|
||||
| Golden fixtures → repo | captured payloads | must not include secrets |
|
||||
| Phase gate → CI | a skip must not count as green | test results |
|
||||
|
||||
---
|
||||
|
||||
## Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation | Status |
|
||||
|-----------|----------|-----------|----------|-------------|------------|--------|
|
||||
| T-19.1-01 | Information disclosure | `sm-userfriends-plugin/api/friends.go` | high | mitigate | Actor from `bouncer.User` (`api/auth.go:18-24`); accept/reject/cancel/unfriend/list scoped to actor in `classes/friendship_service.go`, `ErrNotFound` → 404 (`api/errors.go:54-55`); `community-join` 10/min (`routes.go:60-63,85`) | closed |
|
||||
| T-19.1-08 | Information disclosure | `sm-userfriends-plugin/boot.go` onUserDeleting | medium | mitigate | `boot.go:79-88` on `ev.Tx`; heir/hard-delete, member and ban removal in `classes/group_service.go:729-766`; `TestUserDeletingHeir` (required postgres test in gate) | closed |
|
||||
| T-19.1-04 | Tampering | `sm-forum-plugin/classes/forum_service.go` write path | high | mitigate | `breakwater.Render` on create/reply/edit/preview (`forum_service.go:281,330`, `lifecycle.go:232,387`); allow-list policy and forced `Purify` (`breakwater.go:66-100`); stored HTML served as-is (`api/json.go:132-134`); XSS corpus in `TestForumSemanticHTML`, `TestHTTPTracer`, breakwater tests | closed |
|
||||
| T-19.1-10 | Elevation of privilege | `sm-forum-plugin/classes/deny_all.go` | high | mitigate | DenyAll default on nil/getter/setter (`forum_service.go:82-127`); Boot Warn (`boot.go:59-61`); context-scoped lookups; forbidden/not-found → 404; `TestAccessResolverDefault` | closed |
|
||||
| T-19.1-07 | Elevation of privilege | `sm-chatbots-plugin/api/commands.go` | high | mitigate | Identity only from `bouncer.User` (`api/auth.go`); `DispatchBody` carries only `Input`; spoofed `user_id` test in `http_tracer_test.go:110` | closed |
|
||||
| T-19.1-11 | Elevation of privilege | `sm-chatbots-plugin/classes/deny_all.go` | high | mitigate | DenyAll default, getter/setter fallback (`plugin.go:83-100`), Boot Warn (`boot.go:54-59`); `TestBotAuthResolverDefault` | closed |
|
||||
| T-19.1-02 | Elevation of privilege | `sm-herald-plugin/classes/subscription_service.go`, `sender.go` | high | mitigate | `ValidateEndpoint` at subscribe and patch (`subscription_service.go:39-110`); `fetchguard.PublicOnlyMode` client at send (`sender.go:72-79,205`); `TestSubscribeEndpoint`; no `EndpointGuard` seam | closed |
|
||||
| T-19.1-06 | Information disclosure | `sm-herald-plugin/console/vapid_generate.go` | high | mitigate | Key pair printed via command output only (`vapid_generate.go:21-27`); no key material in slog calls; `GET vapid-public-key` returns `publicKey` only (`api/push.go:113-125`) | closed |
|
||||
| T-19.1-03 | Elevation of privilege | `sm-chatvideo-plugin/classes/livekit.go` token mint | high | mitigate | `sub` = user id, HS256, `ErrNotConfigured` on empty secret (`livekit.go:49-83`); secret from server config only; mint only after `requireParticipant`; `TestLiveKitTokenClaims` | closed |
|
||||
| T-19.1-05 | Elevation of privilege | `sm-chatvideo-plugin/classes/authorizer.go` | high | mitigate | Strict `video:session:{digits}` parse (`authorizer.go:84-103`); participant check (`:54-79`); `prs` capability only; `TestVideoAuthorizer` | closed |
|
||||
| T-19.1-09 | Information disclosure | `sm-chatvideo-plugin/api/call_links.go` | medium | mitigate | `call-link` 10/min (`routes.go:38-40,63`); uniform `ErrNotFound` → 404 (`video_service.go:266-282`, `api/errors.go:37-38`) | closed |
|
||||
| T-19.1-12 | Information disclosure | `testdata/goldens/` (five plugins) | medium | mitigate | Fixtures verified clean (no VAPID private key, LiveKit secret, JWT or key-shaped strings). The planned grep gate is not in `check-phase19.1.sh`; follow-up adds it with a `--self-test` plant | open — below high threshold (non-blocking) |
|
||||
| T-19.1-SC | Tampering | `go.mod` of five plugins, `scripts/check-phase19.1.sh` | high | mitigate | Only decision-noted pins (webpush-go v1.4.0, livekit server-sdk-go/v2 v2.19.0, bluemonday indirect via framework); userfriends `sm-chat-plugin` require accepted as override in VERIFICATION; gate fail-closed (`check-phase19.1.sh:100-145`) with `--self-test` (`:199-251`) | closed |
|
||||
|
||||
*Status: open · closed · open — below high threshold (non-blocking)*
|
||||
*Severity: critical > high > medium > low — only open threats at or above workflow.security_block_on count toward threats_open*
|
||||
*Disposition: mitigate (implementation required) · accept (documented risk) · transfer (third-party)*
|
||||
|
||||
### Unregistered findings (from 19.1-REVIEW.md, no threat ID)
|
||||
|
||||
Security-adjacent review findings still `open` in `19.1-REVIEW-DISPOSITION.md`. Not blocking; listed so they are not lost.
|
||||
|
||||
| Finding | Location | Note |
|
||||
|---------|----------|------|
|
||||
| WR-01 | `sm-chatvideo-plugin/classes/livekit.go:125-146` | "Operator URL → LiveKit: TrustedMode only" boundary not enforced; the fetchguard client is built but the SDK uses its own HTTP client. Operator-configured URL, low risk. |
|
||||
| WR-04 | `sm-chatbots-plugin/classes/dispatcher.go:30-42` | `POST /dispatch` never calls `IsAvailable`; a host-registered restricted command is invocable by any JWT user. Latent (only `/help` registered). |
|
||||
| WR-05 | `sm-chatvideo-plugin/updates/202610180001_create_golem15_chatvideo_baseline.go:27-28` | No `user.Deleting` listener; `ON DELETE CASCADE` drops sessions incl. other users' participant rows, LiveKit room not deleted, issued tokens valid until `token_ttl`. |
|
||||
| IN-02 | `sm-chatvideo-plugin/classes/livekit.go` | `screen_share` config not applied; tokens always grant `canPublish`. |
|
||||
| IN-04 | userfriends `group_invites.created_by` | No FK, not cleared on user delete. |
|
||||
|
||||
---
|
||||
|
||||
## Accepted Risks Log
|
||||
|
||||
No accepted risks.
|
||||
|
||||
---
|
||||
|
||||
## Security Audit Trail
|
||||
|
||||
| Audit Date | Threats Total | Closed | Open | Run By |
|
||||
|------------|---------------|--------|------|--------|
|
||||
| 2026-10-18 | 13 | 12 | 1 (non-blocking, medium) | msd-security-auditor |
|
||||
|
||||
---
|
||||
|
||||
## Sign-Off
|
||||
|
||||
- [x] All threats have a disposition (mitigate / accept / transfer)
|
||||
- [x] Accepted risks documented in Accepted Risks Log
|
||||
- [x] `threats_open: 0` confirmed
|
||||
- [x] `status: verified` set in frontmatter
|
||||
|
||||
**Approval:** verified 2026-10-18
|
||||
Reference in New Issue
Block a user