Construa um Serviço de Importação de Arquivos Seguro
Aplique DARE de ponta a ponta no seu próprio repo para construir um serviço que importa arquivos de URLs fornecidas pelo usuário — blindado contra SSRF, com validação de conteúdo, processamento assíncrono em jobs e trilha de auditoria. Puxa o lab de SSRF e o playbook de jobs.
O que você vai construir
"Importar um arquivo por um link" é uma das funcionalidades mais comuns — e
mais perigosas — da web. O usuário cola uma URL (um CSV para importar, uma
imagem de avatar, um logo) e o seu servidor a busca. No instante em que o seu
servidor faz essa requisição, ele a faz de dentro da sua infraestrutura,
carregando a sua posição de rede e a sua confiança. Um fetch(file_url)
ingênuo é um Server-Side Request Forgery (SSRF) de manual: o atacante nunca
toca a sua rede interna diretamente — ele entrega uma URL ao seu servidor e
deixa que ele faça o alcance por ele.
Neste capstone você constrói um serviço de importação de arquivos real, de
ponta a ponta, no seu próprio repositório, aplicando o método DARE completo:
POST /imports { file_url } aceita um link, uma camada de safe-fetch recusa
tudo que é perigoso, um job em background baixa e valida o conteúdo de forma
assíncrona, o resultado é persistido no storage e todo evento relevante é
escrito numa trilha de auditoria. Isto não é um starter que você clona — é
um guia que você aplica. A stack, a linguagem e o framework são seus; o DARE dá
o formato.
O que você entrega
- Um endpoint HTTP (
POST /imports) que aceita umfile_url, enfileira o
trabalho e retorna imediatamente — nunca buscando inline na requisição web. - Um módulo de safe-fetch que impõe, nesta ordem: só HTTPS + validação de
URL, resolução DNS com bloqueio de privado/loopback/link-local (incluindo o
metadata da cloud169.254.169.254), revalidação de redirect a cada salto e
limites rígidos de tamanho, timeout e content-type. - Um worker/job que roda o safe-fetch, valida o conteúdo baixado, guarda o
resultado e registra o desfecho — com tratamento de falha e retries
idempotentes. - Uma trilha de auditoria (um registro
importsmais uma linha do tempo
import_events) que permite a um operador responder "quem importou o quê, de
onde, e o que aconteceu" sem vazar detalhe interno para quem chamou. - Uma suíte de testes provando que cada defesa falha fechado e que o caminho
feliz funciona.
Como o DARE guia você
Você vai percorrer as fases do método, cada uma como um milestone:
Design → o problema + modelo de ameaça (SSRF, arquivos maliciosos, exaustão)
Blueprint → o contrato do endpoint, as regras do validador SSRF, síncrono-vs-job,
o schema de auditoria, o storage
Tasks → decompor num DAG com dependências mínimas
Execute → (a) o fetch seguro contra SSRF, depois (b) o import assíncrono + auditoria
Harden → testes que provam cada bloqueio, mais o critério de submissão
Dois recursos companheiros alimentam este projeto diretamente. O lab "Proteja
um endpoint de download contra SSRF" é o exercício concept-first do pipeline
de safe-fetch que você vai construir no milestone 4. O playbook "Rode e
diagnostique jobs em background" é a sua referência para o worker, os retries
e a observabilidade de que você vai precisar no milestone 5. Mantenha os dois
abertos enquanto avança.
Isto é um guia, não código. Todo trecho é pseudocódigo ou um esboço de schema
— traduza para o seu cliente HTTP, resolvedor DNS, sistema de jobs e
ferramentas de teste. O objetivo é que o seu repo termine o projeto com um
serviço de importação funcional, testado, auditado e seguro contra SSRF.
Arquitetura
Arquitetura de referência
O serviço é um endpoint de escrita fino na frente de um pipeline assíncrono. A
requisição web faz quase nada: valida o formato da entrada, cria um registro
import num estado pending, enfileira um job e retorna um id. Todo o trabalho
arriscado — DNS, conectar, baixar, validar conteúdo — acontece no worker, fora
da thread da requisição.
flowchart TD
U[Cliente] -->|POST /imports file_url| API[Endpoint de import]
API -->|checa formato + cria registro| DB[(tabela imports)]
API -->|enfileira| Q[[Fila de jobs]]
API -->|202 + id do import| U
Q --> W[Worker de import]
W --> SF{Guarda safe-fetch}
SF -->|1 só https + validação de URL| SF
SF -->|2 resolve DNS + bloqueia privado/loopback/link-local| SF
SF -->|3 revalida redirect por salto max 2| SF
SF -->|4 limites de tamanho + timeout + content-type| SF
SF -->|rejeita| AUD[(auditoria import_events)]
SF -->|bytes ok| CV[Validador de conteúdo]
CV -->|inválido| AUD
CV -->|válido| ST[(Object storage)]
ST --> DB
W -->|cada transição| AUD
BLK[[metadata 169.254.169.254 / 10.x / 127.x / fe80::]]:::danger -.->|bloqueado no passo 2| SF
classDef danger fill:#3b0d0d,stroke:#b71c1c,color:#fff;
Componentes
-
Endpoint de import (
POST /imports). Aceita{ file_url }. Valida só o
formato da requisição (presente, é string, dentro de um limite de tamanho),
escreve uma linhaimport(status: pending), enfileira o job e retorna
202com o id do import. Ele não busca nada. Quem chamou consulta
GET /imports/:idpara o status. -
Guarda safe-fetch. O firewall de SSRF, aplicado em ordem estrita: (1) faça
o parse da URL e exija esquemahttps, rejeiteuserinfoe esquemas
não-HTTPS; (2) resolva o host via DNS e rejeite se qualquer IP resolvido
for privado, loopback ou link-local (bloqueie127.0.0.0/8,10.0.0.0/8,
172.16.0.0/12,192.168.0.0/16,169.254.0.0/16— que contém o metadata da
cloud169.254.169.254—0.0.0.0/8,::1/128,fc00::/7,fe80::/10);
(3) desligue os auto-redirects e revalide o host a cada salto, com limite
de ~2; (4) imponha um timeout total, uma contagem máxima de bytes (header
e stream) e uma allowlist de content-type. -
Validador de conteúdo. Além dos limites de transporte, confirme que os
bytes são o que você pediu: farejar o tipo real (magic bytes, não só o
header) e, para imports estruturados (CSV), limitar contagem de linhas/colunas
e rejeitar em falha de parse. -
Object storage. Os bytes validados vão para object storage (S3/GCS/local),
indexados pelo id do import. O banco guarda um ponteiro, não o blob. -
Trilha de auditoria. Duas tabelas.
importsé o agregado (uma linha por
requisição, status atual).import_eventsé a linha do tempo append-only
(uma linha por transição:queued,fetch_rejected,content_rejected,
stored,failed,retried). A auditoria registra por que algo foi
rejeitado para os operadores; a API retorna só um status genérico a quem
chamou.
Esboço do modelo de dados
imports
id, user_id, file_url, status(pending|processing|succeeded|failed),
content_type, byte_size, storage_key, attempts, created_at, updated_at
import_events
id, import_id (fk), kind, detail(jsonb), created_at
# kind ∈ {queued, fetch_started, fetch_rejected, content_rejected,
# stored, failed, retried}
# detail guarda o motivo no servidor (IP bloqueado, tamanho, tipo) — nunca retornado ao cliente
Trade-offs para decidir no Blueprint
-
Síncrono vs. job. Buscar inline na requisição é mais simples mas errado
aqui: trava as threads web (DoS) e entrega ao atacante um oráculo de timing e
erro para mapear sua rede interna a partir das diferenças de resposta. O
import precisa rodar num job em background; o endpoint retorna antes de a
busca começar. Este é o padrão inegociável deste projeto. -
Onde validar. Divida: o endpoint valida só o formato (barato, síncrono,
rejeita lixo óbvio rápido). O worker valida destino e conteúdo (DNS, faixas
de IP, bytes) — as checagens caras e críticas de segurança que não podem rodar
na thread da requisição. -
Allowlist vs. denylist. Para esquemas e content-types, use
allowlist (sóhttps; os MIME types exatos que você aceita) — uma denylist
que você precisa manter completa é um jogo perdido. Para faixas de IP,
você está negando as faixas conhecidamente internas sobre o endereço
resolvido; se o seu caso permitir, aperte ainda mais para uma allowlist de
hosts de destino aprovados. Prefira a regra mais estreita que o seu produto
tolerar.
Marcos
-
Design — o problema e o modelo de ameaça
Antes de uma linha de código, escreva o Design: o que o serviço faz, quem
o usa e — o mais importante — como ele pode ser abusado. Importar um arquivo
de uma URL fornecida pelo usuário é uma requisição que o seu servidor faz em
nome do usuário, de dentro do seu perímetro, então o modelo de ameaça é o
coração deste projeto.Produza um documento de design curto que responda:
-
A funcionalidade. Um parágrafo: usuários submetem uma URL, o serviço
importa o arquivo (CSV/imagem) de forma assíncrona e guarda o resultado;
usuários podem checar o status. Nomeie os casos de uso concretos que você
vai suportar. -
Os atores. Quem chama
POST /imports(usuários autenticados), quem roda
os workers (sua infra) e quem é o adversário (qualquer usuário que
consiga pôr uma string emfile_url). -
O modelo de ameaça. Enumere os casos de abuso explicitamente:
-
SSRF — a URL aponta para alvos internos: metadata da cloud
(169.254.169.254), serviços de loopback (127.0.0.1:6379Redis),
faixas privadas (10.x,192.168.x) ou esquemas não-HTTP (file://,
gopher://). -
Arquivos maliciosos — uma URL pública válida que retorna uma bomba de
descompressão, um content-type errado/mal rotulado ou um arquivo forjado
para quebrar o parser seguinte. -
Exaustão de recursos — um arquivo enorme, um gotejar lento
(slow-loris) ou uma enxurrada de requisições de import, qualquer um deles
esgotando memória, threads ou tempo. -
Vazamento de informação — ecoar erros/timing do upstream de volta a
quem chamou, transformando o endpoint num oráculo que mapeia sua rede.
-
SSRF — a URL aponta para alvos internos: metadata da cloud
-
Escopo. O que está dentro (fetch seguro, import assíncrono, validação de
conteúdo, auditoria) e explicitamente fora (ex.: varredura de vírus, UI de
retry para o usuário) neste capstone.
Pronto quando: um documento
DESIGNexiste no seu repo nomeando a
funcionalidade, os atores e as quatro classes de ameaça acima (SSRF, arquivos
maliciosos, exaustão, vazamento), cada uma com pelo menos um exemplo concreto
de ataque, mais uma lista explícita de escopo dentro/fora. Se você não
consegue nomear o ataque, não consegue se defender dele — este milestone é a
fonte da verdade de todo teste posterior. -
A funcionalidade. Um parágrafo: usuários submetem uma URL, o serviço
-
Blueprint — contrato do endpoint, regras SSRF, storage e schema de auditoria
Transforme o Design num blueprint de arquitetura: os contratos, regras e
schema concretos contra os quais você vai construir. É aqui que você toma as
decisões para que o Execute seja mecânico.Especifique cada um destes:
-
Contrato do endpoint.
A escrita retorna imediatamente; o status é consultado. O corpo nãoPOST /imports body: { "file_url": "https://cdn.example.com/data.csv" } 202 Accepted → { "id": "imp_123", "status": "pending" } GET /imports/:id 200 OK → { "id": "imp_123", "status": "succeeded|processing|failed" }
carrega detalhe do upstream em falha — só um status genérico. -
Regras do validador SSRF (enumeradas). Escreva-as como uma lista
ordenada e testável, porque a ordem é uma propriedade de segurança:- Parse com um parser de URL real; exija
scheme == https; rejeite
userinfo; exija host não vazio. - Resolva o host para IPs; rejeite se qualquer IP estiver numa faixa
bloqueada (127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,
192.168.0.0/16,169.254.0.0/16incl.169.254.169.254,
0.0.0.0/8,::1/128,fc00::/7,fe80::/10); use IP-em-CIDR
numérico, cubra IPv6 mapeado de IPv4. - Desligue auto-redirects; a cada salto rode de novo as regras 1–2 na nova
URL; limite a ~2 saltos. - Imponha timeout total, máximo de bytes (Content-Length e stream),
allowlist de content-type.
- Parse com um parser de URL real; exija
-
Decisão síncrono vs. job. Declare a regra e o porquê: a busca roda num
job em background, nunca inline — porque buscas inline vazam um oráculo
de timing/erro e expõem as threads web a DoS. Defina a fila, o nome do job e
o que o endpoint faz de forma síncrona (checagem de formato + enfileirar). -
Schema de auditoria. Defina as duas tabelas da arquitetura:
imports
(agregado + status atual) eimport_events(linha do tempo append-only com
kinde umdetailsó de servidor). Liste os tipos de evento exatos. -
Storage. Decida onde os bytes validados moram (object storage indexado
pelo id do import) e que o banco guarda um ponteiro + metadados, não o blob.
Pronto quando: um documento
BLUEPRINTfixa os formatos de
request/response, as quatro regras SSRF ordenadas como lista numerada, a
decisão explícita de síncrono-vs-job com justificativa, o schemaimports+
import_eventscom tipos de evento nomeados, e a escolha de storage. Toda
task posterior deve conseguir citar uma linha deste blueprint. -
Contrato do endpoint.
-
Tasks — decompor num DAG com dependências mínimas
Quebre o blueprint em tasks atômicas e testáveis de forma independente e
conecte suas dependências num DAG. A meta é um grafo onde tudo que pode ser
construído em paralelo é, e cada aresta é uma relação real de "precisa da
saída de" — não um acidente de como você por acaso pensou no assunto.Decomposição sugerida (adapte à sua stack):
T1 Validador de URL (scheme=https, sem userinfo, host não vazio) T2 Guarda de DNS + IP (resolve host, bloqueia privado/loopback/link-local) T3 Fetch protegido (T1 + T2 + sem auto-redirect, revalida por salto, limites) T4 Endpoint de import (checa formato, cria registro, enfileira) — precisa do schema T5 Schema + writer de auditoria (tabelas imports + import_events, escritor de evento) T6 Worker de import (roda T3, valida conteúdo, guarda, audita via T5) T7 Retry + idempotência (re-execução segura de T6 em falha) T8 Suíte de testes (prova que cada bloqueio de T1–T7 falha fechado)Depois desenhe as arestas de dependência e mantenha-as mínimas:
flowchart LR T1[Validador de URL] --> T3[Fetch protegido] T2[Guarda DNS + IP] --> T3 T5[Schema + writer de auditoria] --> T4[Endpoint de import] T5 --> T6[Worker de import] T3 --> T6 T4 --> T6 T6 --> T7[Retry + idempotência] T7 --> T8[Suíte de testes] T3 --> T8Note o paralelismo que o grafo expõe: T1, T2 e T5 não têm dependências e
podem ser construídas ao mesmo tempo. Mantenha as arestas honestas — se uma
task não precisa de verdade da saída de outra, não adicione a aresta, ou você
vai serializar trabalho sem motivo.Pronto quando: você tem uma lista de tasks em que cada task é pequena o
bastante para construir e testar por conta própria, um DAG (dare-dag.yaml
ou equivalente) que valida sem ciclos e sem referências quebradas, e o grafo
deixa as tasks independentes (validador, guarda de IP, auditoria)
visivelmente paralelas. Cada task deve nomear sua checagem de "pronto". -
Execute: validação SSRF — construa o safe-fetch
Implemente o safe-fetch — o firewall de SSRF das tasks T1–T3. Este é o
núcleo de segurança do projeto; construa-o isolado e teste-o com rigor antes
de conectá-lo ao worker. O lab de SSRF companheiro percorre os conceitos;
aqui você o torna real no seu repo.Construa o pipeline em ordem estrita — a ordem é a propriedade de segurança:
MAX_REDIRECTS = 2 BLOQUEADAS = [ "127.0.0.0/8","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16", "169.254.0.0/16","0.0.0.0/8","::1/128","fc00::/7","fe80::/10" ] def safe_fetch(url_crua): url = url_crua for hop in 0..MAX_REDIRECTS: u = parse(url) # parser real, não regex if u.scheme != "https": rejeitar # mata http/file/gopher/ftp/data if u.userinfo presente: rejeitar # https://ok@169.254.169.254/ if u.host vazio: rejeitar ips = dns_resolve(u.host) # pode ser vários A/AAAA if ips vazio: rejeitar for ip in ips: # cheque TODOS os IPs resolvidos if ip_em_cidr_numerico(ip, BLOQUEADAS): rejeitar # incl. 169.254.169.254 resp = http.get(u, follow_redirects=false, connect_to=ips, timeout=30s) if resp.is_redirect: url = resolver_relativo(u, resp.header["Location"]) continue # revalida a NOVA url return ler_limitado(resp) # limites de tamanho + content-type rejeitar("redirects demais")Detalhes inegociáveis:
-
Só HTTPS, parser real, rejeite
userinfo— uma allowlist de um esquema
vence uma denylist que você precisa manter completa;userinforemove a
ambiguidadetrusted.com@internal. -
Bloqueie sobre o IP resolvido, numericamente — faça o parse de cada IP
para bytes e teste a contenção CIDR. Imune a0x7f.0.0.1, decimal
2130706433e zero-padding. Cubra IPv6 mapeado de IPv4
(::ffff:127.0.0.1). Rejeite se qualquer IP resolvido for interno. O
bloqueio de169.254.0.0/16(metadata da cloud) é obrigatório. -
Conduza os redirects você mesmo, revalide cada salto — desligue o
auto-follow; um302 → http://169.254.169.254/precisa passar pela
gauntlet inteira de novo. Limite os saltos a ~2. -
Limites — timeout total; máximo de bytes checado no header e contando
os bytes do stream (Content-Length pode mentir); allowlist de content-type;
limite o tamanho descomprimido para neutralizar bombas de descompressão. -
Falhe fechado e quieto — toda rejeição retorna um erro genérico; o
motivo é logado só no servidor.
Pronto quando:
safe_fetchexiste como um módulo isolado e testado em
unidade que, com a rede mockada, rejeita: esquemas não-HTTPS, um host que
resolve para169.254.169.254/10.0.0.5/127.0.0.1, um redirect para um
IP interno, um corpo grande demais (header e stream) e um content-type não
permitido — e permite um arquivo HTTPS público bem formado. Ele nunca ecoa
detalhe do upstream. -
Só HTTPS, parser real, rejeite
-
Execute: import assíncrono — job, validação de conteúdo, persistência e auditoria
Conecte o safe-fetch ao pipeline de import assíncrono (tasks T4–T7). O
endpoint enfileira; o worker faz o trabalho arriscado; cada passo é auditado.
O playbook companheiro "Rode e diagnostique jobs em background" é a sua
referência para o worker, os retries e a observabilidade.Construa em duas metades:
1. O endpoint (síncrono, minúsculo).
POST /imports (file_url): validar_formato(file_url) # só presente, string, limite de tamanho imp = imports.create(user_id, file_url, status: "pending") audit(imp, kind: "queued") ImportJob.enqueue(imp.id) # retorna antes de qualquer fetch return 202, { id: imp.id, status: "pending" }2. O worker (assíncrono, protegido, auditado).
ImportJob(import_id): imp = imports.find(import_id) imp.update(status: "processing"); audit(imp, "fetch_started") try: bytes = safe_fetch(imp.file_url) # milestone 4; pode rejeitar except Rejeitado as e: imp.update(status: "failed"); audit(imp, "fetch_rejected", detail: e.motivo) return # NÃO faça retry de rejeição de política if not conteudo_valido(bytes, tipo_esperado): # sniff de magic-byte, parse CSV, limite de linhas imp.update(status: "failed"); audit(imp, "content_rejected", detail: motivo) return key = storage.put(import_id, bytes) imp.update(status: "succeeded", storage_key: key, content_type: farejado, byte_size: len(bytes)) audit(imp, "stored")Decisões-chave para acertar:
-
Validação de conteúdo é separada dos limites de transporte. O safe-fetch
limita tamanho/tipo no fio; aqui você confirma que os bytes são o que você
pediu — fareje magic bytes (não confie no header), parseie o CSV e limite
linhas/colunas, rejeite em falha de parse. -
Falha vs. retry. Distinga uma rejeição de política (bloqueio SSRF,
conteúdo ruim — determinística, vai falhar de novo → não faça retry,
marquefailed) de um erro transitório (oscilação de DNS, 503 do
upstream, timeout → retry com backoff). Só faça retry da classe transitória. -
Retries idempotentes. Um job re-executado não pode guardar em dobro nem
auditar em dobro. Indexe o storage porimport_id, faça a transição para
"succeeded" ser uma mudança de estado protegida e registre um evento
retriedpara a linha do tempo continuar verdadeira. -
Audite cada transição.
queued → fetch_started → (fetch_rejected | content_rejected | stored | failed | retried). Odetailguarda o motivo
só de servidor; a API ainda retorna só um status genérico.
Pronto quando:
POST /importsenfileira e retorna202sem buscar; o
worker roda o safe-fetch, valida o conteúdo, guarda o resultado e escreve um
evento de auditoria para cada transição; uma rejeição de política marca
failedsem retry enquanto um erro transitório faz retry com backoff; e um
job re-executado é idempotente (sem linhas de storage ou auditoria
duplicadas). -
Validação de conteúdo é separada dos limites de transporte. O safe-fetch
-
Harden & verify — testes e critério de submissão
Um controle de segurança que você não consegue testar vai apodrecer. Feche o
projeto provando, com a rede mockada, que cada defesa falha fechado e que
o caminho feliz funciona — e então atinja o critério de submissão.Cubra, no mínimo, estes casos (o mock permite simular respostas perigosas sem
infraestrutura maliciosa real):test "rejeita esquema não-https": espera_rejeicao(safe_fetch("http://example.com/x")) espera_rejeicao(safe_fetch("file:///etc/passwd")) test "rejeita host que resolve para IP privado / link-local": stub_dns("evil.test" => ["169.254.169.254"]) # metadata da cloud espera_rejeicao(safe_fetch("https://evil.test/")) stub_dns("lan.test" => ["10.0.0.5"]) espera_rejeicao(safe_fetch("https://lan.test/")) test "rejeita redirect que aponta para IP interno": stub_http("https://ok.test/" => redirect_para("http://169.254.169.254/")) espera_rejeicao(safe_fetch("https://ok.test/")) test "rejeita arquivo grande demais (header e stream)": stub_http("https://big.test/" => corpo_de(500 MB)) espera_rejeicao(safe_fetch("https://big.test/")) test "o job de import processa um arquivo válido e o audita": stub_dns("cdn.test" => ["93.184.216.34"]) # IP público stub_http("https://cdn.test/a.csv" => resposta_csv(1 MB)) imp = rodar_import("https://cdn.test/a.csv") espera(imp.status) == "succeeded" espera(tipos_de_auditoria(imp)) inclui ["queued","fetch_started","stored"] test "um retry é idempotente (sem storage ou auditoria duplicados)": first = rodar_import_falhando_uma_vez("https://cdn.test/a.csv") # erro transitório, depois ok espera(imp.status) == "succeeded" espera(objetos_storage(imp).count) == 1 espera(tipos_de_auditoria(imp)) inclui ["retried"] test "nunca vaza o erro do upstream para quem chamou": stub_http("https://ok.test/" => conexao_recusada()) resp = post_imports("https://ok.test/") espera(resp.body) == status_generico() # sem oráculo de timing/erroAfirme também as regras transversais:
POST /importsretorna antes de
qualquer busca (o trabalho está num job) e o limite de saltos de redirect é
imposto.
Critério de submissão
Submeta quando tudo o que segue for verdade no seu repo:
-
Endpoint —
POST /imports { file_url }valida só o formato, cria um
importpending, enfileira um job em background e retorna202com um
id;GET /imports/:idreporta o status sem detalhe do upstream. -
Safe-fetch — impõe, nesta ordem: só HTTPS + validação de URL →
resolução DNS bloqueando privado/loopback/link-local (incluindo
169.254.169.254) → revalidação de redirect por salto (limite ~2) →
limites de tamanho + timeout + content-type. -
Import assíncrono + auditoria — o worker roda o safe-fetch, valida o
conteúdo, guarda o resultado e escreve uma linhaimport_eventspara cada
transição; rejeições de política marcamfailedsem retry, erros
transitórios fazem retry com backoff e os retries são idempotentes. -
Testes (rede mockada) provando cada bloqueio: não-HTTPS, IP
privado/link-local (incl. metadata), redirect malicioso, arquivo grande
demais — mais um import de happy-path que audita, um retry idempotente e um
teste de "o erro não é vazado".
Inclua uma nota curta sobre hardening de DNS rebinding (conectar por IP
com umHostfixado) como passo futuro, e confirme que nenhum erro, corpo ou
timing do upstream é jamais retornado a quem chamou. -
Endpoint —