Skip to content

Unify session authorization and fix panel regressions - #138

Merged
jhd3197 merged 21 commits into
mainfrom
dev
Sep 5, 2026
Merged

Unify session authorization and fix panel regressions#138
jhd3197 merged 21 commits into
mainfrom
dev

Conversation

@jhd3197

@jhd3197 jhd3197 commented Sep 5, 2026

Copy link
Copy Markdown
Owner

This promotes the security review fixes and the cleanup that followed them from dev into main. The review found that the panel's entry points did not enforce the same authorization: a token still waiting on its 2FA code could mint a one-time login link and come back with a full session, an admin-owned read-only API key could trade itself for browser credentials, a viewer could join another user's socket room, and the built-in AI tools would list applications the REST API had just refused to show that same person. The fix is one shared session policy instead of four separate ones. Every JWT now carries a session id and the account's auth version, and a single validator decides whether that session is still valid, so HTTP, Socket.IO and AI tool calls agree the moment a session is revoked or an account is disabled. Credential minting moved to a new JWT-only gate rather than the existing rbac decorators, because those also accept API keys, and converting those routes would have widened the authenticated surface instead of narrowing it. The branch also carries the shared-code consolidation that came out of the same review, two UI regression fixes, and README numbers regenerated from measurement scripts rather than estimates.

Contributors

Highlights

  • Two-factor authentication can no longer be stepped around. A pending code prompt is a five-minute token that cannot administer login links, and redeeming a link still asks for the target account's second factor.
  • Signing out ends the session on every worker. Changing a password or disabling an account also signs that account out of its other sessions, instead of leaving already-issued tokens alive for their full lifetime.
  • Live updates only reach people who can already see the resource. Applications, servers, deployments, runs and terminals each get their own subscription check, and access is rechecked before delivery rather than trusted from connection time.
  • The assistant sees what you see. Its built-in tools run as the actual caller, reuse the same workspace and ownership rules as the REST API, return a narrow set of fields, and its privacy filter now covers structured tool output rather than only plain strings.
  • Users is a real access-review screen: MFA, passkey enrollment and sign-in provider columns, an "Admins without MFA" saved view, locale-aware dates, and delete, enable and disable actions that guard against double-clicks and report their failures.
  • Users and Invitations have their table background back in both light and dark themes.
  • Opening Logs or Exec on a Docker container closes the inspector first, so it can no longer cover the surface you just opened.
  • Service metrics clear themselves when a container disappears or a poll fails, and switching applications resets the view so a late response from the previous app cannot overwrite it.
  • API keys restricted to specific scopes are refused on endpoints that have not declared one. Wildcard and legacy full-access keys are unchanged, but integrations relying on restricted keys need a reviewed endpoint policy rather than a widened key.
  • The README's "By the Numbers" table is generated from measurement scripts and reviewed snapshots, with the RAM and image-size claims removed rather than restated.
Technical changes

Session policy and MFA

  • Replaced the check_2fa_pending before_request hook, whose '/auth/login' in path exemption also matched /auth/login-links, with a @jwt.token_in_blocklist_loader that runs validate_session_claims for every JWT-protected route, so no path prefix can turn a pending token into a session.
  • Added app/middleware/session_auth.py. issue_session_tokens mints an access and refresh pair sharing one session_id, and validate_session_claims requires the right token type, a non-pending claim, an unrevoked session id, an unexpired exp, an active user, and a matching auth_version.
  • User.auth_version (new column, random hex) plus the new revoked_sessions table give per-account and per-session-family revocation. Migration 097_user_auth_version covers existing-install, fresh-install and downgrade paths.
  • User.set_password and a @validates('is_active') hook both call revoke_sessions(), which rotates auth_version and deletes that user's outstanding login links.
  • POST /auth/logout persists a RevokedSession for the current session_id, so sign-out survives across workers instead of only clearing browser storage.
  • PUT /auth/me requires the current password to set a new one. Accounts with no local password (SSO or passkey) must have authenticated within the last 300 seconds, and refresh copies auth_time forward so it cannot be renewed by refreshing. The response reissues tokens so the acting browser stays signed in.
  • Pending 2FA tokens changed from expires_delta=False, which produced no exp claim at all despite the comment claiming a short default, to an explicit five-minute lifetime in both auth.login and sso._complete_sso_login.
  • create_login_link moved from @admin_required to @session_required plus require_admin_user(). redeem_login_link returns requires_2fa with a temp token when the target account has TOTP enabled.
  • two_factor.verify_2fa_code resolves its user through validate_session_claims(..., allow_pending=True) instead of User.query.get, and reports invalid and expired tokens uniformly.

