Segurança e Hardening

Segredos

Cinco segredos determinam a segurança de um deployment. Gere cada um com openssl rand -hex 32.

SegredoProtegeRotacionável
BETTER_AUTH_SECRETTokens de sessãoSim — invalida todas as sessões
ENCRYPTION_KEYVariáveis de ambiente do workspace, chaves de provedor armazenadas, credenciais OAuth do MCP, segredos de deployment/chatNão — veja abaixo
API_ENCRYPTION_KEYCópia reversível armazenada das chaves de API geradas por pessoasNão — as chaves existentes continuam autenticando, mas a cópia armazenada não pode mais ser exibida
INTERNAL_API_SECRETChamadas entre serviçosSim — rotacione app e realtime juntos
CRON_SECRETEndpoints de jobs em backgroundSim — rotacione app e cron juntos

ENCRYPTION_KEY não pode ser rotacionada sem re-encriptar os dados que ela protege, e não pode ser recuperada se for perdida. Trocá-la torna todos esses dados permanentemente ilegíveis. Faça backup dela separadamente do banco de dados.

BETTER_AUTH_SECRET precisa ser idêntica nos serviços app e realtime — eles compartilham sessões pelo banco de dados, e uma divergência faz o realtime rejeitar todo socket autenticado.

Onde guardá-los

Em ordem crescente de prontidão para produção:

  1. --set na linha de comando — só para desenvolvimento. Os valores aparecem na saída de helm get values e no histórico do shell.
  2. Um Secret do Kubernetes criado previamente — defina app.secrets.existingSecret.enabled: true e o nome do secret. Funciona com Sealed Secrets e SOPS. O secret é consumido por completo e precisa usar os nomes de chave padrão.
  3. External Secrets Operator — sincronize a partir de Vault, AWS Secrets Manager, Azure Key Vault ou GCP Secret Manager. Recomendado.

Nos modos padrão e External Secrets, o chart grava todas as chaves sob app.env e realtime.env em um Secret gerenciado pelo chart e montado via envFrom, então nenhum valor fica embutido em uma spec de pod. (No modo existingSecret, o Secret pré-criado é a fonte da verdade e quaisquer valores de app.env que você ainda passar são renderizados inline — nesse modo, forneça tudo pelo Secret.) De qualquer forma, um segredo comitado no values.yaml é um segredo no seu histórico do git.

Fronteiras de rede

Ingress

Exponha apenas o app (3000) e o realtime (3002). Todo o resto — Postgres, Redis, o serviço de PII, os endpoints de cron — deve ser acessível somente de dentro do deployment.

