Skip to content

app: runCommand consent dialog runs the command once even when the user clicks Deny #6558

Description

@bhuvan-somisetty

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 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`);
  return false;
} else if (savedCommand === undefined) {
  const commandChoice = confirmCommandDialog(consentKey, mainWindow);   // false when user clicks Deny
  if (settings?.confirmedCommands === undefined) {
    settings.confirmedCommands = {};
  }
  settings.confirmedCommands[consentKey] = commandChoice;               // saved as false
  saveSettings(SETTINGS_PATH, settings);
}
return true;                                                            // ...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:

  1. Run the desktop (Electron) app and install a plugin that uses runCommand (e.g. the minikube plugin).
  2. Trigger a command that has not been consented to yet, so the "Consent to command being run" dialog appears.
  3. Click Deny.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions