Skip to main content
O ChatCLI é construído com uma arquitetura de segurança defense-in-depth (segurança em profundidade). Esta página documenta cada camada de proteção, como configurá-las e as boas práticas para ambientes de produção.
O status quer dizer exatamente o que diz. Ativo é ligado sem configuração nenhuma. Opt-in existe e não faz nada até você ligar — vários dos controles mais fortes desta página são opt-in de propósito, porque a alternativa é um padrão que surpreende alguém em produção. Onde um controle é opt-in, a linha diz isso e a seção diz como ligar.

Visão Geral das Proteções

A tabela abaixo resume todas as proteções ativas em cada camada da stack.
O que não existe. Não planeje um deployment contando com estes itens; cada um tem uma mitigação que você aplica:
  • Sem admission webhook. Nada valida RemediationPlan nem qualquer outro recurso no admission; as checagens do schema das CRDs rodam no API server e os controllers conferem de novo ao reconciliar. Restrinja com RBAC do Kubernetes quem pode criar recursos do ChatCLI.
  • Endpoints de métricas são HTTP puro, sem autenticação: a porta de métricas do servidor (padrão 9090, que também serve /healthz) escuta em todas as interfaces, qualquer que seja CHATCLI_BIND_ADDRESS, e o operator serve /metrics na 8080. Limite quem as alcança com NetworkPolicy (networkPolicy.metricsIngressFrom no chart do operator, networkPolicy.ingressFrom no do servidor).
  • Sem OIDC, SSO ou contas de usuário no dashboard — só API keys enviadas em X-API-Key. Mantenha o dashboard atrás de port-forward ou de um proxy/Ingress que autentique, e rotacione as chaves pelo Secret.
  • Sem arquivo de auditoria no operator. O operator registra suas ações como recursos AuditEvent; CHATCLI_AUDIT_LOG_PATH é lido só pelo servidor e pelo CLI.
  • Sem RBAC automático por usuário. O chart pré-provisiona as ClusterRoles chatcli-role-viewer, -operator, -admin e -superadmin; nada faz bind delas, então faça você.
  • Sem NetworkPolicy, PodDisruptionBudget ou HPA para Instances gerenciadas pelo operator. Escreva sua própria NetworkPolicy para os pods das Instances.

Autenticação e Autorização

Autenticação JWT (Recomendada)

O servidor gRPC verifica JWTs com issuer e audience configuráveis e com um segredo compartilhado (HS256) ou uma chave pública RSA (RS256). Os JWTs carregam um claim de papel que mapeia para um nível de RBAC.
O algoritmo vem da configuração, nunca do token. Um verificador que lê alg para decidir como conferir a assinatura é um verificador que o atacante escolhe: a chave pública RSA é pública, então um token HS256 assinado com essa chave como segredo HMAC passaria. O ChatCLI configura exatamente um algoritmo e recusa qualquer token que declare outro — inclusive none.Definir CHATCLI_JWT_PUBLIC_KEY seleciona RS256. Definir só CHATCLI_JWT_SECRET seleciona HS256, a menos que o valor aponte para material de chave PEM — aí seleciona RS256 também. /config server mostra qual algoritmo está em vigor.
Issuer e audience só são conferidos quando configurados. Deixá-los vazios aceita qualquer iss e aud, ou seja, um token emitido para outro serviço pelo mesmo emissor é aceito — configure os dois sempre que a chave de assinatura for compartilhada.
A expiração e o not-before são conferidos com uma tolerância fixa de 30 segundos para diferença de relógio entre o emissor e o servidor; ela não é configurável.

Papéis de RBAC

Existem três níveis de papel. O servidor resolve todo chamador para um deles e confere o papel nos handlers que precisam dele:
O papel não é conferido em todo lugar. Prompts, sessões e os RPCs de AIOps (AnalyzeIssue, AgenticStep e os demais) rodam para qualquer chamador autenticado, qualquer que seja o papel: um token viewer consegue enviar prompts e gerenciar sessões. Trate toda credencial que alcança o servidor como capaz de gastar seu orçamento de LLM, e mantenha a execução no host do servidor atrás de admin.
Cada nível tem duas grafias aceitas, e elas são apelidos exatos — viewer e readonly concedem a mesma coisa.
Um papel não reconhecido vira somente-leitura. Um token cujo claim role traga um valor que este servidor não conhece — um erro de digitação na configuração do emissor, ou um papel de outro sistema — recebe o nível mais baixo, e o servidor registra em log o claim que não reconheceu. É essa a direção em que esse engano precisa falhar; a alternativa é que escrever viewer errado conceda escrita.Um token sem claim role mantém o nível operacional histórico, para que tokens emitidos antes de os papéis existirem não fiquem de fora num upgrade. Emita tokens com papel explícito.
O RPC Health próprio e o serviço padrão grpc.health.v1.Health respondem sem autenticação, para load balancers, grpc-health-probe e sondas gRPC do kubelet. Sob mTLS o handshake TLS ainda exige certificado de cliente, e uma sonda gRPC do kubelet não fala TLS, então sonde /healthz na porta de métricas.
O token compartilhado concede admin. Chamadores identificados só pelo certificado de cliente recebem CHATCLI_MTLS_ROLE (padrão user). Um servidor sem nenhuma credencial (só em loopback) trata todo chamador como admin.

Bearer Token Legado

Para deployments mais simples, o servidor aceita autenticação por bearer token estático com comparação em tempo constante (crypto/subtle.ConstantTimeCompare), o que impede timing attacks. Todo portador do token é o mesmo chamador: sujeito legacy-token, papel admin, um único bucket de rate limit. Prefira a variável de ambiente (ou um Secret do Kubernetes) à flag, que aparece na lista de processos. Os clientes o enviam com chatcli connect --token, ou com CHATCLI_REMOTE_TOKEN.

OAuth 2.0 + PKCE

O ChatCLI aceita OAuth 2.0 com PKCE para os seguintes provedores:
Os tokens OAuth são renovados automaticamente antes de expirar. O fluxo de refresh usa um cliente HTTP simples (sem o transport de logging) com o header User-Agent adequado, para evitar problemas com a Cloudflare.

Criptografia e Proteção de Dados

Criptografia AES-256-GCM de Credenciais

Todas as credenciais OAuth são criptografadas em repouso com AES-256-GCM em ~/.chatcli/auth-profiles.json. A chave de criptografia é gerada automaticamente e guardada com permissões estritas.

Criptografia em Repouso (sessões, memória, contextos, arquivos, custos)

