DAST em CI/CD com GitHub Actions
Execute scans DAST automatizados dentro do seu pipeline do GitHub Actions com o WASViking Sentinel. Analisa a aplicação em localhost por um túnel mTLS efêmero, executa scans autenticados com um token emitido no pipeline, prioriza os endpoints que você lista, faz o build falhar quando há achados reais e publica os resultados na aba Security do GitHub.
Esta integração executa um scan DAST da WASViking® dentro do
seu runner do GitHub Actions, contra a aplicação que você sobe em localhost,
e faz o pipeline falhar quando encontra vulnerabilidades reais. A aplicação
nunca fica exposta à internet pública: o agente Sentinel abre um túnel
mTLS efêmero e o motor de DAST envia as suas sondas de volta por
ele.
A configuração tem duas metades, e a ordem importa: você configura
primeiro o portal da WASViking (um token do agente Sentinel e uma chave de
API com escopo ci:scan) e depois conecta os dois ao GitHub Actions.
O que esta integração faz
- Executa um scan DAST a cada push e pull request, contra o build que você sobe no runner.
- Faz o build falhar por severidade com
--fail-on, de modo que releases vulneráveis são bloqueadas antes do merge. - Compara com a branch base usando
--baseline, de modo que um PR só falha pelo que ele introduz, não pela dívida preexistente. - Executa scans autenticados com um token de curta duração que o seu
pipeline emite em tempo de execução (
--auth-bearer/--auth-header), de modo que as áreas protegidas por login são de fato alcançadas, sem credenciais estáticas em uma configuração. - Prioriza os endpoints que você lista com
--path, analisando primeiro as suas próprias rotas de API, antes da superfície descoberta automaticamente. Essencial para APIs autenticadas e SPAs que não têm links HTML que o crawler consiga seguir. - Emite SARIF 2.1.0, consumido nativamente pelo GitHub Code Scanning, além de um relatório JSON completo.
- Provisiona e remove o agente a cada execução, sem deixar credenciais persistentes no runner.
- Contabiliza o uso na sua cota de scans de CI/CD e registra cada execução no portal.
Como funciona
- O runner baixa e instala o agente Sentinel usando o token do Sentinel.
- O agente provisiona um bundle mTLS efêmero (válido por 60 minutos, não reutilizável) a partir da API da WASViking.
- O agente abre um túnel gRPC sobre mTLS até o servidor de túnel da WASViking.
- O agente solicita um scan contra o seu alvo em
localhost. A API valida que o alvo é um endereço privado, verifica cota e concorrência e inicia o motor de DAST. - O motor executa as suas verificações (OWASP Top 10, SQLi, XSS, headers de segurança e outras) enviando sondas de volta pelo túnel; o agente as executa localmente contra a sua aplicação.
- O agente grava
wasviking-scan.sarifewasviking-scan.json, e o workflow envia o SARIF para o GitHub Code Scanning.
Postura de acesso
- O agente só analisa alvos privados:
localhost, RFC1918 e link-local. Endereços públicos são rejeitados pelo validador de alvos da API. - O bundle mTLS é efêmero (60 minutos) e de uso único.
- Nenhum tráfego de produção é interceptado. Somente a instância de teste que você sobe no runner é analisada.
- Revogue o acesso a qualquer momento revogando a chave de API no portal.
Pré-requisitos
| Requisito | Detalhe |
|---|---|
| Plano WASViking | CI/CD Pipeline Scans ativado (Pro ou superior). |
| Papel no portal | Admin ou Manager, para emitir chaves de API e tokens do Sentinel. |
| Repositório no GitHub | Permissão para criar secrets/variáveis do Actions e arquivos em .github/. |
| Permissões do workflow | contents: read e security-events: write (para o envio do SARIF). |
| Aplicação alvo | Capaz de subir no runner e responder em localhost:<port>. |
| Runner | Hospedado pelo GitHub (ubuntu-latest recomendado) ou self-hosted Linux x86_64. |
| Saída de rede | HTTPS para api.wasviking.com, sentinel.wasviking.com e github.com na porta 443. Nenhuma conexão de entrada é necessária. |
Não usa GitHub Actions? O mesmo gate roda em qualquer sistema de CI em que o binário
wasviking-sentinelpossa ser executado. Comece por wasviking-sentinel em CI/CD.
Passo 1: Crie um token do agente Sentinel (portal)
O runner precisa de um token para baixar e registrar o agente.
No portal, vá em Sentinel → Sentinel Agents e clique em Add Sentinel Agent (adicionar agente Sentinel).
| Campo | Valor |
|---|---|
| Agent display name | Um rótulo exibido no portal, único na sua organização (por exemplo, Production CI/CD Bootstrap). Não é o hostname da máquina; você pode renomeá-lo depois. |
Clique em Create. O portal exibe um token de bootstrap RAW uma única vez.
Copie o token e guarde-o em local seguro. Você vai colá-lo no GitHub no Passo 3
como WASV_SENTINEL_API_KEY. Se você perdê-lo, crie outro agente e use
o novo token.
Sentinel → Sentinel Agents → Add Sentinel Agent.
O token de bootstrap é exibido uma única vez. Copie-o antes de fechar.
Passo 2: Crie uma chave de API com escopo ci:scan (portal)
Esta é a credencial com a qual o scan em si é executado, separada do token do agente.
Vá em Settings → System Settings → API Keys e clique em + New Key.
| Campo | Valor |
|---|---|
| Label | Algo identificável, por exemplo GitHub Actions CI/CD pipeline. Use uma chave por repositório, para poder revogá-la sem afetar outros pipelines. |
| Scopes | Selecione somente ci:scan (Trigger scans from a CI/CD pipeline). Menor privilégio: não adicione escopos de que o pipeline não precisa. |
| Expiration | 90 dias é um padrão sensato; faça a rotação nessa cadência. |
Salve e copie a chave uma única vez. Você vai colá-la no GitHub no Passo 3
como WASV_DAST_API_KEY.
Você pode revisar ou ajustar os escopos de uma chave depois, com Edit na linha da chave. Editar os escopos mantém o mesmo valor da chave, então o pipeline continua funcionando sem reemitir o secret.
Settings → System Settings → API Keys.
Selecione somente ci:scan para uma chave de pipeline de CI/CD.
Passo 3: Adicione os secrets e a variável no GitHub
Nunca faça commit de uma chave no repositório. Guarde as chaves como secrets criptografados do Actions.
No GitHub, vá em Settings → Secrets and variables → Actions no repositório.
Em Secrets, adicione:
| Nome | Valor |
|---|---|
WASV_SENTINEL_API_KEY |
O token de bootstrap do Passo 1. |
WASV_DAST_API_KEY |
A chave de API do Passo 2. |
Em Variables, adicione:
| Nome | Valor |
|---|---|
WASV_TEMPLATE |
O CI/CD slug de um Scan Template do portal, por exemplo ci-fast. |
Use Secrets (criptografados), nunca Variables, para as duas chaves. Para vários ambientes (staging, produção), prefira Environment secrets com revisores obrigatórios e regras de proteção de implantação.
Escolhendo um Scan Template
Um Scan Template é um conjunto nomeado e reutilizável de preferências de scan
(crawl, autenticação, seleção de analisadores, comentários de IA), para que o
pipeline não reconfigure tudo isso a cada execução. Consulte os templates no
portal em Scans → Scan Templates; a coluna CI/CD Slug é exatamente o
valor que você coloca em WASV_TEMPLATE.
Para gates de pipeline, use ci-fast: ele executa somente crawl, headers
de segurança, portas expostas e TLS, pulando os módulos ativos pesados, de modo
que um gate de merge por PR ou por commit responde em segundos. Combine-o com
uma execução agendada de cobertura completa (full-coverage) contra staging
para manter a cobertura profunda sem atrasar os PRs.
Você pode usar os templates do sistema como estão ou criar os seus com + New Template (opcionalmente partindo de um existente). Veja Perfis e templates de scan para a lista completa e como montar um.
Scans → Scan Templates. A coluna CI/CD Slug é o valor de WASV_TEMPLATE.
Passo 4: Adicione o workflow
Crie .github/workflows/wasviking-dast.yml. O exemplo abaixo sobe uma
aplicação de teste com Docker Compose; substitua o step Start app pela
forma como a sua aplicação é iniciada no CI.
name: WASViking DAST
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
actions: read
jobs:
dast:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Start app
run: |
docker compose up -d
for i in {1..30}; do
curl -sf http://localhost:8080/ >/dev/null && break
sleep 2
done
- name: Install WASViking Sentinel
env:
WASV_SENTINEL_API_KEY: ${{ secrets.WASV_SENTINEL_API_KEY }}
run: |
curl -sSL -H "Authorization: ApiKey $WASV_SENTINEL_API_KEY" \
https://api.wasviking.com/api/v1/sentinel/install.sh | sh
- name: Run WASViking scan
env:
WASV_DAST_API_KEY: ${{ secrets.WASV_DAST_API_KEY }}
WV_TEMPLATE: ${{ vars.WASV_TEMPLATE }}
run: |
mkdir -p wasviking-reports
./.wasviking/wasviking-sentinel scan \
--api-key "$WASV_DAST_API_KEY" \
--template "$WV_TEMPLATE" \
--fail-on critical \
--baseline new \
--out ./wasviking-reports \
http://localhost:8080
- name: Upload SARIF to GitHub code scanning
if: always() && hashFiles('wasviking-reports/wasviking-scan.sarif') != ''
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: wasviking-reports/wasviking-scan.sarif
category: wasviking-dast
- name: Upload raw reports as artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: wasviking-reports
path: wasviking-reports/
O envio para o Code Scanning (o step de SARIF) exige o GitHub Advanced Security em repositórios privados. Se você não tem, remova esse step e use o artefato enviado no lugar.
Passo 5: Flags do scan
O comando de scan recebe a URL do alvo como último argumento:
wasviking-sentinel scan [flags] <URL_TARGET>
| Flag | Obrigatória | Descrição |
|---|---|---|
<URL_TARGET> |
Sim | URL do alvo. Precisa resolver para um endereço privado. |
--api-key |
Sim | Chave de API com o escopo ci:scan. |
--template |
Sim | Slug de um Scan Template do portal, para reutilizar uma configuração de scan padrão. |
--auth-bearer |
Não | Bearer token para executar o scan autenticado (Authorization: Bearer <token>). Prefira a variável de ambiente WV_AUTH_BEARER, para que o token nunca apareça no argv do processo nem nos logs do CI. Sobrescreve qualquer autenticação definida em --template. |
--auth-header |
Não | Header de autenticação personalizado no formato Name: value, por exemplo X-Api-Token: <value>. Prefira a variável de ambiente WV_AUTH_HEADER. Sobrescreve qualquer autenticação definida em --template. Use --auth-bearer ou --auth-header, não os dois. |
--path |
Não | Endpoint adicional a analisar primeiro, antes da superfície descoberta automaticamente. Pode ser repetida (--path /api/v1/users --path /api/v1/orders) ou informada via WV_SEED_PATHS (separados por vírgula). Caminhos relativos ou URLs de mesma origem; máximo de 500. |
--fail-on |
Não | Limiar único: critical, high, medium, low ou none. Lógica "ou acima": --fail-on high falha em high e critical. Padrão: critical. |
--baseline |
Não | new (só contam os achados ausentes na branch base) ou all. Padrão: all. |
--out |
Não | Diretório de saída para o SARIF e o JSON. Padrão: diretório atual. |
--timeout |
Não | Timeout total. Padrão: 45m. |
Dois arquivos são gerados:
wasviking-scan.sarif: SARIF 2.1.0 (GitHub Code Scanning, GitLab e outros).wasviking-scan.json: saída completa com os metadados da WASViking.
Scans autenticados
Um scan não autenticado só vê o que um visitante anônimo vê. Para analisar a área atrás de um login, a parte que de fato concentra a sua lógica de negócio, o motor precisa de uma credencial. Em vez de guardar credenciais estáticas em um Scan Template, emita um token de curta duração no pipeline e passe-o para o scan. O token permanece válido somente durante a execução.
Dois modos, escolha um:
| Modo | Flag | Variável de ambiente (preferida) | Enviado como |
|---|---|---|---|
| Bearer token | --auth-bearer <token> |
WV_AUTH_BEARER |
Authorization: Bearer <token> |
| Header personalizado | --auth-header 'Name: value' |
WV_AUTH_HEADER |
Name: value (por exemplo, X-Api-Token: …) |
Sempre passe o token pela variável de ambiente, não pela flag. Um
valor na linha de comando vai parar no argv do processo e no log do CI;
uma variável de ambiente, não. O GitHub ainda a mascara quando você usa
::add-mask::.
Como se comporta:
- Sobrescreve o template. Se uma credencial é informada, ela substitui
por inteiro o bloco
authenticationdo seu--template; todo o restante do template (regras de crawl, seleção de analisadores, perfil de conformidade) é preservado. Assim, um único template atende tanto pipelines anônimos quanto autenticados. - Criptografado em repouso, nunca registrado em log. O secret trafega
pelo mesmo canal TLS da chave de API, é criptografado em repouso e é
registrado na trilha de auditoria da execução apenas como um marcador de
modo (
bearer/header), nunca o valor. - Sem downgrade silencioso. Em caso de sucesso, a CLI imprime
Authenticated scan confirmed (mode=bearer). Se você solicita um scan autenticado mas o servidor não aplica a credencial, a CLI faz o step falhar (exit 2) em vez de rodar sem autenticação e reportar um falso resultado limpo.
Emitindo o token
O step certo depende de como a sua aplicação emite tokens. Dois padrões comuns, ambos contra a instância que você subiu no runner:
# Option A: OAuth2 client-credentials (machine-to-machine)
- name: Mint scan token (OAuth2)
env:
OAUTH_CLIENT_ID: ${{ secrets.SCAN_CLIENT_ID }}
OAUTH_CLIENT_SECRET: ${{ secrets.SCAN_CLIENT_SECRET }}
run: |
TOKEN="$(curl -sf -X POST http://localhost:8080/oauth/token \
-d grant_type=client_credentials \
-d client_id="$OAUTH_CLIENT_ID" \
-d client_secret="$OAUTH_CLIENT_SECRET" | jq -r '.access_token')"
test -n "$TOKEN" && test "$TOKEN" != null || { echo "token mint failed"; exit 1; }
echo "::add-mask::$TOKEN"
echo "WV_AUTH_BEARER=$TOKEN" >> "$GITHUB_ENV"
# Option B: app login endpoint returning {"access_token": "..."}
- name: Mint scan token (login)
env:
TEST_USER: ${{ secrets.SCAN_TEST_USER }}
TEST_PASSWORD: ${{ secrets.SCAN_TEST_PASSWORD }}
run: |
TOKEN="$(curl -sf -X POST http://localhost:8080/api/login \
-H 'Content-Type: application/json' \
-d "{\"username\":\"$TEST_USER\",\"password\":\"$TEST_PASSWORD\"}" \
| jq -r '.access_token')"
test -n "$TOKEN" && test "$TOKEN" != null || { echo "login failed"; exit 1; }
echo "::add-mask::$TOKEN"
echo "WV_AUTH_BEARER=$TOKEN" >> "$GITHUB_ENV"
Use uma conta ou um client de teste dedicado e de baixo privilégio para os scans, nunca um usuário real nem uma credencial de administrador.
Priorizando endpoints específicos (seed paths)
O motor descobre a sua superfície automaticamente (crawl, robots.txt, sitemap,
introspecção de OpenAPI/Swagger e GraphQL). Mas rotas de API e views de SPA
muitas vezes não têm links HTML que o crawler consiga seguir, então o crawler nunca chega até elas.
Liste-as explicitamente com --path e elas são analisadas primeiro, antes
da superfície descoberta automaticamente.
--path /api/v1/users \
--path /api/v1/orders \
--path '/api/v1/admin/dashboard'
Ou, de forma equivalente, pelo ambiente (separados por vírgula):
WV_SEED_PATHS=/api/v1/users,/api/v1/orders,/api/v1/admin/dashboard
- Caminhos relativos (
/api/v1/users) ou URLs de mesma origem; até 500. - Os endpoints informados passam pelo crawl e são testados ativamente primeiro; em seguida o motor segue os links deles, de modo que as subpáginas autenticadas também são descobertas.
- Combine com autenticação: o token libera as rotas protegidas, e
--pathfaz com que o scanner de fato as visite. Em caso de sucesso, a CLI imprimePriority seed paths confirmed (N applied).
Exemplo completo: scan autenticado com endpoints priorizados
.github/workflows/wasviking-dast-auth.yml. Este é o formato
corporativo: um GitHub Environment protegido para os secrets, um token emitido
em tempo de execução, as rotas de API do próprio desenvolvedor analisadas
primeiro e o SARIF publicado no Code Scanning.
name: WASViking DAST (authenticated)
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
actions: read
jobs:
dast-auth:
runs-on: ubuntu-latest
timeout-minutes: 25
# Environment secrets add required reviewers and deployment
# protection to the scan credentials. Configure under
# Settings → Environments → dast.
environment: dast
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Start app
run: |
docker compose up -d
for i in {1..30}; do
curl -sf http://localhost:8080/ >/dev/null && break
sleep 2
done
# Mint a short-lived token against the instance under test.
# Swap for your own auth flow (see "Minting the token" above).
- name: Mint scan token
env:
TEST_USER: ${{ secrets.SCAN_TEST_USER }}
TEST_PASSWORD: ${{ secrets.SCAN_TEST_PASSWORD }}
run: |
TOKEN="$(curl -sf -X POST http://localhost:8080/api/login \
-H 'Content-Type: application/json' \
-d "{\"username\":\"$TEST_USER\",\"password\":\"$TEST_PASSWORD\"}" \
| jq -r '.access_token')"
test -n "$TOKEN" && test "$TOKEN" != null || { echo "login failed"; exit 1; }
echo "::add-mask::$TOKEN"
echo "WV_AUTH_BEARER=$TOKEN" >> "$GITHUB_ENV"
- name: Install WASViking Sentinel
env:
WASV_SENTINEL_API_KEY: ${{ secrets.WASV_SENTINEL_API_KEY }}
run: |
curl -sSL -H "Authorization: ApiKey $WASV_SENTINEL_API_KEY" \
https://api.wasviking.com/api/v1/sentinel/install.sh | sh
- name: Run authenticated WASViking scan
env:
WASV_DAST_API_KEY: ${{ secrets.WASV_DAST_API_KEY }}
WV_TEMPLATE: ${{ vars.WASV_TEMPLATE }}
# WV_AUTH_BEARER was exported by the "Mint scan token" step and is
# masked. It is NOT passed as a flag, so it never reaches argv/logs.
WV_SEED_PATHS: /api/v1/users,/api/v1/orders,/api/v1/admin/dashboard
run: |
mkdir -p wasviking-reports
./.wasviking/wasviking-sentinel scan \
--api-key "$WASV_DAST_API_KEY" \
--template "$WV_TEMPLATE" \
--fail-on high \
--baseline new \
--out ./wasviking-reports \
http://localhost:8080
- name: Upload SARIF to GitHub code scanning
if: always() && hashFiles('wasviking-reports/wasviking-scan.sarif') != ''
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: wasviking-reports/wasviking-scan.sarif
category: wasviking-dast
- name: Upload raw reports as artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: wasviking-reports
path: wasviking-reports/
Tanto
WV_AUTH_BEARERquantoWV_SEED_PATHSsão lidos do ambiente pelo comandoscan, então o step de scan fica livre de secrets e de longas listas de caminhos na linha de comando. Para usar um header personalizado em vez de um bearer token, exporteWV_AUTH_HEADER(por exemplo,X-Api-Token: <value>) no step de emissão, no lugar deWV_AUTH_BEARER.
Política de fail-on
--fail-on define a menor severidade que faz o build falhar, e tudo o que
está acima dela também falha.
| Configuração | Comportamento |
|---|---|
--fail-on none |
Nunca falha. Apenas relatório. |
--fail-on critical |
Falha somente em critical. |
--fail-on high |
Falha em high e critical. Um bom padrão para PRs. |
--fail-on medium |
Falha em medium e acima. Mais rigoroso, mais atrito. |
Um rollout prático: comece em critical para manter baixo o atrito inicial
e depois aperte para high quando a sua baseline estiver limpa (em geral,
algumas sprints).
Diff de baseline
--baseline controla o que conta para a política de fail-on.
| Modo | Comportamento |
|---|---|
all (padrão) |
Todo achado conta. Use em branches de release, scans agendados e auditorias. |
new |
Só contam os achados ausentes na branch base. Use nos pull requests do dia a dia, para evitar atrito com a dívida preexistente. |
O que é e o que não é coletado
A WASViking não coleta o código-fonte do seu repositório, variáveis de
ambiente do runner além das chaves que você passa, conteúdo do sistema de
arquivos do runner fora do diretório .wasviking/, nem qualquer tráfego ou
dado de produção. Os dados são processados na região dos Estados Unidos,
criptografados em trânsito (TLS 1.2+) e em repouso (AES-256). Um DPA e a
residência de dados na União Europeia estão disponíveis sob solicitação; veja o
Trust Center para o mapeamento
completo de conformidade (ISO 27001, SOC 2, LGPD, GDPR, NIST SSDF,
OWASP DSOMM).
Problemas comuns
| Problema | Causa provável |
|---|---|
HTTP 401 Unauthorized |
Chave de API revogada, expirada ou sem o escopo ci:scan. |
HTTP 400 target must be private |
A URL do alvo resolveu para um endereço público. Analise localhost ou uma faixa privada. |
HTTP 429 quota exceeded |
Cota mensal de scans de CI/CD atingida. Aguarde a virada do ciclo, faça upgrade ou compre um pacote add-on. |
HTTP 429 concurrency limit |
Scans simultâneos demais para o seu plano. |
| Scan preso em running | A aplicação alvo não respondeu. Adicione um health check antes do step de scan. |
| SARIF vazio | A aplicação não estava acessível ou retornou apenas erros 5xx. |
Erro no download do install.sh |
Política de rede bloqueando api.wasviking.com ou o bucket de releases. |
| Achados diferem entre execuções | Comportamento não determinístico da aplicação (IDs aleatórios, timestamps). |
| O step termina com exit 2, "server did not apply any credential" | Você solicitou um scan autenticado, mas o token não foi aplicado. Verifique se o token foi emitido (não está vazio) e exportado para WV_AUTH_BEARER/WV_AUTH_HEADER. |
token mint failed / login failed |
O step de emissão recebeu um token vazio. Verifique a URL do endpoint de autenticação, as credenciais de teste e se a aplicação estava saudável antes desse step. |
| Páginas autenticadas continuam aparecendo como não alcançadas | O token expirou no meio da execução ou não tem acesso a essas rotas; e liste as rotas com --path/WV_SEED_PATHS para que o crawler as visite. |
choose only one of --auth-bearer / --auth-header |
Foram informados um bearer token e um header personalizado ao mesmo tempo. Use exatamente um. |
Onde isto se encaixa na plataforma
- A cota de scans de CI/CD e o histórico de execuções ficam em User → CI/CD Pipeline no portal.
- O mesmo gate de DAST no Bitbucket Pipelines está em DAST em CI/CD com Bitbucket Pipelines.
- Os Scan Templates são gerenciados em Perfis e templates de scan.
- O roteamento de alertas está documentado em Slack e Teams e Webhooks.
