Visão Geral das Proteções
A tabela abaixo resume todas as proteções ativas em cada camada da stack.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.- HS256 (segredo compartilhado)
- RS256 (chave pública RSA)
- Via Helm
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.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:viewer e readonly concedem a mesma coisa.
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.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.
- Via flag
- Via variável de ambiente
OAuth 2.0 + PKCE
O ChatCLI aceita OAuth 2.0 com PKCE para os seguintes provedores: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)
CHATCLI_ENCRYPTION_KEY ser definida./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 exportficam 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)
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.
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
- Defina o novo segredo em
CHATCLI_ENCRYPTION_KEYe liste o aposentado emCHATCLI_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. - 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. - 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
- TLS do servidor
- TLS mútuo (mTLS)
- Desenvolvimento (sem TLS)
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).
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:
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.
/config server mostra o backend que de fato está em vigor, que nem sempre é o pedido.
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:Operações de arquivo
Operações de arquivo
Processamento de texto
Processamento de texto
Ferramentas de desenvolvimento
Ferramentas de desenvolvimento
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.Contêineres e infraestrutura
Contêineres e infraestrutura
Rede
Rede
Informações do sistema
Informações do sistema
Editores e visualizadores
Editores e visualizadores
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: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:~/.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:- Flush kernel TTY —
TCIFLUSH(Linux) /TIOCFLUSH(BSD/Darwin) /FlushConsoleInputBuffer(Windows) descarta bytes na fila do kernel antes de renderizar a box. - Drain channel — esvazia o canal centralizado de stdin não-bloqueante (o buffer de 10 linhas que a goroutine leitora usa).
- Intent debounce — descarta qualquer input que chegue nos primeiros 250ms após a box ser desenhada (a janela de reação humana mínima).
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çõesc<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ávelEDITOR é validada contra uma allowlist de editores conhecidos:
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çãoutils.ShellQuote(), que aplica quoting POSIX com aspas simples:
- 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áriostty (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.Gerar o par de chaves de assinatura
Assinar o plugin
Registrar a chave pública nas máquinas que o instalam
Distribuir e conferir
.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
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./plugin quarantine [release <nome>] faz o mesmo.
- Vale só para plugins não assinados, e só onde
CHATCLI_ALLOW_UNSIGNED_PLUGINS=truejá 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.
O que o isolamento de plugins não faz
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.googeinstance-data
https são aceitas:
Rate Limiting
Rate limiting com token bucket protege contra abuso e DoS: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: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
Audit Logging
Todas as operações sensíveis são registradas em logs de auditoria JSON estruturados: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 mesmoCHATCLI_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.
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:Cadeia de Interceptors
Toda requisição passa por uma cadeia de interceptors gRPC:Validação
Audit
Métricas
0.Recovery
Logging
Auth
Rate Limiting
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.gRPC Reflection (Desabilitado por Padrão)
O gRPC reflection expõe o schema completo do serviço, permitindo que ferramentas comogrpcurl e grpcui descubram e chamem todos os RPCs. Em produção, isso pode facilitar o reconhecimento por atacantes.
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 (porta8090, 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:
- Secret
chatcli-operator-secrets(prioridade) — campoapi-keyscom lista YAML de entradas{key, role, name, description}(nameé opcional). O chart do operator o renderiza comapiKeys.create: trueeapiKeys.entries; as chaves ficam então guardadas no release do Helm, então prefira criar o Secret você mesmo (kubectl, External Secrets, Vault). - ConfigMap
chatcli-operator-config(fallback) — mesmo campoapi-keys - Rejeita a requisição (ou aceita como admin em dev mode, se
CHATCLI_OPERATOR_DEV_MODE=true)
Allowlist de Tipos de Recursos
A ação de remediaçãoApplyManifest (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:
CHATCLI_ALLOWED_RESOURCE_TYPES também precisa de uma regra de ClusterRole correspondente.
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.
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.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:
- RBAC namespace-scoped (padrão)
- RBAC cluster-wide
ingressFrom estreita quem pode conectar; o egress é uma escolha à parte:
egress: restricted, a saída é limitada a DNS, HTTPS e a API do Kubernetes:
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.
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 como65532):
securityContext.readOnlyRootFilesystem: true, o chart monta automaticamente um emptyDir em /tmp (limitado a 100Mi) para a aplicação escrever arquivos temporários.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: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.
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: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:
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)
Odocker-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):
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:govulncheck
gosec
Trivy
Dependabot
.github/dependabot.yml.Assinatura de imagens com Cosign
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:/, =, 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: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.Configuração Gerenciada (defaults da organização e políticas travadas)
Um operador pode distribuir ummanaged.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:
.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
Use autenticação JWT com RBAC
Habilite TLS em produção
spec.server.tls.enabled: true com um Secret contendo tls.crt, tls.key e ca.crt.Use o modo de segurança strict no agente
Exija assinatura de plugins
CHATCLI_ALLOW_UNSIGNED_PLUGINS como false (padrão), assine seus plugins e registre a chave pública:CHATCLI_PLUGIN_QUARANTINE=24h para que um binário que ninguém instalou de propósito não rode no momento em que aparece.Exija certificados de cliente
Ligue a criptografia em repouso
/config security verify-audit.Coloque a execução do coder em sandbox
Configure o rate limiting
Habilite o audit logging
Mantenha o gRPC reflection desabilitado
--enable-reflection nem defina CHATCLI_GRPC_REFLECTION=true em produção. Use só para debugging local.Use RBAC namespace-scoped no chart do servidor
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.Cerque as portas sem autenticação
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.Gerencie as API keys do dashboard como Secrets
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.Defina limites de recursos
spec.resources os defina:Habilite a redação de variáveis de ambiente
Use o keychain do SO para a chave de credenciais
/config server depois: ele mostra o backend que de fato entrou em vigor, que é o arquivo onde não há keychain disponível.Monitore o audit log
Mantenha o ChatCLI atualizado
CHATCLI_DISABLE_VERSION_CHECK, confira periodicamente: