-
Notifications
You must be signed in to change notification settings - Fork 0
Add CI gates, OpenAPI & RLS validation, VPS deploy, logger redaction and idempotency support #132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
765ec53
6a34708
06bf5c4
bc34e3a
3ee6dc8
1dc0c9b
ad43043
0ad35b5
213c567
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| .git | ||
| .github | ||
| .husky | ||
| node_modules | ||
| dist | ||
| coverage | ||
| playwright-report | ||
| test-results | ||
| .vscode | ||
| .idea | ||
| *.log | ||
| .env | ||
| .env.* | ||
| !.env.example | ||
| Dockerfile* | ||
| docker-compose*.yml | ||
| README.md | ||
| docs/auditorias |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,102 @@ | ||||||||||
| name: Deploy VPS | ||||||||||
|
|
||||||||||
| on: | ||||||||||
| workflow_dispatch: | ||||||||||
| inputs: | ||||||||||
| environment: | ||||||||||
| description: "Target environment" | ||||||||||
| required: true | ||||||||||
| default: "staging" | ||||||||||
| type: choice | ||||||||||
| options: | ||||||||||
| - staging | ||||||||||
| - production | ||||||||||
| image_tag: | ||||||||||
| description: "Optional image tag override" | ||||||||||
| required: false | ||||||||||
| type: string | ||||||||||
|
|
||||||||||
| permissions: | ||||||||||
| contents: read | ||||||||||
| packages: write | ||||||||||
|
|
||||||||||
| concurrency: | ||||||||||
| group: deploy-vps-${{ inputs.environment }} | ||||||||||
| cancel-in-progress: false | ||||||||||
|
|
||||||||||
| env: | ||||||||||
| REGISTRY: ghcr.io | ||||||||||
| IMAGE_NAME: ${{ github.repository }}/zapp-web | ||||||||||
|
|
||||||||||
| jobs: | ||||||||||
| build-and-push: | ||||||||||
| name: Build and push image | ||||||||||
| runs-on: ubuntu-latest | ||||||||||
| environment: ${{ inputs.environment }} | ||||||||||
| outputs: | ||||||||||
| image: ${{ steps.meta.outputs.image }} | ||||||||||
| steps: | ||||||||||
| - name: Checkout | ||||||||||
| uses: actions/checkout@v4 | ||||||||||
|
|
||||||||||
| - name: Setup Docker Buildx | ||||||||||
| uses: docker/setup-buildx-action@v3 | ||||||||||
|
|
||||||||||
| - name: Login to GHCR | ||||||||||
| uses: docker/login-action@v3 | ||||||||||
| with: | ||||||||||
| registry: ${{ env.REGISTRY }} | ||||||||||
| username: ${{ github.actor }} | ||||||||||
| password: ${{ secrets.GITHUB_TOKEN }} | ||||||||||
|
|
||||||||||
| - name: Compute image metadata | ||||||||||
| id: meta | ||||||||||
| run: | | ||||||||||
| TAG="${{ inputs.image_tag }}" | ||||||||||
| if [ -z "$TAG" ]; then TAG="${{ inputs.environment }}-${GITHUB_SHA::12}"; fi | ||||||||||
| IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}" | ||||||||||
| echo "image=$IMAGE" >> "$GITHUB_OUTPUT" | ||||||||||
|
|
||||||||||
| - name: Build and push | ||||||||||
| uses: docker/build-push-action@v6 | ||||||||||
| with: | ||||||||||
| context: . | ||||||||||
| push: true | ||||||||||
| tags: ${{ steps.meta.outputs.image }} | ||||||||||
| build-args: | | ||||||||||
| VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL }} | ||||||||||
| VITE_SUPABASE_ANON_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY }} | ||||||||||
| VITE_EXTERNAL_SUPABASE_URL=${{ secrets.VITE_EXTERNAL_SUPABASE_URL }} | ||||||||||
| VITE_EXTERNAL_SUPABASE_ANON_KEY=${{ secrets.VITE_EXTERNAL_SUPABASE_ANON_KEY }} | ||||||||||
| VITE_SENTRY_DSN=${{ secrets.VITE_SENTRY_DSN }} | ||||||||||
| VITE_SENTRY_ENVIRONMENT=${{ inputs.environment }} | ||||||||||
| VITE_APP_ENV=${{ inputs.environment }} | ||||||||||
| cache-from: type=gha | ||||||||||
| cache-to: type=gha,mode=max | ||||||||||
|
|
||||||||||
| deploy: | ||||||||||
| name: Restart on VPS | ||||||||||
| runs-on: ubuntu-latest | ||||||||||
| needs: build-and-push | ||||||||||
| environment: ${{ inputs.environment }} | ||||||||||
| if: ${{ vars.VPS_DEPLOY_ENABLED == 'true' }} | ||||||||||
| steps: | ||||||||||
| - name: Deploy via SSH | ||||||||||
| uses: appleboy/ssh-action@v1.2.0 | ||||||||||
| env: | ||||||||||
| ZAPP_WEB_IMAGE: ${{ needs.build-and-push.outputs.image }} | ||||||||||
| ZAPP_WEB_HOST: ${{ vars.ZAPP_WEB_HOST }} | ||||||||||
| ZAPP_WEB_DEPLOY_DIR: ${{ vars.ZAPP_WEB_DEPLOY_DIR }} | ||||||||||
| with: | ||||||||||
| host: ${{ secrets.VPS_HOST }} | ||||||||||
| username: ${{ secrets.VPS_USER }} | ||||||||||
| key: ${{ secrets.VPS_SSH_KEY }} | ||||||||||
| envs: ZAPP_WEB_IMAGE,ZAPP_WEB_HOST,ZAPP_WEB_DEPLOY_DIR | ||||||||||
| script: | | ||||||||||
|
Comment on lines
+91
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In appleboy/ssh-action, the input parameter is named fingerprint. It is documented as: 1) Purpose The fingerprint input is used for SSH host key fingerprint verification to help prevent man-in-the-middle attacks. [1][2] 2) Expected value / format It expects the SHA256 fingerprint of the host public key. The README/CLI docs show computing it from the server’s host key file, extracting the fingerprint with Citations:
🏁 Script executed: cat -n .github/workflows/deploy-vps.yml | sed -n '85,100p'Repository: adm01-debug/zapp-web Length of output: 898 Adicionar pinning da identidade do host SSH no appleboy/ssh-action. Sem o parâmetro with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
+ fingerprint: ${{ secrets.VPS_HOST_FINGERPRINT }}
envs: ZAPP_WEB_IMAGE,ZAPP_WEB_HOST,ZAPP_WEB_DEPLOY_DIRExtraia o fingerprint do servidor com 🤖 Prompt for AI Agents |
||||||||||
| set -euo pipefail | ||||||||||
| cd "/opt/zapp-web" | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The deploy script ignores Prompt for AI agents
Suggested change
|
||||||||||
| export ZAPP_WEB_IMAGE="$ZAPP_WEB_IMAGE" | ||||||||||
| export ZAPP_WEB_HOST="${ZAPP_WEB_HOST:-zapp.atomicabr.com.br}" | ||||||||||
| docker compose pull zapp-web || true | ||||||||||
|
adm01-debug marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Do not swallow Prompt for AI agents
Suggested change
|
||||||||||
| docker compose up -d zapp-web | ||||||||||
|
Comment on lines
+100
to
+101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Primeiro, vamos encontrar e examinar o arquivo de workflow
find . -name "deploy-vps.yml" -type fRepository: adm01-debug/zapp-web Length of output: 99 🏁 Script executed: # Se o arquivo foi encontrado, vamos examinar seu conteúdo
cat -n .github/workflows/deploy-vps.yml | head -120Repository: adm01-debug/zapp-web Length of output: 3970 🌐 Web query:
💡 Result: Behavior summary for Citations:
O workflow conclui com sucesso antes do serviço estar saudável.
🔧 Solução- docker compose up -d zapp-web
+ docker compose up -d --wait --wait-timeout 60 zapp-web📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| docker image prune -f | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Este comando remove todas as imagens dangling do VPS, não apenas as relacionadas ao zapp-web. Se houver outros serviços no mesmo servidor, imagens deles podem ser removidas inadvertidamente. 🔧 Alternativa mais seguraFiltrar apenas imagens do repositório específico: - docker image prune -f
+ docker image prune -f --filter "label=org.opencontainers.image.source=https://github.com/${{ github.repository }}"Ou remover apenas a imagem anterior após confirmar que a nova está rodando: - docker image prune -f
+ # Prune apenas se deploy bem-sucedido
+ docker compose ps zapp-web --format '{{.Status}}' | grep -q 'Up' && docker image prune -f --filter "until=24h"🤖 Prompt for AI Agents |
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -35,25 +35,33 @@ jobs: | |||||||||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||||
| GITLEAKS_ENABLE_SUMMARY: "false" | ||||||||||
|
|
||||||||||
| rls-audit: | ||||||||||
| name: RLS & Compliance Weekly Report | ||||||||||
| dependency-review: | ||||||||||
| name: Dependency Review | ||||||||||
| runs-on: ubuntu-latest | ||||||||||
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | ||||||||||
| if: github.event_name == 'pull_request' | ||||||||||
| steps: | ||||||||||
| - name: Checkout code | ||||||||||
| uses: actions/checkout@v4 | ||||||||||
| - name: Setup Node.js | ||||||||||
| uses: actions/setup-node@v4 | ||||||||||
| - name: Dependency Review | ||||||||||
| uses: actions/dependency-review-action@v4 | ||||||||||
| with: | ||||||||||
| node-version: '20' | ||||||||||
| fail-on-severity: high | ||||||||||
|
|
||||||||||
| rls-audit: | ||||||||||
| name: RLS & Compliance Report | ||||||||||
| runs-on: ubuntu-latest | ||||||||||
| steps: | ||||||||||
| - name: Checkout code | ||||||||||
| uses: actions/checkout@v4 | ||||||||||
| - name: Setup Bun | ||||||||||
| uses: oven-sh/setup-bun@v1 | ||||||||||
| - name: Install dependencies | ||||||||||
| run: npm install --no-audit --no-fund | ||||||||||
| run: bun install --frozen-lockfile | ||||||||||
| - name: Generate RLS Report | ||||||||||
| run: bun scripts/verify_rls_compliance.ts > rls-compliance-report.md | ||||||||||
| - name: Publish Weekly Compliance | ||||||||||
| run: bun run security:rls | tee rls-compliance-report.md | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: The RLS check can fail silently because this piped command runs without Prompt for AI agents
Suggested change
|
||||||||||
| - name: Publish RLS Compliance | ||||||||||
| uses: actions/upload-artifact@v4 | ||||||||||
| if: always() | ||||||||||
| with: | ||||||||||
| name: weekly-security-compliance-report | ||||||||||
| name: rls-compliance-report | ||||||||||
| path: rls-compliance-report.md | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # syntax=docker/dockerfile:1.7 | ||
|
|
||
| FROM oven/bun:1.2-alpine AS deps | ||
| WORKDIR /app | ||
| COPY package.json bun.lock ./ | ||
| RUN bun install --frozen-lockfile | ||
|
|
||
| FROM deps AS builder | ||
| WORKDIR /app | ||
| COPY . . | ||
|
|
||
| ARG VITE_SUPABASE_URL | ||
| ARG VITE_SUPABASE_ANON_KEY | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new VPS image only injects Useful? React with 👍 / 👎. |
||
| ARG VITE_EXTERNAL_SUPABASE_URL | ||
| ARG VITE_EXTERNAL_SUPABASE_ANON_KEY | ||
| ARG VITE_SENTRY_DSN | ||
| ARG VITE_SENTRY_ENVIRONMENT=production | ||
| ARG VITE_APP_ENV=production | ||
|
|
||
| ENV VITE_SUPABASE_URL=${VITE_SUPABASE_URL} | ||
| ENV VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY} | ||
| ENV VITE_EXTERNAL_SUPABASE_URL=${VITE_EXTERNAL_SUPABASE_URL} | ||
| ENV VITE_EXTERNAL_SUPABASE_ANON_KEY=${VITE_EXTERNAL_SUPABASE_ANON_KEY} | ||
| ENV VITE_SENTRY_DSN=${VITE_SENTRY_DSN} | ||
| ENV VITE_SENTRY_ENVIRONMENT=${VITE_SENTRY_ENVIRONMENT} | ||
| ENV VITE_APP_ENV=${VITE_APP_ENV} | ||
|
|
||
| RUN bun run build | ||
|
|
||
| FROM nginx:1.27-alpine AS runtime | ||
| COPY nginx.conf /etc/nginx/conf.d/default.conf | ||
| COPY --from=builder /app/dist /usr/share/nginx/html | ||
| EXPOSE 80 | ||
| HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ | ||
| CMD wget -q -O - http://127.0.0.1/healthz >/dev/null || exit 1 | ||
| CMD ["nginx", "-g", "daemon off;"] | ||
|
Comment on lines
+30
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Dockerfile: instruções relevantes =="
rg -n '^(FROM|USER|EXPOSE|HEALTHCHECK|CMD)\b' Dockerfile
echo
echo "== Verificação de USER explícito =="
if rg -n '^USER\s+' Dockerfile; then
echo "OK: há USER explícito."
else
echo "FALHA: não há USER explícito."
fiRepository: adm01-debug/zapp-web Length of output: 393 🏁 Script executed: # Procurar pelo nginx.conf
fd -name "nginx.conf" -type fRepository: adm01-debug/zapp-web Length of output: 295 🏁 Script executed: # Procurar pelo docker-compose.yml
fd -name "docker-compose.yml" -o -name "docker-compose.yaml" -type fRepository: adm01-debug/zapp-web Length of output: 295 🌐 Web query:
💡 Result: The Docker image nginxinc/nginx-unprivileged:1.27-alpine is available on Docker Hub. [1][2] Evidence: - Docker Hub shows the existence of image layer details specifically for the tag nginxinc/nginx-unprivileged:1.27-alpine (two layer image digests are listed under that tag page), indicating the tag has been published and has stored image layers retrievable from Docker Hub. [1][2] - The nginx/docker-nginx-unprivileged project publishes pre-built images to registries including Docker Hub and describes that “the last built tag of every release is kept indefinitely” in those registries (so a released tag like 1.27-alpine should remain available barring extraordinary registry-side changes). [3] Practical check / pull: - You can verify directly with: docker pull nginxinc/nginx-unprivileged:1.27-alpine (the tag is expected to work given the Docker Hub tag’s layer availability). [1][2] Notes on tag naming (to avoid confusion): - This repository has many tag variants (e.g., -perl or with Alpine/OS versions). The exact tag requested is 1.27-alpine, not alpine3.x-specific tags. [4] Citations:
🏁 Script executed: fd nginx.confRepository: adm01-debug/zapp-web Length of output: 127 🏁 Script executed: fd docker-composeRepository: adm01-debug/zapp-web Length of output: 83 🏁 Script executed: cat -n nginx.confRepository: adm01-debug/zapp-web Length of output: 1528 🏁 Script executed: cat -n docker-compose.ymlRepository: adm01-debug/zapp-web Length of output: 1593 Remova acesso root da imagem nginx no runtime. O container roda como root porque
A imagem unprivileged roda na porta 8080 por padrão e não pode ouvir em portas < 1024 sem privilégios. 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: adm01-debug/zapp-web
Length of output: 3970
Escopo de
packages: writeestá amplo demais.Como
permissionsno topo vale para todos os jobs, odeployherdapackages: writesem precisar. O job apenas executadocker compose pull(read-only) e comandos remotos; não publica imagem. Mude a permissão para o jobbuild-and-pushe deixe o workflow em leitura, reduzindo o impacto de qualquer comprometimento no deploy.Sugestão
📝 Committable suggestion
🤖 Prompt for AI Agents