← Voltar para o blog

william@devops:~$ cat ./laboratorio/imagem-docker.md

26 vulnerabilidades e
nenhuma era do meu código.

O scanner reprovou a imagem da API e o pipeline parou. O mais curioso: a aplicação não tinha nenhuma dependência vulnerável. O problema morava na imagem base. Conto como cheguei a zero sem desligar o scanner.

DE ONDE VEIO

Isso aconteceu no meu laboratório de estudo, no primeiro deploy do app piloto. Os números e os arquivos são os reais.

01 / IMAGEM BARRADA

O pipeline fez exatamente o que devia: parou.

A esteira da API tem um passo antes de mandar a imagem para o ECR: o Trivy procura vulnerabilidades e reprova se achar alguma HIGH ou CRITICAL que já tenha correção. No primeiro deploy, ele reprovou.

A tentação nessa hora é grande. Desliga o scanner, coloca tudo num arquivo de ignorar e segue a vida. Só que aí o scanner vira enfeite. Resolvi entender de onde vinha cada uma.

A REGRA QUE EU SEGUI

Scanner que a gente desliga quando incomoda não protege nada.

02 / DE ONDE VINHA

A aplicação estava limpa. A imagem, não.

A API do piloto é pequena e não usa nenhuma biblioteca externa. Então as vulnerabilidades não podiam ser do código. Olhando a tabela do Trivy com calma, apareceram três culpados:

01

npm dentro da imagem

A imagem oficial do Node já vem com npm, yarn e corepack. A aplicação não usa nenhum deles em produção, mas as dependências deles estavam lá.

02

Sistema desatualizado

Bibliotecas do Alpine, como o openssl, tinham correções que ainda não estavam na imagem base.

03

Node fora de suporte

A versão 20 do Node chegou ao fim do suporte em abril de 2026. Imagem sem suporte para de receber correção.

04

Tudo num estágio só

Ferramenta de build e aplicação moravam na mesma imagem final.

03 / A CORREÇÃO

Levar para produção só o que a aplicação usa.

01

Multi-stage: um estágio para instalar, outro para rodar

O primeiro estágio instala as dependências com o npm. O segundo copia só o resultado. O npm nunca chega na imagem final.

02

Tirar o que sobra e atualizar o sistema

Na imagem final, removi npm, yarn e corepack e rodei apk upgrade para aplicar as correções do Alpine. Também subi para o Node 22, que tem suporte.

03

De quebra, mais segurança

Já que estava mexendo, a aplicação passou a rodar com o usuário node (nunca root) e ganhou um healthcheck.

DockerfileREAL
# Estágio 1: instala as dependências (o npm fica só aqui)
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev --no-audit --no-fund

# Estágio 2: só o Node + o app
FROM node:22-alpine AS runtime
ENV NODE_ENV=production PORT=3000

# correções do Alpine + remove ferramentas de build
RUN apk upgrade --no-cache \
 && rm -rf /usr/local/lib/node_modules/npm \
           /usr/local/lib/node_modules/corepack \
           /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack \
           /usr/local/bin/yarn /usr/local/bin/yarnpkg /opt/yarn-*

WORKDIR /app
COPY --from=deps --chown=node:node /app/node_modules ./node_modules
COPY --chown=node:node package.json ./
COPY --chown=node:node src ./src

USER node
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1:3000/health || exit 1
CMD ["node", "src/server.js"]

04 / O SCANNER

Também confiei menos no próprio scanner.

Um detalhe que pouca gente olha: de onde vem o scanner. Em vez de usar uma action de terceiros, o pipeline baixa o binário oficial do Trivy com versão fixa e confere o checksum antes de rodar. Se alguém trocar o arquivo no caminho, o pipeline falha.

deploy-eks-node.yml (trecho)REAL
- name: Instalar Trivy (versão fixa + checksum)
  run: |
    FILE="trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
    curl -sSfL -o "/tmp/${FILE}" \
      "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/${FILE}"
    echo "${TRIVY_SHA256}  /tmp/${FILE}" | sha256sum -c -
    tar -xzf "/tmp/${FILE}" -C /tmp trivy

- name: Trivy scan
  run: |
    trivy image --input /tmp/image.tar \
      --severity "CRITICAL,HIGH" --ignore-unfixed --exit-code 1

05 / RESULTADO

Zero, e o scanner continua ligado.

✓

26 → 0 vulnerabilidades na imagem da API, sem nenhuma exceção no arquivo de ignorar.

✓

Imagem menor sem ferramentas de build que a aplicação não usa.

✓

Node com suporte 22 LTS no lugar do 20, que já tinha saído de suporte.

✓

Sem root a aplicação roda com o usuário node.

✓

Scanner confiável versão fixa e checksum conferido a cada execução.

06 / APRENDIZADOS

O que eu levo dessa.

Antes de discutir com o scanner, leia de onde vem cada vulnerabilidade. Na maioria das vezes ela não está no seu código, está em algo que veio junto e ninguém precisava.

E arquivo de ignorar existe, mas cada linha nele precisa de um motivo escrito. Se não dá para explicar por que aquela vulnerabilidade não importa, ela importa.

william@devops:~$ echo $LESSON_LEARNED

"Na imagem de produção vai só o que a aplicação usa. O resto é superfície de ataque de graça."
COMPARTILHAR IDEIAS

Quer trocar uma ideia sobre Cloud ou DevOps?

Mandar um e-mail ↗