Changeset 0.28.4 (#190)
This commit is contained in:
103
CHANGELOG.md
103
CHANGELOG.md
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user