Repository navigation
Release v0.3.1: Web Dashboard & Workspace UX - #114
Conversation
…e button emoji policy
…wn toast to blocked & delete views
…task status bypass
…s without emoji clutter (fixes #107)
…coded toast embeds (fixes #107)
…al-messages Arch/107 unify ephemeral messages
…-project-tags feat(adapters): prune forum tags upon project archive within 20-tag limit
…sibility feat(discord_bot): filter active project visibility and standardize permission toasts
…d guard public posts
…enus feat(discord_bot): dismissable hub menus and public message guardrails
…portal feat(api): add read-only Web Dashboard & Portal Overlay (fixes #21)
espdesign
left a comment
There was a problem hiding this comment.
Review: Release v0.3.1: Web Dashboard & Workspace UX
release/v0.3.1 → main · 19 commits · +12,613/−1,292 · all 8 CI checks green. Layering looks clean (adapters → services/ports → domain) and the dashboard tests are thorough. Main concerns are in the new web portal.
Blocking
1. Stored XSS in the dashboard frontend — src/adapters/api/static/app.js
Task titles, bodies, assignee display names, squad names, tag names, and dependency titles are interpolated into innerHTML unescaped:
app.js:442(col.titlefrom assignee/group),:467(t.title),:469(t.assignee_name),:499(t.title),:503-504(assignee_name,combinedMeta),:572-573(item.title)
These values originate from Discord users (task title/body, display names), so a member who can create a task can run JS in the session of anyone viewing the dashboard (leads/admins). Cookies are HttpOnly, but same-origin fetch can still exfiltrate all visible project data. Fix by escaping (escapeHtml) or building nodes with textContent / createElement, as openInspector already does.
2. Forgeable sessions when SESSION_SECRET_KEY is unset — src/config.py:71, auth_routes.py:112,186, dependencies.py:67
The secret defaults to a public constant (dev-session-secret-change-in-production) with no production validation. If an operator forgets to set it, anyone can mint a valid session for a known Discord user ID (IDs are public) and get that member's dashboard access. Add a model_validator that refuses to start when ENVIRONMENT != development and the secret is empty/default.
Should fix
3. OAuth state parameter missing — auth_routes.py:139-206
Login/callback have no state cookie, so the flow is open to login-CSRF (attacker forces a victim into the attacker's session). Generate a random state, store it in a short-lived SameSite=Lax cookie, and verify it in /auth/callback.
4. Cookies lack secure — auth_routes.py:130-137, 200-207
Set secure=True outside development (and consider the __Host- prefix). Also note /auth/logout is reachable via GET, enabling forced-logout CSRF.
5. Third-party CDN scripts without SRI or CSP — index.html:9-11
unpkg.com scripts (dagre/cytoscape) run on an authenticated dashboard with no integrity/crossorigin and no Content-Security-Policy anywhere in src/. A CDN compromise yields full XSS. Self-host the bundles or add SRI hashes plus a CSP.
Performance
6. N+1 queries in dashboard endpoints — dashboard_routes.py:146,217,262-263
list_visible_projectscallsget_project_workspaceper project.get_project_workspace_endpointcallssquad_service.list_membersper squad, andcan_mutate_task/can_delete_taskper task; each of those re-fetches the project and re-lists squad roles (auth_service.py:can_mutate_task). For a few hundred tasks this is hundreds of queries per request.
Resolve project role IDs once per request and pass them down / batch the auth checks.
Minor / nits
prune_archived_project_tagscan delete standard tags (forum_tags.py:352-375): name-only_matches_project_tagmeans an archived project namedCompleted,High Priority, orOverdueprunes the standard PM tag (it gets re-added bysetup_forum_tags, but there's a window of missing tags and unnecessary churn). ExcludeSTANDARD_PM_TAG_DEFINITIONSnames from pruning.- Dev fallback is powerful and easy to leave on:
ENVIRONMENTdefaults todevelopment; withDISCORD_CLIENT_SECRETunset,/auth/login?user_id=…&is_guild_admin=truegrants admin (auth_routes.py:108-128,dependencies.py:110-118). Consider requiring an explicit opt-in flag rather than inferring fromENVIRONMENT. - Duplicated agent assets: the OpenSpec skills/commands are committed twice (~5k lines) under
.agents/and.opencode/. Pick one source of truth to avoid drift. dashboard_routes.py:183/345look up projects by ID without guild scoping; safe today under a single-guild deployment, but worth assertingproject.guild_id == principal.guild_idif multi-guild is ever added.
Positives
- Public-message deletion guard in
MenuSessionManager.register_menu(menu_manager.py:37-60) correctly enforces the AGENTS.md invariant, with tests. - Toast unification (
ui_constants.py+send_toast/edit_toast) removes emoji clutter and standardizes dismissal timing. - Version bump is consistent across
pyproject.toml,src/__init__.py, anduv.lock; CI including Docker build and migration drift is green.
Release v0.3.1
This release follows
v0.3.0-beta.1and focuses on the Web Dashboard portal, workspace visibility and safety, forum lifecycle, and UI consistency. The version is now0.3.1(no beta suffix).Key Highlights
1. Web Dashboard & Portal (#21)
2. Workspace Visibility & Safety
/pmmenu.3. Forum Lifecycle
4. UI/UX Consistency
.icslink and enforce the button emoji policy.5. Tooling & Documentation
pyproject.toml,src/__init__.py, anduv.lock.Verification
make check: Ruff lint, formatting check, and 536 tests passed.make test-e2e: 6 E2E user journeys passed.Full Changelog: v0.3.0-beta.1...develop