Skip to content

ci: add signed fail-closed release supply chain - #38

Merged
itayson merged 8 commits into
mainfrom
feat/release-supply-chain-gates
Jul 16, 2026
Merged

itayson merged 8 commits into
mainfrom
feat/release-supply-chain-gates

Conversation

@itayson

@itayson itayson commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Escopo

Implementa a infraestrutura de release rastreada em #20 sem ativar publicação insegura.

Este PR:

  • adiciona configuração de assinatura release controlada exclusivamente por variáveis de ambiente;
  • permite exigir assinatura com ROCKSERVICE_REQUIRE_RELEASE_SIGNING=true, fazendo o Gradle falhar antes de gerar release sem configuração completa;
  • substitui o workflow placeholder por um pipeline de build/validação e um job separado de publicação;
  • exige os quatro secrets de assinatura antes do build;
  • materializa o keystore apenas em $RUNNER_TEMP com permissões restritas e remoção if: always();
  • executa testes, lint e assembleRelease;
  • valida o APK final com apksigner;
  • gera SHA-256 e SBOM CycloneDX;
  • gera attestation de provenance para o APK;
  • publica somente depois de baixar e reverificar o release candidate;
  • mantém contents: write exclusivamente no job final de publicação;
  • usa actions fixadas por SHA;
  • adiciona documentação de secrets, rotação, comprometimento e operação do release.

A execução manual valida release candidates mas não publica releases. A publicação automática ocorre somente em evento de tag compatível com v*.*.* e após todos os gates do job de build/validação.

Refs #20.

Risco

Moderado. A mudança afeta a infraestrutura de distribuição, mas mantém comportamento fail-closed. Sem secrets válidos, o release falha antes do build. O pipeline ainda não deve ser considerado operacionalmente concluído até os secrets reais, a proteção administrativa de tags e um release candidate assinado serem validados.

Testes

  • CI, testes e lint normais do repositório;
  • Dependency Review, porque app/build.gradle.kts é alterado;
  • Gitleaks/Supply Chain;
  • CodeQL;
  • CodeRabbit;
  • validação estática da configuração Gradle sem secrets, preservando builds debug;
  • execução real do workflow de release permanece bloqueada até configuração segura dos secrets.

Evidência de hardware

Não aplicável diretamente. O workflow não altera transporte USB nem habilita operações destrutivas. Releases destinados a hardware continuam sujeitos aos gates físicos definidos no roadmap.

Segurança

  • Keystore não versionado
  • Secrets obrigatórios antes do build release
  • Arquivo temporário com 0600
  • Remoção do keystore em if: always()
  • apksigner verify bloqueante
  • SHA-256 do APK
  • SBOM CycloneDX
  • Provenance/attestation
  • Actions fixadas por SHA
  • Build sem contents: write
  • Publicação separada e dependente de todos os gates
  • Revalidação do checksum antes da publicação
  • Escrita USB real permanece desativada

Summary by CodeRabbit

  • Novos Recursos
    • Publicação automática de releases ao criar tags de versão (v*.*.*), mantendo workflow_dispatch.
    • Pipeline de build/validação com assinatura, verificação criptográfica do APK, checksum SHA-256, SBOM (CycloneDX) e atestação de proveniência antes da publicação.
    • Geração automática de notas na GitHub Release.
  • Correções
    • Builds falham se a assinatura de release não estiver completamente configurada via variáveis de ambiente.
  • Documentação
    • Documentado o processo de segurança fail-closed para releases assinadas.
  • Chores
    • Ajustes no workflow de supply chain para melhorar a detecção de dependências e o comportamento de comentários em falhas.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@itayson, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7abd0340-dad0-4636-ae7c-414eddc34dda

📥 Commits

Reviewing files that changed from the base of the PR and between b1d398d and fffa748.

📒 Files selected for processing (1)
  • .github/workflows/security.yml
📝 Walkthrough

Walkthrough

O workflow executa releases Android assinados para tags v*.*.*, validando APK, checksum, SBOM e provenance antes da publicação. O Gradle configura a assinatura por variáveis de ambiente, a documentação registra os controles de segurança e a revisão de dependências consulta alterações do pull request.

Changes

Pipeline de release assinado

Layer / File(s) Summary
Disparo e configuração de assinatura
.github/workflows/release.yml, app/build.gradle.kts, docs/RELEASE_SECURITY.md
O workflow aceita tags versionadas e controla concorrência; o Gradle configura assinatura de release quando todas as credenciais estão disponíveis e pode exigir essa configuração.
Build e validação do artefato
.github/workflows/release.yml, docs/RELEASE_SECURITY.md
build-validate verifica o wrapper e os secrets, materializa e remove o keystore, executa testes, lint e build, valida exatamente um APK assinado e gera checksum, SBOM, provenance e artefato candidato.
Publicação da release
.github/workflows/release.yml, docs/RELEASE_SECURITY.md
publish executa após a validação de uma tag, revalida a checksum e cria a GitHub Release com APK, checksum e SBOM; a documentação descreve rotação de chaves, resposta a comprometimento e gates externos.

