💡 Exemplo completo em funcionamento disponível no GitHub:
load-untrusted-documents-safely-python
O Antigo Método Era Doloroso
Você escreveu três linhas para renderizar uma miniatura de um documento enviado. Elas eram assim, e funcionavam bem:
with signature.Signature(upload_path) as sign:
save_page_preview(sign, thumbnail_path)
O que essas linhas faziam, antes do GroupDocs.Signature 26.9, era buscar todos os endereços que o documento apontava. Um arquivo Word pode conter uma imagem que ele não possui – o arquivo armazena uma URL, e o que o abre baixa essa URL. Em um desktop isso é um recurso. Em um servidor que aceita uploads, isso significa que a pessoa que enviou o arquivo decide quais endereços sua infraestrutura solicita.
O ataque tem um nome, server‑side request forgery (SSRF), e três formas que vale a pena nomear. Um endereço interno inacessível da internet é acessível a partir do seu servidor, de modo que um documento mal‑formado pode fazer seu serviço buscar http://169.254.169.254/ ou um endpoint administrativo no localhost. Um caminho UNC pode fazer um host Windows autenticar para fora, entregando credenciais a um servidor controlado pelo atacante. E um link para um host que simplesmente nunca responde mantém a thread de carregamento até o timeout, o que é uma maneira barata de esgotar um pool de workers com documentos que parecem inofensivos.
Nada disso é um bug na biblioteca de documentos. Seguir um link é o que o formato pede. A parte desconfortável era que atender a esse comportamento era o padrão, em código que ninguém sinalizaria em revisão.
Existe uma Maneira Melhor
O carregamento seguro de documentos é o comportamento padrão do GroupDocs.Signature para Python que recusa fazer essas solicitações. A partir da versão 26.9, LoadOptions.skip_external_resources tem o valor padrão True, de modo que as mesmas três linhas agora não buscam nada e renderizam um espaço reservado onde a imagem vinculada estaria.
A mudança é um padrão, não um novo recurso – a propriedade já existia. O que o 26.9 alterou foi para onde ela aponta quando seu código não especifica nada, que é a única configuração que a maioria dos serviços usa.
O Novo Método: Três Modos de Carregamento
Etapa 1 - Mantenha o padrão para qualquer coisa não confiável
Nenhum LoadOptions:
with signature.Signature(source_path) as sign:
return save_page_preview(sign, preview_path)
Nada é solicitado. A pré‑visualização fica menor do que seria de outra forma, e essa diferença de tamanho é a prova mais prática de que nenhuma requisição saiu da máquina.
Etapa 2 - Lista branca de um host que você realmente possui
Muitos documentos vinculam algo legítimo: um CDN da empresa, um servidor interno de imagens, uma loja de modelos. Permita isso e nada mais:
load_options = LoadOptions()
load_options.whitelisted_resources = [trusted_address]
with signature.Signature(source_path, load_options) as sign:
return save_page_preview(sign, preview_path)
A regra de correspondência merece atenção. Ela faz um teste de substring case‑insensitive contra o endereço do recurso, o que torna um fragmento curto perigoso: github combina com github.attacker.example/payload.png tão facilmente quanto com o host que você pretendia. Use esquema, host e caminho – este exemplo coloca na lista branca raw.githubusercontent.com/groupdocs-signature/.
Etapa 3 - Permita tudo, deliberadamente
O comportamento pré‑26.9, ainda disponível:
load_options = LoadOptions()
load_options.skip_external_resources = False
Razoável para documentos produzidos pela sua própria aplicação. Uma armadilha: a propriedade obsoleta load_external_resources tem polaridade oposta, de modo que skip_external_resources = False substitui load_external_resources = True. Copiar um valor da propriedade antiga inverte sua postura de segurança sem gerar erro algum.
Lado a Lado: Antes vs. Depois
Mesmo documento, mesmo caminho de código, três políticas de carregamento. Estes são os tamanhos dos arquivos comprometidos na pasta Result/ do exemplo, para que possam ser verificados ao invés de aceitos por confiança:
| Modo de carregamento | Tamanho da pré‑visualização | Solicitações externas |
|---|---|---|
| padrão (26.9 e posteriores) | 16 435 bytes | nenhuma |
| host em lista branca | 51 738 bytes | uma, para o endereço permitido |
| todos os recursos (padrão pré‑26.9) | 51 738 bytes | uma por recurso vinculado |
A imagem vinculada tem 35 303 bytes dessa diferença. Não confiei na configuração até ver esses dois números lado a lado, e sugeriria o mesmo: ler a propriedade de volta informa o que você configurou, não o que o processo fez.
O que Conta como um Recurso Externo?
É mais restrito do que as pessoas esperam, por isso a atualização costuma ser tranquila. Imagens vinculadas em vez de incorporadas, campos INCLUDEPICTURE, imagens vinculadas em apresentações e planilhas, e as imagens e folhas de estilo que um SVG referencia. Conteúdo incorporado permanece intacto, pois já está dentro do arquivo e nenhuma requisição é necessária para renderizá‑lo.
Essa distinção é todo o limite de segurança. Um documento só pode fazer seu servidor alcançar algo se ele armazenar um endereço em vez dos bytes, então a pergunta para qualquer corpus é simplesmente quantos de seus arquivos vinculam em vez de incorporar. Se nenhum fizer isso, o novo padrão não custa nada e você pode atualizar sem ler mais nada.
Exemplo do Mundo Real: Um Upload que é Assinado
O caso para o qual a mudança de padrão foi criada. Um documento chega de fora e você precisa colocar uma assinatura nele:
with signature.Signature(source_path) as sign:
options = QrCodeSignOptions("Approved by GroupDocs.Signature")
options.encode_type = QrCodeTypes.QR
options.left = 400
options.top = 50
options.width = 120
options.height = 120
result = sign.sign(output_path, options)
Nenhum recurso externo é solicitado enquanto o documento é carregado, assinado ou salvo. O arquivo assinado mantém seu link, de modo que um usuário que o abra no Word depois ainda verá a imagem resolvida na própria máquina. Pular a requisição é uma política do lado do servidor, não uma edição no documento – e é exatamente isso que o torna seguro para aplicar a arquivos que você manipula em nome de terceiros.
O Que Mais Muda Quando Você Atualiza?
Para a maioria dos serviços, nada visível, o que vale a pena afirmar claramente porque um padrão de segurança que altera o comportamento em todo lugar não sobreviveria a uma revisão de atualização. Assinatura, verificação e busca permanecem intactas. A exceção é uma pré‑visualização que antes mostrava uma imagem vinculada e agora mostra um espaço reservado – a mudança cumprindo seu papel. Coloque o host em lista branca se for seu, aceite‑o se não for.
Vale destacar separadamente: SVG. Um SVG pode referenciar imagens e folhas de estilo por URL; essas referências são recursos externos sob a mesma regra, e SVG é tanto um formato de upload comum quanto um vetor frequente de SSRF. Um serviço que aceita avatares SVG e os renderiza no servidor é exatamente o tipo de sistema que essa mudança protege.
Um detalhe do Python: como a pré‑visualização é gravada
PreviewOptions recebe duas fábricas de streams ao invés de um caminho, e callables simples de Python são tudo que ele precisa:
def create_page_stream(page_data):
return open(preview_path, "wb")
def release_page_stream(page_data, page_stream):
page_stream.close()
preview_options = PreviewOptions(create_page_stream, release_page_stream)
preview_options.preview_format = PreviewOptions.PreviewFormats.PNG
sign.generate_preview(preview_options)
Uma cria um stream por página, a outra o libera. O documento de exemplo tem uma única página, então um arquivo é escrito; para entrada com várias páginas, inclua o número da página no nome ou cada página sobrescreve a anterior.
Conclusão
O padrão foi invertido para que o comportamento arriscado exija uma decisão explícita e o seguro não precise de nada. Mantenha o padrão para entrada não confiável, coloque em lista branca de forma restrita onde seus próprios hosts estejam envolvidos, e lembre‑se de que a assinatura nunca precisou da rede.
Se quiser uma verificação mais forte que o tamanho do arquivo, aponte um documento de teste para um host que você controla e observe o log de acesso enquanto a pré‑visualização roda. O tamanho indica se bytes chegaram; o log de acesso indica se alguma requisição foi feita, e esses dois resultados diferem exatamente no caso que importa – um host em lista branca que está indisponível parece idêntico a um bloqueado apenas pelo output.
Executar o exemplo contra um dos seus próprios documentos leva um minuto e mostra, em três tamanhos de arquivo, exatamente o que seu serviço tem buscado em nome de quem enviou o arquivo.