· Droidwatch Security Research team · 8 min de leitura · Static Analysis Deep-Dives ci-cd devsecops varredura-apk
🌐 Read in English → 🌐 Leer en español →

Escanear APK no CI/CD sem travar as entregas

Onde a varredura de segurança entra no pipeline, quais vereditos devem quebrar a build e como tratar as falhas para que a equipe não desative o portão.

Escanear APK no CI/CD sem travar as entregas

A maior parte dos testes de segurança móvel acontece depois que a build que importava já foi publicada. Uma avaliação trimestral encontra uma credencial embutida que chegou à produção onze semanas antes, e o achado aparece como arqueologia em vez de prevenção.

Levar a varredura para o pipeline corrige o momento. Também introduz o modo de falha que faz as equipes desativá-la em um mês: um portão que bloqueia entregas por achados com os quais ninguém pretendia bloquear.

Este artigo trata de acertar a segunda parte.

Onde a varredura entra

A varredura executa contra o artefato construído, depois da montagem e antes da distribuição. Essa posição importa por uma razão concreta: o que é publicado é o pacote montado, com todas as dependências que a build trouxe.

A varredura em nível de código-fonte não enxerga o que entra por uma dependência transitiva, nem o que o próprio processo de construção injeta. O APK, o AAB ou o IPA é o que o usuário instala, então é o que deve ser analisado.

Na prática isso é um passo depois de assembleRelease, e antes do envio para a Play, o App Store Connect ou o canal de distribuição em uso.

O que a varredura do artefato vê e a do código não

O argumento para analisar o pacote e não o repositório é concreto. Há três classes de achado que só existem no artefato montado:

Conteúdo dos SDK de terceiros. O Droidwatch identifica 87 SDK de terceiros por assinatura dentro do pacote construído. Um manifesto de dependências declara o que foi pedido; o artefato declara o que chegou, inclusive os SDK trazidos transitivamente por outros SDK.

Junto da identificação, o OSV.dev é consultado no momento da análise em busca de vulnerabilidades conhecidas. Vale enunciar o limite com honestidade: o OSV cobre 12 dos 87. Os SDK de publicidade e atribuição são fechados e não figuram nas bases públicas de vulnerabilidades, então para a maior parte do conjunto o pipeline estabelece o que há dentro da build, mas não se existe um CVE publicado. Um módulo separado acompanha cerca de 120 rastreadores de privacidade, que frequentemente é o achado que interessa a uma revisão de privacidade mais do que a uma de segurança.

Configuração de build que só existe após a montagem. Um android:debuggable="true" que sobrevive a uma build de release, android:allowBackup="true" junto de armazenamento local sensível, um networkSecurityConfig permissivo, um certificado de assinatura que não é o que o processo de publicação deveria usar. Nada disso é confiavelmente visível no código.

Resultado do merge de manifestos. O conjunto final de permissões é a união do manifesto do aplicativo e do de cada biblioteca mesclada. Uma dependência pode adicionar uma permissão que a equipe de desenvolvimento nunca pediu e não aprovaria.

O que deve quebrar a build

Por padrão, pouco. O runner de CI do Droidwatch aceita um ajuste fail_on_verdict, e por padrão ele vale apenas Malicious, a opção mais conservadora disponível.

Esse padrão é deliberado. Um portão que dispara com qualquer coisa abaixo de malware confirmado vai disparar com o próprio aplicativo da organização, porque um aplicativo legítimo pede permissões legitimamente, inclui SDK de analytics legitimamente e ofusca o próprio código legitimamente.

A escala de veredito vai de 0 a 100 onde mais alto é pior, com quatro faixas: 65 ou mais Malicious, de 40 a 64 High Risk, de 20 a 39 Suspicious, abaixo de 20 Benign.

Uma progressão razoável:

Fase Portão Razão
Semana 1 Malicious Fixar a linha de base sem bloquear nada
Após um mês limpo Malicious\|High Risk Já se conhece a pontuação normal do próprio app
Maturidade Adicionar Suspicious só em ramos de release Ramos de feature seguem rápidos

A ordem é o essencial. Apertar o portão antes de conhecer a pontuação normal do aplicativo produz uma primeira semana de falhas falsas, e uma equipe que o desliga.

Como tratar a falha

Uma build que falha por veredito precisa dizer ao desenvolvedor o que fazer em seguida, e a distinção que importa é entre esta build introduziu algo e este aplicativo sempre pontuou assim.

O runner emite quatro saídas — identificador de execução, veredito, pontuação e uma URL para compartilhar —, e a URL é a que muda a experiência. É um link público para o relatório completo que um desenvolvedor pode abrir sem conta, o que elimina o passo em que ele abre um chamado perguntando qual era o achado.

Três categorias de falha e o que cada uma significa:

A pontuação mudou. Compare com a da build anterior. Um salto costuma significar que uma dependência mudou. É o caso para o qual o portão existe.

