refactor(gateway): split startup and runtime seams#63975
Conversation
Greptile SummaryThis PR refactors Confidence Score: 5/5Safe to merge — refactoring only, all behavior preserved or explicitly improved, build and type checks pass. All findings are P2 style suggestions; no logic errors, data loss, or security concerns identified. The getter pattern for live cron reads and the mutable-set approach for chat.history availability are both correct and well-tested. No files require special attention. Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/gateway/server-aux-handlers.ts
Line: 21-25
Comment:
**Duplicate type shadows exported definition**
`SharedGatewayAuthClient` is already exported from `server-shared-auth-generation.ts` with the exact same shape. This file already imports from that module — importing the type directly would remove the duplication.
```suggestion
import {
disconnectStaleSharedGatewayAuthClients,
setCurrentSharedGatewaySessionGeneration,
type SharedGatewayAuthClient,
type SharedGatewaySessionGenerationState,
} from "./server-shared-auth-generation.js";
```
Then remove the local `type SharedGatewayAuthClient = { ... }` block (lines 21–25).
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "refactor(gateway): split startup and run..." | Re-trigger Greptile |
There was a problem hiding this comment.
Pull request overview
Refactors the gateway server startup/runtime flow by extracting previously inlined logic from src/gateway/server.impl.ts into dedicated “seam” modules, while keeping request handling wired to live runtime state (not a rebuilt/builder context) and adding regression tests for routing correctness.
Changes:
- Split gateway startup into early-runtime and post-attach runtime modules, and extract plugin bootstrap + startup config/secrets activation helpers.
- Move request-context routing (notably cron/store access) onto live
GatewayServerLiveState, avoiding rebuilding handler closures on reload. - Add regression coverage for live cron routing and for
chat.historybecoming available post-attach.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/gateway/server.impl.ts | Rewires gateway server to use extracted startup/runtime seam modules and live runtime state. |
| src/gateway/server-startup.ts | Converts prior monolith startup module into re-exports of new seams. |
| src/gateway/server-startup-post-attach.ts | New: post-attach startup work (update checks, tailscale exposure, sidecars/plugin services, hooks). |
| src/gateway/server-startup-post-attach.test.ts | New: regression test for chat.history enabling after post-attach sidecars start. |
| src/gateway/server-startup-plugins.ts | New: isolates plugin bootstrap/maintenance/migration/subagent registry init. |
| src/gateway/server-startup-early.ts | New: isolates early runtime setup (MCP loopback, discovery, skills timers, maintenance timers). |
| src/gateway/server-startup-config.ts | New: isolates startup config snapshot loading, validation, and secrets activation logic. |
| src/gateway/server-shared-auth-generation.ts | New: centralizes shared-auth session generation state + disconnect helpers. |
| src/gateway/server-session-events.ts | New: extracts transcript/lifecycle broadcast logic into dedicated handlers. |
| src/gateway/server-runtime-subscriptions.ts | New: encapsulates gateway event subscriptions (agent/heartbeat/transcript/lifecycle). |
| src/gateway/server-runtime-services.ts | New: encapsulates runtime services (heartbeat runner, cron start logging, health monitor, outbound recovery, model pricing refresh). |
| src/gateway/server-runtime-handles.ts | New: defines mutable runtime handles/state defaults for the live runtime state container. |
| src/gateway/server-request-context.ts | New: builds request context that reads cron/store path from live runtime state. |
| src/gateway/server-request-context.test.ts | New: regression test validating live cron/store reads through the request context. |
| src/gateway/server-reload-handlers.ts | Refactors reload integration and adds a managed config reloader wired to new seams/state. |
| src/gateway/server-node-session-runtime.ts | New: extracts node/session subscription runtime wiring (registry, subscriptions, voicewake broadcast). |
| src/gateway/server-live-state.ts | New: defines GatewayServerLiveState combining mutable handles + live config-dependent state. |
| src/gateway/server-control-ui-root.ts | New: isolates control UI root resolution/build logic from the server impl. |
| src/gateway/server-close.ts | Adds runGatewayClosePrelude to centralize pre-close cleanup previously duplicated in server impl. |
| src/gateway/server-aux-handlers.ts | New: extracts exec-approval/plugin-approval/secrets RPC handler wiring. |
6a77729 to
242a361
Compare
7eddd30 to
c6e47ef
Compare
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🟡 Unauthorized global session event subscription leaks session metadata and messages over WebSocket
DescriptionThe gateway adds WebSocket subscription methods that allow any connected client with READ scope to subscribe to:
Once subscribed, This creates an information disclosure/IDOR risk in multi-client deployments (multiple paired devices, operator UIs, remote access): any client granted READ scope can passively receive updates about all sessions and potentially subscribe to sessions they should not observe. Vulnerable code (subscription endpoints + broadcast fanout): // sessions.subscribe: global subscription, no further authorization
"sessions.subscribe": ({ client, context, respond }) => {
const connId = client?.connId?.trim();
if (connId) {
context.subscribeSessionEvents(connId);
}
respond(true, { subscribed: Boolean(connId) }, undefined);
},
// broadcast: sends full session snapshot to all global subscribers
for (const connId of params.sessionEventSubscribers.getAll()) {
connIds.add(connId);
}
...
const sessionSnapshot = buildGatewaySessionSnapshot({
sessionRow: loadGatewaySessionRow(sessionKey),
includeSession: true,
});
params.broadcastToConnIds("session.message", { ...sessionSnapshot, ... }, connIds, ...);RecommendationAdd authorization checks to subscription registration and to broadcast fanout. Minimum hardening options:
Example (conceptual): // sessions.messages.subscribe
if (!context.canAccessSession(client, canonicalKey)) {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "unauthorized session"));
return;
}
context.subscribeSessionMessageEvents(connId, canonicalKey);
// server-session-events fanout
const allowedConnIds = new Set<string>();
for (const connId of params.sessionMessageSubscribers.get(sessionKey)) {
allowedConnIds.add(connId);
}
// optionally: add global subscribers only if they have an admin scopeAlso consider minimizing the snapshot: avoid including full Analyzed PR: #63975 at commit Last updated on: 2026-04-10T01:31:11Z |
|
Merged via squash.
Thanks @gumadeiras! |
Merged via squash. Prepared head SHA: c6e47ef Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras
Merged via squash. Prepared head SHA: c6e47ef Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras
Merged via squash. Prepared head SHA: c6e47ef Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras
Merged via squash. Prepared head SHA: c6e47ef Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras
Merged via squash. Prepared head SHA: c6e47ef Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras
Merged via squash. Prepared head SHA: c6e47ef Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras
Merged via squash. Prepared head SHA: c6e47ef Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras
Summary
src/gateway/server.impl.tsinto focused gateway seam moduleschat.historyavailabilityTesting
pnpm tsgopnpm test src/gateway/server-request-context.test.ts src/gateway/server-startup-post-attach.test.ts src/gateway/server-startup.test.ts src/gateway/server-startup-log.test.ts src/gateway/server-close.test.ts src/gateway/server.reload.test.tspnpm checkpnpm buildNotes
pnpm test:gatewaystill hits an existing flaky failure insrc/gateway/server.chat.gateway-server-chat.test.ts(agent.wait keeps lifecycle wait active while same-runId chat.send is active); isolated repro passes.