Revisão de dependências

Layer / File(s) Summary
Detecção e relatório de alterações
.github/workflows/security.yml
Os jobs usam fetch-depth: 1, a detecção consulta arquivos alterados do pull request pela API do GitHub e a dependency review comenta um resumo somente em caso de falha.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: rockservice-devsecops

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed O título descreve claramente a principal mudança: release assinada com fluxo fail-closed na CI.
Description check ✅ Passed A descrição cobre os seções exigidas e detalha escopo, risco, testes e segurança, apesar de pequenas variações de formato.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/release-supply-chain-gates

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 16-24: Adicionar comentários explicativos nos blocos de
permissions de .github/workflows/release.yml#L16-L24 e `#L170-L178`: no job
build-validate, documentar que id-token: write e attestations: write são
necessários para assinatura e attestation via Sigstore; no job da linha 170,
documentar que contents: write é necessário para criar a GitHub Release.
- Around line 105-133: Update the “Collect and verify signed APK” step to
extract the APK certificate fingerprint using apksigner and compare it with a
configured expected fingerprint from a repository secret or variable. Fail the
job when the reference value is missing or does not match, while preserving the
existing signature verification and checksum generation.
- Around line 30-35: Remove cache: gradle from the actions/setup-java step in
the release workflow, leaving the Temurin Java 17 configuration unchanged.
Ensure this release job does not restore or save a Gradle cache shared with
less-trusted workflow contexts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b8e0e1a6-b825-450a-8d4a-1e033d5c7368

📥 Commits

Reviewing files that changed from the base of the PR and between 2ceec8b and f927f4e.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • app/build.gradle.kts
  • docs/RELEASE_SECURITY.md

Comment on lines 16 to +24
jobs:
release-disabled:
build-validate:
name: Build and validate release candidate
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
id-token: write
attestations: write

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Blocos de permissions sem comentário explicativo (zizmor). Ambos os jobs concedem permissões elevadas e mínimas corretamente escopadas, mas o zizmor sinaliza a falta de comentários justificando cada uma — mesma causa raiz nos dois locais.

  • .github/workflows/release.yml#L16-L24: adicionar comentário explicando por que id-token: write e attestations: write são necessários (assinatura/attestation via Sigstore).
  • .github/workflows/release.yml#L170-L178: adicionar comentário explicando por que contents: write é necessário apenas neste job (criação da GitHub Release).
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 23-23: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

📍 Affects 1 file
  • .github/workflows/release.yml#L16-L24 (this comment)
  • .github/workflows/release.yml#L170-L178
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 16 - 24, Adicionar comentários
explicativos nos blocos de permissions de .github/workflows/release.yml#L16-L24
e `#L170-L178`: no job build-validate, documentar que id-token: write e
attestations: write são necessários para assinatura e attestation via Sigstore;
no job da linha 170, documentar que contents: write é necessário para criar a
GitHub Release.

Source: Linters/SAST tools

Comment on lines +30 to +35
- uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
with:
distribution: temurin
java-version: "17"
cache: gradle

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Risco de cache poisoning no setup-java com cache: gradle.

O zizmor sinaliza este passo como potencialmente vulnerável a cache poisoning. Num pipeline de release fail-closed cujo objetivo é garantir a integridade da cadeia de assinatura, restaurar um cache Gradle possivelmente escrito por execuções menos confiáveis (ex.: workflows de PR/branches) permitiria que dependências adulteradas sejam reutilizadas no build que será assinado, atestado e publicado — comprometendo justamente as garantias que o SBOM/provenance deveriam oferecer.

Recomenda-se desabilitar o cache neste job de release (ou usar uma chave de cache isolada de qualquer contexto menos confiável).

🔒 Correção sugerida
       - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
         with:
           distribution: temurin
           java-version: "17"
-          cache: gradle
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
with:
distribution: temurin
java-version: "17"
cache: gradle
- uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
with:
distribution: temurin
java-version: "17"
🧰 Tools
🪛 zizmor (1.26.1)

[error] 30-30: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): this step

(cache-poisoning)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 30 - 35, Remove cache: gradle
from the actions/setup-java step in the release workflow, leaving the Temurin
Java 17 configuration unchanged. Ensure this release job does not restore or
save a Gradle cache shared with less-trusted workflow contexts.

Source: Linters/SAST tools

Comment on lines +105 to +133
- name: Collect and verify signed APK
id: artifact
env:
RELEASE_BASENAME: ${{ steps.naming.outputs.basename }}
run: |
set -euo pipefail
mapfile -t apks < <(find app/build/outputs/apk/release -maxdepth 1 -type f -name '*.apk' -print)
if [ "${#apks[@]}" -ne 1 ]; then
echo "::error title=Unexpected release outputs::Expected exactly one release APK, found ${#apks[@]}."
printf '%s\n' "${apks[@]:-}"
exit 1
fi

