Skip to content

Conversation

@sawka
Copy link
Member

@sawka sawka commented Jan 1, 2026

the PR spiraled and ended up being much larger than anticipated.

it is a refactor of wshrouter to have it track "links" as opposed to just routes. this lets us simplify a lot of things when it comes to multi-level routing.

  • now the router can handle unauthenticated links directly, instead of a weird limbo in wshproxy
  • no more wshmultiproxy
  • no more "authtoken" weirdness
  • more straightforward handling in connserver (when using router option)

also adds more debugging, more logging, some windows fixes, other wsl fixes

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 1, 2026

Walkthrough

This pull request implements a significant refactoring of the routing, RPC, and authentication systems. Key changes include: reworking the router from route-based to link-based architecture with trust levels and control plane support; introducing a new JWT-based authentication system via the wavejwt package with Ed25519 key management; migrating RPC channels to use RpcInputChType with ingress link tracking; adding RpcContext as a unified context type replacing scattered token fields; creating a MainServer object type to persist JWT keys; refactoring connection server initialization with --conn and --dev flags; and requiring WAVETERM_TABID environment variable across multiple CLI commands. Database migrations add a db_mainserver table, TypeScript types are restructured to use the new RpcContext, and frontend RPC API commands are updated (removing SetViewCommand, adding GetJwtPublicKeyCommand and others). The changes span backend Go code, TypeScript frontend code, build configuration, and migrations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.74% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'very large refactor of wshrouter' accurately describes the main focus of the changeset, which is a comprehensive refactoring of the router subsystem to track links instead of routes.
Description check ✅ Passed The description is related to the changeset, explaining the refactoring scope, key improvements (link-based tracking, removal of wshmultiproxy/authtoken), and additional changes (debugging, logging, platform fixes).
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
pkg/util/shellutil/shellquote.go (1)

24-51: Newline handling was intentionally removed from HardQuote — verify this behavior is correct.

