You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In app (Electron) mode, the run-command consent dialog does not block the command the first time you deny it. When a plugin calls runCommand for a command that hasn't been consented to yet, Headlamp shows the native "Allow / Deny" dialog. If you click Deny, your choice is saved correctly, but the command still runs that one time. The denial only takes effect from the next invocation onward.
The dialog message says "Allow this local command to be executed? Your choice will be saved.", so clicking Deny reasonably implies the command will not run. It does.
The problem is in checkCommandConsent in app/electron/runCmd.ts. The first-time branch saves the user's answer but the function falls through to an unconditional return true:
if(savedCommand===false){console.error(`Invalid command: ${consentKey}, command not allowed by users choice`);returnfalse;}elseif(savedCommand===undefined){constcommandChoice=confirmCommandDialog(consentKey,mainWindow);// false when user clicks Denyif(settings?.confirmedCommands===undefined){settings.confirmedCommands={};}settings.confirmedCommands[consentKey]=commandChoice;// saved as falsesaveSettings(SETTINGS_PATH,settings);}returntrue;// ...but returns true anyway
commandChoice is discarded. On the second run the saved false is read back and the command is correctly blocked, so the effect is that a denial is honoured exactly one run late.
This affects every consent-gated command in app mode (the minikube management scripts, az, gh), not a single plugin.
Flow
sequenceDiagram
participant Plugin
participant Main as Electron main<br/>(checkCommandConsent)
participant User
participant Shell as spawn()
Note over Main: first request — confirmedCommands['minikube delete'] is undefined
Plugin->>Main: runCommand('minikube', ['delete'])
Main->>User: consent dialog (Allow / Deny)
User-->>Main: clicks "Deny"
Main->>Main: save confirmedCommands['minikube delete'] = false
Main->>Shell: return true — command runs anyway
Shell-->>Plugin: output
Note over Main: second request — confirmedCommands['minikube delete'] === false
Plugin->>Main: runCommand('minikube', ['delete'])
Main--xShell: return false — blocked (one run too late)
Loading
Invocation
Saved state before
User action
Returns
Command runs?
1st
undefined
clicks Deny
true
yes (wrong)
2nd
false
(no prompt)
false
no
1st
undefined
clicks Allow
true
yes
To Reproduce
Steps to reproduce the bug:
Run the desktop (Electron) app and install a plugin that uses runCommand (e.g. the minikube plugin).
Trigger a command that has not been consented to yet, so the "Consent to command being run" dialog appears.
Click Deny.
The command runs this time regardless. Trigger the same command again and it is now blocked — confirming the denial was saved but applied one run late.
Environment (please provide info about your environment):
Installation type: Desktop app (Electron) — reproduced from source on Windows; the code path is platform-independent so it applies to all desktop builds.
Headlamp Version: 0.43.0 (current main)
Other: app mode only; this path does not exist for the in-cluster/web build.
Are you able to fix this issue?
Yes — I plan to propose a PR. The first-time branch should return the choice the user just made (a denial has to block the current run, not only later ones) instead of falling through to the unconditional return true.
Additional Context
Not a regression from a specific release — the unconditional return true has been present since the consent mechanism was added in #1744, so every version shipping that dialog is affected.
Describe the bug
In app (Electron) mode, the run-command consent dialog does not block the command the first time you deny it. When a plugin calls
runCommandfor a command that hasn't been consented to yet, Headlamp shows the native "Allow / Deny" dialog. If you click Deny, your choice is saved correctly, but the command still runs that one time. The denial only takes effect from the next invocation onward.The dialog message says "Allow this local command to be executed? Your choice will be saved.", so clicking Deny reasonably implies the command will not run. It does.
The problem is in
checkCommandConsentinapp/electron/runCmd.ts. The first-time branch saves the user's answer but the function falls through to an unconditionalreturn true:commandChoiceis discarded. On the second run the savedfalseis read back and the command is correctly blocked, so the effect is that a denial is honoured exactly one run late.This affects every consent-gated command in app mode (the minikube management scripts,
az,gh), not a single plugin.Flow
sequenceDiagram participant Plugin participant Main as Electron main<br/>(checkCommandConsent) participant User participant Shell as spawn() Note over Main: first request — confirmedCommands['minikube delete'] is undefined Plugin->>Main: runCommand('minikube', ['delete']) Main->>User: consent dialog (Allow / Deny) User-->>Main: clicks "Deny" Main->>Main: save confirmedCommands['minikube delete'] = false Main->>Shell: return true — command runs anyway Shell-->>Plugin: output Note over Main: second request — confirmedCommands['minikube delete'] === false Plugin->>Main: runCommand('minikube', ['delete']) Main--xShell: return false — blocked (one run too late)undefinedtruefalsefalseundefinedtrueTo Reproduce
Steps to reproduce the bug:
runCommand(e.g. the minikube plugin).Environment (please provide info about your environment):
main)Are you able to fix this issue?
Yes — I plan to propose a PR. The first-time branch should return the choice the user just made (a denial has to block the current run, not only later ones) instead of falling through to the unconditional
return true.Additional Context
Not a regression from a specific release — the unconditional
return truehas been present since the consent mechanism was added in #1744, so every version shipping that dialog is affected.