Uma única chave cobre todos os stores, derivada por classe de store com HKDF — não há chave separada por perfil. É opt-in e fica desligada até CHATCLI_ENCRYPTION_KEY ser definida.
Payloads versão 2 ficam presos ao seu store. Um arquivo selado carrega, como dado autenticado, o caminho relativo do store e o slug do tenant: um arquivo de sessão, memória ou park copiado do diretório de um tenant para o de outro (ou renomeado) não abre mais como se pertencesse ali. Segredos com menos de 32 bytes são tratados como senha e esticados com Argon2id antes da derivação de chave; uma chave aleatória de pelo menos 32 bytes mantém a derivação direta, então a chave documentada segue sendo a prática recomendada. Payloads versão 1 continuam carregando e são reescritos como v2 presos no próximo save ou pelo /config security reseal, que agora faz fsync. Raízes de tenant carregam um digest de 16 bytes (raízes criadas com o digest curto antigo continuam sendo usadas). A criptografia em repouso é um opt-in explícito: fica ativa enquanto CHATCLI_ENCRYPTION_KEY estiver definida no ambiente do processo. Com ela definida, todo store que embute conteúdo de conversa é selado antes de tocar o disco e aberto de forma transparente na leitura:
  • sessões salvas (/session save, write-through de /session attach)
  • autosaves de saída e espelhos de sessão MCP/ACP (autosave-*, mcp-*)
  • snapshots de park do agente (/park, /resume)
  • o journal de transcript (linha a linha) e os arquivos de /memory export
  • stores JSON da memória de longo prazo (facts, episodes, profile, topics, projects, patterns, cache do grafo, estado do compactor — notas diárias e rollups seguem Markdown editável)
  • contextos de conhecimento (~/.chatcli/contexts/*.json; arquivos de /context export ficam em texto claro de propósito)
  • o arquivo CCR (~/.chatcli/ccr/*.ccr, os originais por trás do @recall)
  • snapshots de custo (~/.chatcli/costs/*.json)
O banco do hub de conversas (SQLite) é o store que resta em texto claro; mantenha-o em volume criptografado.
Formato: cabeçalho CHATCLI_ENC_v1 + nonce de 12 bytes + ciphertext AES-256-GCM. A chave é derivada por HKDF-SHA256 a partir de SHA-256(CHATCLI_ENCRYPTION_KEY); o segredo em si nunca é gravado.
Migração transparente. Arquivos em texto claro gravados antes da chave existir continuam carregando e são regravados criptografados no próximo save. Um arquivo criptografado aberto sem a chave falha com erro claro citando CHATCLI_ENCRYPTION_KEY — nunca é tratado silenciosamente como vazio ou corrompido.Redação persistida e o histórico do REPL. A redação de segredos sempre roda no caminho ao LLM; com CHATCLI_ENV_REDACT_MODE=strict ela também mascara o que o ChatCLI persiste para si — arquivos de sessão, journal de transcript, arquivos CCR, espelhos do hub — sempre numa cópia, nunca no histórico vivo (o padrão permissive mantém os stores literais para /rewind e exportações continuarem fiéis). O redator cobre tokens e webhooks do Slack, chaves privadas PEM, strings de conexão com credenciais, chaves secretas AWS, ids de chave de service account GCP e chaves de conta/SAS do Azure. Com a chave at-rest definida, o histórico de prompts do REPL (.chatcli_history) é selado linha a linha. A retenção roda a cada 6 h no daemon do gateway e expira parks, sessões e segmentos de memória enfileirados de tenants fora da janela de sessão (suas sessões nomeadas nunca são tocadas).Trava somente leitura. Um store de memória cujo arquivo selado este processo não consegue abrir (chave vazia, errada ou aposentada sem CHATCLI_ENCRYPTION_KEY_PREVIOUS) fica travado: carrega vazio em memória, registra erro, recusa toda escrita e aparece em “Stores travados” no /config security e no /memory stats. Um gateway daemon, um cron ou um shell iniciado sem a chave nunca consegue, portanto, sobrescrever sua memória. Notas diárias e rollups seguem Markdown puro por contrato; a fila pendente do memory worker é redigida e selada como os demais stores.

Rotação de chave

  1. Defina o novo segredo em CHATCLI_ENCRYPTION_KEY e liste o aposentado em CHATCLI_ENCRYPTION_KEY_PREVIOUS (separados por vírgula quando forem vários). Leituras tentam a chave atual primeiro, depois as aposentadas; escritas sempre usam a atual.
  2. Rode /config security reseal: todo arquivo de store sob a raiz de estado (sessões, transcripts, memória, contextos, CCR, custos — por tenant sob o gateway) é reescrito com a chave atual; arquivos em texto claro são selados no caminho. O comando informa quantos arquivos mudaram e o fingerprint da chave.
  3. Remova CHATCLI_ENCRYPTION_KEY_PREVIOUS.
/config security mostra se a criptografia está ligada, o fingerprint da chave atual, quantas chaves aposentadas estão configuradas e o que o selo cobre.

Trilha de auditoria à prova de adulteração

Toda linha da trilha de auditoria (CHATCLI_AUDIT_LOG_PATH) carrega seq, prev_hash, chain_v e hash — hash = SHA-256(prev_hash ‖ JSON canônico com chaves ordenadas da entrada), uma forma independente de qual processo escreveu. Uma linha editada, removida ou reordenada quebra a cadeia dali em diante. Vários escritores compartilham um arquivo com segurança: cada append toma um lock exclusivo do arquivo, relê o fim quando o arquivo mudou por baixo (outro escritor anexou, ou o arquivo rotacionou) e só então encadeia a nova linha — o REPL, um daemon de gateway e o servidor gRPC (kind: "grpc") formam uma única cadeia. O arquivo rotaciona em 64 MiB; a primeira linha do novo arquivo nomeia o arquivo que ela continua (rotated_from) e aponta para o último hash dele, então a verificação atravessa a fronteira, e a passada de retenção remove arquivos rotacionados fora da janela de sessão (o arquivo vivo nunca é tocado). Com criptografia em repouso habilitada, toda linha é selada em disco (prefixo enc:) e aberta de forma transparente na verificação. Uma última linha truncada (crash no meio da escrita) é reportada como tal, nunca como adulteração, e a próxima entrada continua a partir da última linha completa. /config security verify-audit [caminho] re-hasheia a trilha e informa a primeira linha quebrada, a contagem de linhas seladas, a origem da rotação, a cauda truncada e os arquivos rotacionados ao lado; trilhas escritas antes da cadeia compartilhada continuam verificando com o hash original.

Segurança de Transporte TLS 1.3

Se o carregamento do certificado TLS falhar, o erro é escrito em stderr e no log estruturado, incluindo os caminhos do certificado e da chave. Em containers, isso garante que o erro seja visível via kubectl logs mesmo que o logger estruturado não consiga fazer flush antes do crash.

Redação de Segredos no Caminho ao LLM

Todo conteúdo que o modelo recebe sem o usuário redigitar passa por um único chokepoint de redação antes de sair do processo: saídas de tools em modo agent/coder (leituras de arquivo, exec, plugins, MCP), saídas de tools dos workers do squad, o contexto @file/@git/@env montado no chat e o segmento de conversa entregue ao extrator de memória — assim um segredo colado na conversa não é reenviado nem destilado em um fato persistido. Duas camadas se compõem. Linhas KEY=VALUE (dumps de env, arquivos .env, logs de compose/CI) são julgadas pelo nome — AWS_SECRET_ACCESS_KEY, DATABASE_URL, qualquer nome terminado em _TOKEN, _PASSWORD, _KEY — mais heurísticas de valor (prefixos conhecidos, hex longo). Texto livre é varrido pelos formatos de valor que os provedores entregam (sk-…, ghp_…, AKIA…, JWTs, cabeçalhos bearer, campos de credencial em JSON).
O humano continua vendo a saída completa no terminal e hooks PostToolUse continuam recebendo-a; só a cópia do modelo é redigida.

Integração com o Keychain do SO

A chave que criptografa as credenciais OAuth guardadas (~/.chatcli/auth-profiles.json) pode morar no keychain do sistema em vez de num arquivo:
Os três backends diferem no que acontece com uma chave que já existe em disco:
  • file — o arquivo, sempre. O keychain nunca é consultado.
  • keychain — o keychain. Uma chave já em disco é migrada para ele uma vez, e o arquivo só é removido depois que o keychain devolveu essa chave. Uma escrita que pareceu dar certo e uma leitura que não devolve nada deixariam credenciais que nenhum processo futuro consegue descriptografar.
  • auto (padrão) — uma chave de arquivo existente continua sendo usada, intocada. Só uma chave criada pela primeira vez vai para o keychain, e só onde há um disponível. Realocar a chave de uma instalação que funciona sem ninguém pedir não é papel de um padrão.
Toda falha mantém o arquivo: keychain inalcançável, escrita recusada, escrita perdida ou um valor guardado que não seja uma chave de 32 bytes deixam a chave em disco exatamente onde estava, com um aviso por processo. /config server mostra o backend que de fato está em vigor, que nem sempre é o pedido.
No Windows a integração usa a API do Credential Manager diretamente (CredReadW/CredWriteW/CredDeleteW), porque o cmdkey cria e lista credenciais mas nunca revela um segredo. As entradas são gravadas por máquina, não roaming.

Segurança do Modo Agente

Allowlist de Comandos (Modo Strict)

O modo strict é o padrão. Só rodam comandos que estão na allowlist — e a regra vale para todo comando da linha, não só para o primeiro. Uma linha é uma sequência de invocações, então conferir apenas a primeira palavra tornaria qualquer comando permitido uma senha para o resto dela:
A decomposição usa um parser de shell de verdade, então aspas, heredocs, subshells e operadores escapados são lidos como o shell os lê. Uma linha que o parser não consegue ler cai para a conferência apenas do comando líder — senão uma máquina cujo shell não é bash perderia todos os comandos — e a denylist abaixo continua valendo para a linha inteira. A allowlist padrão tem cerca de 200 comandos:
A allowlist enxerga o comando base, não subcomandos: git está na lista, e não git status separadamente. O que limita um git push é a denylist e a política do coder, não a allowlist.
A allowlist não é uma lista de capacidades. Ela inclui interpretadores de shell (sh, bash, zsh), eval, exec, source e rm, porque o trabalho de desenvolvimento normal usa isso. O que de fato barra um comando destrutivo é a denylist abaixo, que roda em toda linha nos dois modos, e — onde você ligar — o sandbox do coder.Se você precisa de uma superfície realmente restrita, não dependa só do modo strict: rode o ChatCLI dentro de um contêiner, ligue CHATCLI_CODER_SANDBOX e mantenha CHATCLI_AGENT_WORKSPACE_STRICT ativo.

Allowlist Customizada

Estenda a allowlist com seus próprios comandos. Vírgula e ponto e vírgula funcionam:

Padrões de Denylist

A denylist não se limita ao modo permissive: ela roda em todo comando nos dois modos, como a camada que de fato recusa trabalho destrutivo. Cerca de 50 padrões:
Código inline de interpretador é classificado, não casado por padrão. No modo agente, python -c, perl -e, ruby -e, node -e e php -r não são mais barrados por um regex na invocação — o código inline é analisado e só o de alto risco é recusado, então python -c "print(1)" roda e python -c "import os; os.system(...)" não. O caminho do @coder mantém a forma estrita por regex e recusa a família inteira.

Bloqueio de Caminhos de Leitura

No modo de workspace estrito, o agente só pode ler arquivos dentro do diretório do workspace atual:
Alguns caminhos são recusados mesmo dentro do workspace, diga a allowlist o que disser: ~/.ssh, ~/.gnupg, ~/.aws, ~/.azure, ~/.gcloud e ~/.config/gcloud (tudo abaixo deles); ~/.kube/config (a menos que CHATCLI_AGENT_ALLOW_KUBECONFIG=true); ~/.netrc, ~/.npmrc, ~/.docker/config.json, ~/.pypirc, ~/.gem/credentials, ~/.m2/settings.xml e ~/.gradle/gradle.properties; /etc/shadow, /etc/gshadow, /etc/master.passwd e /proc/*/environ; e material de chave (.pem, .key, .p12, .pfx, .jks, .keystore, .p8, .der) fora do diretório home. A recusa cita o motivo.

Sourcing da Configuração do Shell

Por padrão, os arquivos de configuração do shell (~/.bashrc, ~/.zshrc) não são carregados durante a execução de comandos do agente, para impedir aliases e funções maliciosos:

Input guard — proteção contra typeahead em prompts de segurança

Quando uma security box aparece (modo coder/agent), três camadas defendem contra digitação acidental ser consumida como resposta y/n:
  1. Flush kernel TTY — TCIFLUSH (Linux) / TIOCFLUSH (BSD/Darwin) / FlushConsoleInputBuffer (Windows) descarta bytes na fila do kernel antes de renderizar a box.
  2. Drain channel — esvazia o canal centralizado de stdin não-bloqueante (o buffer de 10 linhas que a goroutine leitora usa).
  3. Intent debounce — descarta qualquer input que chegue nos primeiros 250ms após a box ser desenhada (a janela de reação humana mínima).
Sem essas camadas, digitar acidentalmente durante o stream do LLM faria a security box consumir os bytes prontos como aprovação. A primeira vez que isso aconteceu motivou o input guard. Instruções são preservadas, respostas não. Uma linha completa que você enviou para o agente (por exemplo atualize também o changelog) nunca responde ao prompt, mas também não é mais jogada fora: o drain a reenfileira e ela chega ao modelo na próxima fronteira de turno. Do drain, só continuam descartadas as linhas em forma de resposta de prompt (y, n, yes, no, sim, a, always, d, deny ou Enter vazio). Adicionalmente, no início de cada turno do agente, o ChatCLI faz stty sane no /dev/tty controlador para se recuperar de um teardown anterior do go-prompt que possa ter deixado o terminal em raw mode (echo off). Sem esse reset, você digita e não vê os caracteres na tela — embora o kernel esteja capturando.

Sanitizador de Saída

O stdout e o stderr de todo comando do agente passam por uma redação por regex de formatos de segredo (API keys, tokens, credenciais em strings de conexão) antes de serem guardados no resultado que o modelo recebe, e depois pela redação no caminho ao LLM, como qualquer outra saída de tool. Quando o agente devolve o resultado de um comando ao modelo (as continuações c<N> e ac<N>), stdout e stderr também vão cercados como dados num bloco <COMMAND_OUTPUT cmd="...">, precedidos de um aviso quando frases de prompt injection são detectadas, e limitados a CHATCLI_MAX_COMMAND_OUTPUT bytes (padrão 102400, cortados numa fronteira de caractere e marcados [TRUNCATED: output exceeded N bytes]). O terminal mostra a saída inteira; só a cópia enviada ao modelo é limitada. Os resultados de ferramentas do coder são dimensionados por CHATCLI_TOOL_RESULT_MAX_CHARS.

Validação do EDITOR

Quando o usuário edita comandos no modo agente, a variável EDITOR é validada contra uma allowlist de editores conhecidos:
Se EDITOR tiver um valor desconhecido (por exemplo EDITOR="/tmp/exploit.sh"), a operação é recusada com erro. O editor validado é então resolvido via exec.LookPath para obter o caminho absoluto.

Controle de Acesso ao Kubeconfig

Controla se os comandos do agente podem acessar o kubeconfig:

Proteção contra Injeção de Shell

Todo caminho de código em que valores dinâmicos são interpolados em comandos de shell usa a função utils.ShellQuote(), que aplica quoting POSIX com aspas simples:
Isso protege contra:
  • Injeção de aspas: '; rm -rf /; echo '
  • Substituição de comando: $(malicious) ou `malicious`
  • Expansão de variável: $HOME, ${PATH}
  • Pipe/redirecionamento: | cat /etc/passwd, > /etc/crontab

Resolução de Binários via LookPath

O binário stty (usado para restaurar o terminal) é resolvido uma vez no startup via exec.LookPath("stty"), que devolve o caminho absoluto. Isso impede que um atacante coloque um stty malicioso no PATH.

Segurança de Plugins

Verificação de Assinatura Ed25519

Binários de plugin são verificados com assinaturas Ed25519. Um plugin é assinado com a chave privada do desenvolvedor, e a chave pública correspondente precisa estar registrada em toda máquina que o instala.
1

Gerar o par de chaves de assinatura

O keygen se recusa a sobrescrever uma chave privada existente: regerar por cima invalida silenciosamente toda assinatura já feita com ela.
2

Assinar o plugin

3

Registrar a chave pública nas máquinas que o instalam

Sem esse passo a assinatura é inverificável: o verificador só lê chaves que encontra no diretório de confiança.
4

Distribuir e conferir

O verify diz qual das três coisas está errada — sem assinatura, sem chave confiável para conferir, ou assinatura que não bate — porque cada uma pede uma correção diferente. O ChatCLI faz a mesma checagem toda vez que carrega o diretório de plugins.
A assinatura é destacada, num arquivo .sig ao lado do binário, e cobre o SHA-256 do binário. Não existe manifesto de plugin: trocar o binário quebra a assinatura, que é o que importa. O diretório de plugins e ~/.chatcli/trusted-keys/ são criados com 0700; a chave privada é gravada com 0600, a chave pública e o .sig com 0644.

O que acontece com um plugin não assinado

Com o padrão em vigor e nenhuma chave confiável registrada, nenhum plugin externo carrega. Essa é a postura pretendida, e significa que adotar plugins é um ato deliberado: assine e registre a chave, ou defina CHATCLI_ALLOW_UNSIGNED_PLUGINS=true e aceite o que isso quer dizer.

Quarentena de Plugins Não Assinados

Um plugin não assinado recém-visto pode ser segurado fora do runtime por uma janela — o intervalo entre um binário aparecer no diretório de plugins e esse binário rodar com as permissões do ChatCLI.
Dentro do REPL, /plugin quarantine [release <nome>] faz o mesmo.
  • Vale só para plugins não assinados, e só onde CHATCLI_ALLOW_UNSIGNED_PLUGINS=true já os tolera. Uma assinatura verificada é uma afirmação mais forte que qualquer período de espera.
  • Trocar o binário reinicia a espera. A revisão foi dos bytes, não do nome do arquivo.
  • A liberação registra que um humano avalizou aqueles bytes exatos; o estado sobrevive a um restart, então as esperas não zeram quando o ChatCLI reinicia.
A quarentena vem desligada. Um atraso entre instalar um plugin e poder usá-lo é um custo real, e impor isso a todo mundo para endurecer um modo que já é opt-in trocaria um incômodo certo por um ganho especulativo. Ligue onde plugins não assinados são tolerados mas os não revisados não são.

O que o isolamento de plugins não faz

Não existe manifesto de permissões por plugin. Um plugin é um executável separado que o ChatCLI lança, e ele roda com as mesmas permissões do próprio ChatCLI — mesmo sistema de arquivos, mesma rede, mesma capacidade de iniciar processos. Nada restringe um plugin específico a um conjunto declarado de capacidades.Os controles que existem são os de cima: a assinatura diz quem produziu o binário, e a quarentena atrasa um não revisado. Nenhum dos dois limita o que o plugin faz depois que roda. Trate instalar um plugin como equivalente a rodar o código do autor dele na sua máquina, porque é isso que é. Onde isso não for aceitável, rode o próprio ChatCLI dentro de um contêiner com o acesso que você está disposto a conceder.

Segurança do Servidor gRPC

Prevenção de SSRF

URLs de provedor enviadas por um cliente são conferidas antes de o servidor usá-las, para que um chamador não consiga apontar o servidor para um endereço interno. As tools que buscam na web fazem a própria checagem na hora da conexão, incluindo redirects e DNS rebinding. Faixas bloqueadas:
  • 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (RFC 1918)
  • 127.0.0.0/8 (loopback)
  • 169.254.0.0/16 (link-local, incluindo endpoints de metadata de nuvem)
  • 100.64.0.0/10 (shared address space)
  • ::1/128, fc00::/7, fd00::/8, fe80::/10, ff00::/8, ::ffff:0:0/96 (IPv6 loopback, privado, link-local, multicast, IPv4 mapeado)
  • os hostnames metadata.google.internal, metadata.goog e instance-data
Hostnames são resolvidos e todo endereço devolvido é conferido. Um nome que não resolve passa (a requisição falha sozinha depois). Por padrão só URLs https são aceitas:

Rate Limiting

Rate limiting com token bucket protege contra abuso e DoS:
O limitador roda depois da autenticação e usa como chave o sujeito do chamador — o claim sub do JWT, ou o principal do certificado sob mTLS — em vez do endereço que ele divide com todo outro tenant atrás do mesmo NAT ou ingress. Todo portador do token compartilhado é o único sujeito legacy-token, e um servidor sem credencial (loopback) chama todo chamador de system, então cada um desses formatos divide um único bucket; emita JWTs para dar a cada chamador um bucket próprio. Chamadores com bearer ou JWT passam também por um limitador de falhas por host dentro da autenticação: cada host de cliente pode falhar na autenticação 5 vezes em burst, depois uma a cada 12 segundos; enquanto a cota dele está esgotada, toda chamada desse host recebe Unauthenticated antes de a credencial ser conferida (o log diz auth failure rate limit exceeded). Só autenticações que falharam consomem a cota — credenciais válidas nunca são limitadas, por mais chamadas que um host ou um ingress faça —, então ele limita a adivinhação de credenciais sem mexer em CHATCLI_RATE_LIMIT_RPS. A tabela de hosts é limpa a cada 5 minutos; chamadores identificados só pelo certificado de cliente não passam por ele. Uma chamada acima do limite por sujeito recebe ResourceExhausted.

Limites de Tamanho de Mensagem

Impedem esgotamento de memória por mensagens grandes demais:
O padrão de 50MB existe porque um prompt de contexto longo com histórico e marcadores de cache passa de alguns megabytes com facilidade; um teto de 4MB recusaria requisições normais. Baixe onde o servidor não carrega contextos longos — é o limite mais barato para a memória que um chamador consegue forçar o processo a alocar.

Validação de Entrada

Todo RPC exposto pelo serviço tem um validador, e um teste percorre o descritor de serviço gerado para manter assim — um RPC novo não entra sem alguém decidir como sua requisição é limitada.
  • Limites de tamanho de string e de bytes em todo campo de texto
  • Campos repetidos limitados (histórico de conversa, recomendações de insight, mapas de metadados)
  • Validação de enum para severidade
  • Regras de nomenclatura do Kubernetes (RFC 1123) para namespaces, nomes de objeto e kinds
  • Faixas numéricas para score de risco, contadores de passo e limites de página
RPCs de streaming também são validados. Um interceptor de stream não vê as mensagens por si, então a validação embrulha o stream e confere cada uma na chegada. Isso cobre a requisição única de um RPC server-streaming e toda mensagem da sessão bidirecional — o único caminho em que uma conexão pode ficar enviando enquanto estiver aberta.

Audit Logging

Todas as operações sensíveis são registradas em logs de auditoria JSON estruturados:
Exemplo de entrada do audit log:
O campo result distingue success, error e denied — uma chamada que a autenticação recusou é um evento de segurança e não se parece com uma falha de handler. RPCs de streaming também geram entrada, com details.stream e a quantidade de mensagens recebidas.

Trilha de requisições ao LLM (toda superfície, todo provedor)

O audit gRPC acima registra metadados de transporte. Com o mesmo CHATCLI_AUDIT_LOG_PATH, o CLI também registra toda requisição ao LLM em toda superfície — REPL, one-shot, gateway, servidor MCP/ACP, workers do squad — como linhas kind: "llm" no mesmo arquivo, uma no envio e uma no recebimento. O sink fica no chokepoint de observabilidade por onde passam os quinze adaptadores de provedor, então um provedor novo é auditado no dia em que entra. Uma linha carrega quando, provedor e modelo, tamanho do payload, tamanho do histórico, marcadores de cache, resultado, latência, o uso de tokens reportado pelo provedor e o total acumulado de segredos que o redator do caminho ao LLM reescreveu neste processo — nunca o conteúdo do prompt.
Quando a requisição foi feita por um run de agent (o orquestrador, um squad worker, um subagent, uma task do task graph), fields também leva caller, o id desse run, tanto na linha de envio quanto na de recebimento: a trilha diz não só que quarenta requisições foram feitas, mas qual agent fez cada uma. Um turno de chat ou um job em background não tem caller e o campo fica ausente. É o mesmo id que a Dash ao Vivo usa para pendurar a requisição no agent dela. O caminho precisa ser absoluto; um valor relativo desliga a trilha com erro no log. O arquivo é criado com 0600 e recebe append de todo processo que compartilhar o caminho.

Bind Address

Controla em qual interface de rede o servidor escuta:
Em Kubernetes, o bind address é automaticamente configurado para 0.0.0.0 — nenhuma configuração manual é necessária. O servidor detecta o ambiente via a variável KUBERNETES_SERVICE_HOST.
Um bind alcançável é fail-closed: sem credencial configurada o servidor recusa subir em vez de admitir todo chamador como administrador. Qualquer uma destas satisfaz a guarda, espelhando o que o interceptor de auth e o listener TLS de fato aplicam: CHATCLI_SERVER_TOKEN, CHATCLI_JWT_SECRET (HS256), CHATCLI_JWT_PUBLIC_KEY (RS256) ou CHATCLI_SERVER_TLS_CLIENT_CA (mTLS). Material JWT só conta se carregar: quando é a única credencial e falha ao carregar, o servidor recusa subir; ao lado de um token ou de uma CA de cliente, uma chave quebrada recusa só os chamadores JWT.

Cadeia de Interceptors

Toda requisição passa por uma cadeia de interceptors gRPC:
1

Validação

Limita cada campo da requisição pelo validador do RPC, antes de qualquer outra coisa tocá-la.
2

Audit

Envolve tudo o que vem depois, para registrar recusas tanto quanto sucessos, e nomeia o chamador que a autenticação resolve mais adiante.
3

Métricas

Conta requisições, durações e chamadas em andamento. Só existe quando a porta de métricas não é 0.
4

Recovery

Captura panics e devolve um erro gRPC em vez de derrubar o servidor.
5

Logging

Registra método, duração e status de cada requisição.
6

Auth

Valida o JWT ou o bearer token — ou nomeia o chamador pelo certificado de cliente verificado sob mTLS — e anexa o papel do chamador.
7

Rate Limiting

Rate limiter com token bucket por sujeito autenticado (sub do JWT ou principal do certificado), ou por endereço para chamadores anônimos. Roda depois do auth de propósito, para que tenants atrás de um mesmo ingress não dividam o bucket.
RBAC não é um interceptor: cada handler pede o nível de que precisa, porque a resposta depende do que a chamada faz e não de qual método foi invocado.

gRPC Reflection (Desabilitado por Padrão)

O gRPC reflection expõe o schema completo do serviço, permitindo que ferramentas como grpcurl e grpcui descubram e chamem todos os RPCs. Em produção, isso pode facilitar o reconhecimento por atacantes.
Por padrão, o reflection fica desabilitado. Habilite só para debugging local.
O campo spec.server.security.enableReflection da Instance e o valor server.grpcReflection do chart do servidor definem essa variável.

Segurança do Operator Kubernetes

Autenticação Fail-Closed

A API REST do operator (porta 8090, que também serve o dashboard) usa autenticação fail-closed: sem nenhuma API key carregada, toda chamada a /api/ recebe 401 (“no API keys configured”), a menos que o dev mode esteja ligado explicitamente, e uma requisição sem uma chave conhecida no header X-API-Key é negada. API keys são o único mecanismo; não há OIDC, SSO nem login de usuário. A página do dashboard em si carrega sem chave e guarda a chave digitada no localStorage do navegador. /healthz e /readyz nessa porta respondem sem chave. Os papéis são viewer (leitura), operator (acknowledge, snooze, resolve, approve, reject, edição de runbooks) e admin (tudo, incluindo excluir runbooks). Qualquer outro papel não concede nada. Cada entrada de chave também aceita um name opcional: a identidade registrada nas decisões de aprovação tomadas com aquela chave (na falta dele vale o description, e depois uma impressão digital key-<hash>). Um approve ou reject pela API REST ou pelo dashboard fica registrado como <nome digitado> (api-key: <identidade>), e um quorum conta cada chave uma única vez, então dê a cada aprovador a sua própria chave: uma chave compartilhada, ou o dev mode, não satisfaz uma regra que exige dois aprovadores. A API atende em todas as réplicas do operator. As requisições passam por rate limit antes da autenticação: uma requisição sem chave válida é limitada por host de cliente a 30 por minuto (atrás de um Ingress ou proxy, todos os clientes dividem o host do proxy, e headers de encaminhamento não são confiados), e uma chave válida é limitada a 600 por minuto. O excedente recebe 429 com Retry-After. As API keys são carregadas com hot-reload a cada 30 segundos, na seguinte ordem de prioridade:
  1. Secret chatcli-operator-secrets (prioridade) — campo api-keys com lista YAML de entradas {key, role, name, description} (name é opcional). O chart do operator o renderiza com apiKeys.create: true e apiKeys.entries; as chaves ficam então guardadas no release do Helm, então prefira criar o Secret você mesmo (kubectl, External Secrets, Vault).
  2. ConfigMap chatcli-operator-config (fallback) — mesmo campo api-keys
  3. Rejeita a requisição (ou aceita como admin em dev mode, se CHATCLI_OPERATOR_DEV_MODE=true)
Não confunda os dois Secrets de Auth do projeto — ambos costumam aparecer juntos no namespace do operator:O Secret chatcli-operator-secrets precisa estar no mesmo namespace que o pod do operator (o controller chama Secrets(resolveNamespace()).Get(...) — resolveNamespace() lê a env var POD_NAMESPACE, o arquivo de namespace do ServiceAccount, ou cai para o padrão chatcli-system). Se você fez helm install --namespace <X>, crie o Secret em <X>.
API keys armazenadas no Secret são recarregadas a cada 30s — nenhum restart do operator é necessário. Remover uma entrada do Secret revoga aquela chave em até 30 segundos, e uma lista vazia ([]) não deixa nenhuma chave válida. Um Secret sem a entrada api-keys (ou com ela em branco) cai para o ConfigMap. Quando nenhum dos dois fornece chaves (chatcli-operator-secrets e chatcli-operator-config apagados, ou nenhum tem a entrada), todas as chaves são revogadas na próxima leitura (em até ~30 segundos, 401 a partir daí). Uma entrada api-keys que não é YAML válido mantém em vigor o último conjunto de chaves válido e é registrada no log uma vez por versão, para que um erro de digitação não tranque todo mundo do lado de fora; revogue removendo entradas, não quebrando o YAML. Se a leitura do Secret ou do ConfigMap falhar por qualquer motivo que não seja “não encontrado”, as chaves em vigor são mantidas, para que uma instabilidade do API server não tranque todo mundo do lado de fora. A inicialização aplica essas mesmas regras.

Allowlist de Tipos de Recursos

A ação de remediação ApplyManifest (que aplica um manifest guardado num ConfigMap, somente no namespace do alvo) só cria ou atualiza kinds que estão numa allowlist. As outras ações de remediação agem direto no workload alvo e são governadas por políticas de aprovação, não por esta lista. O padrão é mais largo que um único tipo de workload, porque remediação que não pode tocar um Service ou um HPA é remediação que escala para um humano em trabalho de rotina:
ReplicaSet não está na lista: o Deployment dono dele reverteria uma escrita direta. O RBAC do operator concede create e update em cada kind padrão; um kind que você acrescenta com CHATCLI_ALLOWED_RESOURCE_TYPES também precisa de uma regra de ClusterRole correspondente.
CHATCLI_ALLOWED_RESOURCE_TYPES soma a essa lista; não substitui. Definir uma lista curta não restringe nada — os dezesseis padrões continuam, e suas entradas entram por cima. Os kinds casam pela grafia exata do Kind (Deployment, não deployments), então um plural minúsculo não acrescenta nada.
O RBAC do próprio operator também não estreita isso: o chart do operator concede uma ClusterRole cluster-wide (incluindo Secrets, workloads, nodes e objetos de RBAC), porque o operator age onde quer que Instances e Issues existam. Para restringir o que ele pode tocar, edite essa ClusterRole para o seu cluster, ou mantenha recursos do ChatCLI fora de namespaces em que ele não deve agir.
Uma segunda lista nomeia kinds tratados como perigosos, e a recusa cita o motivo — ClusterRole e ClusterRoleBinding (escalonamento cluster-wide), Role, RoleBinding, Namespace, Node, PersistentVolume, StorageClass, Secret, ServiceAccount, NetworkPolicy, PodSecurityPolicy, as configurações de webhook mutating e validating, CustomResourceDefinition, PriorityClass, ResourceQuota e LimitRange. O ApplyManifest recusa esses kinds, e qualquer kind fora das duas listas, e a tentativa de remediação falha com esse erro; não existe caminho de aprovação que deixe um manifest desses passar.

Log Scrubbing

Antes de enviar ao LLM o contexto de enriquecimento de um incidente (logs de pod, eventos, métricas, trechos de código), o operator substitui valores sensíveis por [REDACTED:<tipo>]. Dezoito padrões embutidos cobrem access keys e secrets da AWS, JWTs, bearer tokens, atribuições api_key=/password=/token=/secret=, URIs de banco com credenciais, tokens de service account do Kubernetes, tokens e PATs fine-grained do GitHub, tokens do Slack, API keys sk-, cabeçalhos de chave privada, endereços IPv4, endereços de e-mail, strings base64 longas e strings hex longas. A saída de log do próprio operator não passa por esse filtro.
No chart do operator: security.logScrubPatterns.

Política de CORS

A API REST do operator é deny-all até que uma origem seja nomeada: sem nenhuma configurada, nenhum header CORS é escrito e o navegador bloqueia toda chamada cross-origin.
Ou diretamente:
Uma allowlist com várias origens devolve a origem da própria requisição depois de casá-la, com Vary: Origin, porque o header carrega um valor só e devolver uma origem não casada transformaria a lista em “qualquer site”. "*" é aceito; junto com credenciais ele devolve a origem, já que navegadores rejeitam o asterisco literal nessa combinação. O operator registra em log qual política entrou em vigor no boot.

RBAC e NetworkPolicy

Operator. O chart do operator (rbac.create: true) concede ao operator uma ClusterRole cluster-wide: acesso total às CRDs do ChatCLI; get/list/watch/create/update/patch em Secrets e ConfigMaps de todos os namespaces; os workloads, Services, PVCs, Jobs e objetos de RBAC que ele provisiona para as Instances; get/list/watch/create/update/delete em pods (remediações de pod e os pods de stress do chaos); create em pods/eviction (o DrainNode despeja pela Eviction API, então os PodDisruptionBudgets são respeitados); update de nodes (remediações de cordon/drain); create/patch em Events do core e de events.k8s.io; create/update em todo kind que o allowlist do ApplyManifest admite, inclusive os kinds do Prometheus Operator (servicemonitors, podmonitors, prometheusrules) e do Istio (serviceentries, virtualservices, destinationrules) (regras para um API group não instalado ficam inertes); ReplicaSets só leitura; leases para leader election. As mesmas regras estão em operator/config/rbac/role.yaml. Não existe modo namespace-scoped para o operator. O chart também pré-provisiona a ClusterRole chatcli-watcher (leitura das cargas observadas, inclusive Jobs e CronJobs) e as ClusterRoles chatcli-role-*, e o operator só pode fazer bind dessas. Chart do servidor. O chart standalone do servidor é namespace-scoped por padrão:
NetworkPolicy do chart do servidor, desligada por padrão. Ligá-la restringe o ingress às portas gRPC e de métricas; ingressFrom estreita quem pode conectar; o egress é uma escolha à parte:
Com egress: restricted, a saída é limitada a DNS, HTTPS e a API do Kubernetes:
NetworkPolicy do chart do operator, também desligada por padrão:
restricted permite DNS, HTTPS, a API do Kubernetes, a porta gRPC das Instances e, quando prometheusUrl aponta para uma, a porta do Prometheus. Os manifests crus trazem a mesma política em operator/config/network-policy/network-policy.yaml (make deploy-network-policy). Instances gerenciadas pelo operator não recebem NetworkPolicy; escreva uma para os pods delas (label app.kubernetes.io/instance: <nome da instance>) que admita o namespace do operator na porta gRPC e o seu Prometheus na porta de métricas.
Comece com egress: allowAll, confirme que o ingress se comporta, e só então aperte. O DNS está incluído na forma restrita e não é opcional: um pod que não resolve nomes falha de um jeito que não se parece em nada com um problema de política de rede. Se o servidor chama um endpoint de modelo privado ou um serviço interno, acrescente a porta em egressExtraPorts antes de trocar.

SecurityContext dos Pods

O chart do servidor define um SecurityContext restritivo por padrão (o chart do operator faz o mesmo sem fixar UID; a imagem do operator roda como 65532):
Com securityContext.readOnlyRootFilesystem: true, o chart monta automaticamente um emptyDir em /tmp (limitado a 100Mi) para a aplicação escrever arquivos temporários.
Pods de Instances gerenciadas pelo operator recebem o mesmo formato sem configuração: runAsNonRoot, UID 1000, seccomp RuntimeDefault, sem escalonamento de privilégio, root filesystem somente-leitura e todas as capabilities removidas, inclusive no init container plugin-loader, então passam no Pod Security Standard restricted. spec.securityContext substitui a parte de nível de pod. Nada rotula namespaces para o Pod Security Admission; adicione pod-security.kubernetes.io/enforce: restricted você mesmo.

Dev Mode do Operator

Para desenvolvimento local, o operator pode rodar em dev mode:
O dev mode muda só isso: quando nenhuma API key está carregada, a API REST admite toda requisição como admin em vez de recusá-la. Com chaves carregadas, elas valem normalmente. Ele não mexe em TLS nem na conexão do operator com os servidores.
Nunca ligue CHATCLI_OPERATOR_DEV_MODE em produção: um operator sem chaves entrega admin a qualquer um que alcance a porta 8090.

TLS do Operator

O operator tem duas superfícies TLS. API REST e dashboard (porta 8090). HTTP por padrão; TLS 1.3 quando os dois caminhos estão definidos:
Operator para os servidores ChatCLI (gRPC). O operator sempre disca TLS 1.3, então toda Instance que ele precisa alcançar precisa de spec.server.tls.enabled: true com um secretName cujo certificado seja válido para <instance>.<namespace>.svc.cluster.local. A raiz de confiança é a chave ca.crt desse Secret, senão as CAs do sistema. A credencial que ele apresenta vem da Instance (spec.server.token, security.operatorTokenRef, JWTs HS256 de vida curta que ele gera a partir de security.jwtSecretRef, ou o certificado de cliente em security.operatorClientCertSecretName). Fallbacks globais do operator, montados do mesmo jeito via extraVolumes:
A Instance mostra o que encontrou nas conditions do status: TLSConfigured fica False (e nenhum Deployment é criado) quando o TLS está ligado sem nome de Secret, AuthenticationConfigured fica False quando um servidor alcançável não tem credencial, OperatorCredentialConfigured diz qual credencial o operator apresenta e ServerReachable traz a última sonda, repetida a cada cinco minutos, ou a cada 30 segundos depois de uma sondagem que falhou (veja Condições da Instance). Rotacionar qualquer Secret referenciado por uma Instance reinicia os pods dela.

Segurança de Containers (Docker)

O docker-compose.yml de desenvolvimento traz estas medidas de hardening (ele também faz bind em 0.0.0.0 dentro do container, então precisa de CHATCLI_SERVER_TOKEN ou material JWT para subir):
A imagem do servidor é distroless (gcr.io/distroless/static-debian12:nonroot, UID 65532) e traz o grpc-health-probe para o HEALTHCHECK. A imagem do operator é baseada em Alpine e roda como 65532.

Segurança de CI/CD

O pipeline de CI/CD do ChatCLI inclui várias verificações de segurança:
1

govulncheck

Verifica as dependências Go contra vulnerabilidades conhecidas usando o banco de vulnerabilidades do Go.
2

gosec

Scanner de análise estática de segurança para código Go que detecta vulnerabilidades comuns.
Os resultados sobem como relatório SARIF para o code scanning; achados do gosec sozinhos não reprovam o workflow.
3

Trivy

Toda imagem de release é escaneada antes de o manifest ser publicado; um achado HIGH ou CRITICAL com correção disponível bloqueia a release. As últimas tags estáveis são reescaneadas e reconstruídas quando surge uma correção.
4

Dependabot

Atualizações automáticas de dependências com alertas de segurança para pacotes vulneráveis. Configurado em .github/dependabot.yml.
5

Assinatura de imagens com Cosign

Imagens de container e charts Helm são assinados keyless com Sigstore Cosign pelo workflow de release; as imagens também levam atestados de SBOM e proveniência.

Governança do Modo Coder (Policy Manager)

Casamento por Limite de Palavra

O sistema de políticas usa casamento por limite de palavra para impedir escalonamento de permissão por prefixo. Exemplo: A lógica confere se o próximo caractere depois do casamento é um separador (espaço, /, =, etc.) e não a continuação de uma palavra (letra, dígito, -, _). Isso garante que read não case com readlink.

Regras Padrão

Comandos de leitura são permitidos; execução sempre pergunta:
O arquivo de política é gravado com 0600 em ~/.chatcli/coder_policy.json; um coder_policy.json no diretório de trabalho o substitui para aquele projeto.

O guard de comando perigoso fica abaixo da política

Todo subcomando do @coder que roda uma linha de shell — exec e test — é conferido contra a lista de padrões perigosos independentemente do que a política diz, inclusive depois de um “allow always”. Um comando que casa é recusado e o modelo é instruído a não tentar de novo.
--allow-unsafe e --allow-sudo existem nos dois subcomandos para os casos que realmente precisam deles, e só retiram a checagem do próprio engine — o guard acima continua valendo nos modos agent e coder.
Para mais detalhes sobre o sistema de governança, veja a documentação do Modo Coder.

Configuração Gerenciada (defaults da organização e políticas travadas)

Um operador pode distribuir um managed.env com a imagem da máquina ou o perfil de MDM e fazer todo processo do ChatCLI naquela máquina respeitá-lo — REPL, one-shot, gateway, servidor MCP/ACP: O arquivo tem formato dotenv com dois tipos de linha:
Precedência, da maior para a menor: gerenciado travado → ambiente do usuário / .env → default gerenciado → default do código. /config managed mostra o arquivo, as entradas e quais estão travadas; toda seção do /config marca valores vindos dele como (gerenciado) ou (gerenciado · travado). Arquivo ilegível é reportado uma vez no boot e ignorado (nunca derruba); arquivo ausente não muda nada.

Referência de Variáveis de Segurança

Referência completa de todas as variáveis de ambiente relacionadas à segurança:

Segurança do Servidor

Segurança do Agente

Segurança de Plugins e Autenticação

Segurança do Operator


Verificação de Versão

O ChatCLI confere automaticamente se há versões mais novas no GitHub. Para desabilitar (por exemplo, em ambientes air-gapped ou em CI/CD):

Boas Práticas para Produção

1

Use autenticação JWT com RBAC

2

Habilite TLS em produção

Sob o operator isso não é opcional: ele disca toda Instance em TLS, então defina spec.server.tls.enabled: true com um Secret contendo tls.crt, tls.key e ca.crt.
3

Use o modo de segurança strict no agente

4

Exija assinatura de plugins

Mantenha CHATCLI_ALLOW_UNSIGNED_PLUGINS como false (padrão), assine seus plugins e registre a chave pública:
Onde plugins não assinados precisam ser tolerados, defina CHATCLI_PLUGIN_QUARANTINE=24h para que um binário que ninguém instalou de propósito não rode no momento em que aparece.
5

Exija certificados de cliente

6

Ligue a criptografia em repouso

Verifique a trilha de auditoria periodicamente com /config security verify-audit.
7

Coloque a execução do coder em sandbox

8

Configure o rate limiting

9

Habilite o audit logging

10

Mantenha o gRPC reflection desabilitado

Não passe --enable-reflection nem defina CHATCLI_GRPC_REFLECTION=true em produção. Use só para debugging local.
11

Use RBAC namespace-scoped no chart do servidor

Mantenha rbac.clusterWide: false (padrão), a menos que precise monitorar vários namespaces. O operator sempre precisa da ClusterRole cluster-wide dele; revise-a antes de instalar.
12

Cerque as portas sem autenticação

Os endpoints de métricas (servidor 9090, operator 8080) não têm autenticação. Ligue networkPolicy nos dois charts, restrinja metricsIngressFrom / ingressFrom ao namespace de monitoramento e apiIngressFrom ao que fica na frente do dashboard, e escreva uma NetworkPolicy para os pods das Instances gerenciadas pelo operator.
13

Gerencie as API keys do dashboard como Secrets

Crie chatcli-operator-secrets no namespace do operator com uma chave por time e o menor papel que funcione, gere as chaves com openssl rand -hex 32 e rotacione editando o Secret (uma entrada removida para de funcionar em até 30 segundos). Nunca rode com security.devMode: true.
14

Defina limites de recursos

Sempre defina limites de CPU e memória para impedir consumo excessivo. Os charts trazem padrões; uma Instance gerenciada pelo operator não recebe nenhum, a menos que spec.resources os defina:
15

Habilite a redação de variáveis de ambiente

16

Use o keychain do SO para a chave de credenciais

Confira o /config server depois: ele mostra o backend que de fato entrou em vigor, que é o arquivo onde não há keychain disponível.
17

Monitore o audit log

18

Mantenha o ChatCLI atualizado

A verificação de versão vem ligada por padrão. Se você a desligou com CHATCLI_DISABLE_VERSION_CHECK, confira periodicamente:

Próximos Passos

Governança do Modo Coder

Regras de política para controlar o que o Coder pode executar.

Configure o Servidor

Deploy e configuração do servidor gRPC.

Deploy com Docker e Helm

Guia completo de deploy containerizado.

Variáveis de Ambiente

Referência completa das variáveis de ambiente.

Operator K8s

Plataforma AIOps de remediação autônoma.

Sistema de Plugins

Estenda o ChatCLI com plugins customizados.