mkdir -p dist
apk="dist/${RELEASE_BASENAME}.apk"
cp "${apks[0]}" "$apk"

apksigner_bin="$(command -v apksigner || true)"
if [ -z "$apksigner_bin" ]; then
apksigner_bin="$(find "$ANDROID_HOME/build-tools" -type f -name apksigner -print | sort -V | tail -n 1)"
fi
test -n "$apksigner_bin"
"$apksigner_bin" verify --verbose --print-certs "$apk"

(
cd dist
sha256sum "${RELEASE_BASENAME}.apk" > "${RELEASE_BASENAME}.apk.sha256"
)
echo "apk=$apk" >> "$GITHUB_OUTPUT"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

apksigner verify confirma assinatura, mas não a identidade da chave.

O passo garante que o APK está assinado e íntegro, mas não compara o fingerprint do certificado com um valor esperado conhecido. Em caso de erro operacional (ex.: keystore/alias trocados por engano no secret), o pipeline continuaria "verde" mesmo assinando com a chave errada — o próprio docs/RELEASE_SECURITY.md (linha 80) menciona registrar o fingerprint do certificado, mas isso não é validado automaticamente aqui.

Considere extrair o fingerprint com apksigner verify --print-certs e compará-lo a um valor de referência armazenado em secret/variável do repositório, falhando o job em caso de divergência.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 105 - 133, Update the “Collect
and verify signed APK” step to extract the APK certificate fingerprint using
apksigner and compare it with a configured expected fingerprint from a
repository secret or variable. Fail the job when the reference value is missing
or does not match, while preserving the existing signature verification and
checksum generation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/security.yml:
- Around line 53-55: Atualize a coleta de arquivos em changed_files no workflow
para detectar truncamento: compare a quantidade obtida com
pulls/$PR_NUMBER.changed_files e encerre o job com erro quando houver
divergência, antes de executar a Dependency Review. Como alternativa, derive a
lista usando git diff entre os SHAs base e head, garantindo que nenhum manifesto
omitido permita pular a verificação.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 95703b35-3940-4da3-89e6-6e628920e1ce

📥 Commits

Reviewing files that changed from the base of the PR and between f208fdf and b1d398d.

📒 Files selected for processing (1)
  • .github/workflows/security.yml

Comment thread .github/workflows/security.yml Outdated
Comment on lines +53 to +55
changed_files="$(gh api --paginate \
--jq '.[].filename' \
"repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## .github/workflows/security.yml"
wc -l .github/workflows/security.yml
sed -n '1,220p' .github/workflows/security.yml | cat -n

echo
echo "## Search for changed_files / dependency review / pulls/$PR_NUMBER usage"
rg -n "changed_files|dependency review|changed_files|pulls/\\$PR_NUMBER|changed_files" .github/workflows/security.yml .github/workflows -g '*.yml' -g '*.yaml' || true

Repository: itayson/Rockservice-mobile

Length of output: 4177


🌐 Web query:

GitHub REST API pull requests list files maximum 3000 files truncation changed_files documentation

💡 Result:

The GitHub REST API endpoint for listing files in a pull request (GET /repos/{owner}/{repo}/pulls/{pull_number}/files) is documented to return a maximum of 3,000 files [1][2][3]. When a pull request contains more than 3,000 files, the API will truncate the results [4]. Crucially, the API does not provide a warning or error message when this truncation occurs; it simply stops returning further pages [4]. Because the API silently caps the total number of files returned, you cannot rely on standard pagination (using the Link header) to retrieve files beyond this 3,000-file limit [4]. Developers encountering this limit are often advised to check the changed_files property available in the main Pull Request object (GET /repos/{owner}/{repo}/pulls/{pull_number}) [4]. The changed_files property provides an accurate count of all files changed in the pull request, which can be compared against the number of files actually returned by the list files endpoint to detect if truncation has occurred [4]. If you need to access files beyond the 3,000-file limit, common workarounds include fetching the files commit-by-commit using the List pull request commits endpoint, although this approach has its own limitations regarding total commit counts [4].

Citations:


Falhe fechado quando a lista de arquivos do PR for truncada. O endpoint GET /pulls/{pull_number}/files pode cortar resultados acima de 3.000 arquivos; nesse caso, um manifesto pode ficar fora de changed_files e a Dependency Review ser pulada indevidamente. Compare com pulls/$PR_NUMBER.changed_files e encerre o job com erro se houver divergência; outra opção é usar git diff a partir dos SHAs base e head.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/security.yml around lines 53 - 55, Atualize a coleta de
arquivos em changed_files no workflow para detectar truncamento: compare a
quantidade obtida com pulls/$PR_NUMBER.changed_files e encerre o job com erro
quando houver divergência, antes de executar a Dependency Review. Como
alternativa, derive a lista usando git diff entre os SHAs base e head,
garantindo que nenhum manifesto omitido permita pular a verificação.

Source: MCP tools

@itayson
itayson merged commit 053ea6b into main Jul 16, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant