Changeset 0.28.4 (#190)

This commit is contained in:
2026-03-14 19:36:33 +00:00
parent fa6b04434a
commit 85d5e3cc13
54 changed files with 7355 additions and 102 deletions

View File

@@ -1,5 +1,108 @@
# Changelog
## [0.28.4] — 2026-03-14
### Summary
Security tier red team. 58 adversarial tests added to the ICD runner
exposing 5 bugs (2× P0, 2× P1, 1× P2), all fixed in this release.
Auth middleware rewritten with a user-status cache so deactivation and
role changes take effect within 30 seconds. `surfaces/` directory
added to the repo with a build script for packaging extension surfaces.
### Security Fixes
- **[P0] Deactivated user JWT accepted.** Auth middleware validated
signature + expiry only — never checked `is_active` in the database.
A deactivated user's existing JWT continued working until natural
expiry (15 min). Fix: `middleware/auth.go` now performs a cached DB
lookup (`UserStatusCache`, 30 s TTL) on every request. Deactivated
users receive 401 within seconds.
- **[P0] Demoted admin JWT retained admin access.** The `role` context
value was read from JWT claims, not the database. Demoting an admin
via `PUT /admin/users/:id/role` had no effect until the user's token
expired. Fix: role is now resolved from the DB on every request via
the same cached lookup. `RequireAdmin()` reads the live role.
- **[P1] Null byte in path parameter → 500.** Gin passed `%00` through
to the database layer, causing a scan error. Fix: new
`ValidatePathParams()` middleware rejects null bytes with 400.
- **[P1] 4096-char path parameter → 500.** Same root cause — oversized
params reached the DB. Fix: `ValidatePathParams()` rejects params
longer than 255 characters with 400.
- **[P2] CORS `Access-Control-Allow-Origin: *` in all environments.**
Fix: production mode now defaults to same-origin only. Override with
`CORS_ALLOWED_ORIGINS=https://app.example.com` (comma-separated).
Development mode retains `*`.
### Added
- **ICD Security Tier** — 58 adversarial tests across 6 domains:
auth-boundary (8), cross-tenant (20), input-validation (18),
session (6), escalation (7), transport (3). Red "Security" button
in the runner UI. Requires fixtures.
- **`UserStatusCache`** (`middleware/auth.go`) — per-user `{isActive,
role, fetchedAt}` cache with 30 s TTL. Shared by `Auth()`,
`AuthOrSession()`, and `AuthOrRedirect()`. Cache eviction on
`UpdateUserRole` and `ToggleUserActive` via callback.
- **`ValidatePathParams()` middleware** (`middleware/validate.go`) —
rejects null bytes and >255-char path params before they reach
handlers.
- **`surfaces/` directory** — source tree for extension surfaces with
`build.sh` that packages each subdirectory into `dist/<n>.surface`.
`hello-dashboard` and `icd-test-runner` moved from loose archives
into version-controlled source.
- **`surfaces/README.md`** — structure docs, build instructions,
conventions.
- ICD runner: `safeDelete()` now retries 2× with 500 ms backoff on
502/503, swallows cleanup errors with `console.warn` instead of
throwing.
- ICD runner: `assertDenied()` / `assertRawDenied()` helpers treat
502 as INCONCLUSIVE instead of CRITICAL.
### Changed
- `middleware.Auth()` signature: `(cfg)` → `(cfg, users, cache)`.
- `middleware.AuthOrSession()` signature: `(cfg, stores)` →
`(cfg, stores, cache)`.
- `middleware.AuthOrRedirect()` signature: `(cfg)` →
`(cfg, users, cache)`.
- `middleware.CORS()` signature: `()` → `(cfg)`.
- `AdminHandler` gains `OnUserChanged(func(userID string))` — wired
to `cache.Evict` in `main.go`.
- 10 test files updated for new middleware signatures.
- `.gitignore`: added `*.surface` under build artifacts.
- ICD runner manifest bumped to `0.28.4.0`.
### Removed
- `hello-dashboard.surface` at repo root (replaced by
`surfaces/hello-dashboard/` source directory).
### Verified (not bugs)
- Cross-tenant isolation: 20/20 — notes, channels, messages, tasks,
workspaces, projects, BYOK configs, memories, team providers/
personas/members all properly isolated.
- SQL injection: 7 payloads across 3 search endpoints — all
parameterized.
- XSS storage: 3 round-trip tests — no server crashes (frontend
sanitizes via DOMPurify).
- Escalation: self-promote, cross-role create, team-admin →
platform-admin, user_id substitution — all blocked.
- JWT tampering + alg=none: both rejected.
- Refresh token rotation + revocation: both working.
- Auth rate limiting: 429 after burst.
- Unauthenticated access: all 6 endpoints return 401.
### Known
- `[P2]` WebSocket `?token=` query parameter exposes JWT in server
logs and browser history. Informational — consider ticket exchange
pattern post-1.0.
- Cross-visitor session isolation: enforced by `session_auth.go`
channel binding, but no E2E test yet (requires public_link workflow
setup — defer to visitor E2E milestone).
## [0.28.3.5] — 2026-03-14
### Summary