WASViking Docs
⌘K
Primeiros passos

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

  1. O runner baixa e instala o agente Sentinel usando o token do Sentinel.
  2. O agente provisiona um bundle mTLS efêmero (válido por 60 minutos, não reutilizável) a partir da API da WASViking.
  3. O agente abre um túnel gRPC sobre mTLS até o servidor de túnel da WASViking.
  4. 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.
  5. 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.
  6. O agente grava wasviking-scan.sarif e wasviking-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-sentinel possa 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.

Diálogo Add Sentinel Agent com o campo Agent display name Sentinel → Sentinel Agents → Add Sentinel Agent.

Agente Sentinel criado, mostrando o token de bootstrap exibido uma única vez 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.

System Settings, aba API Keys, com a lista de chaves Settings → System Settings → API Keys.

Seletor de escopos da chave de API com ci:scan selecionado 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.

Lista de Scan Templates com a coluna CI/CD Slug 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 authentication do 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 --path faz com que o scanner de fato as visite. Em caso de sucesso, a CLI imprime Priority 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_BEARER quanto WV_SEED_PATHS são lidos do ambiente pelo comando scan, 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, exporte WV_AUTH_HEADER (por exemplo, X-Api-Token: <value>) no step de emissão, no lugar de WV_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