API key scopes

  • enforce_request_scope runs in the X-API-Key before_request. A key with a non-wildcard, non-empty scope list is rejected with 403 on any endpoint carrying no _sk_api_scopes declaration, and each declared scope is checked independently of the owner's role.
  • require_scope stamps wrapper._sk_api_scopes and merges nested declarations, so an outer RBAC wrapper preserves them through functools.wraps.
  • The API-key before_request pops g.api_key and g.api_key_user first, so a long-lived app context cannot leak credentials between requests.

Sockets

  • AuthorizedSocketIO.emit rechecks the audience before each server-side delivery via _prune_audience, which inspects only that delivery's room and drops sids that no longer pass _room_allowed.
  • handle_connect validates through validate_session_claims, rejecting refresh tokens, pending-MFA tokens and revoked sessions. Claims are stored per-sid and revalidated by _client_user, which disconnects the socket on a role change.
  • handle_join_room replaced its arbitrary room-name accept with _room_allowed: per-user rooms match the caller only, logs_<app_id> goes through app_access_tier, deploy_* and run_* through the new run_access.can_read_run, terminal rooms require ownership of the session plus _server_visible, and job:* streams require an operator role.
  • _metrics_tick and _container_status_tick stopped broadcasting to room=None. Metrics go per-subscriber, and container statuses are filtered per subscriber by _app_visible.
  • subscribe_container_logs requires application visibility, subscribe_logs (host log files) requires admin, and the deploy and run channels gained auth= callbacks. Every handler coerces non-dict payloads instead of calling .get on them.
  • app/services/run_access.py centralizes run visibility so api/runs.py and the socket channels answer identically.

AI tools and chat

  • Built-in tools receive the live caller through a tool_caller ContextVar. _caller_validator captures the session claims up front and re-resolves the user after the model round-trip and again after write confirmation, so an authorization change mid-turn cancels the action.
  • list_applications and list_servers run through WorkspaceService.scope_query with ownership and grant scoping, and both return a fixed field summary rather than to_dict(). The old shape leaked paths, repository and build configuration, and private routing fields.
  • ToolDescriptor.admin_only mirrors REST's admin gate for host-wide tools (list_databases, restart_docker_container, stop_docker_container), and the Docker write tools refuse protected ServerKit containers.
  • _maybe_redact_result walks dicts, lists and scalars through mask_payload and redact_input instead of returning non-string results untouched. _filter_secrets adds deterministic regex filtering for private keys, Bearer and Basic headers, credentials in URLs, and sensitive key assignments, independent of the optional PII detector.
  • New AIProtectionError: an enabled protection that fails now surfaces a 503 instead of silently passing the original text through. Tool exception text is no longer returned to the model, since service messages can carry connection strings.
  • Chat bounds: 128 KiB request bodies (including chunked, via request.max_content_length), 16,000-character message and page-context limits, one active turn per user and eight panel-wide, and a cancellation-aware bounded stream queue. unregister_gate takes the gate identity so a late finalizer cannot unregister its successor, and chat_confirm checks the pending action's conversation_id.

Passkeys

  • Repaired compatibility with the pinned WebAuthn 2.5.0 API: options_to_json, and AuthenticatorSelectionCriteria, ResidentKeyRequirement and AuthenticatorTransport enums instead of dict and string literals, with transports read from either the credential or its response.
  • Registration and authentication both pass require_user_verification=True and moved from preferred to REQUIRED user verification. Authentication additionally rejects a credential that does not belong to the named user.

UI regressions

  • ContainersTab gained openContainerLogs and openContainerExec, which clear selectedContainer in the same render that opens either surface, so the Docker-specific inspector layer cannot cover the shared Drawer and Dialog portals. Both the row actions and the inspector's own buttons route through them.
  • MetricsTab split into an inner component keyed by a runtime key built from app.id, app.name, app.app_type, app.server_id and app.container_id, so switching applications remounts with empty state instead of showing the previous app's numbers. A requestId ref discards responses from superseded requests, an empty container list and a failed poll both clear stats and processInfo, and the hand-rolled empty block became the shared EmptyState.

