Conversation
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.
There was a problem hiding this comment.
🟡 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
loadMetricschanges, but it doesn't resetstats/processInfoor 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.
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
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
Technical changes
Session policy and MFA
check_2fa_pendingbefore_requesthook, whose'/auth/login' in pathexemption also matched/auth/login-links, with a@jwt.token_in_blocklist_loaderthat runsvalidate_session_claimsfor every JWT-protected route, so no path prefix can turn a pending token into a session.app/middleware/session_auth.py.issue_session_tokensmints an access and refresh pair sharing onesession_id, andvalidate_session_claimsrequires the right token type, a non-pending claim, an unrevoked session id, an unexpiredexp, an active user, and a matchingauth_version.User.auth_version(new column, random hex) plus the newrevoked_sessionstable give per-account and per-session-family revocation. Migration097_user_auth_versioncovers existing-install, fresh-install and downgrade paths.User.set_passwordand a@validates('is_active')hook both callrevoke_sessions(), which rotatesauth_versionand deletes that user's outstanding login links.POST /auth/logoutpersists aRevokedSessionfor the currentsession_id, so sign-out survives across workers instead of only clearing browser storage.PUT /auth/merequires the current password to set a new one. Accounts with no local password (SSO or passkey) must have authenticated within the last 300 seconds, andrefreshcopiesauth_timeforward so it cannot be renewed by refreshing. The response reissues tokens so the acting browser stays signed in.expires_delta=False, which produced noexpclaim at all despite the comment claiming a short default, to an explicit five-minute lifetime in bothauth.loginandsso._complete_sso_login.create_login_linkmoved from@admin_requiredto@session_requiredplusrequire_admin_user().redeem_login_linkreturnsrequires_2fawith a temp token when the target account has TOTP enabled.two_factor.verify_2fa_coderesolves its user throughvalidate_session_claims(..., allow_pending=True)instead ofUser.query.get, and reports invalid and expired tokens uniformly.API key scopes
enforce_request_scoperuns in theX-API-Keybefore_request. A key with a non-wildcard, non-empty scope list is rejected with 403 on any endpoint carrying no_sk_api_scopesdeclaration, and each declared scope is checked independently of the owner's role.require_scopestampswrapper._sk_api_scopesand merges nested declarations, so an outer RBAC wrapper preserves them throughfunctools.wraps.before_requestpopsg.api_keyandg.api_key_userfirst, so a long-lived app context cannot leak credentials between requests.Sockets
AuthorizedSocketIO.emitrechecks 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_connectvalidates throughvalidate_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_roomreplaced its arbitrary room-name accept with_room_allowed: per-user rooms match the caller only,logs_<app_id>goes throughapp_access_tier,deploy_*andrun_*through the newrun_access.can_read_run, terminal rooms require ownership of the session plus_server_visible, andjob:*streams require an operator role._metrics_tickand_container_status_tickstopped broadcasting toroom=None. Metrics go per-subscriber, and container statuses are filtered per subscriber by_app_visible.subscribe_container_logsrequires application visibility,subscribe_logs(host log files) requires admin, and the deploy and run channels gainedauth=callbacks. Every handler coerces non-dict payloads instead of calling.geton them.app/services/run_access.pycentralizes run visibility soapi/runs.pyand the socket channels answer identically.AI tools and chat
tool_callerContextVar._caller_validatorcaptures 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_applicationsandlist_serversrun throughWorkspaceService.scope_querywith ownership and grant scoping, and both return a fixed field summary rather thanto_dict(). The old shape leaked paths, repository and build configuration, and private routing fields.ToolDescriptor.admin_onlymirrors 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_resultwalks dicts, lists and scalars throughmask_payloadandredact_inputinstead of returning non-string results untouched._filter_secretsadds deterministic regex filtering for private keys,BearerandBasicheaders, credentials in URLs, and sensitive key assignments, independent of the optional PII detector.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.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_gatetakes the gate identity so a late finalizer cannot unregister its successor, andchat_confirmchecks the pending action'sconversation_id.Passkeys
options_to_json, andAuthenticatorSelectionCriteria,ResidentKeyRequirementandAuthenticatorTransportenums instead of dict and string literals, with transports read from either the credential or itsresponse.require_user_verification=Trueand moved frompreferredtoREQUIREDuser verification. Authentication additionally rejects a credential that does not belong to the named user.UI regressions
ContainersTabgainedopenContainerLogsandopenContainerExec, which clearselectedContainerin 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.MetricsTabsplit into an inner component keyed by a runtime key built fromapp.id,app.name,app.app_type,app.server_idandapp.container_id, so switching applications remounts with empty state instead of showing the previous app's numbers. ArequestIdref discards responses from superseded requests, an empty container list and a failed poll both clearstatsandprocessInfo, and the hand-rolled empty block became the sharedEmptyState.Settings and access review
.users-table-containerownsbackground: var(--surface), resolving the contradiction where a more specific rule stripped the shared.sk-dtable-wrapsurface and left rows with color only on hover. The Tailwind-flavoredtext-destructiveandtext-warningutilities on those actions becameusers-action--*SCSS classes.UsersTabaddsmfa,passkeyandauthProvidercolumns plus the "Admins without MFA" saved view, swaps its page-local delete modal for the shareduseConfirm, usesuseFormatinstead of hardcodeden-US, translates status labels, and disables actions while one is in flight.InvitationsTabgained the same pending guards and surfaces load, revoke and resend errors that were previously swallowed.Security CI
security-scan.ymlgained a weekly schedule andworkflow_dispatch,permissions: contents: read, extension sources plus requirement files andfrontend/package*.jsonin its path filters, and annpm audit --package-lock-only --omit=dev --audit-level=highjob.scripts/check-bandit-report.pygates it againstscripts/security/bandit-exceptions.json, which binds each accepted finding to one function's AST sha256 and a count. The blanket--skip B602,B402,B321,B202is 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
application_lifecycle_service.py(start, stop, restart, local BYO-image compose materialization, build-pack single-container detection) andrepository_application_service.pyout ofapp/api/apps.py, which shrank by roughly 314 lines. The controller-boundary baseline dropped from 511 to 496.server_metrics_service.latest_metrics_by_serverreplaces the per-serverORDER BY timestamp DESC LIMIT 1loop inFleetMonitorService.get_fleet_overviewand the Prometheus exporter with one windowed query, plusjoinedload(Server.group). Theorder_by='id'mode preserves the list view's insertion-order semantics.backup_schedule_service.pycentralizes the next-run and interval math shared bybackup_serviceand the API.app/utils/actor.current_actor_idandconnect_format.iso_datetimereplace duplicated helpers, andResourceTierService._detect_containerdelegates tohost_inventory_service.request_profilingmiddleware addsServer-Timingwith request duration, SQL duration and statement count behindSERVERKIT_PROFILE_REQUESTS. It installs no SQLAlchemy listeners when disabled and never emits SQL text or parameters.Frontend consolidation
use*hooks into sibling modules (useAuth.js,useTheme.js,useToast.jsand the rest), andbuttonVariantsmoved out ofbutton.jsx, clearing thereact-refresh/only-export-componentswarnings at the source.JSXAttribute[name.name="style"]ban with a customserverkit/no-static-inline-stylesrule that reports only fixed presentation, including conditional and template-literal values, and leaves computed geometry alone.scripts/lint.mjswraps ESLint with a per-file, per-rule warning baseline over git-tracked sources.lint-warning-baseline.jsonis now{}, down from 923 warnings, andnpm run lint:baselineregenerates it.scripts/check-style-cascade.mjscompilesmain.scssand diffs rendered computed styles in Chromium before and after an ownership change.STYLE_OWNERSHIP_CEILINGwent from 114 to 0 as duplicate class definitions were consolidated into single-owner partials (_monitoring.scssinto_monitors.scss, plus new_agent-fleet.scss,_style-guide.scss,_detail-tabs.scss,_empty-state.scssand_connection-status.scss).WorkspaceServicesTabandWorkspaceSitesTabcollapsed into a sharedWorkspaceApplicationsTabkeyed bykind. The unusedappdetailduplicates (OverviewTab,LogsTab,PackagesTab,CommandsTab,GunicornTab) were deleted, leaving only the still-importedBuildTabandDeployTab.utils/backupSchedule.js,utils/metricsSubscription.jsandutils/runRecipe.js, plus theuseBackupScheduleshook, pull logic out ofBackups.jsx(178 lines lighter) anduseMetrics.js.useFormabsorbed the repeated submit and error handling.?k=${v}interpolations inservices/api/*throughencodeURIComponent, including the file-download token.docs/MIGRATION_INVENTORY.mdrecords that ratchet as an invariant at 0.Measurements and docs
scripts/measure-repository.py,frontend/scripts/measure-build.mjsandscripts/update-readme-measurements.pyregenerate the README table between theBEGIN/END GENERATED MEASUREMENTSmarkers fromdocs/measurements/*.json.measurements-ci.ymlre-runs the tools and checks all four translated READMEs against the reviewed snapshot.docs/METRICS.mddocuments each definition and its reproduction command.scripts/profile-api.pydrives theServer-Timingmiddleware for local latency and query-count profiling.docs/reviews/2026-09-05-serverkit-review.md,-remediation.mdand the historical-security-probes.pyrecord the review, the fix-by-fix handoff, upgrade behavior, and what remains open: aggregate AI spend accounting, CSPunsafe-inlineandunsafe-eval, plugin tool authorization, and the existingaudit_logs.user_idaccount-deletion issue found during cross-review.Upgrade notes
097_user_auth_versionthrough the normal upgrade process.[skip version]on the final fix so CI did not bump again.