@igor-guari/pi-sudo-prompt
Prompt seguro de senha do sudo para o Pi Coding Agent — modal mascarado via SUDO_ASKPASS; a senha nunca passa pelo modelo
Package details
Install @igor-guari/pi-sudo-prompt from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@igor-guari/pi-sudo-prompt- Package
@igor-guari/pi-sudo-prompt- Version
0.1.5- Published
- Nov 2, 2026
- Downloads
- 814/mo · 814/wk
- Author
- igor-guari
- License
- MIT
- Types
- extension
- Size
- 87.2 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"image": "https://raw.githubusercontent.com/IgorGuariroba/pi-sudo-prompt/main/docs/media/cover.png",
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-sudo-prompt
Prompt seguro de senha do sudo para o Pi Coding Agent, no estilo do Sudo password prompting do Cursor.
Quando o agente (ou você, via !/!!) roda um comando com sudo, o pi abre um modal Sudo Authentication Required com campo mascarado. A senha vai direto para o sudo e nunca passa pelo modelo: não aparece no output do tool, na sessão nem no histórico.

╭────────────────────────────────────────────────────────────────────╮
│ 🔐 Sudo Authentication Required │
│ │
│ Command: sudo apt update │
│ │
│ [sudo] senha para movida: │
│ │
│ Password: •••••••• │
│ │
│ Press Enter to submit, Escape to cancel · auto-cancel in 1:54 │
│ │
│ Your password is secure and will not be visible to the AI model. │
╰────────────────────────────────────────────────────────────────────╯
Instalação
pi install npm:@igor-guari/pi-sudo-prompt
# ou
pi install git:github.com/IgorGuariroba/pi-sudo-prompt@v0.1.3
Requer Linux e o pi no modo interativo (TUI).
Como funciona
agente: sudo whoami
│
▼
sudo -A ──exec──► pi-askpass (SUDO_ASKPASS)
▲ │ valida: pai é sudo/doas com euid 0 e stdout é o pipe do sudo
│ stdout (senha) │ Unix socket privado + HMAC
│ ▼
│ extensão (sessão do pi)
│ │ revalida o processo solicitante
│ ▼
│ modal "Sudo Authentication Required"
- Na sessão, a extensão cria um diretório privado (
$XDG_RUNTIME_DIR/pi-sudo-prompt-*, modo0700) com o socket e o launcherpi-askpass. - Cada comando do bash tool e dos comandos
!/!!recebeSUDO_ASKPASS,PI_ASKPASS_SOCKET,PI_ASKPASS_SECRETe uma funçãosudo()que acrescenta-A. A função não acrescenta-Aquando o comando já usa-S/--stdinou-A. - O
sudoexecuta o launcher, que limpa o ambiente (env -i) e inicia o auxiliar com o prompt ([sudo] senha para movida:). O auxiliar confere se o processo pai ésudo/doasrodando como root e se o stdout é o pipe privado do sudo (FIFO com dono root, modo600). Depois envia umpassword_requestassinado. - A extensão revalida tudo no
/procantes de abrir o modal: pai é sudo privilegiado; argv exatonode askpass.mjs <prompt>; interpretador esperado; ambiente exatamente o do launcher (semNODE_OPTIONS,LD_PRELOADetc.); stdout é o pipe do sudo. - Enter responde com
password_response; Escape, o timeout de 120 s ou o fim dosudo(por abort do tool, por exemplo) respondem compassword_denied. - O auxiliar imprime a senha no pipe do
sudoe termina. Se a senha estiver errada, osudopede de novo, e o modal mostraIncorrect password, try again (2/3).
Cancelar conta como tentativa falha: para o PAM, um askpass cancelado (Escape ou timeout) é uma falha de autenticação, exatamente como no sudo sem a extensão. Com o pam_faillock padrão, 3 falhas em 15 minutos bloqueiam o sudo por 10 minutos. Para liberar antes: faillock --user "$USER" --reset.
Cache de credenciais: sem TTY, o sudo (com o padrão timestamp_type=tty) guarda o cache por processo pai, e cada comando do agente roda num shell novo. Por isso cada comando com sudo abre o modal de novo. Agrupe as etapas num único comando (sudo apt update && sudo apt upgrade -y) para autenticar uma vez só. Dentro do mesmo comando, o cache vale normalmente.
Limites
- Só Linux. A validação usa
/proc. O suporte a macOS fica para depois. - Só no modo interativo. Em
pi -p,--mode jsone RPC nada é injetado, e osudose comporta como sem a extensão. - Substitui o tool
bash. A extensão registra seu própriobash(mesma implementação e renderização do built-in, comshellCommandPrefix/shellPathdas settings). Outra extensão que também substitua obashvai conflitar. - A função
sudo()só existe no shell do comando. Scripts filhos exargs sudoainda usam o modal (sem TTY, oSUDO_ASKPASSbasta), mas não recebem o-Aforçado.
Modelo de ameaça
A senha nunca entra no contexto do modelo nem nos registros da sessão. Além disso, a extensão resiste a um agente que tente desviar a senha usando o próprio mecanismo de askpass. Todos estes casos são recusados antes de o modal abrir e estão cobertos por test/exfiltration.test.mjs com o sudo real:
SUDO_ASKPASSapontando para um wrapper que roda o auxiliar com stdout redirecionado (arquivo, fifo com leitor em background, pipe próprio). O pipe legítimo é criado pelo sudo como root, e um processo do seu usuário não consegue criar nem reabrir um pipe assim.- Injeção de código no auxiliar via
NODE_OPTIONS,--importno argv ou wrapper que contorna o launcher.
O que não é coberto: um agente que altere arquivos que pertencem a você (askpass.mjs, protocol.mjs, a extensão em ~/.pi/agent/..., o binário node do nvm). Qualquer código que rode como o seu usuário pode ser trocado por outro processo do seu usuário. Isso só se resolve com um auxiliar de dono root, previsto como modo blindado opcional numa versão futura. Até lá: o modal mostra o comando que será autorizado, confira antes de digitar, e desconfie de transcripts em que o agente edita arquivos da extensão ou do node.
Segredos que não são do sudo (pi-secret-run)
Para um segredo durável de outro consumidor (senha mestra do keepassxc-cli, chave de API, token de login), o agente roda:
pi-secret-run --label "senha mestra do KeePassXC" -- keepassxc-cli show -q -t db.kdbx npm
O pi abre o modal Pedido de segredo, com título diferente do modal do sudo. O modal mostra o argv real lido pelo broker, a descrição do agente marcada como "não verificada" e, quando o formato do comando não está na allowlist, o aviso vermelho "este comando pode mostrar ao agente o que você digitar". O segredo digitado vai para o stdin do comando. O agente vê só a saída do comando (um OTP, por exemplo). Não há cache: cada chamada abre o modal. Decisões em docs/adr/0002-broker-root-para-segredos-duraveis.md.
Instalação do broker (uma vez, como root). No TUI do pi, o usuário pode executar /sudo-prompt install-broker: a senha vem do modal existente do sudo, sem passar pelo agente. É um comando de usuário, não uma tool; fora do TUI ele recusa. Para instalar manualmente, leia o script antes de rodar:
sudo sh ~/.pi/agent/<caminho do pacote>/broker/install.sh # a mensagem de recusa do pi-secret-run mostra o caminho exato
As duas opções usam o mesmo instalador: broker em /usr/local/libexec/pi-sudo-prompt/pi-secret-broker (Python, só biblioteca padrão, dono root), regra /etc/sudoers.d/pi-sudo-prompt (NOPASSWD só para o broker), validada com visudo -c, e allowlist inicial em /etc/pi-sudo-prompt/allowlist (root, vazia com exemplos comentados). Uma allowlist existente nunca é sobrescrita. Rode de novo depois de atualizar o pacote: o broker é atualizado e a extensão recusa versões divergentes do protocolo.
Como funciona: pi-secret-run chama o broker por sudo -n. O broker, como root, pede o segredo à extensão pelo socket da sessão e prova que é ele com um arquivo em /run/pi-sudo-prompt que só root pode criar. A extensão confere euid 0, o argv exato /usr/bin/python3 -I <broker> … e os arquivos com dono root. Depois o broker cria um pipe como root (0600), executa o comando com o seu uid/gid (nunca root) e escreve o segredo no stdin dele. O segredo nunca vai por variável de ambiente.
Sem suporte a --fd/--password-file por enquanto: no Linux, o consumidor sem root não consegue reabrir o pipe root 0600 por /dev/fd/N. Não há fallback com permissões mais fracas. Reprodução e alternativas em docs/issue-6-bloqueio-fd.md.
Recusas (código de saída diferente de zero, com o motivo): broker ausente (a mensagem mostra como instalar), versão divergente, fora do TUI, sistema que não é Linux, modal cancelado ou expirado. Não há modo sem root.
Allowlist: /etc/pi-sudo-prompt/allowlist, criada vazia pelo instalador, com exemplos comentados. Reinstalar preserva o arquivo existente. Edite com sudoedit /etc/pi-sudo-prompt/allowlist. Apenas arquivos regulares com dono root, sem escrita para grupo/outros e em diretórios protegidos são aceitos; ausente ou inseguro equivale a uma lista vazia.
Cada linha contém o caminho real absoluto do executável e o formato exato de argumentos, separados por espaços. Linhas iniciadas por # são comentários. Um marcador como <db> ou <entrada> aceita exatamente um argumento não vazio que não comece com - (inclusive um caminho com espaços). Não há glob, expansão de shell nem argumentos opcionais. Por exemplo:
/usr/bin/keepassxc-cli show -q -t <db> <entrada>
Esse formato não libera show -s, export nem argumentos extras. O broker resolve symlinks antes de pedir consentimento e executa o mesmo caminho apresentado. Comandos permitidos têm modal normal; os demais continuam funcionando mediante aprovação, mas com aviso vermelho. A descrição do agente continua marcada como "não verificada" nos dois casos.
Liberar pelo TUI: depois de um modal com aviso vermelho, execute /sudo-prompt allow. O comando usa apenas o último argv não verificado mostrado nesta sessão (inclusive se o pedido foi cancelado), abre um editor para ajustar o formato e exige uma confirmação explícita antes de chamar sudo. Para keepassxc-cli show -q -t, sugere <db> <entrada>; nos demais casos conserva os argumentos, sem adivinhar quais opções são seguras. Você pode substituir valores por marcadores. O formato editado precisa corresponder ao pedido anterior. Sem histórico ou fora do TUI, recusa.
A gravação acrescenta uma linha sem substituir comentários, dono root ou permissões existentes. Não segue symlinks nem cria uma allowlist ausente/insegura: instale o broker primeiro. Usa o mesmo modal do sudo; com credenciais em cache, o sudo pode não pedir senha, mas a confirmação do formato continua obrigatória. O próximo pedido relê o arquivo e só perde o aviso se corresponder ao formato aprovado.
Barreira usuário/agente: allow não é uma tool e texto de resposta do modelo não executa comandos. Porém, no runtime do pi inspecionado, sendUserMessage(..., { expandPromptTemplates: true }) de uma extensão pode despachar comandos registrados, antes do evento input; o contexto do handler não informa a origem. Portanto não há garantia absoluta de que apenas uma entrada humana possa abrir o comando. A confirmação no TUI impede a gravação automática pelo caminho de allow, inclusive com sudo em cache. Leia o formato antes de confirmar. Código do mesmo uid que altere a extensão, controle o terminal ou use sudo por outro caminho não é isolado por essa UI. O critério de origem exclusivamente humana da issue #8 permanece uma limitação do runtime, não uma propriedade garantida por registerCommand.
Limites da garantia:
- Fora da allowlist, a segurança depende de ler o comando e o aviso vermelho. Se você aprovar um comando que ecoa o que recebe (
cat, por exemplo), o segredo vai para o agente por escolha sua. Leia o comando no modal. - O comando roda com o seu uid, então a memória dele fica ao alcance do ptrace: com
kernel.yama.ptrace_scope=0, por qualquer processo seu; comptrace_scope=1, por um processo seu que seja ancestral dele (um programa que lança opi-secret-rune depois depura o comando). A garantia contra reabrir o pipe e ler oenvironnão cobre esse caso.
Desenvolvimento
PI_SUDO_PROMPT_TEST_NO_SUDO=1 npm test # modo sem chamadas a sudo; pula testes que o exigem
npm test # protocolo, auxiliar, função sudo(), ataques de exfiltração (sudo real, sem autenticar)
# e pi-secret-run → broker real (pulados se o broker não estiver instalado)
pi install ./ # carrega a cópia local
Decisões de arquitetura em docs/adr/.
Licença
MIT