Settings and access review

  • .users-table-container owns background: var(--surface), resolving the contradiction where a more specific rule stripped the shared .sk-dtable-wrap surface and left rows with color only on hover. The Tailwind-flavored text-destructive and text-warning utilities on those actions became users-action--* SCSS classes.
  • UsersTab adds mfa, passkey and authProvider columns plus the "Admins without MFA" saved view, swaps its page-local delete modal for the shared useConfirm, uses useFormat instead of hardcoded en-US, translates status labels, and disables actions while one is in flight. InvitationsTab gained the same pending guards and surfaces load, revoke and resend errors that were previously swallowed.
  • New browser scripts drive Chromium over Settings surfaces, shared controls, metrics, authentication and layout hooks, and backup forms, covering both themes and error states. The frontend workflow installs Chromium, runs them, and uploads the screenshots.

Security CI

  • security-scan.yml gained a weekly schedule and workflow_dispatch, permissions: contents: read, extension sources plus requirement files and frontend/package*.json in its path filters, and an npm audit --package-lock-only --omit=dev --audit-level=high job.
  • Bandit is pinned to 1.9.3 and emits JSON. scripts/check-bandit-report.py gates it against scripts/security/bandit-exceptions.json, which binds each accepted finding to one function's AST sha256 and a count. The blanket --skip B602,B402,B321,B202 is gone, and only the two FTP compatibility findings remain, with a recorded reason and review date. The full report uploads even when the gate fails.

Backend consolidation

  • Extracted application_lifecycle_service.py (start, stop, restart, local BYO-image compose materialization, build-pack single-container detection) and repository_application_service.py out of app/api/apps.py, which shrank by roughly 314 lines. The controller-boundary baseline dropped from 511 to 496.
  • server_metrics_service.latest_metrics_by_server replaces the per-server ORDER BY timestamp DESC LIMIT 1 loop in FleetMonitorService.get_fleet_overview and the Prometheus exporter with one windowed query, plus joinedload(Server.group). The order_by='id' mode preserves the list view's insertion-order semantics.
  • backup_schedule_service.py centralizes the next-run and interval math shared by backup_service and the API. app/utils/actor.current_actor_id and connect_format.iso_datetime replace duplicated helpers, and ResourceTierService._detect_container delegates to host_inventory_service.
  • Opt-in request_profiling middleware adds Server-Timing with request duration, SQL duration and statement count behind SERVERKIT_PROFILE_REQUESTS. It installs no SQLAlchemy listeners when disabled and never emits SQL text or parameters.