A pontuação sempre foi essa. O portão foi apertado além da linha de base do aplicativo. Ou se corrigem os achados de fundo, ou se alarga o portão deliberadamente, mas como decisão registrada e não desativando o passo.

A própria varredura falhou. Isso precisa ser distinguido de uma falha de política. O runner usa códigos de saída separados: 1 significa que o veredito correspondeu ao portão, 2 que a análise falhou, 3 que o tempo expirou, 4 que falta uma dependência ou um arquivo, 5 um erro HTTP. Tratar uma falha de infraestrutura como falha de segurança ensina a equipe a ignorar as duas.

Um pipeline que reporta o código 3 como «a varredura de segurança falhou» terá ensinado, em um trimestre, a todos os engenheiros que a varredura de segurança é instável. Atribua aos códigos 2 a 5 um estado distinto — um aviso, um alerta de infraestrutura, uma nova tentativa — e reserve a build vermelha para o código 1.

Tempos

A análise leva entre dois e cinco minutos para um APK de 50 MB, e de oito a dez em média acima de 100 MB. É tempo suficiente para pesar em cada commit e curto o bastante para passar despercebido em um ramo de release.

Medições sobre relatórios de produção em 19 de setembro de 2026:

Tamanho do pacote Tempo de análise
48 MB 157 s
97 MB 503 s
111 MB 611 s
165 MB 595 s

Duas consequências práticas. Execute o portão completo em ramos de release e em integrações ao ramo principal, não a cada push em um ramo de feature. E defina timeout_seconds acima da duração esperada do maior artefato: os 300 segundos padrão são folgados para um aplicativo de 50 MB e apertados para um de 150 MB.

Há um atalho que vale programar no pipeline. Quando um artefato já foi analisado antes, a chamada de análise devolve cached: true com o identificador de trabalho nulo e o relatório existente; não há o que consultar. Reexecutar um pipeline sobre um artefato sem alterações custa segundos em vez de minutos, o que barateia a nova tentativa após uma falha de infraestrutura alheia.

As integrações

O Droidwatch oferece seis integrações de CI/CD: GitHub Actions, GitLab CI/CD, Bitrise, Jenkins, CircleCI e Azure Pipelines.

As seis chamam o mesmo script runner, que cada wrapper baixa em tempo de build em vez de incorporá-lo ao repositório. O efeito prático é que uma correção do runner chega a todos os pipelines na execução seguinte, sem versões a perseguir. As dependências são curl e jq: sem Python, sem SDK a instalar.

Um passo do GitHub Actions são quatro linhas:

- uses: Omar1123/droidwatch-scan@v1
  with:
    apk_path: app/build/outputs/apk/release/app-release.apk
    api_key: ${{ secrets.DROIDWATCH_API_KEY }}

A chave de API fica no cofre de segredos do CI e não no arquivo de workflow, e o acesso à API começa no plano Pro. Os limites diários de análise se aplicam por plano — 100 no Pro, 500 no Team —, que é o número a confrontar com o volume do pipeline antes de estender o portão a todos os repositórios de uma organização.

iOS no mesmo pipeline

Artefatos .ipa são aceitos e analisados estaticamente em sete módulos: binário Mach-O, Info.plist, entitlements, configuração de App Transport Security, class-dump, frameworks vinculados e manifestos de privacidade declarada.

Não há instrumentação em execução para iOS, em nenhum plano. Para um portão de build isso importa menos do que parece: o portão existe para capturar regressões de configuração e de dependências, e essas são propriedades estáticas. Uma exceção de ATS adicionada para entregar uma funcionalidade, um entitlement que não deveria estar em uma build de release, um framework que ninguém revisou: tudo visível sem executar nada.

O que o portão de iOS não fará é estabelecer comportamento. Vale deixar isso escrito na documentação interna do pipeline, porque a alternativa é uma equipe que pressupõe uma cobertura que não tem.

O que isto não substitui

Um portão de pipeline captura o que a build produziu. Não captura o que outra pessoa publicou sob a marca da organização: a versão reempacotada de um aplicativo bancário distribuída por smishing não passa pelo CI da instituição em momento algum.

Isso é outro fluxo: monitorar pacotes que imitam o aplicativo e confrontar seus certificados de assinatura com o legítimo. O fluxo de triagem explica como essa análise roda e o que produz. Vale construir, e vale não confundir com o portão de build.

Tampouco substitui a leitura do relatório. Um veredito é uma decisão de roteamento; a evidência que o sustenta está nos achados, e o percurso do relatório explica onde vive cada parte dessa evidência.


Para montar uma varredura no pipeline, consulte o guia de CI/CD.

Threat Research

Droidwatch's research team analyzes Android & iOS malware — banking trojans, spyware, droppers and overlay kits — and writes up the static and dynamic signals that give them away. Every post is grounded in real platform output.

Analyze your first app free — drag an APK or iOS .ipa onto the homepage for a full static report in about a minute. Get started free