Os endpoints de jobs em background sob /api/cron/*, /api/webhooks/poll/* e /api/schedules/execute são autenticados pelo CRON_SECRET, mas não há motivo para expô-los publicamente. Aponte o cron para o Service interno ao cluster.

NetworkPolicy

O chart traz uma policy opcional que isola o tráfego leste-oeste e bloqueia os endpoints de metadados da nuvem (169.254.169.254/32, 169.254.170.2/32) na saída — vale ativar, porque esses endpoints são o alvo padrão de escalonamento de SSRF.

networkPolicy:
  enabled: true

networkPolicy.ingressFrom tem como padrão [{}] — um seletor de peer vazio que permite ingress de qualquer pod do cluster. Em um cluster compartilhado ou multi-tenant, restrinja-o ao seu ingress controller:

networkPolicy:
  ingressFrom:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: ingress-nginx

A policy já permite saída HTTPS (443) para tudo, exceto os CIDRs de metadados, o que cobre APIs de provedores de modelos, APIs de integrações e endpoints de armazenamento de objetos na nuvem. Ela também permite o Postgres e o Redis empacotados, por seletor de pod.

O que ela não cobre é qualquer datastore que você rode fora do chart — um Postgres ou Redis gerenciado em uma porta diferente de 443. Adicione uma regra para cada:

networkPolicy:
  enabled: true
  egress:
    - to:
        - ipBlock:
            cidr: 10.0.0.0/16   # your VPC / managed-service subnet
      ports:
        - protocol: TCP
          port: 6379           # managed Redis
        - protocol: TCP
          port: 5432           # managed Postgres

Isso vale mesmo quando REDIS_URL chega ao pod por um Secret em vez do values.yaml — o chart não consegue ver o host, então não consegue gerar a regra. Um deployment que aceita a URL mas não tem uma regra de egress correspondente não vai conseguir alcançar o Redis com networkPolicy.enabled: true.

Se manter listas de CIDR não valer o esforço, remova a restrição de portas:

networkPolicy:
  enabled: true
  allowExternalEgress: true

Os endpoints de metadados da nuvem continuam bloqueados nos dois casos. O padrão é false porque o chart do Studio é deliberadamente mais rígido que o padrão comum de charts, que permite saída sem restrições.

Pod Security Standards

Todas as workloads definem runAsNonRoot, removem todas as capabilities do Linux, desabilitam escalonamento de privilégios e usam seccompProfile: RuntimeDefault — os quatro controles exigidos pelo perfil restricted. Rotule o namespace para aplicá-lo:

kubectl label namespace studio pod-security.kubernetes.io/enforce=restricted

readOnlyRootFilesystem não é definido por padrão: Postgres e Ollama precisam de um root gravável, e o container do app escreve no .next/cache do Next.js. É viável nos serviços genuinamente sem estado (realtime, pii, copilot) — defina <component>.securityContext.readOnlyRootFilesystem: true e monte um emptyDir em /tmp via extraVolumes / extraVolumeMounts.

Onde o código das pessoas roda

Workflows podem executar JavaScript e Python escritos por pessoas. Saiba qual sandbox você está usando antes de expor o Studio a autores não confiáveis.

ModoConfiguraçãoIsolamento
isolated-vm (padrão)nenhumaIsolate V8 no mesmo processo, dentro do container do app. Sem namespace de rede nem separação de filesystem em relação ao processo do app — o isolamento é no nível do engine JS. Só JavaScript.
E2BE2B_ENABLED=true, E2B_API_KEYSandbox remoto por execução. Isolamento mais forte; exige acesso de saída ao E2B.
DaytonaSANDBOX_PROVIDER=daytona, DAYTONA_API_KEYSandbox remoto por execução.

Python, Shell, JavaScript com imports externos e blocks dependentes de ferramentas exigem um provedor de sandbox remoto. JavaScript sem import ou require continua rodando no isolate em processo quando nenhum provedor remoto está configurado.

Com o sandbox em processo padrão, trate qualquer pessoa que possa criar um workflow como alguém executando código no contexto de segurança do container do app. Se a sua instância do Studio está aberta a um público amplo ou parcialmente confiável, use um provedor de sandbox remoto e ative as restrições de egress da NetworkPolicy.

Tetos de recursos para o caminho em processo:

VariávelControla
IVM_MAX_EXECUTIONS_PER_WORKERExecuções antes de um worker ser reciclado
IVM_MAX_BROKERS_PER_EXECUTIONBrokers de chamadas ao host por execução
IVM_MAX_BROKER_ARGS_JSON_CHARSTamanho máximo do payload de argumentos
IVM_MAX_BROKER_RESULT_JSON_CHARSTamanho máximo do payload de resultado

A fronteira de SSRF

O Studio bloqueia requisições de saída de ferramentas de banco de dados e de conectores para endereços privados, reservados e de loopback. Isso impede que um workflow seja usado para escanear a sua rede interna.

Deployments auto-hospedados muitas vezes precisam, legitimamente, alcançar um banco interno pelo nome do serviço. Isso é opt-in:

ALLOW_PRIVATE_DATABASE_HOSTS=true

Isso afrouxa a fronteira de SSRF para toda pessoa que cria workflows na instância. Ative apenas em uma rede privada confiável, e de preferência combine com uma NetworkPolicy que restrinja o que o app pode realmente alcançar.

IP do cliente e headers encaminhados

Atrás de um load balancer, X-Forwarded-For é controlável pelo cliente. Defina AUTH_TRUSTED_PROXIES com os endereços reais dos seus proxies para que o Better Auth resolva o IP real do cliente, e TRUSTED_ORIGINS se as pessoas acessam o Studio por mais de uma origem. Os dois estão cobertos em Autenticação.

Restringindo quem pode usar a instância

Listas de permissão e de bloqueio para cadastro, controles de login social, SSO e a saída de emergência DISABLE_AUTH estão todos cobertos em Autenticação. O resumo relevante para segurança: restrinja o cadastro antes de expor a instância, e nunca defina DISABLE_AUTH=true atrás de um ingress voltado para a internet.

Redação de PII

O serviço opcional baseado em Presidio dá suporte ao block Guardrails PII e, quando ativado, à redação automática de PII dos logs de workflow:

pii:
  enabled: true

app:
  env:
    PII_REDACTION: "true"
    INTERNAL_API_BASE_URL: "http://studio-app.studio.svc.cluster.local:3000"

INTERNAL_API_BASE_URL precisa ser a URL do Service interno ao cluster. O caminho de redação chama a própria API do app, e uma URL pública de ingress normalmente não é alcançável por hairpin de dentro do cluster. Sem um valor alcançável, o caminho falha de forma fechada — os campos afetados são substituídos por [REDACTION_FAILED] em vez de vazarem, mas a redação não roda de fato.

O serviço embute cerca de 2,2 GB de modelos spaCy, então a primeira inicialização leva por volta de três minutos e ele precisa de pelo menos 4 GB de memória.

Checklist pré-lançamento

  • Todos os cinco segredos gerados do zero, guardados em um gerenciador de segredos e com a ENCRYPTION_KEY em backup separado
  • BETTER_AUTH_SECRET idêntica no app e no realtime
  • Imagens fixadas em uma tag ou digest explícito no app, no realtime e nas migrations
  • TLS terminando no ingress; HTTP redirecionado ou desabilitado
  • NEXT_PUBLIC_APP_URL e BETTER_AUTH_URL apontando para a origem pública real
  • AUTH_TRUSTED_PROXIES definido se estiver atrás de um load balancer
  • Cadastro restrito (DISABLE_REGISTRATION ou ALLOWED_LOGIN_DOMAINS)
  • DISABLE_AUTH não definido
  • NetworkPolicy ativada e ingressFrom restrito ao ingress controller
  • Namespace rotulado com pod-security.kubernetes.io/enforce=restricted
  • Buckets de armazenamento de objetos privados, com CORS limitado à sua origem do Studio
  • Banco de dados acessível apenas de dentro do deployment; TLS obrigatório (sslMode: require)
  • Backups configurados e uma restauração ensaiada
  • Estratégia de sandbox definida para código escrito por pessoas

Common Questions

Não sem re-encriptar tudo que ela protege. Trocá-la torna as variáveis de ambiente do workspace, as chaves de API de provedores armazenadas, as credenciais OAuth do MCP e os segredos de deployment permanentemente ilegíveis. Trate-a como um valor permanente e com backup, não como um segredo rotativo.
Por padrão em um isolate V8 no mesmo processo, dentro do container do app, que isola no nível do engine JS mas compartilha o contexto de rede e de filesystem do container. Para autores não confiáveis, ou para rodar Python de alguma forma, use E2B ou Daytona para que cada execução aconteça em um sandbox remoto.
networkPolicy.ingressFrom tem como padrão um seletor de peer vazio, um padrão simples que funciona em qualquer cluster. Em um cluster compartilhado, você deve restringi-lo ao namespace do seu ingress controller.
Ela permite que ferramentas de banco de dados e de conectores alcancem endereços privados, reservados e de loopback — necessário para conectar a um banco interno pelo nome do serviço no Kubernetes. Também amplia a fronteira de SSRF para toda pessoa que cria workflows, então ative apenas em uma rede confiável.