The most recent commit removed explicit newline escaping from HardQuote (previously: case '\n': buf.append(buf, '\\', '\n')). This creates a functional difference:

  • HardQuote: Does NOT escape newlines (literal \n in double quotes)
  • HardQuotePowerShell: Escapes newlines as `n
  • HardQuoteFish: Does NOT escape newlines (documented in code comment)

Since HardQuote is used with sh -c to execute commands (see genconn.go:154, conncontroller.go:315), literal newlines in the quoted string could affect command parsing. While bash accepts literal newlines inside double quotes, this differs from PowerShell and may complicate debugging.

No test cases cover newline handling for any quoting function. Please confirm whether this removal was intentional and ensure it doesn't break command construction when environment variables or other inputs unexpectedly contain newlines.

frontend/app/store/wps.ts (1)

43-58: Missing null check for WpsRpcClient before RPC calls.

The updateWaveEventSub function uses WpsRpcClient directly in RPC calls, but there's no guard against it being undefined. If updateWaveEventSub is called before setWpsRpcClient initializes the client, this will throw a runtime error.

🔎 Proposed fix
 function updateWaveEventSub(eventType: string) {
+    if (WpsRpcClient == null) {
+        console.warn("WpsRpcClient not initialized, skipping event subscription update");
+        return;
+    }
     const subjects = waveEventSubjects.get(eventType);
     if (subjects == null) {
         RpcApi.EventUnsubCommand(WpsRpcClient, eventType, { noresponse: true });
         return;
     }
pkg/wshutil/wshproxy.go (1)

43-52: The ingressLinkId parameter cannot be used in this implementation.

Unlike the parallel WshRpc.SendRpcMessage which uses this parameter to construct RpcInputChType, WshRpcProxy sends only raw bytes to ToRemoteCh. Since the channel type is chan []byte, the ingressLinkId is dropped and lost. Either WshRpcProxy needs a different channel type that can carry link ID information, or this parameter should not be part of the interface contract if it cannot be used by all implementations.

♻️ Duplicate comments (3)
cmd/wsh/cmd/wshcmd-view.go (1)

56-59: Validation pattern matches other commands.

This follows the same TabId retrieval and validation pattern as other commands in this PR. See the refactoring suggestion in wshcmd-launch.go for a way to consolidate this logic.

cmd/wsh/cmd/wshcmd-web.go (1)

115-118: Validation pattern matches other commands.

This follows the same TabId retrieval and validation pattern as other commands in this PR. See the refactoring suggestion in wshcmd-launch.go for a way to consolidate this logic.

cmd/wsh/cmd/wshcmd-run.go (1)

136-139: Validation pattern matches other commands.

This follows the same TabId retrieval and validation pattern as other commands in this PR. See the refactoring suggestion in wshcmd-launch.go for a way to consolidate this logic.

🧹 Nitpick comments (11)
pkg/util/utilfn/utilfn.go (1)

1019-1023: Good refactoring to parameterize output destination.

The change from hardcoded os.Stdout to an io.Writer parameter improves flexibility and testability.

Consider returning the write error to allow callers to handle write failures (e.g., disk full scenarios):

🔎 Proposed enhancement
-func DumpGoRoutineStacks(w io.Writer) {
+func DumpGoRoutineStacks(w io.Writer) error {
 	buf := make([]byte, 1<<20)
 	n := runtime.Stack(buf, true)
-	w.Write(buf[:n])
+	_, err := w.Write(buf[:n])
+	return err
 }
Taskfile.yml (1)

598-603: LGTM! Consider completing the migration for consistency.

The platform-specific tsunami binary cleanup commands are correct. However, note that this PR creates a mixed approach: some tasks now use explicit platform-specific commands while others still use the RMRF template variable (defined at line 11 and used in tasks like clean, dev:clearconfig, artifacts:download, etc.).

Consider either:

  1. Completing the migration by converting remaining RMRF usages to explicit commands
  2. Documenting why certain cleanup operations warrant explicit commands vs. template-based approach
frontend/app/store/wshrouter.ts (1)

149-156: Optional chaining inconsistency.

Line 155 uses optional chaining (this.upstreamClient?.recvRpcMessage), while lines 53 and 142 call the same method without it. Since the constructor guarantees upstreamClient is non-null (lines 39-41), the optional chaining is unnecessary.

🔎 Suggested fix for consistency
-        this.upstreamClient?.recvRpcMessage(unannounceMsg);
+        this.upstreamClient.recvRpcMessage(unannounceMsg);
pkg/wshutil/wshrouter_controlimpl.go (1)

105-168: Consider extracting common token validation logic.

AuthenticateTokenCommand (root branch, lines 121-142) and AuthenticateTokenVerifyCommand (lines 170-202) share nearly identical logic for retrieving the token swap entry, validating the RPC context, and building the return data.

🔎 Suggested refactor to reduce duplication

Extract common logic into a helper:

func (impl *WshRouterControlImpl) validateAndConsumeTokenSwap(token string) (*wshrpc.CommandAuthenticateRtnData, *wshrpc.RpcContext, error) {
    entry := shellutil.GetAndRemoveTokenSwapEntry(token)
    if entry == nil {
        return nil, nil, fmt.Errorf("no token entry found")
    }
    _, err := validateRpcContextFromAuth(entry.RpcContext)
    if err != nil {
        return nil, nil, err
    }
    if entry.RpcContext.IsRouter {
        return nil, nil, fmt.Errorf("cannot auth router via token")
    }
    if entry.RpcContext.RouteId == "" {
        return nil, nil, fmt.Errorf("no routeid")
    }
    rtnData := wshrpc.CommandAuthenticateRtnData{
        Env:            entry.Env,
        InitScriptText: entry.ScriptText,
        RpcContext:     entry.RpcContext,
    }
    return &rtnData, entry.RpcContext, nil
}

Then both methods can call this helper.

Also applies to: 170-202

cmd/wsh/cmd/wshcmd-secret.go (1)

179-185: LGTM! TabId requirement properly enforced.

The addition of WAVETERM_TABID validation and propagation to the block creation data is correct and consistent with the broader PR changes.

Optional: Consider a helper to reduce duplication

The pattern of retrieving and validating WAVETERM_TABID appears across multiple command files. If not already present, consider a helper function:

func requireTabIdFromEnv() (string, error) {
    tabId := getTabIdFromEnv()
    if tabId == "" {
        return "", fmt.Errorf("no WAVETERM_TABID env var set")
    }
    return tabId, nil
}

This would consolidate the validation logic in one place.

cmd/wsh/cmd/wshcmd-launch.go (1)

51-54: Consider consolidating validation into the helper function.

The pattern of retrieving and validating WAVETERM_TABID is repeated across multiple command files (launch, view, web, run). While getTabIdFromEnv() centralizes retrieval, the validation logic remains duplicated. Consider having the helper return an error when the tabId is empty, reducing repetition.

Example consolidated helper

In wshcmd-root.go, refactor the helper to:

func getValidTabIdFromEnv() (string, error) {
	tabId := os.Getenv("WAVETERM_TABID")
	if tabId == "" {
		return "", fmt.Errorf("WAVETERM_TABID environment variable not set")
	}
	return tabId, nil
}

Then simplify call sites to:

-	tabId := getTabIdFromEnv()
-	if tabId == "" {
-		return fmt.Errorf("no WAVETERM_TABID env var set")
-	}
+	tabId, err := getValidTabIdFromEnv()
+	if err != nil {
+		return err
+	}
pkg/wcore/wcore.go (1)

216-225: Consider security implications of logging the public key.

The public key is logged to stdout in PEM format. While public keys are meant to be shared, logging them to stdout may expose them in production logs where they could be collected by log aggregation systems. Consider:

  1. Whether this logging is only needed during development
  2. Using debug-level logging instead of standard logging
  3. Adding a flag to control key logging behavior
pkg/web/ws.go (1)

291-295: Validate stableId format/length to prevent abuse.

While authentication is performed, the stableId is used directly in logging and as a map key. Consider adding basic validation (e.g., max length, allowed characters) to prevent potential log injection or memory issues from excessively long IDs.

pkg/wavejwt/wavejwt.go (2)

63-67: Consider returning a copy to prevent mutation of internal state.

GetPublicKey() returns the internal slice directly. While Ed25519 public keys are typically treated as immutable, returning a copy would be more defensive.

🔎 Proposed fix
 func GetPublicKey() []byte {
 	globalLock.Lock()
 	defer globalLock.Unlock()
+	if publicKey == nil {
+		return nil
+	}
+	rtn := make([]byte, len(publicKey))
+	copy(rtn, publicKey)
-	return publicKey
+	return rtn
 }

114-131: Sign mutates the input claims struct.

The function modifies claims.IssuedAt, claims.Issuer, and claims.ExpiresAt in place if they're nil/empty. This side effect could be surprising to callers. Consider documenting this behavior or working on a copy.

pkg/wshutil/wshrouter.go (1)

519-529: Panic for programming error is acceptable but consider error return.

Using panic in RegisterUpstream for root router is reasonable for catching programming errors early. However, if this could be called dynamically based on configuration, an error return might be safer.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90011a7 and 8081138.

📒 Files selected for processing (63)
  • .gitignore
  • Taskfile.yml
  • cmd/server/main-server.go
  • cmd/wsh/cmd/wshcmd-connserver.go
  • cmd/wsh/cmd/wshcmd-createblock.go
  • cmd/wsh/cmd/wshcmd-debug.go
  • cmd/wsh/cmd/wshcmd-editconfig.go
  • cmd/wsh/cmd/wshcmd-editor.go
  • cmd/wsh/cmd/wshcmd-launch.go
  • cmd/wsh/cmd/wshcmd-root.go
  • cmd/wsh/cmd/wshcmd-run.go
  • cmd/wsh/cmd/wshcmd-secret.go
  • cmd/wsh/cmd/wshcmd-ssh.go
  • cmd/wsh/cmd/wshcmd-term.go
  • cmd/wsh/cmd/wshcmd-view.go
  • cmd/wsh/cmd/wshcmd-wavepath.go
  • cmd/wsh/cmd/wshcmd-web.go
  • db/migrations-wstore/000009_mainserver.down.sql
  • db/migrations-wstore/000009_mainserver.up.sql
  • emain/emain-menu.ts
  • emain/emain.ts
  • frontend/app/store/wps.ts
  • frontend/app/store/ws.ts
  • frontend/app/store/wshclientapi.ts
  • frontend/app/store/wshrouter.ts
  • frontend/app/store/wshrpcutil-base.ts
  • frontend/app/store/wshrpcutil.ts
  • frontend/types/gotypes.d.ts
  • pkg/baseds/baseds.go
  • pkg/blockcontroller/shellcontroller.go
  • pkg/remote/conncontroller/conncontroller.go
  • pkg/shellexec/shellexec.go
  • pkg/tsgen/tsgen.go
  • pkg/util/packetparser/packetparser.go
  • pkg/util/shellutil/shellquote.go
  • pkg/util/shellutil/tokenswap.go
  • pkg/util/sigutil/sigusr1_notwindows.go
  • pkg/util/utilfn/utilfn.go
  • pkg/waveapp/waveapp.go
  • pkg/wavebase/wavebase.go
  • pkg/wavejwt/wavejwt.go
  • pkg/waveobj/wtype.go
  • pkg/wcloud/wcloud.go
  • pkg/wcore/wcore.go
  • pkg/web/ws.go
  • pkg/wshrpc/wshclient/barerpcclient.go
  • pkg/wshrpc/wshclient/wshclient.go
  • pkg/wshrpc/wshrpctypes.go
  • pkg/wshrpc/wshserver/wshserver.go
  • pkg/wshrpc/wshserver/wshserverutil.go
  • pkg/wshutil/wshadapter.go
  • pkg/wshutil/wshcmdreader.go
  • pkg/wshutil/wshmultiproxy.go
  • pkg/wshutil/wshproxy.go
  • pkg/wshutil/wshrouter.go
  • pkg/wshutil/wshrouter_controlimpl.go
  • pkg/wshutil/wshrpc.go
  • pkg/wshutil/wshrpcio.go
  • pkg/wshutil/wshutil.go
  • pkg/wsl/wsl-unix.go
  • pkg/wsl/wsl-win.go
  • pkg/wslconn/wslconn.go
  • pkg/wstore/wstore_dbops.go
💤 Files with no reviewable changes (5)
  • pkg/wavebase/wavebase.go
  • pkg/wstore/wstore_dbops.go
  • pkg/util/shellutil/tokenswap.go
  • pkg/wshutil/wshmultiproxy.go
  • cmd/wsh/cmd/wshcmd-debug.go
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-01-22T01:28:41.417Z
Learnt from: esimkowitz
Repo: wavetermdev/waveterm PR: 1790
File: pkg/remote/fileshare/wshfs/wshfs.go:122-122
Timestamp: 2025-01-22T01:28:41.417Z
Learning: The RpcClient in pkg/remote/fileshare/wshfs/wshfs.go is initialized and handled downstream by either main-server or wshcmd-connserver, as documented in the package comment.

Applied to files:

  • cmd/wsh/cmd/wshcmd-view.go
  • frontend/app/store/wshrpcutil-base.ts
  • pkg/wshrpc/wshclient/wshclient.go
  • pkg/wslconn/wslconn.go
  • pkg/wshrpc/wshserver/wshserverutil.go
  • pkg/wshrpc/wshclient/barerpcclient.go
  • pkg/waveapp/waveapp.go
  • pkg/wshutil/wshadapter.go
  • pkg/remote/conncontroller/conncontroller.go
  • cmd/wsh/cmd/wshcmd-root.go
  • pkg/wshutil/wshrouter_controlimpl.go
  • pkg/web/ws.go
  • frontend/app/store/wshrpcutil.ts
  • frontend/app/store/wps.ts
  • pkg/blockcontroller/shellcontroller.go
  • pkg/wshutil/wshutil.go
  • cmd/server/main-server.go
  • pkg/wshutil/wshproxy.go
  • pkg/wshrpc/wshrpctypes.go
  • pkg/wshutil/wshrpc.go
  • cmd/wsh/cmd/wshcmd-connserver.go
  • pkg/wshutil/wshrouter.go
📚 Learning: 2025-01-29T04:21:11.649Z
Learnt from: esimkowitz
Repo: wavetermdev/waveterm PR: 1725
File: pkg/remote/fileshare/wavefs/wavefs.go:441-494
Timestamp: 2025-01-29T04:21:11.649Z
Learning: The `CopyRemote` function in WaveTerm's file operations has proper error handling that prevents partial writes by failing fast and using context cancellation. Each step (path cleaning, file operations, tar reading/writing) is guarded by error checks that prevent proceeding with writes on error.

Applied to files:

  • cmd/wsh/cmd/wshcmd-wavepath.go
📚 Learning: 2025-01-22T22:27:25.739Z
Learnt from: esimkowitz
Repo: wavetermdev/waveterm PR: 1790
File: pkg/remote/connparse/connparse.go:76-82
Timestamp: 2025-01-22T22:27:25.739Z
Learning: The GetRpcContext() method in wshutil package is guaranteed to never return nil due to type constraints, making nil checks unnecessary.

Applied to files:

  • pkg/wshrpc/wshserver/wshserverutil.go
  • pkg/wshrpc/wshclient/barerpcclient.go
  • pkg/waveapp/waveapp.go
  • pkg/wshutil/wshadapter.go
  • cmd/wsh/cmd/wshcmd-root.go
  • pkg/wshutil/wshrouter_controlimpl.go
  • pkg/blockcontroller/shellcontroller.go
  • frontend/types/gotypes.d.ts
  • pkg/wshutil/wshutil.go
  • cmd/server/main-server.go
  • pkg/wshutil/wshproxy.go
  • pkg/wshutil/wshrpc.go
  • cmd/wsh/cmd/wshcmd-connserver.go
📚 Learning: 2025-10-21T05:09:26.916Z
Learnt from: sawka
Repo: wavetermdev/waveterm PR: 2465
File: frontend/app/onboarding/onboarding-upgrade.tsx:13-21
Timestamp: 2025-10-21T05:09:26.916Z
Learning: In the waveterm codebase, clientData is loaded and awaited in wave.ts before React runs, ensuring it is always available when components mount. This means atoms.client will have data on first render.

Applied to files:

  • frontend/app/store/wps.ts
🧬 Code graph analysis (30)
pkg/util/sigutil/sigusr1_notwindows.go (1)
pkg/util/utilfn/utilfn.go (1)
  • DumpGoRoutineStacks (1019-1023)
cmd/wsh/cmd/wshcmd-view.go (1)
pkg/wshrpc/wshrpctypes.go (1)
  • CommandCreateBlockData (429-438)
pkg/waveobj/wtype.go (1)
pkg/waveobj/metamap.go (1)
  • MetaMapType (8-8)
frontend/app/store/wshrpcutil-base.ts (1)
frontend/app/store/wps.ts (1)
  • setWpsRpcClient (151-151)
cmd/wsh/cmd/wshcmd-run.go (1)
pkg/wshrpc/wshrpctypes.go (1)
  • CommandCreateBlockData (429-438)
cmd/wsh/cmd/wshcmd-createblock.go (1)
pkg/wshrpc/wshrpctypes.go (1)
  • CommandCreateBlockData (429-438)
pkg/wshutil/wshcmdreader.go (1)
pkg/baseds/baseds.go (1)
  • RpcInputChType (11-14)
frontend/app/store/wshclientapi.ts (3)
frontend/app/store/wshclient.ts (1)
  • WshClient (159-159)
pkg/remote/fileshare/wshfs/wshfs.go (1)
  • WshClient (22-22)
pkg/wshrpc/wshrpctypes.go (3)
  • CommandAuthenticateTokenData (398-400)
  • RpcOpts (375-381)
  • CommandAuthenticateRtnData (391-396)
pkg/wslconn/wslconn.go (4)
pkg/wavebase/wavebase.go (1)
  • IsDevMode (112-114)
pkg/remote/conncontroller/conncontroller.go (1)
  • ConnServerCmdTemplate (74-78)
pkg/util/shellutil/shellquote.go (1)
  • HardQuote (24-51)
pkg/wshutil/wshrouter.go (1)
  • MakeConnectionRouteId (93-95)
cmd/wsh/cmd/wshcmd-editor.go (1)
pkg/wshrpc/wshrpctypes.go (1)
  • CommandCreateBlockData (429-438)
pkg/wshrpc/wshserver/wshserverutil.go (5)
pkg/wshutil/wshrpc.go (1)
  • MakeWshRpc (233-235)
pkg/wshrpc/wshrpctypes.go (1)
  • RpcContext (383-389)
cmd/wsh/cmd/wshcmd-root.go (1)
  • RpcContext (33-33)
pkg/wshrpc/wshclient/barerpcclient.go (1)
  • WshServerImpl (18-18)
pkg/wshrpc/wshserver/wshserver.go (1)
  • WshServerImpl (71-71)
pkg/util/packetparser/packetparser.go (1)
pkg/baseds/baseds.go (1)
  • RpcInputChType (11-14)
pkg/wshrpc/wshclient/barerpcclient.go (3)
pkg/wshrpc/wshrpctypes.go (1)
  • RpcContext (383-389)
cmd/wsh/cmd/wshcmd-root.go (1)
  • RpcContext (33-33)
pkg/wshutil/wshrouter.go (1)
  • DefaultRouter (121-121)
cmd/wsh/cmd/wshcmd-web.go (1)
pkg/wshrpc/wshrpctypes.go (1)
  • CommandCreateBlockData (429-438)
pkg/waveapp/waveapp.go (5)
frontend/app/store/wshclientapi.ts (1)
  • AuthenticateCommand (21-23)
pkg/wshrpc/wshclient/wshclient.go (1)
  • AuthenticateCommand (33-36)
cmd/wsh/cmd/wshcmd-root.go (1)
  • RpcClient (32-32)
pkg/wshrpc/wshrpctypes.go (1)
  • RpcOpts (375-381)
pkg/wshutil/wshrouter.go (1)
  • ControlRoute (26-26)
cmd/wsh/cmd/wshcmd-root.go (3)
pkg/wshrpc/wshrpctypes.go (4)
  • RpcContext (383-389)
  • CommandAuthenticateTokenData (398-400)
  • RpcOpts (375-381)
  • CommandResolveIdsData (420-423)
pkg/wshutil/wshutil.go (1)
  • SetupDomainSocketRpcClient (276-298)
pkg/wshrpc/wshclient/wshclient.go (2)
  • SetPeerInfoCommand (662-665)
  • ResolveIdsCommand (614-617)
emain/emain-menu.ts (1)
frontend/app/store/wps.ts (1)
  • waveEventSubscribe (152-152)
cmd/wsh/cmd/wshcmd-secret.go (1)
pkg/wshrpc/wshrpctypes.go (1)
  • CommandCreateBlockData (429-438)
pkg/wshutil/wshrouter_controlimpl.go (8)
pkg/wshutil/wshrouter.go (4)
  • WshRouter (75-91)
  • LinkKind_Router (50-50)
  • LinkKind_Leaf (49-49)
  • ControlRootRoute (27-27)
pkg/wshutil/wshrpc.go (3)
  • GetRpcSourceFromContext (83-89)
  • GetRpcResponseHandlerFromContext (99-105)
  • GetWshRpcFromContext (75-81)
pkg/baseds/baseds.go (1)
  • NoLinkId (9-9)
pkg/wshutil/wshproxy.go (1)
  • WshRpcProxy (15-21)
pkg/wshutil/wshutil.go (1)
  • ValidateAndExtractRpcContextFromToken (329-335)
cmd/wsh/cmd/wshcmd-root.go (1)
  • RpcContext (33-33)
pkg/util/shellutil/tokenswap.go (1)
  • GetAndRemoveTokenSwapEntry (88-97)
pkg/util/utilfn/marshal.go (1)
  • ReUnmarshal (36-42)
pkg/web/ws.go (4)
pkg/baseds/baseds.go (3)
  • LinkId (7-7)
  • RpcInputChType (11-14)
  • NoLinkId (9-9)
pkg/wshutil/wshproxy.go (2)
  • WshRpcProxy (15-21)
  • MakeRpcProxy (23-30)
pkg/wshutil/wshrouter.go (1)
  • DefaultRouter (121-121)
pkg/eventbus/eventbus.go (2)
  • RegisterWSChannel (36-43)
  • UnregisterWSChannel (45-49)
frontend/app/store/wshrpcutil.ts (1)
frontend/app/store/wps.ts (1)
  • setWpsRpcClient (151-151)
frontend/app/store/wps.ts (3)
frontend/app/store/wshclient.ts (1)
  • WshClient (159-159)
frontend/app/store/wshclientapi.ts (1)
  • RpcApi (707-707)
pkg/wps/wpstypes.go (1)
  • SubscriptionRequest (39-43)
pkg/blockcontroller/shellcontroller.go (4)
pkg/wshrpc/wshrpctypes.go (1)
  • RpcContext (383-389)
pkg/wshutil/wshrouter.go (2)
  • MakeRandomProcRouteId (105-107)
  • MakeControllerRouteId (97-99)
pkg/wshutil/wshutil.go (1)
  • MakeClientJWTToken (300-317)
pkg/wshutil/wshproxy.go (1)
  • MakeRpcProxy (23-30)
frontend/types/gotypes.d.ts (2)
pkg/wshrpc/wshrpctypes.go (1)
  • RpcContext (383-389)
cmd/wsh/cmd/wshcmd-root.go (1)
  • RpcContext (33-33)
pkg/wshrpc/wshserver/wshserver.go (5)
frontend/app/store/wshclientapi.ts (2)
  • GetJwtPublicKeyCommand (316-318)
  • CreateBlockCommand (116-118)
pkg/wshrpc/wshclient/wshclient.go (2)
  • GetJwtPublicKeyCommand (384-387)
  • CreateBlockCommand (147-150)
pkg/wavejwt/wavejwt.go (1)
  • GetPublicKeyBase64 (69-75)
pkg/wshrpc/wshrpctypes.go (1)
  • CommandCreateBlockData (429-438)
pkg/waveobj/metaconsts.go (2)
  • MetaKey_View (9-9)
  • MetaKey_File (13-13)
cmd/server/main-server.go (4)
pkg/wshutil/wshrouter.go (4)
  • DefaultRouter (121-121)
  • DefaultRoute (24-24)
  • MakeConnectionRouteId (93-95)
  • NewWshRouter (123-137)
pkg/wshutil/wshrpc.go (2)
  • MakeWshRpc (233-235)
  • ServerImpl (39-41)
cmd/wsh/cmd/wshcmd-root.go (1)
  • RpcContext (33-33)
pkg/wavebase/wavebase.go (2)
  • WaveVersion (25-25)
  • BuildTime (26-26)
pkg/wshrpc/wshrpctypes.go (5)
frontend/app/store/wshclientapi.ts (6)
  • AuthenticateTokenVerifyCommand (31-33)
  • DisposeCommand (151-153)
  • RouteAnnounceCommand (521-523)
  • RouteUnannounceCommand (526-528)
  • SetPeerInfoCommand (551-553)
  • GetJwtPublicKeyCommand (316-318)
pkg/wshrpc/wshclient/wshclient.go (6)
  • AuthenticateTokenVerifyCommand (45-48)
  • DisposeCommand (189-192)
  • RouteAnnounceCommand (626-629)
  • RouteUnannounceCommand (632-635)
  • SetPeerInfoCommand (662-665)
  • GetJwtPublicKeyCommand (384-387)
cmd/wsh/cmd/wshcmd-root.go (1)
  • RpcContext (33-33)
pkg/waveobj/waveobj.go (1)
  • ORef (30-34)
pkg/waveobj/metamap.go (1)
  • MetaMapType (8-8)
pkg/wshutil/wshrpc.go (1)
pkg/baseds/baseds.go (2)
  • LinkId (7-7)
  • RpcInputChType (11-14)
cmd/wsh/cmd/wshcmd-connserver.go (8)
pkg/panichandler/panichandler.go (1)
  • PanicHandler (25-43)
pkg/baseds/baseds.go (2)
  • NoLinkId (9-9)
  • LinkId (7-7)
cmd/wsh/cmd/wshcmd-root.go (1)
  • RpcContext (33-33)
pkg/util/packetparser/packetparser.go (1)
  • Parse (48-72)
frontend/app/store/wshclientapi.ts (1)
  • GetJwtPublicKeyCommand (316-318)
pkg/wshrpc/wshclient/wshclient.go (1)
  • GetJwtPublicKeyCommand (384-387)
pkg/wavejwt/wavejwt.go (1)
  • SetPublicKey (53-61)
pkg/wshrpc/wshremote/sysinfo.go (1)
  • RunSysInfoLoop (64-72)
pkg/wshutil/wshrouter.go (5)
pkg/baseds/baseds.go (3)
  • LinkId (7-7)
  • RpcInputChType (11-14)
  • NoLinkId (9-9)
pkg/wshrpc/wshrpctypes.go (2)
  • Command_Message (64-64)
  • CommandMessageData (407-409)
pkg/panichandler/panichandler.go (1)
  • PanicHandler (25-43)
pkg/wps/wps.go (1)
  • Broker (46-50)
pkg/wps/wpstypes.go (2)
  • WaveEvent (27-33)
  • Event_RouteGone (19-19)
🪛 golangci-lint (2.5.0)
cmd/wsh/cmd/wshcmd-connserver.go

[error] 250-250: ineffectual assignment to logWriter

(ineffassign)

pkg/wshutil/wshrouter.go

[error] 415-415: ineffectual assignment to exitReason

(ineffassign)


[error] 425-425: ineffectual assignment to exitReason

(ineffassign)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Build for TestDriver.ai
🔇 Additional comments (110)
.gitignore (1)

19-19: LGTM!

The addition of manifests/ to the ignore list is appropriate and follows the existing directory exclusion patterns. This aligns with the PR's Windows/WSL manifest handling improvements.

pkg/wshutil/wshadapter.go (2)

56-76: LGTM! Clean simplification of function signature.

The removal of the rpcCtx parameter simplifies the function and aligns with the PR's goal of unified RPC context handling. The function now focuses solely on re-marshalling command data, which is its core responsibility.


107-107: Call site correctly matches updated function signature.

The call at line 107 properly uses the new 2-parameter signature for recodeCommandData. Search of the codebase confirms this is the only call site, and it has been correctly updated.

pkg/util/sigutil/sigusr1_notwindows.go (1)

17-17: Exported constant added for dump file path.

The path /tmp/waveterm-usr1-dump.log is appropriate for Unix-like systems where /tmp is standard.

emain/emain-menu.ts (2)

387-392: LGTM! Good refactoring to control subscription timing.

Moving the event subscription from module load time to an explicit initialization function is a solid improvement. This gives better control over when the workspace update listener is registered and integrates cleanly with the startup sequence.


500-500: LGTM!

The export statement correctly exposes both functions for use in other modules.

emain/emain.ts (2)

26-26: LGTM!

The import correctly adds the new initialization function alongside existing imports.


353-353: LGTM! Correct initialization sequence.

The menu event subscriptions are properly initialized after the RPC client setup, ensuring that the workspace update handler can successfully make RPC calls when triggered.

Taskfile.yml (3)

179-180: LGTM! Platform-specific cleanup commands are correct.

The explicit platform-specific removal commands for wavesrv* binaries are syntactically correct and appropriately handle non-existent files. The transition from template-based to explicit commands improves clarity for each platform's build process.

Also applies to: 226-227, 239-240


263-268: LGTM! Platform-filtered cleanup approach is well-structured.

The use of platforms filters to conditionally execute platform-specific removal commands is the idiomatic Task approach and correctly handles the cleanup of wsh* binaries across all target architectures.


533-538: LGTM! Consistent platform-specific cleanup.

The platform-specific removal of package.json before running npm init follows the established pattern and correctly handles both Unix-like and Windows platforms.

frontend/app/store/wshrouter.ts (2)

11-11: LGTM! Clear constant for control route.

The introduction of ControlRouteName with the $ prefix clearly identifies this as a reserved control route, consistent with the PR's control-plane routing refactor.


47-54: Consistent control route integration across lifecycle messages.

The addition of route: ControlRouteName to announce, reannounce, and unannounce messages properly routes control-plane messages through the control route via the routing infrastructure. The backend RpcMessage struct defines the Route field for routing/forwarding, and the RouteAnnounceCommand and RouteUnannounceCommand handlers are correctly implemented to process these messages when they arrive at the control plane.

pkg/wsl/wsl-unix.go (1)

65-67: LGTM! Critical bug fix.

The method now correctly assigns to c.Stderr instead of incorrectly writing to c.Stdout. This fix ensures error output is properly routed to stderr, which is essential for tools that parse stdout and stderr separately.

pkg/wsl/wsl-win.go (1)

104-106: LGTM! Cross-platform parity achieved.

This fix mirrors the correction in pkg/wsl/wsl-unix.go, ensuring SetStderr correctly assigns to c.c.Stderr on Windows. The cross-platform consistency is now established.

pkg/wcloud/wcloud.go (1)

143-147: LGTM! Clean observability control.

The verbose parameter allows selective suppression of request logging. The conditional logging on lines 145-147 is well-placed and maintains existing behavior for most callers (passing true), while allowing SendDiagnosticPing to suppress logs by passing false.

pkg/waveobj/wtype.go (1)

31-31: LGTM! Proper wave object type registration.

The MainServer type is correctly registered in ValidOTypes and AllWaveObjTypes, following the established pattern for wave object types. The GetOType() implementation is consistent with other types.

Also applies to: 43-43, 310-319

pkg/tsgen/tsgen.go (1)

502-504: LGTM! Appropriate exclusion of sensitive type.

Skipping TypeScript generation for MainServer prevents exposing JWT key fields to the frontend. This is the correct approach since MainServer contains sensitive authentication data (JwtPrivateKey, JwtPublicKey) that should remain server-side only.

frontend/app/store/ws.ts (4)

21-26: Good fix for the reconnect handler removal.

The previous code likely referenced this.reconnectHandlers which doesn't exist on the class. This correctly references the module-level reconnectHandlers array.


40-62: Consistent rename from routeId to stableId.

The rename is applied consistently across the class property, constructor parameter, and assignment.


77-79: Good URL encoding practice.

Using encodeURIComponent on the stableId ensures special characters are properly escaped in the query string.


222-234: Smart guard to drop stale routing messages when offline.

Dropping routeannounce and routeunannounce RPC messages when the WebSocket is not open prevents queueing outdated routing information that would be invalid upon reconnection. The guard correctly allows other RPC messages to be queued.

frontend/types/gotypes.d.ts (2)

1002-1009: RpcContext type correctly mirrors the Go struct.

The TypeScript type aligns with the Go definition in pkg/wshrpc/wshrpctypes.go:382-388. The routeid is correctly required while other fields are optional, matching the omitempty JSON tags in Go.


197-202: CommandAuthenticateRtnData updated to use RpcContext.

The transition from separate routeid/authtoken fields to the unified RpcContext type aligns with the broader authentication refactoring in this PR.

pkg/wshutil/wshrouter_controlimpl.go (3)

17-21: Clean struct and interface implementation.

The WshRouterControlImpl struct properly holds a reference to the router and implements the WshServerImpl marker interface.


23-53: RouteAnnounce and RouteUnannounce handlers are well-structured.

Both methods follow a consistent pattern: validate source, get response handler, check ingress link, then perform the route operation. Error messages are clear and actionable.


204-218: Validation logic is clear and handles edge cases.

The validateRpcContextFromAuth function correctly enforces that routers cannot have a routeId while leaves must have one.

pkg/wshutil/wshrpc.go (7)

43-47: AbstractRpcClient interface properly extended.

The interface now includes GetPeerInfo() for debugging and updates SendRpcMessage to accept ingressLinkId for link tracking.


49-62: WshRpc struct correctly updated for RpcInputChType.

The InputCh type change to chan baseds.RpcInputChType enables ingress link tracking throughout the RPC flow.


120-132: RpcMessage simplified by removing AuthToken.

The removal of AuthToken from RpcMessage aligns with the JWT-based authentication refactoring. The new DataType field addition appears intentional for type discrimination.


207-235: Constructors properly initialize with RpcInputChType channels.

MakeWshRpcWithChannels correctly creates the typed channel, and MakeWshRpc provides a convenient wrapper. The channel initialization on line 210 correctly uses baseds.RpcInputChType.


289-341: Request handling correctly propagates ingressLinkId.

The ingressLinkId is properly threaded through handleRequestInternal to the RpcResponseHandler, enabling downstream code to identify the originating link.


343-397: Server loop correctly processes RpcInputChType.

The loop properly extracts MsgBytes for unmarshaling (line 372) and IngressLinkId for request handling (line 384). The debug logging on line 361 correctly accesses inputVal.MsgBytes.


609-611: GetIngressLinkId accessor completes the ingress tracking API.

This accessor allows command handlers to retrieve the ingress link ID for routing decisions.

db/migrations-wstore/000009_mainserver.down.sql (1)

1-1: Standard down migration.

The DROP TABLE IF EXISTS correctly reverses the corresponding up migration and handles the case where the table may not exist.

pkg/wshutil/wshrpcio.go (1)

19-23: Correctly adapted to use RpcInputChType.

The function now wraps line bytes in RpcInputChType before sending. The IngressLinkId defaults to 0 (NoLinkId), which is appropriate for stream-based input where there's no link context to track.

db/migrations-wstore/000009_mainserver.up.sql (1)

1-5: Standard migration for MainServer persistence.

The table structure follows the waveobj pattern with oid (UUID primary key), version (for optimistic locking), and data (JSON blob). The IF NOT EXISTS clause ensures idempotency.

cmd/wsh/cmd/wshcmd-wavepath.go (1)

59-68: LGTM! Consistent TabId handling.

The WAVETERM_TABID validation and propagation to PathCommandData follows the established pattern and is correctly implemented.

cmd/wsh/cmd/wshcmd-term.go (1)

59-73: LGTM! TabId requirement properly integrated.

The validation and propagation of WAVETERM_TABID to the terminal block creation data is correct and aligns with the PR's authentication and routing refactor.

cmd/wsh/cmd/wshcmd-editor.go (1)

58-64: LGTM! Editor command properly requires TabId.

The addition of WAVETERM_TABID validation and propagation to the editor block creation is correct and consistent with the broader authentication refactor.

frontend/app/store/wshrpcutil.ts (2)

4-4: LGTM! Import correctly updated.

The addition of setWpsRpcClient to the import statement is necessary for wiring the RPC client to the WPS subsystem.


22-22: LGTM! WPS client wiring properly positioned.

The call to setWpsRpcClient(TabRpcClient) is correctly placed after client instantiation and before route registration, ensuring the WPS subsystem has access to the RPC client for event handling.

cmd/wsh/cmd/wshcmd-launch.go (1)

57-62: LGTM!

The TabId is correctly populated in the CommandCreateBlockData structure after validation. The integration is clean and consistent with the new tab-context requirement.

cmd/wsh/cmd/wshcmd-view.go (1)

64-74: LGTM!

Both preview paths (web URL and local file) correctly populate the TabId field in their respective CommandCreateBlockData structures. The implementation ensures tab context is consistently provided regardless of the preview type.

Also applies to: 91-101

cmd/wsh/cmd/wshcmd-web.go (1)

120-130: LGTM!

The TabId field is correctly populated in the web block creation path. The implementation is consistent with other command files and properly integrates the tab context requirement.

frontend/app/store/wshrpcutil-base.ts (2)

4-4: LGTM!

The import of setWpsRpcClient is clean and correctly grouped with the existing wpsReconnectHandler import from the same module.


114-127: LGTM!

The setWpsRpcClient(electronClient) call is correctly placed in the initialization sequence. The ordering (router setup → WebSocket init → WPS client config → route registration → reconnect handlers) follows a logical initialization flow.

cmd/wsh/cmd/wshcmd-run.go (1)

141-153: LGTM!

The TabId field is correctly populated in the CommandCreateBlockData structure for the run command. The integration is clean and maintains the existing logic while adding the required tab context.

cmd/wsh/cmd/wshcmd-editconfig.go (1)

41-47: LGTM! Proper validation of required environment variable.

The TabId retrieval and validation logic is correct. The error handling ensures that the required WAVETERM_TABID environment variable is present before proceeding with block creation.

cmd/wsh/cmd/wshcmd-createblock.go (1)

37-47: LGTM! Consistent implementation with other commands.

The TabId validation and propagation follows the same pattern as other block-creation commands, ensuring consistency across the codebase.

pkg/wshrpc/wshclient/barerpcclient.go (1)

32-33: LGTM! Aligns with the new link-based routing architecture.

The migration from explicit channel wiring and RegisterRoute to MakeWshRpc with RegisterTrustedLeaf is consistent with the PR's objectives to refactor routing to a link-based model with trust levels. The empty RpcContext{} is appropriate for a bare client that doesn't require specific route or block context.

pkg/wshutil/wshcmdreader.go (1)

69-69: LGTM! Channel type migration is correct.

The migration to baseds.RpcInputChType wraps the escape sequence bytes appropriately. The IngressLinkId field is left at its zero value, which appears intentional for this local PTY buffer processing context where ingress link tracking isn't required.

pkg/waveapp/waveapp.go (1)

166-189: Validate RouteId from RPC context.

The code validates BlockId at lines 171-173 but doesn't validate RouteId before assigning it at line 188. Since the RouteId is now extracted from the JWT context and may be used for routing decisions, consider adding validation to ensure it's not empty.

🔎 Suggested validation
 	client.RpcContext = rpcCtx
 	if client.RpcContext == nil || client.RpcContext.BlockId == "" {
 		return fmt.Errorf("no block id in rpc context")
 	}
+	if client.RpcContext.RouteId == "" {
+		return fmt.Errorf("no route id in rpc context")
+	}
pkg/wshrpc/wshserver/wshserverutil.go (1)

22-27: LGTM! Simplified RPC client construction.

The change correctly adapts to the new MakeWshRpc signature, removing manual channel management and simplifying initialization. The empty RpcContext is appropriate for the main server RPC client singleton.

pkg/baseds/baseds.go (1)

1-14: LGTM! Clean shared type definitions.

The new LinkId type, NoLinkId constant, and RpcInputChType struct provide well-structured primitives for link tracking across the routing and RPC systems. The design is minimal and appropriate for shared infrastructure.

pkg/wcore/wcore.go (1)

198-214: LGTM! Robust key initialization with comprehensive error handling.

The key decoding and loading logic correctly handles all error cases. The wavejwt.SetPrivateKey and SetPublicKey calls likely validate key formats internally, which should catch any corrupted data.

pkg/wslconn/wslconn.go (3)

69-73: LGTM! Template updated for name-driven routing.

The ConnServerCmdTemplate correctly adds --router and --conn flags, aligning with the new connection naming and routing architecture. The additional placeholder supports optional flags like --dev.


270-274: LGTM! Dev mode flag correctly integrated.

The dev mode detection and flag construction is clean. The devFlag variable is safely constructed (either empty or --dev), and the connection name is properly shell-quoted.


342-342: The route ID consistency is correct—no action needed. The connserver is started with conn.GetName() (line 274), which becomes the --conn parameter that the connserver uses to register via MakeConnectionRouteId(). This ensures both the registration and wait call use the same route ID format: "conn:wsl://distro_name".

Likely an incorrect or invalid review comment.

cmd/wsh/cmd/wshcmd-ssh.go (1)

57-60: This is not an ssh-specific breaking change—it's part of the broader TabId architecture used across all wsh commands that create blocks.

The WAVETERM_TABID requirement applies consistently across 13+ commands (ssh, term, run, createblock, editconfig, editor, etc.), all following the same pattern. The environment variable is automatically set by shellcontroller.go when commands execute within a Wave terminal block, so users invoking wsh ssh --new from the Wave UI will have WAVETERM_TABID provided transparently. The requirement only affects programmatic or out-of-context invocations, which appear to be outside the intended usage scope.

pkg/util/packetparser/packetparser.go (1)

22-48: LGTM - Clean migration to RpcInputChType.

The refactor correctly wraps raw packet bytes in baseds.RpcInputChType{MsgBytes: ...}. The IngressLinkId field is appropriately left unset here since the packet parser operates at a lower level and doesn't have link context - the ingress link will be set by upstream routing components.

pkg/remote/conncontroller/conncontroller.go (1)

287-313: LGTM - Connserver startup refactored with RpcContext and dev mode support.

The changes correctly:

  • Construct an RpcContext with RouteId, SockName, and Conn for JWT token creation
  • Add the --conn flag with properly quoted connection name via shellutil.HardQuote
  • Conditionally append --dev flag for development mode

The command template update aligns with the connserver CLI changes in this PR.

pkg/wshrpc/wshclient/wshclient.go (1)

44-48: LGTM - Generated RPC client wrappers follow established patterns.

The new commands (AuthenticateTokenVerifyCommand, GetJwtPublicKeyCommand, SetPeerInfoCommand) are correctly generated following the existing pattern of using sendRpcRequestCallHelper with appropriate type parameters.

Also applies to: 383-387, 661-665

frontend/app/store/wps.ts (1)

9-13: LGTM - Clean dependency injection pattern for RPC client.

The setWpsRpcClient setter allows the RPC client to be injected after initialization, decoupling the WPS module from the client setup lifecycle. This aligns with the broader RPC wiring changes in wshrpcutil-base.ts.

pkg/wshrpc/wshserver/wshserver.go (3)

73-75: LGTM - Clean implementation of GetJwtPublicKeyCommand.

Returns the base64-encoded public key via wavejwt.GetPublicKeyBase64(). This aligns with the new JWT-based authentication flow introduced in this PR.


473-486: LGTM - Input validation added for block deletion commands.

Adding explicit checks for empty BlockId in both DeleteSubBlockCommand and DeleteBlockCommand is a good defensive measure that provides clearer error messages than downstream failures.


1348-1356: This concern is not valid. TabId validation occurs downstream in createBlockObj (pkg/wcore/block.go, lines 132-135), which retrieves the tab and returns an error "tab not found" if the TabId is invalid or empty. The CLI caller (the only caller found) also validates TabId upfront by reading it from the WAVETERM_TABID environment variable and returning an error if absent. There is no code path that allows CreateBlockCommand to be called with an empty or invalid TabId.

Likely an incorrect or invalid review comment.

frontend/app/store/wshclientapi.ts (1)

30-33: LGTM - Generated TypeScript RPC API matches Go backend.

The new commands (AuthenticateTokenVerifyCommand, GetJwtPublicKeyCommand, SetPeerInfoCommand) are correctly generated with appropriate type signatures that match their Go counterparts in wshclient.go.

Also applies to: 315-318, 550-553

cmd/wsh/cmd/wshcmd-root.go (1)

205-207: Usage of getTabIdFromEnv helper is correctly implemented.

The helper is actively used across 11 different command files (createblock, secret, web, editconfig, view, term, ssh, run, launch, wavepath, and editor), confirming it's being properly utilized by other commands as expected.

pkg/web/ws.go (3)

35-42: LGTM: Clean struct and map refactor for link-based routing.

The StableConnInfo struct correctly encapsulates connection identity with both the WebSocket connection ID and the router link ID. The map key change from route ID to stable ID aligns with the broader refactor.


260-275: Good defensive handling in registerConn.

The implementation properly handles the case of replacing an existing connection for the same stable ID by unregistering the old link before registering the new one. This prevents link leaks.


277-289: Proper ownership check in unregisterConn.

The check ensuring curConnInfo.ConnId != wsConnId prevents a race where a newer connection could be inadvertently unregistered by an older connection's cleanup path.

pkg/wavejwt/wavejwt.go (2)

41-51: LGTM: Proper Ed25519 key generation.

Uses crypto/rand for secure random generation and returns raw key bytes in the KeyPair struct.


129-130: Verify 1-year default expiry is appropriate for all token use cases.

A 365-day expiry is quite long for JWTs. If some tokens should be short-lived (e.g., for ephemeral shell sessions), consider requiring callers to explicitly set ExpiresAt or using different defaults based on token purpose.

cmd/server/main-server.go (3)

460-465: LGTM: Proper router initialization sequence.

Creating the router before other initialization steps and immediately setting it as root ensures the routing infrastructure is ready for subsequent registrations.


389-397: LGTM: Clean trusted leaf registration for internal routes.

The main RPC client and local connection are correctly registered as trusted leaves with their respective route IDs. The MakeWshRpc call properly constructs an RpcContext with the connection name.


551-555: Good: InitMainServer error handling aborts startup.

Failing to initialize the main server (which sets up JWT keys) correctly prevents the server from running in an invalid state.

pkg/blockcontroller/shellcontroller.go (5)

423-434: LGTM: Consistent RpcContext construction for WSL.

The context is properly populated with RouteId, SockName, BlockId, and Conn fields before JWT token generation.


456-467: LGTM: Consistent RpcContext construction for SSH.

Follows the same pattern as WSL, correctly using conn.Opts.String() for the Conn field.


483-493: LGTM: Consistent RpcContext construction for Local.

The Local branch correctly omits the Conn field (empty for local connections).


518-522: Good: Error handling added to RegisterTrustedLeaf.

Returning an error when controller route registration fails prevents the shell from running without proper RPC routing.


599-600: LGTM: Link-based cleanup in wait loop.

Using UnregisterLink(controllerLinkId) in the deferred cleanup ensures the link is properly removed when the shell process exits.

pkg/wshutil/wshproxy.go (2)

14-21: LGTM: Clean struct update for typed channels and peer info.

The FromRemoteCh type change to baseds.RpcInputChType and addition of PeerInfo align with the link-based routing refactor.


54-57: LGTM: RecvRpcMessage correctly extracts message bytes.

The function properly unwraps MsgBytes from the typed channel input.

pkg/wshutil/wshutil.go (6)

204-208: LGTM: Channel type and RPC constructor update.

Using baseds.RpcInputChType and MakeWshRpcWithChannels aligns with the typed channel architecture.


300-317: Good dev-mode validation for RpcContext invariants.

The panic checks ensure that routers don't have route IDs and non-routers always have route IDs. This catches configuration errors early in development.


319-327: LGTM: Clean helper for claims-to-context conversion.

Centralizes the mapping between JWT claims and RpcContext.


360-376: LGTM: Link-based lifecycle for stdio client.

Properly registers a trusted router link, and the cleanup function correctly unregisters the link and closes the channel.


406-436: LGTM: Proper link lifecycle management for domain socket clients.

Using atomic.Int32 for the link ID container and RegisterUntrustedLink for domain socket clients (which need JWT verification) is the correct approach.


276-277: Good: Home directory expansion for socket paths.

Expanding ~ in socket paths before connecting prevents connection failures when paths contain home directory references.

cmd/wsh/cmd/wshcmd-connserver.go (5)

68-99: LGTM: Proper panic handling and link lifecycle in listener handler.

The defer at line 69-71 catches panics, and the atomic-based link ID tracking with cleanup on connection close prevents link leaks.


120-129: LGTM: Clean RPC client setup with proper context construction.

The route ID is derived from the connection name, and the RpcContext is properly populated. Registration as a trusted leaf is correct for the connserver's own RPC client.


171-187: Good: JWT public key fetch and validation sequence.

Fetching the public key from the upstream server and setting it locally enables JWT validation for incoming domain socket connections. The base64 decode and size validation (in SetPublicKey) ensure the key is valid.


266-271: Good: Early validation of required --conn parameter.

Failing early when the required parameter is missing provides a clear error message and prevents running in an invalid state.


219-224: Good: Panic protection added to sysinfo loop.

The deferred panic handler ensures the goroutine doesn't crash the entire process if something goes wrong.

pkg/wshrpc/wshrpctypes.go (6)

53-63: Well-organized control plane command constants.

The command constants are clearly documented with their routing context (e.g., $control, $control:root). This improves code clarity for the new routing architecture.


200-208: LGTM on new interface methods.

The new AuthenticateTokenVerifyCommand, SetPeerInfoCommand, and GetJwtPublicKeyCommand methods align with the frontend client API and server-side helpers shown in the relevant code snippets.


380-380: Improved cancellation signature.

Changing StreamCancelFn from func() to func(context.Context) error allows for context-aware cancellation and proper error propagation from the cancellation handler.


383-389: Clean RpcContext struct design.

The explicit RpcContext struct with clear fields (SockName, RouteId, BlockId, Conn, IsRouter) replaces the previous reflection-based tag injection approach. This is more maintainable and type-safe.


391-396: Appropriate return data structure.

Including RpcContext as a pointer in the authentication return data allows optional context binding while maintaining backward compatibility with the existing Env and InitScriptText fields.


407-409: Consistent tag cleanup across command data types.

The removal of wshcontext tags in favor of plain json tags across all command data structures is consistent with the shift to explicit RpcContext fields. This simplifies serialization and eliminates the need for reflection-based context injection.

Also applies to: 411-413, 415-418, 420-423, 429-431, 445-449, 457-462, 523-527, 534-536, 711-717, 809-811, 827-832

pkg/wshutil/wshrouter.go (9)

23-37: Clean routing constant organization.

The control plane routing constants (ControlRoute, ControlRootRoute, ControlPrefix) are well-structured and the prefixes provide clear namespace separation.


49-73: Good encapsulation with internal types.

The linkMeta, rpcRoutingInfo, and messageWrap types provide clean abstractions for the link-based routing model. The Name() method on linkMeta is useful for logging.


75-91: Well-structured router state.

The refactored WshRouter struct clearly separates concerns: link management (linkMap), route resolution (routeMap), RPC tracking (rpcMap), and upstream buffering. The use of a separate mutex for upstream buffering (upstreamBufLock) avoids contention with the main lock.


400-457: Static analysis false positive - defer captures exitReason by reference.

The golangci-lint warnings about "ineffectual assignment to exitReason" at lines 415 and 425 are false positives. The defer at line 411 captures exitReason by reference, so the assignments before break correctly update what gets logged when the deferred function executes.

The recv loop implementation is solid with proper panic handling and clear exit reason logging.


459-491: Good defensive copying pattern.

Both getLinkMeta and getLinkForRoute return copies of linkMeta rather than pointers to map values. This correctly prevents data races when the caller uses the returned struct outside the lock.


531-547: Control plane registration looks correct.

The control plane is properly registered as a trusted leaf, and the source route is correctly bound. The root router also binds $control:root in SetAsRootRouter.


633-664: Thorough validation in bindRouteLocally.

Good defensive checks for: NoLinkId, non-bindable routes, missing links, untrusted links, and link kind mismatches. The source route conflict check prevents accidental rebinding.


695-701: Broker integration for route lifecycle.

The unsubscribeFromBroker properly cleans up subscriptions and publishes a RouteGone event. The panic handler protects against broker-related panics.


703-714: Clear unauthenticated error handling.

The error response correctly identifies the unauthenticated link and the attempted command, providing useful debugging information.

Comment on lines 248 to +265
func serverRun(cmd *cobra.Command, args []string) error {
var logFile *os.File
logWriter := io.Writer(os.Stderr)
if connServerDev {
var err error
logFile, err = os.OpenFile("/tmp/connserver.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open log file: %v\n", err)
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.SetPrefix(fmt.Sprintf("[PID:%d] ", os.Getpid()))
} else {
defer logFile.Close()
logWriter = io.MultiWriter(os.Stderr, logFile)
log.SetOutput(logWriter)
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.SetPrefix(fmt.Sprintf("[PID:%d] ", os.Getpid()))
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix ineffectual assignment to logWriter.

The static analysis correctly identifies that logWriter is assigned at line 250 but never used if the else branch is taken. The variable should only be declared where it's used.

🔎 Proposed fix
 func serverRun(cmd *cobra.Command, args []string) error {
 	var logFile *os.File
-	logWriter := io.Writer(os.Stderr)
 	if connServerDev {
 		var err error
 		logFile, err = os.OpenFile("/tmp/connserver.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "failed to open log file: %v\n", err)
 			log.SetFlags(log.LstdFlags | log.Lmicroseconds)
 			log.SetPrefix(fmt.Sprintf("[PID:%d] ", os.Getpid()))
 		} else {
 			defer logFile.Close()
-			logWriter = io.MultiWriter(os.Stderr, logFile)
-			log.SetOutput(logWriter)
+			log.SetOutput(io.MultiWriter(os.Stderr, logFile))
 			log.SetFlags(log.LstdFlags | log.Lmicroseconds)
 			log.SetPrefix(fmt.Sprintf("[PID:%d] ", os.Getpid()))
 		}
 	}
🧰 Tools
🪛 golangci-lint (2.5.0)

[error] 250-250: ineffectual assignment to logWriter

(ineffassign)

Comment on lines +164 to +169
wshclient.AuthenticateCommand(RpcClient, jwtToken, &wshrpc.RpcOpts{Route: wshutil.ControlRoute})
blockId := os.Getenv("WAVETERM_BLOCKID")
if blockId != "" {
peerInfo := fmt.Sprintf("domain:block:%s", blockId)
wshclient.SetPeerInfoCommand(RpcClient, peerInfo, &wshrpc.RpcOpts{Route: wshutil.ControlRoute})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

AuthenticateCommand error is silently ignored.

The return value and potential error from AuthenticateCommand are not handled. If authentication fails, the RPC client will continue operating in an unauthenticated state without any indication to the caller.

🔎 Proposed fix
-	wshclient.AuthenticateCommand(RpcClient, jwtToken, &wshrpc.RpcOpts{Route: wshutil.ControlRoute})
+	_, err = wshclient.AuthenticateCommand(RpcClient, jwtToken, &wshrpc.RpcOpts{Route: wshutil.ControlRoute})
+	if err != nil {
+		return fmt.Errorf("error authenticating: %v", err)
+	}
🤖 Prompt for AI Agents
In cmd/wsh/cmd/wshcmd-root.go around lines 164 to 169, the call to
wshclient.AuthenticateCommand ignores its return value and any error; change it
to capture the error, check if err != nil, log the authentication failure (with
the err) and abort startup (e.g., return the error or call os.Exit(1)) so the
RPC client does not continue unauthenticated; only proceed to set peerInfo when
authentication succeeds.

Comment on lines +187 to +194
blockId := os.Getenv("WAVETERM_BLOCKID")
if blockId == "" {
return nil, fmt.Errorf("no WAVETERM_BLOCKID env var set")
}
rtnData, err := wshclient.ResolveIdsCommand(RpcClient, wshrpc.CommandResolveIdsData{
BlockId: blockId,
Ids: []string{id},
}, &wshrpc.RpcOpts{Timeout: 2000})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n 'resolveSimpleId|resolveBlockArg' --type=go -C3

Repository: wavetermdev/waveterm

Length of output: 5714


🏁 Script executed:

# Read the full resolveSimpleId implementation
sed -n '179,210p' cmd/wsh/cmd/wshcmd-root.go

Repository: wavetermdev/waveterm

Length of output: 1069


🏁 Script executed:

# Check if getThisBlockMeta exists or was removed
rg 'getThisBlockMeta' --type=go

Repository: wavetermdev/waveterm

Length of output: 46


🏁 Script executed:

# Check where WAVETERM_BLOCKID is set or used
rg 'WAVETERM_BLOCKID' --type=go -B2 -A2

Repository: wavetermdev/waveterm

Length of output: 1505


🏁 Script executed:

# Check if there's documentation about wsh or how it's meant to be used
find . -name "README*" -o -name "*.md" | grep -i wsh | head -5

Repository: wavetermdev/waveterm

Length of output: 46


🏁 Script executed:

# Look at the initialization code in wshcmd-root.go to understand the full context
sed -n '1,150p' cmd/wsh/cmd/wshcmd-root.go | tail -50

Repository: wavetermdev/waveterm

Length of output: 1639


🏁 Script executed:

# Check the full context of how environment is handled and wsh is invoked
sed -n '130,175p' cmd/wsh/cmd/wshcmd-root.go

Repository: wavetermdev/waveterm

Length of output: 2006


🏁 Script executed:

# Check git history to see if getThisBlockMeta existed and what it did
git log --all --oneline --grep="getThisBlockMeta" 2>/dev/null || echo "Git history not available"

Repository: wavetermdev/waveterm

Length of output: 46


🏁 Script executed:

# Check if there are any examples or tests showing wsh usage patterns
find . -type f -name "*.go" -path "*/test*" -o -path "*/example*" | xargs grep -l "wsh" 2>/dev/null | head -5

Repository: wavetermdev/waveterm

Length of output: 87


🏁 Script executed:

# Check the test file to understand how wsh is used
cat cmd/test/test-main.go | head -100

Repository: wavetermdev/waveterm

Length of output: 1385


🏁 Script executed:

# Let's look for any pattern of how this worked before - check if there are conditionals or fallback patterns
rg -A5 -B5 'resolveSimpleId.*this' --type=go

Repository: wavetermdev/waveterm

Length of output: 46


resolveSimpleId requires WAVETERM_BLOCKID environment variable for relative ID resolution.

When resolving relative identifiers (e.g., "this" or simple names), resolveSimpleId now requires WAVETERM_BLOCKID to be set. Full ORef format identifiers (e.g., "block:xxx") continue to work without it. This affects all commands that use resolveBlockArg() and rely on the default "this" resolution when no explicit block argument is provided—including getmeta, readfile, setmeta, web, debug, setvar, getvar, and deleteblock.

In normal waveterm execution, WAVETERM_BLOCKID is automatically set by the shell controller. The constraint only impacts usage patterns where wsh is invoked outside of a waveterm block context with relative identifiers.

🤖 Prompt for AI Agents
cmd/wsh/cmd/wshcmd-root.go around lines 187 to 194, resolveSimpleId currently
always requires WAVETERM_BLOCKID which breaks full ORef identifiers; change the
logic to first detect if the provided id is already a full ORef (e.g., starts
with "block:" or matches the ORef pattern) and, if so, call ResolveIdsCommand
directly without reading WAVETERM_BLOCKID, otherwise (for relative ids like
"this") keep requiring WAVETERM_BLOCKID; also update the error returned when
WAVETERM_BLOCKID is missing to clearly state it is required only for relative
IDs.

Comment on lines +268 to 271
if jwtToken != "" && cmdOpts.ForceJwt {
conn.Debugf(ctx, "adding JWT token to environment\n")
cmdCombined = fmt.Sprintf(`%s=%s %s`, wavebase.WaveJwtTokenVarName, jwtToken, cmdCombined)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file to confirm the line numbers and code
wc -l pkg/shellexec/shellexec.go

Repository: wavetermdev/waveterm

Length of output: 94


🏁 Script executed:

# Read the section around line 268 to see the change
sed -n '260,280p' pkg/shellexec/shellexec.go

Repository: wavetermdev/waveterm

Length of output: 1007


🏁 Script executed:

# Check line 447 for StartRemoteShellProc JWT handling
sed -n '440,460p' pkg/shellexec/shellexec.go

Repository: wavetermdev/waveterm

Length of output: 955


🏁 Script executed:

# Check line 514 for StartLocalShellProc JWT handling
sed -n '505,525p' pkg/shellexec/shellexec.go

Repository: wavetermdev/waveterm

Length of output: 991


🏁 Script executed:

# Find all calls to StartWslShellProc
rg -n 'StartWslShellProc\s*\(' --type=go -A 2 -B 2

Repository: wavetermdev/waveterm

Length of output: 911


🏁 Script executed:

# Search for CommandOptsType struct definition and ForceJwt field
rg -n 'type CommandOptsType' --type=go -A 15

Repository: wavetermdev/waveterm

Length of output: 1293


🏁 Script executed:

# Check the context around the caller at line 435 in shellcontroller.go
sed -n '420,450p' pkg/blockcontroller/shellcontroller.go

Repository: wavetermdev/waveterm

Length of output: 1236


🏁 Script executed:

# Search for all StartWslShellProc calls with broader context
rg -n 'StartWslShellProc' --type=go -B 10 -A 5

Repository: wavetermdev/waveterm

Length of output: 6895


🏁 Script executed:

# Look for where cmdOpts is populated in shellcontroller.go
rg -n 'CommandOptsType\s*\{' pkg/blockcontroller/shellcontroller.go -A 10

Repository: wavetermdev/waveterm

Length of output: 46


🏁 Script executed:

# Look earlier in the function to see how cmdOpts is initialized
sed -n '350,415p' pkg/blockcontroller/shellcontroller.go

Repository: wavetermdev/waveterm

Length of output: 2692


🏁 Script executed:

# Search for ForceJwt assignments in shellcontroller.go
rg -n 'ForceJwt' pkg/blockcontroller/shellcontroller.go -B 3 -A 3

Repository: wavetermdev/waveterm

Length of output: 261


🏁 Script executed:

# Search for where ForceJwt might be set to true in the WSL code path
rg -n 'ForceJwt\s*=' --type=go -B 5 -A 5

Repository: wavetermdev/waveterm

Length of output: 984


🏁 Script executed:

# Check if there's logic to set ForceJwt based on JWT presence
rg -n 'cmdOpts.ForceJwt' --type=go -B 10 -A 2

Repository: wavetermdev/waveterm

Length of output: 4106


🏁 Script executed:

# Look at the entire WSL path to see if ForceJwt is set before the call
sed -n '414,446p' pkg/blockcontroller/shellcontroller.go

Repository: wavetermdev/waveterm

Length of output: 1271


Critical issue: JWT tokens are prepared but not injected for WSL Shell blocks.

While the consistency across the three shell functions is correct (all require ForceJwt flag), the calling code has a bug: on line 434, a JWT token is created and stored in swapToken.Env, but cmdOpts.ForceJwt is never set to true before calling StartWslShellProc at line 435. This means the prepared JWT will silently fail to inject because the condition if jwtToken != "" && cmdOpts.ForceJwt at line 268 will evaluate to false (ForceJwt defaults to false for Shell blocks).

Add cmdOpts.ForceJwt = true in the WSL WSH path (around line 433-434) after confirming the JWT token is created, to ensure the token is actually injected into the shell environment.

🤖 Prompt for AI Agents
In pkg/shellexec/shellexec.go around lines 433-434 (and relevant to the JWT
check at lines 268-271), the code creates and stores a JWT in swapToken.Env but
never sets cmdOpts.ForceJwt, so the injection conditional (if jwtToken != "" &&
cmdOpts.ForceJwt) prevents the token from being added to the WSL shell
environment; after confirming the JWT was successfully created for the WSL/WSH
path, set cmdOpts.ForceJwt = true before invoking StartWslShellProc (only in the
WSL/WSH branch) so the existing environment-injection code at lines ~268-271
will include the JWT in the shell command.


var (
safePattern = regexp.MustCompile(`^[a-zA-Z0-9_/.-]+$`)
safePattern = regexp.MustCompile(`^[a-zA-Z0-9_@:,+=/.-]+$`)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n pkg/util/shellutil/shellquote.go | head -60

Repository: wavetermdev/waveterm

Length of output: 1460


🏁 Script executed:

# Find usages of HardQuote and related functions
rg -n 'HardQuote|SoftQuote' --type=go pkg/util/shellutil/

Repository: wavetermdev/waveterm

Length of output: 1324


🏁 Script executed:

# Look for test files to understand expected behavior
fd -e _test.go shellquote

Repository: wavetermdev/waveterm

Length of output: 46


🏁 Script executed:

# Search for HardQuote usage across entire codebase
rg -l 'HardQuote' --type=go | head -20

Repository: wavetermdev/waveterm

Length of output: 315


🏁 Script executed:

cat -n pkg/util/shellutil/shellquote_test.go

Repository: wavetermdev/waveterm

Length of output: 3088


🏁 Script executed:

# Check if HardQuote is used in any shell command execution contexts
rg -B5 -A5 'HardQuote.*\(' pkg/genconn/genconn.go pkg/shellexec/shellexec.go pkg/wslconn/wslconn.go

Repository: wavetermdev/waveterm

Length of output: 2744


🏁 Script executed:

# Look at tokenswap.go more carefully to understand the context
cat -n pkg/util/shellutil/tokenswap.go | head -140

Repository: wavetermdev/waveterm

Length of output: 4520


🏁 Script executed:

# Check if test file has any tests for strings starting with dash
grep -n '^\s*input:\s*"-' pkg/util/shellutil/shellquote_test.go

Repository: wavetermdev/waveterm

Length of output: 46


🏁 Script executed:

# Let's verify the exact behavior by checking what HardQuote does with "-rf"
# Look at the pattern more carefully
cat -n pkg/util/shellutil/shellquote.go | sed -n '15,35p'

Repository: wavetermdev/waveterm

Length of output: 600


Flag injection risk: strings starting with - are returned unquoted by safePattern.

The expanded safePattern regex ^[a-zA-Z0-9_@:,+=/.-]+$ matches strings starting with - and returns them unquoted. While not traditional command injection, this causes flag injection vulnerabilities in command contexts.

For example, in pkg/genconn/genconn.go line 73, if opts.Cwd is a string like -rf, the code generates cd -rf && ... where cd misinterprets -rf as flags rather than a directory name. Similar issues occur in environment variable assignments in tokenswap.go and elsewhere.

Additionally, the test file contains no test case for dash-prefixed strings, indicating a coverage gap in validation.

Recommendation: Either remove - from safePattern or quote all strings that start with -.

🤖 Prompt for AI Agents
In pkg/util/shellutil/shellquote.go around line 16, safePattern currently allows
strings beginning with '-' which leads to flag injection (unquoted dash-prefixed
strings are returned raw); update the logic so dash-prefixed values are always
quoted: either remove '-' from the safePattern character class or add a
conditional that detects strings starting with '-' and forces quoting, and add
unit tests covering dash-prefixed inputs to verify they are quoted.

Comment on lines +27 to +32
file, err := os.Create(DumpFilePath)
if err != nil {
continue
}
utilfn.DumpGoRoutineStacks(file)
file.Close()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add logging for file creation failures.

When os.Create fails (e.g., permission issues, disk full), the error is silently ignored, making it difficult to diagnose why stack dumps aren't being generated.

🔎 Proposed improvements

Improvement 1: Log file creation errors

 		for range sigCh {
 			file, err := os.Create(DumpFilePath)
 			if err != nil {
+				log.Printf("failed to create stack dump file: %v\n", err)
 				continue
 			}
 			utilfn.DumpGoRoutineStacks(file)
 			file.Close()
 		}

Improvement 2 (optional): Check close error

 			utilfn.DumpGoRoutineStacks(file)
-			file.Close()
+			if err := file.Close(); err != nil {
+				log.Printf("failed to close stack dump file: %v\n", err)
+			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
file, err := os.Create(DumpFilePath)
if err != nil {
continue
}
utilfn.DumpGoRoutineStacks(file)
file.Close()
file, err := os.Create(DumpFilePath)
if err != nil {
log.Printf("failed to create stack dump file: %v\n", err)
continue
}
utilfn.DumpGoRoutineStacks(file)
file.Close()
Suggested change
file, err := os.Create(DumpFilePath)
if err != nil {
continue
}
utilfn.DumpGoRoutineStacks(file)
file.Close()
file, err := os.Create(DumpFilePath)
if err != nil {
continue
}
utilfn.DumpGoRoutineStacks(file)
if err := file.Close(); err != nil {
log.Printf("failed to close stack dump file: %v\n", err)
}
Suggested change
file, err := os.Create(DumpFilePath)
if err != nil {
continue
}
utilfn.DumpGoRoutineStacks(file)
file.Close()
file, err := os.Create(DumpFilePath)
if err != nil {
log.Printf("failed to create stack dump file: %v\n", err)
continue
}
utilfn.DumpGoRoutineStacks(file)
if err := file.Close(); err != nil {
log.Printf("failed to close stack dump file: %v\n", err)
}
🤖 Prompt for AI Agents
In pkg/util/sigutil/sigusr1_notwindows.go around lines 27 to 32, the code
currently ignores errors returned by os.Create and by file.Close; modify it to
log the os.Create error when it fails (including the error value and the path),
skip writing the dump when create fails, and after
utilfn.DumpGoRoutineStacks(file) attempt to close the file and log any Close()
error as well so failures are visible for debugging.

Comment on lines +298 to +308
type MainServer struct {
OID string `json:"oid"`
Version int `json:"version"`
Meta MetaMapType `json:"meta"`
JwtPrivateKey string `json:"jwtprivatekey"` // base64
JwtPublicKey string `json:"jwtpublickey"` // base64
}

func (*MainServer) GetOType() string {
return OType_MainServer
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for all usages of JwtPrivateKey to understand how it's handled
rg -n 'JwtPrivateKey' --type=go -B2 -A2

Repository: wavetermdev/waveterm

Length of output: 1319


🏁 Script executed:

#!/bin/bash
# Search for encryption-related functions or patterns
rg -n 'encrypt|Encrypt|cipher|Cipher|crypto' --type=go | grep -i 'key\|private' | head -20

Repository: wavetermdev/waveterm

Length of output: 612


🏁 Script executed:

#!/bin/bash
# Look for database operations and field handling around MainServer
rg -n 'MainServer' --type=go -B1 -A5 | head -50

Repository: wavetermdev/waveterm

Length of output: 2393


🏁 Script executed:

#!/bin/bash
# Search for any secret management, key rotation, or vault-related code
rg -n 'secret|Secret|vault|Vault|rotate|Rotate' --type=go | head -30

Repository: wavetermdev/waveterm

Length of output: 2506


Encrypt JWT private key at rest in database.

The JwtPrivateKey is stored in the database as a base64-encoded string without encryption. Base64 encoding is not encryption and provides no protection if the database is compromised. Implement one of the following:

  • Encrypt the private key at rest using database encryption or application-level encryption
  • Use the existing secret management system instead of direct database storage
  • Add a key rotation mechanism with secure key derivation

This is necessary to prevent private key exposure in case of database breach.

🤖 Prompt for AI Agents
In pkg/waveobj/wtype.go around lines 298–308, the JwtPrivateKey field is stored
as a base64 string which is not secure; replace direct storage with an
encrypted-at-rest approach: change the model to hold an encrypted blob or a
reference to the secret manager (e.g., EncryptedJwtPrivateKey or
JwtPrivateKeyRef), implement application-side encryption using authenticated
encryption (AES-GCM) with a KMS/secret-manager data key (store ciphertext,
nonce/iv, and tag or store a secret-manager pointer), and provide
SetJwtPrivateKey/DecryptJwtPrivateKey methods that encrypt on write and decrypt
on read; ensure keys are not logged, support key rotation by storing key-version
metadata, and persist only ciphertext (optionally base64-encoded) or a
secret-manager identifier instead of plaintext/base64 private key.

Comment on lines +352 to +370
func (router *WshRouter) runUpstreamBufferLoop() {
defer func() {
panichandler.PanicHandler("WshRouter:runUpstreamBufferLoop", recover())
}()
for {
router.upstreamBufLock.Lock()
for len(router.upstreamBuf) == 0 {
router.upstreamBufCond.Wait()
}
for {
msgBytes, ok := rpc.RecvRpcMessage()
if !ok {
break
msg := router.upstreamBuf[0]
router.upstreamBuf = router.upstreamBuf[1:]
router.upstreamBufLock.Unlock()

upstream := router.getUpstreamClient()
if upstream != nil {
upstream.SendRpcMessage(msg.msgBytes, baseds.NoLinkId, msg.debugStr)
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential memory growth and missing exit condition in upstream buffer loop.

Two concerns with runUpstreamBufferLoop:

  1. The slicing pattern router.upstreamBuf = router.upstreamBuf[1:] doesn't release memory from the underlying array. For a long-running server with high message throughput, this can cause gradual memory growth.

  2. The loop has no exit condition. If the router is shut down or upstream is permanently gone, this goroutine will spin indefinitely.

🔎 Suggested improvement for memory management
 func (router *WshRouter) runUpstreamBufferLoop() {
 	defer func() {
 		panichandler.PanicHandler("WshRouter:runUpstreamBufferLoop", recover())
 	}()
 	for {
 		router.upstreamBufLock.Lock()
 		for len(router.upstreamBuf) == 0 {
 			router.upstreamBufCond.Wait()
 		}
 		msg := router.upstreamBuf[0]
-		router.upstreamBuf = router.upstreamBuf[1:]
+		// Copy to avoid retaining old array memory
+		router.upstreamBuf = append([]messageWrap(nil), router.upstreamBuf[1:]...)
 		router.upstreamBufLock.Unlock()
 
 		upstream := router.getUpstreamClient()
 		if upstream != nil {
 			upstream.SendRpcMessage(msg.msgBytes, baseds.NoLinkId, msg.debugStr)
 		}
 	}
 }

Committable suggestion skipped: line range outside the PR's diff.

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.

2 participants