From 26c2ed95ced60a2db075b5d4a8702011395dacd7 Mon Sep 17 00:00:00 2001 From: Denis Scarabelli <87149177+DenisCDev@users.noreply.github.com> Date: Mon, 14 Sep 2026 11:16:28 -0300 Subject: [PATCH] chore: SBOM, SECURITY.md e fuzz no CI de PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O fuzz só corria em workflow_dispatch, então um PR podia entrar sem os três alvos. Sem SECURITY.md não havia canal nem prazo interno, e o README não dizia que production-grade depende de auditoria externa. 1. SECURITY.md em pt-BR: relatório privado, prazos internos por severidade, o que ainda não é (sem auditoria externa, sem LTS). 2. Job sbom gera CycloneDX do crate e sobe artefacto; não vai para o git. 3. Job docker documenta cosign keyless e admite que não há registry. 4. Fuzz waf/protocol/dlp em pull_request, em paralelo, 45s cada. Prova: python parse do workflow (fuzz sem if, matrix dos 3 alvos); cargo cyclonedx --format json --spec-version 1.5 → ferroada.cdx.json CycloneDX 1.5, 215 componentes, gitignored. --- .github/workflows/security.yml | 71 +++++++++++++++++++++++++++++++--- .gitignore | 6 +++ README.md | 4 ++ SECURITY.md | 53 +++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 SECURITY.md diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 5c879a3..af00eaa 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -3,6 +3,7 @@ name: Segurança e qualidade on: push: branches: [main] + tags: ["v*"] pull_request: workflow_dispatch: @@ -54,6 +55,28 @@ jobs: - uses: actions/checkout@v4 - run: docker build --tag ferroada:ci . - run: docker build --tag ferroada-coraza:ci deploy/coraza + # Sem registry neste repositório: o job constrói ferroada:ci localmente + # e não faz push. Cosign keyless (OIDC do GitHub, id-token: write) fica + # para quando o operador publicar a imagem — não inventamos GHCR/Hub. + - name: Cosign (sem registry) + run: | + cat <<'EOF' + Este repositório não publica a imagem num registry (GHCR, Docker Hub + ou outro). O job constrói ferroada:ci localmente e para aí. + + Quando existir registry, assinar com cosign keyless via OIDC do + GitHub (sem chave longa no repo): + + permissions: + id-token: write + contents: read + - uses: sigstore/cosign-installer@v3 + - docker push "$IMAGE" + - cosign sign --yes "$IMAGE@$DIGEST" + + Identidade: https://token.actions.githubusercontent.com + Subject: repo:DenisCDev/ferroada:* + EOF packs: runs-on: ubuntu-latest @@ -72,14 +95,52 @@ jobs: version: v3.17.3 - run: python3 deploy/helm/assert-chart.py + sbom: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@v2 + with: + tool: cargo-cyclonedx + - name: Gerar CycloneDX do crate ferroada + run: | + set -euo pipefail + cargo cyclonedx --format json --spec-version 1.5 --describe crate + if [ -f ferroada.cdx.json ]; then + : + elif [ -f bom.json ]; then + mv bom.json ferroada.cdx.json + else + echo "SBOM JSON do crate ferroada não foi gerado" >&2 + ls -la + exit 1 + fi + python3 -c "import json; d=json.load(open('ferroada.cdx.json')); assert d.get('bomFormat')=='CycloneDX', d" + - uses: actions/upload-artifact@v4 + with: + name: sbom-ferroada-cyclonedx + path: ferroada.cdx.json + if-no-files-found: error + retention-days: 30 + fuzz: - if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + target: [waf, protocol, dlp] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly + with: + components: rust-src + - uses: Swatinem/rust-cache@v2 + with: + workspaces: fuzz + shared-key: fuzz-nightly - run: cargo install cargo-fuzz --locked - - run: cargo fuzz run waf -- -max_total_time=45 - - run: cargo fuzz run protocol -- -max_total_time=45 - - run: cargo fuzz run dlp -- -max_total_time=45 + - run: cargo fuzz run ${{ matrix.target }} -- -max_total_time=45 diff --git a/.gitignore b/.gitignore index be0eb05..c0da60e 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,9 @@ Thumbs.db # Claude Code .claude/ + +# SBOM gerado localmente — o CI publica como artefacto, não no git +*.cdx.json +*.cdx.xml +bom.json +bom.xml diff --git a/README.md b/README.md index 83075c6..87a67a2 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ conexão HTTP/2 a 100 streams simultâneos, rejeitando headers excessivos com 43 antes da aplicação. O Ferroada fixa a versão exata no manifesto e no lockfile para que build local, container e CI usem a mesma base. +O Ferroada **não** é production-grade: falta auditoria de segurança externa +e não há linha LTS. Relatar falhas e o que isto cobre: [SECURITY.md](SECURITY.md). + ``` Internet → [Ferroada :3000] → Seu Sistema :8080 ↓ @@ -739,3 +742,4 @@ Cliente (resposta limpa e hardened) - **Rust** — binário único, sem runtime, sem garbage collector - **Distroless** — imagem Docker sem shell, sem package manager (~20MB) - **Zero dependências externas** — não precisa de Redis, banco de dados nem serviço auxiliar +- **Ainda não é production-grade** — falta auditoria externa e LTS. Ver [SECURITY.md](SECURITY.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..d0e1e6c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,53 @@ +# Política de segurança + +O Ferroada **não** é production-grade. Falta auditoria de segurança externa +e não há linha LTS. Tratar o binário como “pronto para produção global” +depende dessas duas coisas, não deste ficheiro. + +`SMAUG_AUDIT.md` (2026-08-31) é revisão interna do repositório. Não substitui +auditoria externa. + +## Como reportar uma falha + +Não abra issue pública com PoC, payload ou passos que permitam explorar. + +1. Use o [relatório privado de vulnerabilidade do GitHub](https://github.com/DenisCDev/ferroada/security/advisories/new) neste repositório. +2. Inclua: versão ou commit, configuração relevante (**sem** segredos), passos para reproduzir, impacto esperado. +3. Se o formulário privado não estiver activo, contacte o maintainer por mensagem privada no GitHub. Não publique o exploit. + +Não há CNA neste projecto, não atribuímos CVE, e não há bug bounty. + +## Prazos internos + +Metas de trabalho, não SLA contratual e não janela LTS. Um atraso não torna +o produto production-grade. + +| Severidade | O que conta | Primeira resposta | Correção visada | +| --- | --- | --- | --- | +| Crítica | RCE no proxy, bypass total do WAF em rota fail-closed, leak de token/dashboard | 2 dias úteis | 7 dias úteis | +| Alta | Bypass parcial, DoS no data plane, leak de PII que o DLP deveria parar | 5 dias úteis | 14 dias úteis | +| Média | Falha que exige config insegura do operador, ou impacto limitado | 10 dias úteis | 30 dias úteis | +| Baixa | Hardening, disclosure menor, dívida de dependência transitiva | 15 dias úteis | próximo ciclo | + +“Correção visada” é o patch na `main` (ou o workaround documentado). Versões +antigas **não** têm janela de patch. + +## O que isto ainda não é + +- Sem auditoria de segurança externa. +- Sem linha LTS: só a `main` e a última tag, se existir, recebem correção. +- Sem CNA, sem atribuição de CVE daqui, sem programa de recompensa. +- Sem operator Kubernetes, sem OIDC/mTLS no dashboard. +- A imagem de CI **não** é publicada nem assinada: este repositório não tem + registry do Ferroada (nem GHCR, nem Docker Hub). Cosign keyless (OIDC do + GitHub Actions, sem chave longa no repo) está documentado no job `docker` + de `.github/workflows/security.yml` para quando houver registry. Até lá, + o job constrói `ferroada:ci` localmente e para. + +## O que já existe (sem vender como selo) + +- `cargo audit --deny warnings` no `Dockerfile` e no job `rust` do CI. + Exceções transitivas do Pingora 0.8.1 estão em `.cargo/audit.toml`. +- Fuzz `waf`, `protocol` e `dlp` no CI de pull request (tempo curto por alvo). +- SBOM CycloneDX do crate `ferroada` como **artefacto** de CI, não commitado + no git.