Segurança do provedor¶
Para: Dev · Adm — Conferido em 2026-10-31.
Um provedor de liquidação é o único processo do Dracma que fala com um terceiro que move dinheiro e que recebe, por chamada, a credencial do tenant em claro. Por isso a fronteira dele é desenhada como a de um componente comprometível: se ele cair nas mãos de um atacante, o estrago precisa parar nele. Esta página reúne o que o desenvolvedor do provedor e o administrador do host precisam cumprir.
O que é¶
O conjunto de regras de segurança do SPP v1 (decisões S2, S3, S9, S12 e S13 do contrato) aplicado ao dia a dia:
- isolamento do processo (usuário Linux próprio, unit systemd endurecida);
- rede mínima (escuta só em loopback, saída só para os hosts do descriptor);
- autenticação do único chamador permitido (o executor), com HMAC e anti-replay;
- credencial por chamada, sem guarda de segredo de longa duração;
- PII mínima e logs limpos;
- guarda de ambiente e nenhum efeito externo real fora de
prd; - cadeia de suprimentos e auditoria.
Quando usar¶
- Antes de escrever a primeira rota de um provedor novo (desenho).
- No code review de qualquer provedor, nosso ou de terceiro.
- Ao preparar o host (ops) e antes do
enableemhmlouprd. - Na revisão periódica de provedores plugados: use o checklist no final.
Como funciona¶
Fronteiras de confiança:
| Fronteira | Quem confia em quem |
|---|---|
| executor para provedor | O provedor só aceita o executor (HMAC v2, Dracma-Caller: executor). O executor trata resposta do provedor como dado não confiável de negócio (leitor tolerante, unrecognized). |
| provedor para parceiro | O parceiro é um terceiro. O provedor só sai para egress_hosts, sem repassar traceparent, e trata toda resposta dele como entrada não confiável. |
| parceiro para ingress | Callback é afirmação, nunca prova. Só o fetch-back confirma. |
| provedor para o resto do host | Nenhum acesso: sem banco, sem cofre, sem KMS, sem outro serviço. |
Isolamento¶
Usuário Linux e unit systemd¶
Cada provedor roda com usuário Linux próprio, sem shell, sem home e sem grupo compartilhado, em uma unit systemd endurecida. Quem cria usuário, unit e porta é o ops; nunca à mão no servidor. Exemplo completo de unit para o provedor maximaxpay (caminhos e nomes são ilustrativos; o ops gera o equivalente):
[Unit]
Description=Dracma PSP maximaxpay (SPP v1)
Documentation=https://developers.dracma.asia/provedores
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=dracma_psp_maximaxpay
Group=dracma_psp_maximaxpay
ExecStart=/dracma/dracma_psp_maximaxpay/bin/dracma_psp_maximaxpay
WorkingDirectory=/dracma/dracma_psp_maximaxpay
EnvironmentFile=/etc/dracma/psp_maximaxpay.env
Restart=on-failure
RestartSec=2s
TimeoutStopSec=20s
# Estado minimo (repositorio de idempotencia sem segredo), unico caminho gravavel
StateDirectory=dracma_psp_maximaxpay
StateDirectoryMode=0700
UMask=0077
# Privilegios
NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=
RestrictSUIDSGID=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictNamespaces=yes
MemoryDenyWriteExecute=yes
RemoveIPC=yes
# Sistema de arquivos
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
ProtectProc=invisible
ProcSubset=pid
# Rede e syscalls
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources @mount @debug
# Limites
MemoryMax=512M
TasksMax=256
LimitNOFILE=4096
LimitCORE=0
[Install]
WantedBy=multi-user.target
Pontos que não podem faltar:
NoNewPrivileges,CapabilityBoundingSet=vazio,ProtectSystem=strict(sistema de arquivos somente leitura excetoStateDirectory),ProtectHome,PrivateTmp,RestrictAddressFamilies.MemoryDenyWriteExecute=yesquando a linguagem permitir (Rust, Go e Zig permitem; runtimes com JIT não). Se não puder ligar, registre o motivo no ADR do provedor.LimitCORE=0: dump de memória é um vazamento de credencial em claro.- O arquivo de ambiente contém só o par HMAC com o executor (
INTERNAL_HMAC_<PROVEDOR>_EXECUTOR), a porta e a configuração de telemetria. Nunca a credencial de nenhum tenant e nunca o par HMAC de outro provedor. Modo0400, donoroot(o systemd lê como root). - Em container (quando houver): usuário não-root, sistema de arquivos de raiz somente leitura, imagem por digest.
Verificação do isolamento¶
O ops confere e a certificação exige. Teste manual no host:
systemd-analyze security dracma_psp_maximaxpay.service
ps -o user,pid,cmd -C dracma_psp_maximaxpay
ss -ltnp | grep 7101
O esperado: nota de exposição baixa, processo com o usuário do provedor e escuta somente em 127.0.0.1:7101 (nunca 0.0.0.0 nem ::).
Rede: loopback e egress por UID¶
Loopback¶
- O provedor escuta só em
127.0.0.1:<porta>. Uma porta por provedor, atribuída pelo ops. - Não há TLS em loopback; o isolamento é o do host (usuário próprio por serviço). Por isso o corpo carrega o hash no HMAC: a credencial no corpo é protegida contra adulteração local.
- Provedor remoto (fora do host) não existe na v1.
Egress por UID derivado de egress_hosts¶
O ops lê egress_hosts do descriptor e gera uma regra de saída por UID do provedor: apenas esses host:porta, mais DNS e telemetria locais (loopback). Todo o resto é descartado e alerta. O ops gera e confere; ninguém edita regra à mão.
Exemplo do que o ops gera (nftables; o endereço é de documentação):
table inet dracma_psp_egress {
set maximaxpay_hosts {
type ipv4_addr
elements = { 203.0.113.10 }
}
chain output {
type filter hook output priority 0; policy accept;
meta skuid "dracma_psp_maximaxpay" oifname "lo" accept
meta skuid "dracma_psp_maximaxpay" ip daddr @maximaxpay_hosts tcp dport 443 accept
meta skuid "dracma_psp_maximaxpay" log prefix "psp-egress-drop " counter drop
}
}
Regras:
| Regra | Motivo |
|---|---|
A lista vem só de egress_hosts (todos os environments, não apenas o ativo) |
Fonte única; trocar de parceiro muda o descriptor, não regra solta. |
| Porta explícita em cada item | Evita liberar o host inteiro. |
| Resolução de nomes feita pelo ops e reaplicada em intervalo | Parceiro troca de IP; a regra precisa acompanhar sem abrir um domínio inteiro. |
| Pacote descartado gera log e alerta | Saída bloqueada é sinal de bug, de configuração errada ou de comprometimento. |
| Sem proxy e sem redirecionamento HTTP para outro host | Um 302 do parceiro (ou de um atacante que o represente) não pode levar a credencial para outro lugar. |
Mudou egress_hosts ou environments? É mudança incompatível de descriptor: o provedor volta a registered até o ops reaplicar o egress. Veja Descriptor.
HMAC e replay¶
Toda rota do provedor (inclusive GET /descriptor e GET /health) exige o HMAC interno v2 do ADR-0015. Cabeçalhos:
| Cabeçalho | Conteúdo |
|---|---|
Dracma-Caller |
Deve ser executor. Qualquer outro chamador autenticado recebe 403 caller_not_allowed (e alerta). |
Dracma-Internal-Timestamp |
Segundos Unix, forma canônica (sem sinal, sem zero à esquerda). |
Dracma-Internal-Nonce |
32 hexadecimais minúsculos, CSPRNG, único por chamada. |
Dracma-Internal-Signature |
v2=<hex> onde hex = HMAC-SHA256(segredo, canônico). |
Texto canônico (separado por \n, método em maiúsculas, path completo com query):
v2
<timestamp>
<nonce>
<METODO>
<path?query>
<sha256 hexadecimal do corpo>
Exemplo de assinatura para teste (segredo obviamente falso):
SECRET='whsec_test_exemplo-nao-usar'
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY='{"deposit_id":"00000000-0000-0000-0000-000000000000"}'
HASH=$(printf '%s' "$BODY" | sha256sum | cut -d' ' -f1)
CANON=$(printf 'v2\n%s\n%s\n%s\n%s\n%s' "$TS" "$NONCE" POST /spp/v1/charges/get "$HASH")
SIG=$(printf '%s' "$CANON" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
echo "Dracma-Internal-Signature: v2=$SIG"
Regras de implementação:
- Comparação em tempo constante da assinatura (
verify_sliceem Rust,hmac.Equalem Go,CryptographicOperations.FixedTimeEqualsem C#). Nunca==em string. - Janela de ±300 s no timestamp; fora dela,
timestamp_out_of_window. - Nonce visto por 600 s: o provedor guarda os nonces recebidos por 600 s e recusa repetição com
replayed_nonce. Memória basta; reinício apaga o cache e a janela do timestamp limita o dano. Registre o nonce depois que a assinatura confere (quem não tem o segredo não queima nonce alheio). - Ordem de recusas (adendo P051 do ADR-0015):
missing_signature,unknown_caller,unsupported_signature_version,timestamp_out_of_window,invalid_nonce,invalid_signature,replayed_nonce,caller_not_allowed. Respostas401(autenticação) e403(chamador), comeffect: none. - Segredo por provedor: o par
INTERNAL_HMAC_<PROVEDOR>_EXECUTORé único por provedor. Comprometer o de um não assina como outro. Rotacionar não toca credencial de tenant. - Deny-default por chamador: cada rota do OpenAPI tem
x-callers: [executor]. Rota nova sem declaração de chamador não existe. - Teste de matriz de acesso no CI do provedor: anônimo, chamador errado, assinatura adulterada, nonce repetido, timestamp fora da janela, para cada rota (número de testes igual ao número de rotas do OpenAPI).
Replay do callback do parceiro¶
O HMAC acima protege o canal executor-provedor. O callback do parceiro tem a sua própria defesa, escolhida pelo esquema do parceiro:
| Parceiro | Assinatura | Anti-replay |
|---|---|---|
| MaximaxPay | x-signature: sha256=<hex> sobre o corpo cru |
Sem timestamp assinado. O callback só dispara fetch-back; repetir o mesmo callback não duplica efeito (idempotência por chave). |
| Mercado Pago | x-signature: ts=<ts>,v1=<hex> sobre o manifesto |
ts assinado, janela de 600 s no adaptador (aceita segundos ou milissegundos). data.id é obrigatório e só aceita [A-Za-z0-9_-] até 64 caracteres. |
Regras comuns: verificar sobre os bytes crus de body_base64 (nunca reserializar o JSON), em tempo constante, e não devolver o motivo fino da falha (422 callback_unauthenticated apenas). O parceiro que não assina tem callbacks.signature: null e callbacks: false.
Credencial por chamada¶
O executor guarda a credencial cifrada, decifra na hora do uso e a envia no corpo da chamada, em loopback. O provedor a usa e a descarta. Não existe segredo de tenant de longa duração no provedor. A alternativa de o provedor buscar a credencial no cofre foi rejeitada: ela daria ao provedor acesso ao KMS e a todos os tenants.
O que o provedor PODE fazer¶
| Pode | Condição |
|---|---|
Ler credential do corpo e usá-la para chamar o parceiro declarado |
Somente os hosts de egress_hosts. |
| Derivar um token de curta duração (por exemplo OAuth) e guardá-lo em memória | Chave (connection_id, sha256(credential)); TTL menor ou igual ao menor valor entre a expiração do token e 10 minutos; nunca em disco. |
Validar credential contra o credential_schema |
Campo desconhecido gera 422 misconfigured com field. |
| Guardar o par HMAC com o executor | É o único segredo permanente. |
Guardar connection_id como escopo de cache e circuito |
Não é segredo. |
O que o provedor NÃO PODE fazer¶
| Não pode | Por quê |
|---|---|
Persistir credential (disco, banco, arquivo temporário) |
Comprometer o provedor expõe só as chamadas em voo, nunca um estoque. |
Escrever credential em log, trace, métrica, mensagem de erro ou detail do problema |
Vazamento por telemetria é o caminho mais comum. |
| Devolvê-la em resposta | A resposta é registrada pelo executor. |
| Enviá-la a qualquer terceiro que não seja o parceiro declarado | Exfiltração. |
| Colocá-la em URL, query ou cabeçalho do canal SPP | O corpo entra no hash do HMAC; URL e cabeçalho vazam em log de proxy. (Para o parceiro, siga o que o parceiro exige, como x-client-secret ou Authorization: Bearer.) |
| Reutilizar um token derivado depois que o hash da credencial mudou | Rotação vale na próxima chamada; hash diferente significa cache diferente. |
| Cachear em disco, banco ou compartilhado entre processos | O cache é local, em memória, por processo. |
Imprimir a credencial em Debug ou toString |
Redija. |
Zeroize e redação¶
- Zerar buffers da credencial assim que a chamada ao parceiro termina (
zeroizeem Rust,Array.ClearouCryptographicOperations.ZeroMemoryem C#, equivalente na sua linguagem). - Redigir em
DebugetoString: a representação de texto de qualquer tipo que contém segredo imprime***. - Tipos de segredo não implementam serialização para saída; só para entrada.
O adaptador da MaximaxPay faz assim (Rust), e é o modelo a seguir:
#[derive(Deserialize, Zeroize, ZeroizeOnDrop)]
pub struct MaximaxCredentials {
pub client_id: String,
pub client_secret: String,
pub webhook_secret: String,
}
impl std::fmt::Debug for MaximaxCredentials {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("MaximaxCredentials(***)")
}
}
Conexão desconhecida e credencial isca
O executor chama callbacks/verify com uma credencial aleatória do formato do schema quando a conexão não existe. O provedor não pode tentar distinguir isca de credencial real (nem por tempo de resposta, nem por log diferente). Se a isca "autenticar", o executor registra erro grave e recusa mesmo assim.
PII mínima e logs¶
O provedor recebe só o que o parceiro exige.
| Dado | Recebe? | Observação |
|---|---|---|
| Nome e documento do pagador (cobrança) ou titular (saque) | Sim, quando o parceiro exige | Só dígitos no documento; descarte após a chamada. |
IPv4 do jogador (owner.ip) |
Sim, no saque | Obrigatório (C198). |
| Chave PIX do destino | Sim, no saque | Nunca em log. |
tenant_id, e-mail de operador, saldo do ledger |
Não | O provedor não conhece tenant nem ledger. |
connection_id, operation_id |
Sim | Correlação e escopo de cache; não são segredo. |
Documento completo em keys/lookup |
Devolve, não registra | O chamador compara e descarta; devolva também a versão mascarada. |
Logs, traces e métricas:
- Formato JSON, com
trace_id,span_id,connection_id,provider,op,outcome. - Nunca: credencial, corpo devolvido pelo parceiro, documento, chave PIX, nome, IP do jogador.
- Rótulos de métrica sem
tenantnemconnection_id(cardinalidade e privacidade): useopeoutcome. - O
traceparentdo executor continua o trace no provedor, mas não é repassado ao parceiro. - Mensagem de erro (
detaildo problema) sem corpo do parceiro, sem credencial, sem PII. - Não use
statusdo parceiro livre como motivo:reasonde saque falho é curto,[a-z0-9_]{1,40}.
Exemplo de linha de log aceitável:
{
"ts": "2026-10-31T12:00:00Z",
"level": "warn",
"service": "dracma_psp_maximaxpay",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"connection_id": "11111111-1111-4111-8111-111111111111",
"provider": "maximaxpay",
"op": "payouts.create",
"outcome": "upstream_timeout",
"upstream_status": null
}
Anti-SSRF de base_url¶
config.base_url é entrada do tenant (ou do operador). Sem controle, vira um pedido do host para onde o atacante quiser, com credencial anexada. Regras:
- O host de
base_urldeve estar emegress_hostsdo descriptor (comparação exata do host e da porta; sem curinga). Fora disso:422 misconfiguredcomfield: base_url. - Esquema
https. Semhttp, semfile, sem credenciais embutidas (usuario@host) na URL. - Rejeite IP literal e nomes que resolvam para loopback, link-local, rede privada ou metadados de nuvem. Resolva o nome e confira o IP na hora da conexão (a conexão usa o IP validado, para não haver troca entre verificação e uso).
- Sem redirecionamento HTTP: o cliente não segue
3xx. Uma resposta de redirecionamento é erroupstream_unavailableouunrecognized_response. - O egress por UID é a segunda camada: mesmo com bug na validação, o pacote para host fora da lista é descartado.
- Fora de
prd, a URL usada é a deenvironments.sandbox; alivesó na exceção auditada. Veja a tabela da guarda.
Dependências e SBOM¶
- Toda dependência nova tem nome, licença e versão verificados (typosquatting e licença incompatível são bloqueios).
- Versões fixadas (lockfile commitado) e instalação reproduzível.
- SBOM gerada no CI a cada build e guardada com o artefato.
- Scan de vulnerabilidades no CI; achado crítico bloqueia a promoção.
- Imagem (quando houver) por digest, assinada, não-root, somente leitura.
- Sem dependência de domínio compartilhada entre provedores (nada de biblioteca
commons): cada provedor é um serviço independente. - Binário promovido a partir do git (commit SHA); nunca compilado ou editado no servidor.
Provedor de terceiro: modelo de ameaça¶
Provedor de terceiro roda na mesma fronteira que o nosso, sem privilégio extra. A certificação é requisito, não confiança. Ele nunca recebe credencial de outro provedor, acesso ao cofre ou rede interna.
Resumo STRIDE:
| Categoria | Ameaça | Vetor típico | Mitigação |
|---|---|---|---|
| Spoofing | Alguém se passa pelo executor | Chamada local forjada, par HMAC vazado | HMAC v2 com nonce e janela; x-callers deny-default; segredo por provedor; usuário próprio e loopback |
| Spoofing | Alguém se passa pelo parceiro no callback | POST forjado no ingress | Assinatura do parceiro sobre corpo cru, tempo constante, IPs de origem, fetch-back antes de qualquer efeito |
| Tampering | Adulteração do pedido em trânsito local | Processo local lê e altera | Hash do corpo no HMAC; usuário e namespace separados |
| Tampering | Adulteração do resultado do saque | Provedor mente settled |
Executor confirma por consulta; leitor tolerante (unrecognized nunca move dinheiro); certificação |
| Repudiation | Negar uma operação | Falta de rastro | trace_id fim a fim; auditoria de register, enable, drain, disable, unplug, certify com descriptor_sha256 |
| Information disclosure | Vazamento de credencial ou PII | Log, métrica, erro, dump de memória, rede | Credencial só em memória e zerada; LimitCORE=0; logs sem PII; egress por UID; sem traceparent ao parceiro |
| Information disclosure | Oráculo de existência de conexão | Diferença de resposta no callback | Credencial isca; 401 uniforme; sem motivo fino |
| Denial of service | Provedor lento ou fora derruba o núcleo | Parceiro caindo, flood | Processo independente (piso 10); breaker no executor; 429 provider_busy; limites de corpo (1 MiB); Spp-Timeout-Ms |
| Denial of service | Estouro de memória por corpo gigante | Callback ou body_base64 enorme |
Teto de 1 MiB, decodificação com limite, schema estrito |
| Elevation of privilege | Provedor comprometido alcança outros serviços | Escalada local | Usuário sem shell, NoNewPrivileges, capacidades vazias, ProtectSystem=strict, sem rede interna, sem banco e sem KMS |
| Elevation of privilege | SSRF via base_url |
Config maliciosa | Host em egress_hosts, sem redirecionamento, egress por UID |
| Supply chain | Dependência ou binário malicioso | Pacote envenenado, imagem alterada | Versões fixadas, SBOM, scan, digest, certificação e promoção só por SHA |
Específico de terceiro:
- O código do terceiro passa por revisão e pela suíte de conformidade antes de
enableemhml/prd. - Credencial de tenant só chega ao provedor do seu tipo. Um provedor de terceiro nunca vê credencial de MaximaxPay ou Mercado Pago.
- Terceiro não tem rota própria de entrada para o mundo: callbacks passam pelo ingress e pelo executor.
- Desplugar um terceiro não deixa segredo para trás (a credencial fica no cofre do executor, cifrada); o ciclo
draining,disabled,unpluggedpreserva o dado financeiro.
Guarda de ambiente e efeito externo¶
Regra de ouro (piso 18): fora de prd, nada real chega a ninguém. A guarda é dupla: o executor aplica pelo descriptor e o provedor reaplica como defesa em profundidade.
| Situação | Resposta do provedor |
|---|---|
mode: live com runtime_env diferente de prd, sem environment_policy permitir e sem context.live_exception: true |
403 environment_forbidden, effect: none |
mode: sandbox com runtime_env == prd |
403 environment_forbidden, effect: none |
Credencial de teste em live, ou credencial de produção em sandbox (via credential_mode) |
403 environment_forbidden |
synthetic: true em prd |
O executor recusa; o próprio provedor sintético recusa também |
runtime_env ausente |
Tratado como não-prd (fail-closed) |
Regras para o desenvolvedor:
- Fora de
prd, o provedor opera só contra sandbox ou fake local. Nunca contra a URLlive. - A exceção "real em
hml" (environment_policy.live_outside_prd = flag,PSP_REAL_OUTSIDE_PRDcom oname, ambientehml, operaçãoproduction) é do dono, gravada só pelo ops, com auditoria. Código de provedor não tem atalho. - E-mail, SMS e webhook do parceiro nunca saem com destinatário real fora de
prd. Fixtures usam apenas domínios de teste (.invalid,.example,.test) e endereços sintéticos. - Chave sandbox em não-
prd; a chave deprdnunca existe emdevouhml. O egress SMTP é bloqueado nesses ambientes. - Cobrança, saque e transferência real em teste automatizado são vetados. Teste do provedor roda contra servidor HTTP fake local.
- A certificação prova a guarda (casos de ambiente e modo cruzados).
Auditoria¶
| Evento | Gravado |
|---|---|
register, enable, drain, disable, unplug |
Quem, quando, motivo, descriptor_sha256, trace_id |
certify |
Resultado da certificação (descriptor_sha256, versão da suíte, aprovados e total, hash do relatório) |
unplug com force |
Motivo obrigatório (mínimo de 10 caracteres), break-glass auditado |
Escrita de PSP_REAL_OUTSIDE_PRD |
Mesma auditoria da ADR-0026, só pelo ops |
| Chamada com HMAC inválido, chamador errado, replay | Log de segurança e alerta |
| Decisão de guarda de ambiente (recusa) | Log com trace_id, sem credencial |
No provedor: registre outcome, op, provider, connection_id e trace_id por chamada, e as métricas spp_upstream_requests_total{op,outcome}, spp_upstream_duration_seconds{op}, spp_idempotent_replays_total{op} e spp_callbacks_verified_total{outcome}. Dashboards e alertas (provedor down, breaker aberto, unrecognized_response, dreno parado) são versionados como código.
Checklist de segurança do provedor¶
Marque cada item antes de pedir enable. Item não marcado bloqueia a promoção.
Isolamento e host
- [ ] Roda com usuário Linux próprio, sem shell e sem home.
- [ ] Unit systemd com
NoNewPrivileges,CapabilityBoundingSet=vazio,ProtectSystem=strict,ProtectHome,PrivateTmp,RestrictAddressFamilies,LimitCORE=0. - [ ]
MemoryDenyWriteExecuteligado (ou motivo registrado no ADR). - [ ] Único caminho gravável é o
StateDirectory, sem segredo dentro. - [ ] Arquivo de ambiente
0400, só com o par HMAC do próprio provedor. - [ ] Container (se houver): não-root, raiz somente leitura, imagem por digest.
Rede
- [ ] Escuta só em
127.0.0.1:<porta>(confirmado comss). - [ ] Egress por UID gerado pelo ops a partir de
egress_hosts; pacote fora da lista é descartado e alerta. - [ ]
egress_hostscobre todos osenvironmentse temhost:porta. - [ ] Cliente HTTP sem proxy e sem seguir redirecionamento.
- [ ]
base_urlfora deegress_hostsé recusada (422 misconfigured).
Autenticação
- [ ] HMAC v2 em todas as rotas, com a ordem de recusas P051.
- [ ] Comparação de assinatura em tempo constante.
- [ ] Janela de ±300 s e nonce guardado por 600 s.
- [ ] Só
Dracma-Caller: executor; outro chamador recebe403 caller_not_allowed. - [ ] Teste de matriz de acesso no CI cobre todas as rotas do OpenAPI.
- [ ] Segredo HMAC exclusivo deste provedor.
Credencial e dados
- [ ] Nenhuma credencial de tenant em disco, log, trace, métrica, erro ou resposta.
- [ ] Buffers da credencial zerados e tipos de segredo redigidos em
DebugetoString. - [ ] Cache só de token derivado, em memória, chave
(connection_id, sha256(credential)), TTL no máximo 10 minutos. - [ ] Corpo, documento, chave PIX, nome e IP do jogador fora dos logs.
- [ ] Rótulos de métrica sem tenant e sem
connection_id. - [ ]
traceparentnão é repassado ao parceiro.
Protocolo
- [ ] Schemas estritos nos pedidos (
additionalProperties: false) e teto de corpo de 1 MiB. - [ ] Falha ambígua nunca vira definitiva (
unknown) e recusa definitiva nunca vira ambígua. - [ ] Idempotência efetiva em
chargesepayouts(replay comSpp-Replayed: true,409em corpo diferente). - [ ] Saque nunca é reenviado;
payouts/getresponde para toda chave aceita. - [ ] Status desconhecido do parceiro vira
unrecognized, nuncasettled; saldo desconhecido nunca vira zero. - [ ] Callback verificado sobre bytes crus, em tempo constante, sem motivo fino, com isca indistinguível.
- [ ] Capacidade
truefunciona efalseresponde501 unsupported.
Ambiente e efeito externo
- [ ]
403 environment_forbiddenpara modo e ambiente cruzados. - [ ] Fora de
prd, só sandbox ou fake; nenhuma chamada à URLlive. - [ ] Nenhum destinatário real em fixture, seed ou teste (somente
.invalid,.example,.test). - [ ] Testes contra servidor fake local, nunca contra o parceiro real.
Cadeia de suprimentos e operação
- [ ] Dependências com nome, licença e versão verificados; lockfile commitado.
- [ ] SBOM gerada no CI e guardada com o artefato; scan sem achado crítico.
- [ ] Artefato promovido do git por commit SHA; nada editado no servidor.
- [ ] Suíte de conformidade aprovada para o
descriptor_sha256atual. - [ ] Auditoria de register, enable, drain, disable, unplug e certify funcionando.
- [ ] Dashboards e alertas versionados no Helm chart.
- [ ] Observabilidade:
service.nameigual adracma_psp_<nome>e métricasspp_*publicadas.
Erros comuns¶
| Erro | Consequência | Correção |
|---|---|---|
| Logar o corpo do parceiro "para depurar" | Documento, nome e chave PIX em log | Logue só outcome, upstream_status e trace_id |
Cachear o token OAuth pela connection_id apenas |
Credencial trocada continua usando o token antigo | Inclua sha256(credential) na chave |
Comparar assinatura com == |
Ataque de tempo | Função de tempo constante |
| Reserializar o JSON antes de verificar o callback | Assinatura nunca confere (ou confere em corpo adulterado) | Verifique os bytes de body_base64 |
Escutar em 0.0.0.0 "para testar" |
Provedor exposto à rede | Só 127.0.0.1 |
| Seguir redirecionamento do parceiro | Credencial enviada a outro host | Desligue o seguimento de redirecionamento |
| Liberar o domínio inteiro no egress | Exfiltração fácil | host:porta exatos, por UID |
Tratar timeout do saque como falha definitiva |
Saque duplicado depois | upstream_timeout com effect: unknown e consulta |
| Reutilizar o segredo HMAC entre provedores | Um comprometido assina como todos | Um par por provedor |
Usar chave real em hml sem a exceção auditada |
Efeito real fora de prd |
Sandbox ou fake; exceção só pelo ops |
Próximos passos¶
- Referência do documento que declara tudo isso: Descriptor do provedor.
- Conclua o checklist, rode a suíte de conformidade e só então peça o
enableao ops.