Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cmd/server/main-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,16 @@ func grabAndRemoveEnvVars() error {
if err != nil {
return err
}

// Remove WAVETERM env vars that leak from prod => dev
os.Unsetenv("WAVETERM_CLIENTID")
os.Unsetenv("WAVETERM_WORKSPACEID")
os.Unsetenv("WAVETERM_TABID")
os.Unsetenv("WAVETERM_BLOCKID")
os.Unsetenv("WAVETERM_CONN")
os.Unsetenv("WAVETERM_JWT")
os.Unsetenv("WAVETERM_VERSION")

return nil
}

Expand Down
1 change: 1 addition & 0 deletions docs/docs/customwidgets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ The `WidgetConfigType` takes the usual options common to all widgets. The `MetaT
| "cmd:env" | (optional) A key-value object represting environment variables to be run with the command. Defaults to an empty object. |
| "cmd:cwd" | (optional) A string representing the current working directory to be run with the command. Currently only works locally. Defaults to the home directory. |
| "cmd:nowsh" | (optional) A boolean that will turn off wsh integration for the command. Defaults to false. |
| "cmd:jwt" | (optional) A boolean that forces adding JWT token to the environment. Required for running waveapps as widgets (both local and remote). Defaults to false. |
| "term:localshellpath" | (optional) Sets the shell used for running your widget command. Only works locally. If left blank, wave will determine your system default instead. |
| "term:localshellopts" | (optional) Sets the shell options meant to be used with `"term:localshellpath"`. This is useful if you are using a nonstandard shell and need to provide a specific option that we do not cover. Only works locally. Defaults to an empty string. |
| "cmd:initscript" | (optional) for "shell" controller only. an init script to run before starting the shell (can be an inline script or an absolute local file path) |
Expand Down
1 change: 1 addition & 0 deletions frontend/types/gotypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ declare global {
"cmd:args"?: string[];
"cmd:shell"?: boolean;
"cmd:allowconnchange"?: boolean;
"cmd:jwt"?: boolean;
"cmd:env"?: {[key: string]: string};
"cmd:cwd"?: string;
"cmd:initscript"?: string;
Expand Down
1 change: 1 addition & 0 deletions pkg/blockcontroller/blockcontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ func createCmdStrAndOpts(blockId string, blockMeta waveobj.MetaMapType, connName
cmdStr = cmdStr + " " + utilfn.ShellQuote(arg, false, -1)
}
}
cmdOpts.ForceJwt = blockMeta.GetBool(waveobj.MetaKey_CmdJwt, false)
return cmdStr, &cmdOpts, nil
}

Expand Down
35 changes: 14 additions & 21 deletions pkg/shellexec/shellexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
Expand Down Expand Up @@ -43,6 +42,7 @@ type CommandOptsType struct {
ShellPath string `json:"shellPath,omitempty"`
ShellOpts []string `json:"shellOpts,omitempty"`
SwapToken *shellutil.TokenSwapEntry `json:"swapToken,omitempty"`
ForceJwt bool `json:"forcejwt,omitempty"`
}

type ShellProc struct {
Expand Down Expand Up @@ -212,6 +212,7 @@ func StartWslShellProc(ctx context.Context, termSize waveobj.TermSize, cmdStr st
shellOpts = append(shellOpts, cmdOpts.ShellOpts...)
shellType := shellutil.GetShellTypeFromShellPath(shellPath)
conn.Infof(ctx, "detected shell type: %s\n", shellType)
conn.Debugf(ctx, "cmdStr: %q\n", cmdStr)

if cmdStr == "" {
/* transform command in order to inject environment vars */
Expand Down Expand Up @@ -245,7 +246,6 @@ func StartWslShellProc(ctx context.Context, termSize waveobj.TermSize, cmdStr st
cmdCombined = fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
} else {
// TODO check quoting of cmdStr
shellPath = cmdStr
shellOpts = append(shellOpts, "-c", cmdStr)
cmdCombined = fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
}
Expand All @@ -266,6 +266,7 @@ func StartWslShellProc(ctx context.Context, termSize waveobj.TermSize, cmdStr st
}
jwtToken := cmdOpts.SwapToken.Env[wavebase.WaveJwtTokenVarName]
if jwtToken != "" {
conn.Debugf(ctx, "adding JWT token to environment\n")
cmdCombined = fmt.Sprintf(`%s=%s %s`, wavebase.WaveJwtTokenVarName, jwtToken, cmdCombined)
}
log.Printf("full combined command: %s", cmdCombined)
Expand Down Expand Up @@ -363,6 +364,7 @@ func StartRemoteShellProc(ctx context.Context, logCtx context.Context, termSize
shellType := shellutil.GetShellTypeFromShellPath(shellPath)
conn.Infof(logCtx, "detected shell type: %s\n", shellType)
conn.Infof(logCtx, "swaptoken: %s\n", cmdOpts.SwapToken.Token)
conn.Debugf(logCtx, "cmdStr: %q\n", cmdStr)

if cmdStr == "" {
Comment on lines 366 to 369
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

Remove token logs and redact combined command on SSH path

  • conn.Infof(... "swaptoken: %s", cmdOpts.SwapToken.Token) at Line 366 logs a sensitive token at info level. This is a credential leak.
  • Logging cmdCombined at info level (Line 404 below) also leaks env-injected secrets.

Switch to Debugf and redact, or drop entirely.

Apply:

- conn.Infof(logCtx, "swaptoken: %s\n", cmdOpts.SwapToken.Token)
+ conn.Debugf(logCtx, "swaptoken: <redacted>\n")
- conn.Infof(logCtx, "starting shell, using command: %s\n", cmdCombined)
+ conn.Debugf(logCtx, "starting shell, using command: %s\n", redactSensitive(cmdCombined))

Optional: search for similar leaks elsewhere:

#!/bin/bash
rg -nP '(Infof|Printf)\([^)]*(WAVETERM_(JWT|SWAP_TOKEN)|swaptoken|packed swaptoken|PackForClient)'

/* transform command in order to inject environment vars */
Expand Down Expand Up @@ -396,7 +398,6 @@ func StartRemoteShellProc(ctx context.Context, logCtx context.Context, termSize
cmdCombined = fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
} else {
// TODO check quoting of cmdStr
shellPath = cmdStr
shellOpts = append(shellOpts, "-c", cmdStr)
cmdCombined = fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
}
Expand Down Expand Up @@ -442,6 +443,11 @@ func StartRemoteShellProc(ctx context.Context, logCtx context.Context, termSize
conn.Debugf(logCtx, "packed swaptoken %s\n", packedToken)
cmdCombined = fmt.Sprintf(`%s=%s %s`, wavebase.WaveSwapTokenVarName, packedToken, cmdCombined)
}
jwtToken := cmdOpts.SwapToken.Env[wavebase.WaveJwtTokenVarName]
if jwtToken != "" && cmdOpts.ForceJwt {
conn.Debugf(logCtx, "adding JWT token to environment\n")
cmdCombined = fmt.Sprintf(`%s=%s %s`, wavebase.WaveJwtTokenVarName, jwtToken, cmdCombined)
}
shellutil.AddTokenSwapEntry(cmdOpts.SwapToken)
session.RequestPty("xterm-256color", termSize.Rows, termSize.Cols, nil)
sessionWrap := MakeSessionWrap(session, cmdCombined, pipePty)
Expand All @@ -453,24 +459,6 @@ func StartRemoteShellProc(ctx context.Context, logCtx context.Context, termSize
return &ShellProc{Cmd: sessionWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
}

func isZshShell(shellPath string) bool {
// get the base path, and then check contains
shellBase := filepath.Base(shellPath)
return strings.Contains(shellBase, "zsh")
}

func isBashShell(shellPath string) bool {
// get the base path, and then check contains
shellBase := filepath.Base(shellPath)
return strings.Contains(shellBase, "bash")
}

func isFishShell(shellPath string) bool {
// get the base path, and then check contains
shellBase := filepath.Base(shellPath)
return strings.Contains(shellBase, "fish")
}

func StartLocalShellProc(logCtx context.Context, termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType) (*ShellProc, error) {
shellutil.InitCustomShellStartupFiles()
var ecmd *exec.Cmd
Expand Down Expand Up @@ -522,6 +510,11 @@ func StartLocalShellProc(logCtx context.Context, termSize waveobj.TermSize, cmdS
blocklogger.Debugf(logCtx, "packed swaptoken %s\n", packedToken)
shellutil.UpdateCmdEnv(ecmd, map[string]string{wavebase.WaveSwapTokenVarName: packedToken})
}
jwtToken := cmdOpts.SwapToken.Env[wavebase.WaveJwtTokenVarName]
if jwtToken != "" && cmdOpts.ForceJwt {
blocklogger.Debugf(logCtx, "adding JWT token to environment\n")
shellutil.UpdateCmdEnv(ecmd, map[string]string{wavebase.WaveJwtTokenVarName: jwtToken})
}

/*
For Snap installations, we need to correct the XDG environment variables as Snap
Expand Down
1 change: 1 addition & 0 deletions pkg/waveobj/metaconsts.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const (
MetaKey_CmdArgs = "cmd:args"
MetaKey_CmdShell = "cmd:shell"
MetaKey_CmdAllowConnChange = "cmd:allowconnchange"
MetaKey_CmdJwt = "cmd:jwt"
MetaKey_CmdEnv = "cmd:env"
MetaKey_CmdCwd = "cmd:cwd"
MetaKey_CmdInitScript = "cmd:initscript"
Expand Down
1 change: 1 addition & 0 deletions pkg/waveobj/wtypemeta.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type MetaTSType struct {
CmdArgs []string `json:"cmd:args,omitempty"` // args for cmd (only if cmd:shell is false)
CmdShell bool `json:"cmd:shell,omitempty"` // shell expansion for cmd+args (defaults to true)
CmdAllowConnChange bool `json:"cmd:allowconnchange,omitempty"`
CmdJwt bool `json:"cmd:jwt,omitempty"` // force adding JWT to environment

// these can be nested under "[conn]"
CmdEnv map[string]string `json:"cmd:env,omitempty"`
Expand Down
Loading