Vaultwarden é seguro? Guia de hardening
O Vaultwarden cifra os itens no cliente, mas o token de admin e o backup continuam críticos. Veja os riscos reais e como proteger sua instalação.
O Vaultwarden é seguro? A resposta curta
O Vaultwarden é seguro no ponto mais importante, porque cada item do cofre é encriptado no seu dispositivo antes de chegar ao servidor. O servidor armazena blobs que não consegue ler. Mesmo alguém que copie a base de dados completa ainda precisa da palavra-passe principal para obter algo útil a partir dela.
Essa resposta depende de vários fatores, e os pontos que falham são os que configura. Um painel de administração protegido por um token fácil de adivinhar. Uma porta do contentor publicada para toda a Internet. Um config.json em texto simples. Um ficheiro tar de backup guardado no diretório pessoal na mesma máquina. Nenhum desses problemas é de criptografia. Todos explicam por que motivo os cofres alojados localmente ficam vazios.
Tudo abaixo pressupõe uma instalação funcional. Se ainda não tem uma, configure-a primeiro com o guia de instalação do Vaultwarden para um VPS e depois siga esta lista pela ordem apresentada.
O que o servidor realmente armazena
O Vaultwarden implementa o modelo de dados do Bitwarden. O nome, o nome de utilizador, a palavra-passe, as notas e os URIs de um item do cofre são encriptados no cliente com uma chave derivada da sua palavra-passe principal, antes de qualquer pedido ser enviado. O conteúdo dos ficheiros anexados é encriptado da mesma forma. O servidor recebe dados opacos associados a um UUID (identificador universalmente exclusivo).
Alguns dados não são texto cifrado. Deve saber exatamente quais:
- O endereço de e-mail da conta, em texto simples.
- As definições e o salt da sua KDF (função de derivação de chaves), porque o cliente precisa deles para reconstruir a chave no início da sessão seguinte.
- Um hash no servidor do hash da palavra-passe principal enviado pelo cliente, usado para autenticar o início da sessão.
- Metadados: associação a organizações, nomes dos dispositivos e horas do último início de sessão.
- O segredo do método de autenticação de dois fatores que protege o início de sessão no Vaultwarden. Este segredo fica na tabela
twofactorsem encriptação, porque o servidor precisa de calcular o código esperado para o comparar com o seu. Não é o mesmo que um segredo TOTP (palavra-passe de utilização única baseada no tempo) armazenado dentro de um item do cofre, que é encriptado como qualquer outro campo.
A pasta de dados é pequena. Numa instalação Docker, corresponde ao caminho que montou em /data.
sudo ls -l /vw-data/db.sqlite3 contém quase todo o estado. attachments/ contém os ficheiros enviados, um por UUID, e é a única classe importante de dados que não fica em tabelas da base de dados. sends/ contém anexos do Send e destina-se a ser temporário. icon_cache/ pode ser eliminado sem consequências. rsa_key.pem e os ficheiros associados assinam os JWTs (JSON Web Tokens) dos utilizadores com sessão iniciada. Por isso, uma cópia dessa chave privada pode ser usada para falsificar uma sessão de acesso ao cofre. config.json só existe depois de ativar a página de administração. O projeto é explícito quanto a isto: contém o token de administração e as credenciais SMTP em texto simples.
Na prática, o modelo de ameaças centra-se no acesso ao sistema de ficheiros, não na criptografia da rede. O acesso de leitura a esse diretório fornece o endereço de e-mail de cada utilizador, os respetivos segredos de 2FA de início de sessão, uma chave que permite falsificar sessões e uma cópia offline de todos os cofres, que pode ser atacada sem pressa. Cada passo abaixo existe para impedir o acesso a esse diretório.
Corrija primeiro o token de administrador
/admin é um painel de controlo completo: lista de utilizadores, convites, eliminação e todas as definições de runtime. Está protegido apenas por um segredo partilhado. Não existe nome de utilizador nem autenticação de dois fatores por utilizador.
Os guias mais antigos indicam gerar ADMIN_TOKEN com openssl rand -base64 48. Isso funciona, mas grava o segredo em texto simples em config.json e no seu ficheiro Compose. O Vaultwarden também aceita uma string PHC do Argon2 (password hashing competition), pelo que o valor armazenado é um hash. Gere uma contra um contentor em execução:
docker exec -it vaultwarden /vaultwarden hashOu sem tocar no contentor em execução:
docker run --rm -it vaultwarden/server /vaultwarden hashÉ pedida uma palavra-passe duas vezes. Depois, o comando apresenta uma linha que começa por $argon2id$. Numa instalação bare metal, execute ./vaultwarden hash. Se preferir usar diretamente a CLI argon2, o projeto documenta os parâmetros mínimos da OWASP:
echo -n 'MySecretPassword' | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1Agora, o problema que pode consumir uma hora. Uma string PHC contém muitos caracteres $, e o Docker Compose trata $ como interpolação de variáveis. Se a colar sem escape num bloco environment:, o valor recebido pelo contentor fica alterado. Por isso, /admin rejeita um token que sabe estar correto. Existem duas formas seguras. Em docker-compose.yml, duplique cada $:
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$UUZxK1FZMkZoRHFQRlVrTXZvS0E3bHpNQW55c2dBN2NORzdsa0Nxd1JhND0$$cUoId+JBUsJutlG4rfDZayExfjq4TCt48aBc9qsc3UINum ficheiro .env, não é necessário fazer escape, mas use aspas simples:
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'Depois, limite a taxa de pedidos ao painel e reduza a duração da sessão:
ADMIN_RATELIMIT_SECONDS=300
ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_SESSION_LIFETIME=20Três tentativas inválidas em cinco minutos fazem com que o painel deixe de responder a esse cliente. A sessão de administrador expira após 20 minutos de inatividade.
Melhor do que qualquer uma destas opções: desative a página. A maioria das instâncias precisa dela uma vez, para configurar o SMTP e convidar os primeiros utilizadores, e nunca mais. Para a desativar, não defina ADMIN_TOKEN nem DISABLE_ADMIN_TOKEN, remova qualquer chave "admin_token" de config.json e recrie o contentor. É importante remover a chave do ficheiro porque a página de administração grava as definições nesse ficheiro, e o valor em config.json tem precedência sobre o ambiente. Remover apenas a variável deixa a página aberta.
Feche o registo antes que alguém encontre o domínio
SIGNUPS_ALLOWED=false
SIGNUPS_VERIFY=true
SHOW_PASSWORD_HINT=falseSIGNUPS_ALLOWED tem o valor predefinido true. Deixe-o assim e qualquer pessoa que aceda ao seu domínio poderá criar uma conta, ficando os respetivos dados no mesmo db.sqlite3 que os seus. Defina-o como false e adicione utilizadores através de convites enviados pela página de administração, o que requer um SMTP funcional. INVITATIONS_ALLOWED também tem o valor predefinido true e permite que os proprietários de organizações convidem outras pessoas. Isto é adequado quando confia nos seus utilizadores e deve ser definido como false numa instância para um único utilizador. Se apenas determinados domínios puderem criar contas, SIGNUPS_DOMAINS_WHITELIST=example.com é mais restritivo do que o registo aberto e muito menos seguro do que os convites.
SHOW_PASSWORD_HINT tem o valor predefinido false e deve permanecer assim. Quando está ativado, introduzir um endereço de e-mail válido no formulário de início de sessão devolve a dica da palavra-passe principal dessa conta. Isto expõe a dica e confirma que o endereço existe.
Se a sua instância teve o registo aberto durante algum tempo, abra a página de administração e consulte a lista de utilizadores antes de assumir que é a única conta existente.
A porta que não pretendia publicar
A imagem Docker escuta na porta 80 dentro do contentor. Uma instalação bare-metal usa ROCKET_PORT=8000 por predefinição. O comando de execução documentado publica-a desta forma:
--publish 127.0.0.1:8000:80O prefixo 127.0.0.1: é essencial. Se escrever -p 8000:80, o Docker associa 0.0.0.0 e faz isso escrevendo regras DNAT (tradução de endereços de rede de destino) na tabela nat. Essas regras são avaliadas antes das cadeias filter que o ufw gere. Por isso, ufw status indica que a porta está negada, enquanto a porta responde normalmente à Internet. Vale a pena ler o mecanismo completo no guia sobre portas do Docker que ignoram o ufw.
Verifique o que está realmente a escutar:
sudo ss -tlnp | grep 8000Um resultado correto contém uma única linha associada a 127.0.0.1:8000. Uma linha associada a 0.0.0.0:8000 significa que o vault está exposto diretamente. Corrija o mapeamento e recrie o contentor. A associação de uma porta fica definida quando o contentor é criado, e docker compose restart não a altera:
docker compose up -d --force-recreateHá ainda uma porta presente em guias antigos: 3012, a porta separada para WebSocket. O suporte para essa porta foi removido no Vaultwarden 1.31.0, porque o tráfego de notificações passou para a porta HTTP principal. WEBSOCKET_ENABLED e WEBSOCKET_PORT são ignorados desde a versão 1.29.0. A opção atual é ENABLE_WEBSOCKET, que usa true por predefinição. Se o firewall ou o ficheiro compose ainda abrir a porta 3012, feche-a.
Termine o TLS num reverse proxy, não no Rocket
O Vaultwarden pode servir TLS (transport layer security) diretamente através do Rocket, o seu framework web, mas o projeto recomenda que não faça isso em produção. O TLS integrado do Rocket não tem suporte rigoroso para SNI (server name indication). Por isso, as recomendações de hardening também indicam aceder à instância pelo nome de host e nunca por um endereço IP isolado. Os intervalos de IP públicos são analisados continuamente. Um vault que responde num endereço IP é um vault que será encontrado.
As partes de um server block do nginx que importam:
client_max_body_size 525M;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}O nginx define client_max_body_size como 1 MB por predefinição. Sem essa linha, o envio de um anexo falha com 413 Request Entity Too Large no log de erros do nginx, enquanto o Vaultwarden não regista nada. Os cabeçalhos Upgrade e Connection transportam o handshake WebSocket para /notifications/hub. Se os remover, o vault continua a funcionar, mas as alterações deixam de aparecer nos outros dispositivos até recarregar manualmente a página.
O Caddy requer menos configuração e obtém o certificado automaticamente:
vw.example.com {
reverse_proxy 127.0.0.1:8000 {
header_up X-Real-IP {remote_host}
}
}Depois, informe o Vaultwarden sobre esse URL:
DOMAIN=https://vw.example.com
IP_HEADER=X-Real-IPIP_HEADER já tem X-Real-IP como valor predefinido. Por isso, é necessário garantir que o proxy define efetivamente esse cabeçalho. Caso contrário, todas as linhas de log e todos os limites de tentativas de início de sessão veem 127.0.0.1, que é o próprio proxy. Isso faz com que as falhas de um atacante sejam contabilizadas para todos os utilizadores da instância. Defina também DOMAIN como o URL https real. O Vaultwarden usa esse valor para criar links de convite e de reposição de palavra-passe. As chaves de segurança WebAuthn também ficam associadas a essa origem.
Há um detalhe que é frequentemente ignorado: a ligação WebSocket transporta o token da sessão na query string, como /notifications/hub?access_token=[JWT]. Esse valor fica exposto no log de acesso do proxy. Redija o parâmetro access_token no formato do log ou garanta que esses logs não são enviados para nenhum local que não controle.
Bloquear ataques de força bruta no endpoint de login
Os limites de taxa estão ativados por predefinição (LOGIN_RATELIMIT_SECONDS=60, LOGIN_RATELIMIT_MAX_BURST=10). Tornam o ataque mais lento. Não o impedem. O fail2ban impede-o, mas o Vaultwarden tem primeiro de escrever num ficheiro de log, e isso não acontece por predefinição:
LOG_FILE=/data/vaultwarden.log
LOG_LEVEL=info
EXTENDED_LOGGING=trueUm login falhado produz então exatamente uma linha. Esta é a cadeia que o filtro tem de encontrar:
[2026-08-07 09:14:22][vaultwarden::api::identity][ERROR] Username or password is incorrect. Try again. IP: 203.0.113.10. Username: user@example.com.Escreva o filtro em /etc/fail2ban/filter.d/vaultwarden.local:
[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =E a jail em /etc/fail2ban/jail.d/vaultwarden.local:
[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
banaction = %(banaction_allports)s
logpath = /vw-data/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400Se manteve a página de administração, adicione uma segunda jail cujo failregex seja ^.*Invalid admin token\. IP: <ADDR>.*$, porque as falhas de administração são registadas com uma mensagem diferente e o filtro de login nunca as verá. Depois, verifique o resultado:
sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwardenUma jail funcional lista o seu ficheiro de log em File list e indica Currently failed: 0. Introduza uma palavra-passe errada três vezes a partir de outra rede. Esse contador deve aumentar e o endereço deve aparecer em Banned IP list. Se o contador nunca aumentar, a causa habitual é logpath: tem de ser o caminho do ficheiro no host, não o caminho /data/... dentro do contentor. A segunda causa habitual é a ausência de X-Real-IP, o que faz com que todos os bloqueios tenham como destino o seu próprio proxy. O restante procedimento, incluindo a jail SSH que já deveria estar a executar, está no guia do fail2ban para Ubuntu 24.04.
A senha mestra continua a ser todo o sistema
A encriptação do lado do cliente significa que a senha mestra é a chave. Uma senha mestra curta numa instância cuja base de dados foi copiada por um atacante não fica protegida por nada neste post, porque o atacante pode atacar essa cópia offline à velocidade permitida pelo próprio hardware. Nenhuma configuração do servidor chega à máquina do atacante.
PASSWORD_ITERATIONS=600000 pode ser facilmente interpretado de forma errada. Define o número de iterações do hash do lado do servidor aplicado ao hash de início de sessão enviado pelo cliente, e o modelo de configuração do projeto indica que uma alteração desse valor é aplicada aos utilizadores existentes no início de sessão seguinte. Não altera o KDF do lado do cliente, que é a configuração que determina o custo de um ataque offline ao cofre. Essa configuração pertence a cada conta: alguém que criou a conta há anos pode ainda usar uma configuração KDF antiga e mais baixa até a aumentar nas definições de segurança do cofre web, o que volta a encriptar a respetiva chave. Informe os utilizadores, porque o servidor não pode fazer isso por eles.
Depois, ative a autenticação de dois fatores para cada conta. Ela não protege o texto cifrado, porque a chave do cofre deriva apenas da senha mestra. Impede, no entanto, que uma senha roubada seja suficiente para iniciar sessão e sincronizar uma cópia. REQUIRE_DEVICE_EMAIL=true torna obrigatório o email de início de sessão num novo dispositivo: se o Vaultwarden não conseguir enviá-lo, o início de sessão falha, pelo que um início de sessão a partir de um dispositivo não reconhecido nunca fica sem aviso. É necessário ter o SMTP funcional, caso contrário todos os inícios de sessão em novos dispositivos serão recusados.
Backups são onde os vaults autoalojados falham
Uma tar czf da pasta de dados, deixada num diretório pessoal no mesmo VPS, anula todos os passos anteriores. Esse arquivo contém db.sqlite3 com o texto cifrado de todos os utilizadores, rsa_key.pem que falsifica sessões de início de sessão e config.json com o token de administrador e a palavra-passe SMTP em texto simples. O acesso de leitura a esse único ficheiro dá acesso de leitura ao vault.
Duas regras são suficientes. Retire o arquivo do servidor. Encripte-o antes de o transferir.
Também existe um problema de integridade. Copiar db.sqlite3 com cp enquanto o serviço está em execução pode produzir um ficheiro que está a ser escrito e que não abre. Só descobrirá o problema durante a restauração. Use o snapshot nativo do SQLite:
sqlite3 /vw-data/db.sqlite3 ".backup '/tmp/vw-db-backup.sqlite3'"A restauração, que é a parte que quase ninguém testa, está descrita em o guia de backup e restauração do Vaultwarden.
O que fica por sua conta em comparação com o Bitwarden alojado
Contabilidade honesta. O serviço alojado do Bitwarden é operado por pessoas cujo trabalho a tempo inteiro é mantê-lo em funcionamento, com auditorias de terceiros publicadas e alguém de prevenção às 3 da manhã. O self-hosting substitui isso pela sua própria cadência de aplicação de patches.
O Vaultwarden distribui correções de segurança como releases normais, e elas são frequentes. Verificado em 7 de outubro de 2026 no feed de releases do projeto, o release atual é o 1.37.4, publicado em 5 de outubro de 2026. As notas abrangem sete avisos de segurança. O mais grave tem classificação High, e as notas recomendam vivamente atualizar assim que possível. É o quarto release desde o 1.37.0, de 24 de julho de 2026: o 1.37.1 saiu em 29 de julho, o 1.37.2 em 22 de agosto (necessário para clientes da versão 2026.8.0 e posteriores) e o 1.37.3 em 13 de setembro. Este revoga os tokens de memorização de 2FA quando as credenciais ou o 2FA mudam e aplica limites de taxa aos endpoints de pedidos prelogin e auth. Se executar organizações com administradores em quem não confia totalmente, as notas do 1.37.4 também recomendam rodar a API key de cada organização depois da atualização. Uma instância configurada há um ano e esquecida está a executar código com um ano. A tag latest não resolve o problema por si só: um container em execução mantém a image com que foi iniciado até executar docker compose pull e recriá-lo. Configure atualizações automáticas no Ubuntu para os pacotes do host e coloque a atualização do container num lembrete de calendário que irá realmente ler.
A conclusão que um leitor honesto deve tirar é a seguinte: a criptografia é a do design do Bitwarden e continua sólida, mas o risco operacional passa inteiramente para si. Se aplicar patches e fizer backup noutro local, uma instância Vaultwarden num VPS que controla é um local razoável para guardar as suas palavras-passe. Se esses dois hábitos não forem acontecer, pague pelo serviço alojado e dedique a sua atenção a outra coisa. A comparação funcional está em Vaultwarden em comparação com Bitwarden self-hosted.
Proteja o host subjacente ao contentor
O Vaultwarden é um processo num sistema Linux, e o root desse sistema pode ler /vw-data, independentemente do que a aplicação estiver configurada para fazer. Execute o contentor com um utilizador sem privilégios usando user: "1000:1000" no seu ficheiro compose, atribua a propriedade da pasta de dados de forma compatível e monte como somente leitura, com :ro, tudo aquilo em que o contentor não escreve. Depois feche a porta de entrada: Proteção do SSH num VPS aborda o início de sessão apenas com chaves e a desativação da autenticação por palavra-passe. É isso que impede o ataque básico que ultrapassa todas as medidas anteriores.
FAQ
Alguém pode ler as minhas palavras-passe se roubar a base de dados do Vaultwarden?
Não diretamente. Cada item do cofre é cifrado no cliente com uma chave derivada da palavra-passe mestra, por isso db.sqlite3 contém texto cifrado. O que essa pessoa obtém imediatamente é o endereço de email de cada conta, as definições do KDF, os metadados de início de sessão e do dispositivo, e os segredos de autenticação de dois fatores na tabela twofactor, que são armazenados sem cifragem porque o servidor tem de calcular o código esperado. Também pode atacar o texto cifrado do cofre offline durante o tempo que quiser. Por isso, o comprimento da palavra-passe mestra é o fator que decide o resultado.
Devo usar ADMIN_TOKEN ou desativar completamente a página de administração?
Desative-a se puder. A maioria das instâncias precisa dela apenas uma vez para configurar o SMTP e convidar utilizadores. Depois disso, normalmente não volta a ser necessária. Para a desativar, não defina ADMIN_TOKEN nem DISABLE_ADMIN_TOKEN, remova qualquer chave "admin_token" de config.json e recrie o container. Remover apenas a variável de ambiente não é suficiente, porque as definições gravadas pela página de administração ficam em config.json e têm precedência. Se mantiver a página, armazene o token como um hash Argon2 produzido por vaultwarden hash, em vez de uma string aleatória em texto simples, e defina ADMIN_RATELIMIT_MAX_BURST=3.
O meu ADMIN_TOKEN está correto, mas /admin rejeita-o. Qual é o problema?
Quase sempre é a interpolação de $. Uma string PHC Argon2 contém vários caracteres $, e o Docker Compose expande-os como variáveis dentro de um bloco docker-compose.yml environment:. Assim, o container recebe um valor alterado, embora o ficheiro pareça correto. Duplique cada $ para $$ no ficheiro compose ou mova o valor para um ficheiro .env delimitado por aspas simples. Nesse caso, não é necessário fazer escaping. Recrie o container depois, porque as alterações de ambiente não são aplicadas por um simples restart.
Ainda preciso de abrir a porta 3012 para as notificações?
Não. O suporte para tráfego WebSocket na porta 3012 foi removido no Vaultwarden 1.31.0 porque as notificações passaram para a porta HTTP principal. Além disso, WEBSOCKET_ENABLED e WEBSOCKET_PORT são ignorados desde a versão 1.29.0. A definição atual é ENABLE_WEBSOCKET, que é true por predefinição. Feche a porta 3012 na firewall e remova-a do ficheiro compose. Depois, confirme que o reverse proxy encaminha os cabeçalhos Upgrade e Connection, porque a sincronização em tempo real depende agora deles.