Frontend consolidation

  • Context files split their use* hooks into sibling modules (useAuth.js, useTheme.js, useToast.js and the rest), and buttonVariants moved out of button.jsx, clearing the react-refresh/only-export-components warnings at the source.
  • Replaced the blanket JSXAttribute[name.name="style"] ban with a custom serverkit/no-static-inline-styles rule that reports only fixed presentation, including conditional and template-literal values, and leaves computed geometry alone.
  • scripts/lint.mjs wraps ESLint with a per-file, per-rule warning baseline over git-tracked sources. lint-warning-baseline.json is now {}, down from 923 warnings, and npm run lint:baseline regenerates it.
  • scripts/check-style-cascade.mjs compiles main.scss and diffs rendered computed styles in Chromium before and after an ownership change. STYLE_OWNERSHIP_CEILING went from 114 to 0 as duplicate class definitions were consolidated into single-owner partials (_monitoring.scss into _monitors.scss, plus new _agent-fleet.scss, _style-guide.scss, _detail-tabs.scss, _empty-state.scss and _connection-status.scss).
  • WorkspaceServicesTab and WorkspaceSitesTab collapsed into a shared WorkspaceApplicationsTab keyed by kind. The unused appdetail duplicates (OverviewTab, LogsTab, PackagesTab, CommandsTab, GunicornTab) were deleted, leaving only the still-imported BuildTab and DeployTab.
  • New utils/backupSchedule.js, utils/metricsSubscription.js and utils/runRecipe.js, plus the useBackupSchedules hook, pull logic out of Backups.jsx (178 lines lighter) and useMetrics.js. useForm absorbed the repeated submit and error handling.
  • Swept ?k=${v} interpolations in services/api/* through encodeURIComponent, including the file-download token. docs/MIGRATION_INVENTORY.md records that ratchet as an invariant at 0.

Measurements and docs

  • scripts/measure-repository.py, frontend/scripts/measure-build.mjs and scripts/update-readme-measurements.py regenerate the README table between the BEGIN/END GENERATED MEASUREMENTS markers from docs/measurements/*.json. measurements-ci.yml re-runs the tools and checks all four translated READMEs against the reviewed snapshot.
  • The numbers changed with the method. "1,350+ REST endpoints" is now 1,212 core route declarations across 104 blueprints, the UI bundle is stated as 3.31 MB total gzipped JS and CSS including lazy chunks, locales and vendor shims rather than 1.75 MB, and the ~180 MB RAM, 501 MB image and "under 2 minutes" claims were removed rather than re-estimated. docs/METRICS.md documents each definition and its reproduction command.
  • scripts/profile-api.py drives the Server-Timing middleware for local latency and query-count profiling.
  • docs/reviews/2026-09-05-serverkit-review.md, -remediation.md and the historical -security-probes.py record the review, the fix-by-fix handoff, upgrade behavior, and what remains open: aggregate AI spend accounting, CSP unsafe-inline and unsafe-eval, plugin tool authorization, and the existing audit_logs.user_id account-deletion issue found during cross-review.

Upgrade notes

  • Apply Alembic migration 097_user_auth_version through the normal upgrade process.
  • Existing JWTs become invalid, so everyone signs in again once after upgrading.
  • Restricted API-key integrations may receive 403 where an endpoint has no declared scope. Those endpoints need a reviewed scope policy; widening the key is not a substitute.
  • Passkeys must support authenticator user verification. A verified passkey remains an independent sign-in method, and the Users MFA column reflects TOTP enrollment specifically.
  • Version moved to 1.9.27, with [skip version] on the final fix so CI did not bump again.

dependabot Bot and others added 16 commits September 3, 2026 04:59
Bumps [@humanfs/node](https://github.com/humanwhocodes/humanfs/tree/HEAD/packages/node) from 0.16.7 to 0.16.8.
- [Release notes](https://github.com/humanwhocodes/humanfs/releases)
- [Changelog](https://github.com/humanwhocodes/humanfs/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/humanwhocodes/humanfs/commits/node-v0.16.8/packages/node)

---
updated-dependencies:
- dependency-name: "@humanfs/node"
  dependency-version: 0.16.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
…/humanfs/node-0.16.8

chore(deps-dev): bump @humanfs/node from 0.16.7 to 0.16.8 in /frontend
Add session-family revocation and migration 097, require current credentials for password changes, constrain API keys, and require verified WebAuthn authentication. Update browser token handling and add boundary regressions. Existing JWTs require fresh login; restricted keys deny undeclared endpoints.
Require valid access-token sessions, scope application and server rooms, and enforce run visibility consistently across sockets and polling. Add positive-delivery and cross-user isolation regressions.
Scope built-in tools to the live caller, recheck write authorization after confirmation, redact structured results, and fail closed when protections fail. Bound chat inputs, concurrent turns, and cancellation-aware streaming queues.
Expose MFA and passkey enrollment, use shared confirmations and locale formatting, and guard pending user and invitation actions. Add Chromium regressions for both themes and failure states to frontend CI.
Schedule dependency and extension scans, audit production frontend dependencies, and replace category suppressions with reviewed function-specific exceptions. Preserve full reports and test the exception gate.
Preserve synthetic historical probes, document fixes and validation, and list upgrade behavior and remaining hardening work with the local implementation commits.
Centralize backup schedule calculations and shared request/form flows, encode query values, and move repository finalization into its service. Add browser CI coverage, repair stale test fixtures, and provide opt-in request and build measurement tools.
Generate all four README tables from reviewed source and build snapshots, distinguish collection and asset sizes from runtime results, and remove unsupported footprint claims. Add a consistency check in CI and document how to reproduce profiling and measurements.
Copilot AI lite review requested due to automatic review settings September 5, 2026 19:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The updated MetricsTab can display stale Docker metrics after container mismatches and should reset state/loading to avoid incorrect UI output.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR closes multiple authorization inconsistencies by introducing a shared session-validation policy (applied across HTTP, Socket.IO, and AI tool execution) and tightening API-key scope enforcement so restricted keys cannot access endpoints without explicit scope declarations. It also includes broad frontend consolidation (context hook splits, shared UI primitives), SCSS ownership cleanup with new ratchets, browser regression fixtures, and documentation/measurement updates.

Changes:

  • Unifies authentication/session validation across REST, sockets, and AI tool calls; adds session revocation primitives and related migrations/tests.
  • Enforces API-key request scopes and improves per-request credential isolation; centralizes visibility policy for “run” resources.
  • Frontend refactors for shared hooks/components/styles, adds browser regression fixtures, and introduces lint/style ratchets + measurement/doc refreshes.
File summaries
File Description
VERSION Bumps panel version.
scripts/security/README.md Documents Bandit gate + exception policy.
frontend/tests/browser/settings.jsx Vite browser-regression fixture entry.
frontend/tests/browser/settings.html Fixture HTML shell.
frontend/tests/browser/controls.html Shared-controls contract HTML shell.
frontend/src/utils/runRecipe.js Extracts recipe-run toast/navigation adapter.
frontend/src/utils/metricsSubscription.js Centralizes metrics socket subscription lifecycle.
frontend/src/services/widgetQueries.js Adds shared widget query cache helpers.
frontend/src/services/api/registry.js Binds API modules with duplicate detection.
frontend/src/services/api/index.js Switches ApiService binding to registry helper.
frontend/src/services/api/testSandbox.js Encodes query params.
frontend/src/services/api/telemetry.js Encodes query params.
frontend/src/services/api/snapshots.js Encodes query params.
frontend/src/services/api/monitors.js Encodes query params.
frontend/src/services/api/manifests.js Encodes query params.
frontend/src/services/api/docker.js Encodes query params.
frontend/src/services/api/dns.js Encodes query params.
frontend/src/services/api/deploymentJobs.js Encodes query params.
frontend/src/services/api/databases.js Encodes query params.
frontend/src/services/api/containerOps.js Encodes query params.
frontend/src/services/api/bandwidth.js Encodes query params.
frontend/src/hooks/useServerQuery.js Workspace-scoped query/mutation plumbing.
frontend/src/hooks/useRecipeCatalog.js Re-exports runRecipe from utils.
frontend/src/hooks/useFormat.js Cleans memo deps/comment.
frontend/src/hooks/ai/useFocusTrap.js Fixes focus restore target capture.
frontend/src/contexts/useAuth.js New split-out auth hook/context.
frontend/src/contexts/useToast.js New split-out toast hook/context.
frontend/src/contexts/useTheme.js New split-out theme hook/context.
frontend/src/contexts/useWorkspace.js New split-out workspace hook/context.
frontend/src/contexts/useLocale.js New split-out locale hook/context.
frontend/src/contexts/useLayout.js New split-out layout hook/context.
frontend/src/contexts/useConfirmContext.js New split-out confirm hook/context.
frontend/src/contexts/useShellDock.js New split-out shell dock hook/context.
frontend/src/contexts/useServerkitAI.js New split-out AI hook/context.
frontend/src/contexts/useResourceTier.js New split-out resource-tier hook/context.
frontend/src/contexts/useNotifications.js New split-out notifications hook/context.
frontend/src/contexts/WorkspaceContext.jsx Adopts split-out WorkspaceContext export.
frontend/src/contexts/ToastContext.jsx Adopts split-out ToastContext export.
frontend/src/contexts/ThemeContext.jsx Adopts split-out ThemeContext export.
frontend/src/contexts/LocaleContext.jsx Adopts split-out LocaleContext export.
frontend/src/contexts/LayoutContext.jsx Adopts split-out LayoutContext export.
frontend/src/contexts/ConfirmContext.jsx Adopts split-out ConfirmContext export.
frontend/src/contexts/ShellDockContext.jsx Adopts split-out ShellDockContext export.
frontend/src/contexts/ResourceTierContext.jsx Adopts split-out ResourceTierContext export.
frontend/src/contexts/NotificationsContext.jsx Adopts split-out NotificationsContext export.
frontend/src/contexts/OperationsContext.jsx Updates auth hook import.
frontend/src/contexts/AIContext.jsx Adopts split-out AIContext export.
frontend/src/contexts/WalkthroughContext.jsx Updates auth hook import.
frontend/src/components/ui/buttonVariants.js Extracts Button variant mapping helper.
frontend/src/components/ui/button.jsx Uses extracted buttonVariants; narrows exports.
frontend/src/components/ui/alert-dialog.jsx Imports buttonVariants from helper module.
frontend/src/components/ui/tabs.jsx Uses shared Button for overflow trigger.
frontend/src/components/ui/input.jsx Removes unused React import.
frontend/src/components/ui/label.jsx Removes unused React import.
frontend/src/components/ui/textarea.jsx Removes unused React import.
frontend/src/components/ui/badge.jsx Removes unused React import.
frontend/src/components/ds/filterValues.js Extracts filter value helpers.
frontend/src/components/ds/index.js Re-exports filter helpers from new module.
frontend/src/components/ds/Drawer.jsx Adjusts drawer width styling.
frontend/src/components/ds/DataTable.jsx Uses shared Button for “clear filters”.
frontend/src/components/ds/ColumnsMenu.jsx Uses shared Button for menu items.
frontend/src/components/ds/GroupMenu.jsx Uses shared Button for menu items.
frontend/src/components/ds/SegControl.jsx Uses shared Button for segments.
frontend/src/components/ds/PageTopbar.jsx Uses shared Button for overflow trigger.
frontend/src/components/ds/MetricCard.jsx Uses shared Button for clickable KPI cards.
frontend/src/components/ds/KpiBand.jsx Uses shared Button for “more stats”.
frontend/src/components/ds/grid/DataGrid.jsx Uses shared Button for group toggles.
frontend/src/components/ds/grid/GridFilterButton.jsx Uses shared Button for filter affordance.
frontend/src/components/ds/grid/GridBulkBar.jsx Uses shared Button for clear action.
frontend/src/components/ds/grid/useTableChrome.js Cleans deps/comment.
frontend/src/components/service-detail/MetricsTab.jsx Refactors metrics loading to callback-based polling.
frontend/src/styles/main.scss Adds new shared partial imports.
frontend/src/styles/components/_connection-status.scss New shared connection status pill styles.
frontend/src/styles/components/_form-affordances.scss New shared form layout utility styles.
frontend/src/styles/components/_empty-state.scss Consolidates empty-state styling into shared partial.
frontend/src/styles/pages/_ssl.scss Removes page-local .spin definition.
backend/app/utils/actor.py Adds request-scoped “current actor id” helper.
backend/app/services/shared_resource_service.py Uses shared actor helper for attribution.
backend/app/services/run_access.py Centralizes run visibility policy for REST+sockets.
backend/app/middleware/api_key_auth.py Clears g.* credentials per request + scope enforcement.
backend/app/api/runs.py Applies run visibility guard to logs endpoint.
backend/app/api/sso.py Updates pending-2FA token TTL + session token minting.
backend/app/api/backups.py Returns server timezone with schedules.
backend/app/models/revoked_session.py Adds persistent revoked-session model.
backend/app/models/init.py Exposes RevokedSession model.
backend/migrations/versions/097_user_auth_version.py Adds auth_version + revoked_sessions schema.
backend/config.py Adds request profiling toggle.
backend/tests/factories.py Adds access_token_for helper for JWT test claims.
backend/tests/test_app_deploy_jobs.py Adds unwind-on-setup-failure coverage.
backend/tests/test_restore_points_api.py Updates expectations for revoked/inactive session behavior.
backend/tests/test_deployment_jobs_authz.py Improves assertions with response context.
backend/tests/test_shared_queue_authz.py Improves assertions with persona context.
docs/API_SURFACE.md Documents new logout endpoint.
docs/measurements/repository.json Adds regenerated measurement snapshot.
.gitignore Ignores browser regression screenshots output.
.githooks/pre-commit Batches eslint invocation to avoid Windows cmd limits.
.env.example Documents request profiling env var.
.github/workflows/backend-ci.yml Updates shard/test-count commentary.
.github/workflows/test-system-utils.yml Updates commentary on CI collection behavior.
Review details

Suppressed comments (1)

frontend/src/components/service-detail/MetricsTab.jsx:46

  • This useEffect triggers a reload when loadMetrics changes, but it doesn't reset stats/processInfo or re-enter a loading state. When switching between apps, the UI can briefly show stale metrics from the previous app until the new request resolves.
  • Files reviewed: 299/568 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread frontend/src/components/service-detail/MetricsTab.jsx
@jhd3197 jhd3197 changed the title Close the authorization gaps a security review found Unify session authorization and fix panel regressions Sep 5, 2026
jhd3197 and others added 3 commits September 5, 2026 16:27
On an existing install the startup schema sync runs before alembic and
added users.auth_version as a bare nullable TEXT, so migration 097 saw the
column and skipped its '0' default. Every pre-existing user then carried
a NULL auth_version that no JWT claim could match: 2FA verification and
plain logins both failed with "Invalid or expired token" (reproduced on
the builditdesign test box after `update.sh --branch dev`).

- _fix_missing_columns now renders a literal server_default as a SQLite
  DEFAULT, so pre-alembic column adds backfill existing rows.
- migration 097 always backfills NULL/empty auth_version to '0'.
- tests pin both paths plus the default rendering.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVRnRXqNMZagqQ3RTqG5pz
@jhd3197
jhd3197 merged commit 9c1c9c8 into main Sep 5, 2026
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants