---
name: sshp-secrets
description: Chaves de API, senhas e tokens da aplicação no cofre do servidor, sem passar pelo chat. Mostra o que existe e o que falta (só nomes, nunca valores), entrega o comando para a pessoa cadastrar no próprio terminal, gera segredo no servidor, aplica com reinício verificado e volta atrás quando a aplicação não sobe. Use com "/sshp-secrets", "colocar minha chave da OpenAI no servidor", "configurar o .env", "cadastrar a senha", "variável de ambiente", "a aplicação não acha a chave".
license: MIT
compatibility: Claude Code, Codex, Cursor, Gemini CLI e demais agentes compativeis com Agent Skills.
metadata:
  author: sandeco
  version: "0.0.10"
  framework: sshepherd
  role: segredos
---

Você cuida das chaves da aplicação sem nunca ver uma. Essa frase é a skill inteira: o resto é como.

## A regra que define tudo

**O valor de um segredo não entra nesta conversa.** Nem para conferir, nem "só o começo", nem porque a pessoa ofereceu. O cofre existe para que o caminho da chave seja teclado da pessoa → servidor, e você fica de fora desse caminho de propósito.

O que você vê, e é tudo que precisa: o **nome** de cada variável, se está **definida ou vazia**, quando o cofre mudou, e se a aplicação já reiniciou com ele.

Se a pessoa colar um valor no chat, mesmo sem você pedir:

> Esse valor acabou de passar por uma conversa com uma IA e por quem armazena ela. Considere a chave queimada: gere outra no painel do provedor e cadastre a nova pelo caminho abaixo. A antiga, revogue.

Diga isso uma vez, sem sermão, e siga.

## Onde o segredo mora

No servidor, em `/var/lib/sshepherd/secrets/app.env`, um arquivo que **só root abre**. A aplicação roda como um usuário próprio (`sshepherd-app`, sem senha e sem SSH) e recebe as variáveis do gerente do sistema, que lê o cofre antes de trocar de usuário. O usuário `deploy`, que é o que você usa para operar, não consegue ler nem listar o que há lá dentro: só consegue chamar um programa fechado (`sshepherd-secrets`) que responde com nomes.

Explique assim, uma vez:

> Suas chaves ficam num cofre do servidor que só o dono absoluto da máquina abre. A aplicação recebe as chaves na hora de ligar, sem nunca ler o arquivo. Eu também não leio: só sei quais nomes existem.

## Pré-condição

O servidor precisa ter passado pelo `/sshp-provision` desta versão (ou pelo Dia Zero desta versão). Confira com `status`. Se vier `cofre=ausente`, ou o sudo for recusado, o cofre não está instalado: mande para `/sshp-provision`. Em servidor já endurecido que foi provisionado numa versão antiga, o caminho é `secrets-install.sh` (veja o fim desta skill).

**Aplicação por compose não usa o cofre.** Os contêineres leem `shared/.env` no servidor, legível pelo usuário de operação, e isso é um limite declarado no README. Nesse caso você ainda não pede valor no chat: a pessoa edita `shared/.env` no terminal dela, e você só confere nomes com `attack-surface.sh`.

## O fluxo

### 1. Ver o que existe

```
bash .sshepherd/scripts/sh/secrets.sh --host <ip> --user deploy --key <chave> --action status
```

Saída típica, e o que cada linha quer dizer para a pessoa:

```
var=DATABASE_URL estado=definida      ← gravada pelo db.sh, nunca precisou digitar
var=OPENAI_API_KEY estado=definida
var=STRIPE_SECRET_KEY estado=vazia    ← existe o nome, não existe valor
aplicado=não (há alteração que ainda não passou por apply)
variaveis=3
servico=active
```

### 2. Descobrir o que falta

Leia o `.env.example` do projeto (ele não tem valor, por isso pode) e compare com os nomes do `status`. O que está no exemplo e não está no cofre, ou está vazio, é o que falta. Apresente como lista curta:

> Faltam no cofre: `STRIPE_SECRET_KEY`, `RESEND_API_KEY`. As outras cinco já estão lá.

Se não houver `.env.example`, procure `process.env.X`, `os.environ["X"]`, `env("X")` no código e monte a lista. Não invente nome.

### 3. Cadastrar: a pessoa, no terminal dela

Para cada variável que falta, entregue o comando pronto. O script monta ele com a chave e a porta certas:

```
bash .sshepherd/scripts/sh/secrets.sh --host <ip> --user deploy --key <chave> --action set --name STRIPE_SECRET_KEY
```

Rodado por você, sem terminal, ele **não pede valor**: imprime o comando `ssh -t ...` que a pessoa cola no terminal dela, e sai com 10. Mostre esse comando e explique:

> Cole isso no seu terminal. Ele abre o servidor e pede o valor duas vezes, sem mostrar na tela. O valor vai direto para o cofre: não passa por mim, não passa por esta conversa.

Uma variável por comando. Não tente abreviar com um loop nem um arquivo.

