Controle — assina (sempre privada)
Produz assinaturas com a sua chave. Quem a alcança pode agir como o seu servidor. Fica em loopback, sempre.
O Agent segura os segredos mais sensíveis da sua integração: a chave privada com que ele assina em nome do seu servidor e a API key de serviço. Um Agent mal isolado é uma porta aberta para a sua conta Lootfy. Esta página reúne o que você precisa garantir para rodá-lo com segurança — parte disso o Agent já força por conta própria, parte é responsabilidade sua na host.
Este é o ponto mais importante da página. A porta de controle (127.0.0.1:8899 por padrão) assina sob demanda e não tem autenticação. Quem alcança essa porta consegue fazer o Agent assinar qualquer operação com a sua chave — ou seja, detém toda a autoridade da sua conta Lootfy. Nenhuma checagem posterior recupera isso.
A defesa dessa porta é uma só: o isolamento de loopback. Ela ouve apenas em 127.0.0.1, então só um processo já dentro da sua máquina a alcança. Uma senha ali seria pior que inútil — daria a impressão de proteger algo que só o isolamento de rede protege, e não protege.
Para tornar esse erro difícil de cometer, o Agent recusa subir quando LOOTFY_CONTROL_HOST é um bind que expõe a porta para fora da máquina — os valores rejeitados no boot são 0.0.0.0, ::, * e o valor vazio. Ele falha em vez de avisar e continuar, porque um aviso em log é lido depois do incidente, não antes. O Dockerfile reforça a mesma disciplina: não há EXPOSE de propósito, para que publicar a porta exija um esforço explícito e consciente.
A porta de controle assina; a porta de webhook verifica. Essa separação é o que permite que uma seja estritamente privada e a outra possa ser pública.
Controle — assina (sempre privada)
Produz assinaturas com a sua chave. Quem a alcança pode agir como o seu servidor. Fica em loopback, sempre.
Webhook — verifica (pode ser pública)
Só confere a assinatura da plataforma nos eventos de entrada e nunca assina nada. Não tem nada a entregar a quem a alcança, então pode ficar atrás de um proxy público — desde que com TLS.
O material de assinatura sai da API da Lootfy uma vez e nunca mais: a chave privada é gerada no provisionamento, entregue naquele instante, e a plataforma não guarda cópia. Se você a perder, não há como recuperá-la — só rotacionar. Trate-a como o segredo mais valioso da integração.
Prefira o arquivo à variável inline. Aponte LOOTFY_JWS_PRIVATE_KEY_FILE para um PEM em disco, com permissões restritas. Um PEM em variável de ambiente (LOOTFY_JWS_PRIVATE_KEY) vaza em ps, em dump de container e em painel de orquestrador.
Rode o Agent como usuário sem privilégio. A imagem Docker roda como o usuário node, nunca como root — porque um bug de leitura de arquivo em um processo root lê a máquina inteira, e esse processo segura a sua chave. Se você roda a partir do código, faça o mesmo: um usuário dedicado, sem privilégio.
Restrinja o acesso ao arquivo de chave e ao de configuração. O Agent redige segredos no log, mas não controla o seu .env nem o seu PEM. Permissões de arquivo são a sua camada.
O Agent assina, e a plataforma verifica, sempre com RS256 — o algoritmo é fixo, e a verificação nunca lê o header do JWS para escolher o algoritmo. Isso fecha a confusão de algoritmo (alg-confusion) clássica, em que um atacante troca o alg do header por none, ou por um HMAC usando a chave pública como segredo, e passa pela verificação. Como o algoritmo esperado é constante dos dois lados, esse vetor simplesmente não existe.
A assinatura é um JWS compacto destacado (RFC 7515, Apêndice F): o corpo da requisição é o payload, e não uma cópia dele dentro da assinatura. O envelope vale por uma janela curta (exp − iat), o que limita por quanto tempo um envelope coletado numa máquina de jogador pode ser reusado.
O jti do envelope tem significados invertidos conforme o modo de autenticação da rota, e confundir os dois cria bugs de duplicação ou de recusa indevida.
| Modo | jti é | Retry |
|---|---|---|
service | nonce (anti-replay) | cada tentativa usa um jti novo |
signature | chave de idempotência de domínio | o retry reusa o mesmo jti |
Numa rota signature (criar transação e registrar anúncio), o retry legítimo e um replay malicioso são bit a bit idênticos: recusar o segundo puniria o primeiro. Por isso a plataforma trata o jti como chave de idempotência ali — a mesma oferta de compra reenviada devolve a mesma transação, nunca cria uma segunda. Numa rota service, ao contrário, cada tentativa precisa de um jti novo, ou seria recusada como replay. O Agent aplica a regra certa para cada operação automaticamente; do seu lado, o id de idempotência da interface A é o que garante que um retro do seu servidor reuse o resultado sem chamar a plataforma de novo.
Isolamento de rede
Porta de controle em loopback, nunca publicada. Servidor de jogo na mesma host, ou spool, ou túnel.
Segredos
Chave privada em arquivo com permissão restrita, não inline. Processo como usuário sem privilégio. .env protegido.
Webhook
HTTPS com TLS terminado por você à frente do Agent. Path casando com o webhookUrl registrado.
Taxa e abuso
Rate limiting do seu lado, já que o Agent ainda não o faz. Controle de quem pode alcançar a porta de controle.