Skip to content

feat(shell): opt-in sudo askpass flow (#1551)#3163

Merged
dgageot merged 2 commits into
mainfrom
feat/shell-sudo-askpass
Jun 19, 2026
Merged

feat(shell): opt-in sudo askpass flow (#1551)#3163
dgageot merged 2 commits into
mainfrom
feat/shell-sudo-askpass

Conversation

@Sayt-0

@Sayt-0 Sayt-0 commented Jun 18, 2026

Copy link
Copy Markdown
Member

Closes #1551.

Problem

Shell commands that run sudo get no controlling terminal, so an interactive sudo hangs until the command times out and the agent falls back to printing manual instructions.

Approach

Opt-in sudo_askpass: true on the shell toolset, bridging sudo's standard SUDO_ASKPASS mechanism to the existing elicitation prompt. Nothing changes unless the flag is set and a command actually runs sudo.

toolsets:
  - type: shell
    sudo_askpass: true

Flow when a sudo command runs:

  1. The toolset lazily starts a private unix-socket server in a 0700 temp dir, writes a SUDO_ASKPASS wrapper script, and injects the bridge env (SUDO_ASKPASS, CAGENT_ASKPASS_SOCKET, CAGENT_ASKPASS_TOKEN) only into that command.
  2. A sudo() { command sudo -A "$@"; } function is prepended (POSIX shells) so sudo uses askpass instead of a TTY.
  3. The hidden __askpass subcommand dials the socket; the server asks the user via a masked elicitation prompt and returns the password to sudo on stdout.

Security

Concern Handling
Password exposure Only transits the local socket and the helper stdout. Never logged, stored, or put on the command line.
Socket access 0700 dir, single-user; request carries a crypto/rand token compared with subtle.ConstantTimeCompare.
Wrapper script Executable path is single-quote escaped, so metacharacters cannot run as commands.
__askpass stdout Forced onto the standalone command path even under the docker CLI plugin reexec env, so plugin usage output never corrupts the password sudo reads.
Fail-closed Decline, cancel, error, headless, bad token, and missing env all make sudo abort, never a spurious success.

Limitations (documented)

  • Unix only; no effect on Windows.
  • Interactive runs only; non-interactive runs decline the prompt and sudo fails as before.
  • Only a bare sudo ... in a POSIX shell is intercepted (/usr/bin/sudo, env sudo, nested scripts, and non-POSIX shells like fish are not).
  • No cross-tool-call credential cache: each shell tool call is a fresh, TTY-less shell, so a prompt appears once per sudo-using command. Within a single command, sudo's own caching applies.

Tests

  • pkg/tools/builtin/shell: socket round-trip, decline, bad token, missing env, helper-death cancellation, shQuote, sudo wrapping, Elicitable discovery through the toolset wrappers.
  • pkg/config: sudo_askpass validation (shell only) plus schema parity.
  • pkg/tui/dialog: password field masked and not trimmed.
  • cmd/root: __askpass routing and management-invocation gating.

Verified end to end with the built binary in both standalone and docker-plugin invocation, plus cross-compile for windows and js/wasm.

Shell commands that run `sudo` had no controlling terminal, so an
interactive sudo would hang until the command timed out and the agent
fell back to printing manual instructions.

This adds an opt-in `sudo_askpass: true` option on the shell toolset that
bridges sudo's standard SUDO_ASKPASS mechanism to the agent's elicitation
prompt. When enabled and a command runs `sudo`, the toolset starts a
private unix-socket server (0700 dir) plus a SUDO_ASKPASS wrapper script,
injects the bridge env, and forces `sudo -A` via a shell function. The
hidden `__askpass` subcommand dials the socket and returns the password to
sudo on stdout.

The password is requested through a masked elicitation prompt; it only
transits the local socket (0700 dir, crypto/rand token compared in
constant time) and the helper's stdout, and is never logged, stored, or
placed on the command line. The flow is Unix-only, opt-in, and declines
automatically in non-interactive runs (sudo then fails as before).

The `__askpass` helper is forced onto the standalone command path even
under the docker CLI plugin reexec env, so its stdout carries only the
password and is never corrupted by plugin usage output.
@Sayt-0 Sayt-0 requested a review from a team as a code owner June 18, 2026 13:46
- Detect sudo with a word boundary (\bsudo\b) instead of a substring, so
  the bridge env is no longer injected for commands that merely mention
  "pseudo", "sudoers", etc.
- Serialize password prompts (one at a time) so two parallel sudo calls in
  one command do not open two dialogs at once.
- Document that the bridge env vars are visible to all child processes of a
  sudo command, and the concurrent-prompt serialization.
- Clarify the watchConn no-leak invariant and the gosec suppression comment.
@Sayt-0 Sayt-0 requested a review from docker-agent June 18, 2026 14:03
@docker-agent

Copy link
Copy Markdown

PR Review Failed — The review agent encountered an error and could not complete the review. View logs.

@aheritier aheritier added area/security Authentication, authorization, secrets, vulnerabilities area/tools For features/issues/fixes related to the usage of built-in and MCP tools kind/feat PR adds a new feature (maps to feat: commit prefix) labels Jun 18, 2026
@dgageot dgageot merged commit 0ee5c65 into main Jun 19, 2026
10 checks passed
@dgageot dgageot deleted the feat/shell-sudo-askpass branch June 19, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/security Authentication, authorization, secrets, vulnerabilities area/tools For features/issues/fixes related to the usage of built-in and MCP tools kind/feat PR adds a new feature (maps to feat: commit prefix)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handling password protected commands

4 participants