### 4. Gerar, quando o valor não precisa existir em outro lugar

Segredo de sessão, chave que assina cookie ou token, senha de serviço interno: não tem por que a pessoa inventar e digitar. Gere no servidor:

```
bash .sshepherd/scripts/sh/secrets.sh --host <ip> --user deploy --key <chave> --action generate --name SESSION_SECRET --bytes 32 --confirmed
```

O valor nasce lá e nunca sai. Senha de banco já vem assim do `db.sh`.

### 5. Aplicar

A aplicação só enxerga o cofre quando reinicia. Depois de todos os cadastros, uma vez:

```
bash .sshepherd/scripts/sh/secrets.sh --host <ip> --user deploy --key <chave> --action apply --health http://127.0.0.1/ --confirmed
```

O que acontece: reinicia o serviço, espera ele ficar ativo e estável, bate no endereço de saúde. Se respondeu, este cofre vira **o último que funcionou**. Se não respondeu, o script **volta sozinho** para o último que funcionou, reinicia de novo e diz `apply=revertido`: o site continua no ar com as chaves antigas, e o cofre que falhou fica guardado para a pessoa corrigir.

Passe sempre `--health`. Sem ele, o cofre não vira "o que funcionou", porque serviço ativo não prova aplicação respondendo.

### 6. Conferir

`status` de novo. `aplicado=sim` e `servico=active` fecham o ciclo.

## Quando o apply falha

Leia a linha `apply=`:

| Linha | O que aconteceu | O que fazer |
|---|---|---|
| `apply=revertido` | o cofre novo não subiu; o anterior voltou e está no ar | olhe o registro (`ssh-exec.sh --op logs`), corrija o valor errado com `set` de novo, aplique de novo |
| `apply=falhou_sem_versao_anterior` | primeira vez, não há para onde voltar | o serviço pode estar parado: registro, corrigir, aplicar |
| `apply=revertido_mas_servico_nao_subiu` | nem o cofre antigo sobe | a causa não é o cofre: `/sshp-doctor` |
| `apply=sem_publicacao` | ainda não houve deploy | normal; o primeiro `/sshp-deploy` lê o cofre |

O registro vem redigido pelo script. Mesmo assim, se aparecer algo que pareça uma chave, não repita na conversa.

## Voltar atrás

```
bash .sshepherd/scripts/sh/secrets.sh --host <ip> --user deploy --key <chave> --action rollback --confirmed
```

Descarta o que ainda não foi aplicado com sucesso e volta ao último cofre que funcionou. Se o atual já é esse, volta para o anterior a ele. Uma versão atrás, não um histórico: diga isso.

## Remover

```
bash .sshepherd/scripts/sh/secrets.sh --host <ip> --user deploy --key <chave> --action unset --name NOME --confirmed
```

Depois, `apply`. Remover do cofre não revoga a chave no provedor: lembre a pessoa.

## Nomes que o cofre recusa

`NODE_OPTIONS`, `LD_PRELOAD`, `PYTHONPATH`, `PATH`, `BASH_ENV` e parecidos. Eles mudam **como** o programa executa, não o que ele sabe, e com um deles no cofre quem escolhe o nome escolhe o código que roda com as chaves. Se a aplicação precisa de um desses, é configuração do serviço, não segredo: `/sshp-provision`.

Também recusa valor com quebra de linha, barra invertida, espaço nas pontas ou aspa no início. Chave de API nunca tem isso; se um valor tiver, provavelmente foi copiado com sobra.

## Servidor provisionado antes do cofre

O `attack-surface.sh` avisa: `o cofre existe, mas a aplicação ainda roda como deploy`, ou `cofre=ausente`. Caminho:

1. `/sshp-provision` de novo: ele cria o cofre, migra o que havia em `shared/.env` e troca o serviço para o usuário de serviço. Linhas que não couberem no formato do cofre são listadas por nome, e o arquivo antigo fica até a pessoa cadastrar essas e apagar o arquivo ela mesma.
2. Se o provisionamento não conseguir root (`sudo` recusado), o cofre pode ser instalado por `secrets-install.sh --print`, que imprime o comando para a pessoa colar no console do provedor, como root.
3. Os backups de `shared/` feitos antes da migração carregam o `.env` antigo. O provisionamento lista esses arquivos. Diga para apagá-los ou, melhor, trocar as chaves que estavam neles.

## O que você nunca faz

- Pedir, aceitar ou repetir o valor de um segredo.
- Abrir `.env` local com qualquer ferramenta (o hook bloqueia, e está certo).
- Montar `ssh` na mão para "só olhar" o cofre. Não existe `get`, e não vai existir.
- Rodar `apply` sem avisar que a aplicação reinicia.
- Dizer que o cofre protege contra código que imprime o próprio ambiente. Não protege: quem publica código que faz `console.log(process.env)` vê tudo. O cofre protege contra leitura direta e vazamento acidental; o código que sobe é responsabilidade de quem aprova o deploy.
