Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .dockerignore
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
24 changes: 22 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,32 @@ jobs:
- name: Install dependencies
run: bun install

- name: Typecheck
run: bun run typecheck

- name: Strict core typecheck
run: bun run typecheck:strict:core

- name: Lint
run: bun run lint


- name: Domain boundary checks
run: |
bun run check:domain
bun run check:barrels

- name: API contract validation
run: bun run api:validate

- name: VPS readiness checks
run: bun run vps:check

- name: Build
run: bun run build


- name: Performance budget
run: bun run perf:budget

- name: Vitest (Unit & Fuzz)
run: bunx vitest run --coverage

Expand Down
102 changes: 102 additions & 0 deletions .github/workflows/deploy-vps.yml
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
Comment on lines +19 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/deploy-vps.yml

Repository: adm01-debug/zapp-web

Length of output: 3970


Escopo de packages: write está amplo demais.

Como permissions no topo vale para todos os jobs, o deploy herda packages: write sem precisar. O job apenas executa docker compose pull (read-only) e comandos remotos; não publica imagem. Mude a permissão para o job build-and-push e deixe o workflow em leitura, reduzindo o impacto de qualquer comprometimento no deploy.

Sugestão
 permissions:
   contents: read
-  packages: write
 
 jobs:
   build-and-push:
     name: Build and push image
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
     environment: ${{ inputs.environment }}
📝 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
permissions:
contents: read
packages: write
permissions:
contents: read
jobs:
build-and-push:
name: Build and push image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
environment: ${{ inputs.environment }}
🤖 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/deploy-vps.yml around lines 19 - 21, A permissão global
"packages: write" é demasiada — remova ou altere a entrada "packages: write" do
bloco permissions e mantenha apenas "contents: read" no topo; então adicionar
explicitamente "permissions: packages: write" apenas no bloco do job
"build-and-push" (onde as ações publicam imagens) e garanta que o job "deploy"
permaneça com leitura (ou sem escrever pacotes), já que este só executa "docker
compose pull" e comandos remotos; atualize a definição dos jobs referenciando o
nome "build-and-push" e verifique que não exista outro job que precise de write
global.


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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

appleboy/ssh-action fingerprint parameter documentation

💡 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 cut -d ' ' -f2. [1][2] Example command (replace ed25519 with your key type and example.com with your host): ssh example.com ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub | cut -d ' ' -f2 [1][2] 3) When it’s optional / default The documentation states the default behavior is to skip host fingerprint verification unless you provide the fingerprint value. [3] 4) Where it appears in configuration It’s shown in the action inputs example as fingerprint: ${{ secrets.FINGERPRINT }} alongside host, username, key, and port. [1][2] 5) Common pitfall: matching the correct host key type If your host has multiple host key types (rsa/ecdsa/ed25519), you must use the fingerprint corresponding to the host key type the SSH client negotiates; otherwise you can get a “host key fingerprint mismatch”. Community troubleshooting indicates that switching between ed25519/ecdsa/rsa fingerprints can make verification succeed. [4] So, the “fingerprint parameter documentation” answer: - Parameter name: fingerprint [1] - Description: SHA256 fingerprint of the host public key; used for host fingerprint verification to prevent MITM; default is to skip verification if not set. [1][3] - How to derive it: run ssh-keygen against the host’s /etc/ssh/ssh_host_<keytype>_key.pub file and extract the fingerprint field with cut -d ' ' -f2 (as shown in the docs). [1][2] - Provide the right key-type fingerprint to avoid mismatch errors. [4]

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 fingerprint, o deploy conecta no servidor SSH sem validar sua chave pública, deixando aberta a porta para ataques de MITM. O appleboy/ssh-action@v1.2.0 suporta esse parâmetro nativamente — é direto adicionar:

         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_DIR

Extraia o fingerprint do servidor com ssh-keygen -l -f /etc/ssh/ssh_host_<keytype>_key.pub | cut -d ' ' -f2 e armazene como secret.

🤖 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/deploy-vps.yml around lines 91 - 95, Add SSH host key
pinning to the appleboy/ssh-action invocation by setting the fingerprint
parameter to the server's public key fingerprint (stored as a secret); update
the deploy workflow entry that uses appleboy/ssh-action@v1.2.0 (the block with
host, username, key, envs, script) to include fingerprint: ${{
secrets.VPS_SSH_FINGERPRINT }} so the action validates the server key before
connecting (generate and save the fingerprint using ssh-keygen as described and
add it to repository secrets).

set -euo pipefail
cd "/opt/zapp-web"

@cubic-dev-ai cubic-dev-ai Bot May 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The deploy script ignores ZAPP_WEB_DEPLOY_DIR and hardcodes /opt/zapp-web, which can fail deployments in environments configured with a different path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/deploy-vps.yml, line 97:

<comment>The deploy script ignores `ZAPP_WEB_DEPLOY_DIR` and hardcodes `/opt/zapp-web`, which can fail deployments in environments configured with a different path.</comment>

<file context>
@@ -0,0 +1,102 @@
+          envs: ZAPP_WEB_IMAGE,ZAPP_WEB_HOST,ZAPP_WEB_DEPLOY_DIR
+          script: |
+            set -euo pipefail
+            cd "/opt/zapp-web"
+            export ZAPP_WEB_IMAGE="$ZAPP_WEB_IMAGE"
+            export ZAPP_WEB_HOST="${ZAPP_WEB_HOST:-zapp.atomicabr.com.br}"
</file context>
Suggested change
cd "/opt/zapp-web"
cd "${ZAPP_WEB_DEPLOY_DIR:-/opt/zapp-web}"
Fix with Cubic

export ZAPP_WEB_IMAGE="$ZAPP_WEB_IMAGE"
export ZAPP_WEB_HOST="${ZAPP_WEB_HOST:-zapp.atomicabr.com.br}"
docker compose pull zapp-web || true
Comment thread
adm01-debug marked this conversation as resolved.

@cubic-dev-ai cubic-dev-ai Bot May 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Do not swallow docker compose pull failures; this can silently deploy a stale image while the workflow still passes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/deploy-vps.yml, line 100:

<comment>Do not swallow `docker compose pull` failures; this can silently deploy a stale image while the workflow still passes.</comment>

<file context>
@@ -0,0 +1,102 @@
+            cd "/opt/zapp-web"
+            export ZAPP_WEB_IMAGE="$ZAPP_WEB_IMAGE"
+            export ZAPP_WEB_HOST="${ZAPP_WEB_HOST:-zapp.atomicabr.com.br}"
+            docker compose pull zapp-web || true
+            docker compose up -d zapp-web
+            docker image prune -f
</file context>
Suggested change
docker compose pull zapp-web || true
docker compose pull zapp-web
Fix with Cubic

docker compose up -d zapp-web
Comment on lines +100 to +101

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# Primeiro, vamos encontrar e examinar o arquivo de workflow
find . -name "deploy-vps.yml" -type f

Repository: 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 -120

Repository: adm01-debug/zapp-web

Length of output: 3970


🌐 Web query:

docker compose up --wait flag documentation behavior

💡 Result:

Behavior summary for docker compose up --wait (as of Docker Compose CLI docs): 1) What --wait does - docker compose up --wait makes Compose wait until services are “running|healthy” before the up command returns. It also implies detached mode (-d) [1]. 2) What it waits for (running vs healthy) - The docs describe it as “Wait for services to be running|healthy” [1]. In practice, this corresponds to Compose checking container state and/or healthcheck status to reach a suitable ready state. 3) Which services it waits for - When using --wait, Compose will wait for all services in the project to become running/healthy (not just dependency services). This is explicitly discussed as an intended effect: “up --wait makes compose wait for all services to be healthy, even for those not used as dependencies” [2]. 4) How it relates to healthchecks - If services have healthcheck configured, Compose uses that to determine the “healthy” state that --wait is waiting for [1]. 5) Timeout behavior - docker compose up also has --wait-timeout, described as “Maximum duration in seconds to wait for the project to be running|healthy” [1]. - Community reports indicate --wait-timeout is a maximum wait but Compose can stop waiting early if the engine reports a container as unhealthy (i.e., not just “keep waiting until the full timeout elapses”) [3]. 6) Exit/return-code expectations (important caveat) - --wait is not a general “wait until everything completes successfully” mechanism for short-lived jobs. - Compose expects services to stay running and become healthy; if containers exit (even successfully) while other services/jobs are involved, up --wait may still return a non-zero status because a “service” exited [4][5]. One issue report notes that init/setup containers exiting (0) can lead up --wait to exit with code 1 [4]. Another issue frames the limitation as: Compose doesn’t have an explicit concept for “jobs expected to exit” when using --wait [5]. Concrete takeaway for your wording - Use docker compose up --wait when you want your stack to be started and ready (running/healthy) before continuing, and expect the command to return while containers continue in the background (detached) [1]. - If you need to wait for one-off completion of tasks/jobs, --wait is not the right primitive; Compose issues report that it’s designed around services becoming running/healthy rather than jobs exiting successfully [4][5].

Citations:


O workflow conclui com sucesso antes do serviço estar saudável.

docker compose up -d retorna imediatamente, deixando o container iniciando em background. Sem esperar explicitamente, o job marca sucesso enquanto o zapp-web ainda está subindo, causando falsos positivos de deploy bem-sucedido.

🔧 Solução
-            docker compose up -d zapp-web
+            docker compose up -d --wait --wait-timeout 60 zapp-web
📝 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
docker compose pull zapp-web || true
docker compose up -d zapp-web
docker compose pull zapp-web || true
docker compose up -d --wait --wait-timeout 60 zapp-web
🤖 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/deploy-vps.yml around lines 100 - 101, The deploy job
currently runs "docker compose up -d zapp-web" and exits before the service is
healthy; modify the workflow to wait for the container to reach a healthy state
after "docker compose up -d zapp-web" by polling the service health (e.g., use
"docker compose ps" or "docker inspect" on the "zapp-web" container ID or run
repeated HTTP probes against the app) and only exit successfully once the health
status is "healthy" or the HTTP probe returns a 2xx/3xx; fail the step if a
configurable timeout is exceeded. Ensure the wait logic is implemented
immediately after the existing "docker compose up -d zapp-web" command so the
job reflects the actual service readiness.

docker image prune -f

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

docker image prune -f é operação destrutiva não escopada.

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 segura

Filtrar 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
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/deploy-vps.yml at line 101, O comando "docker image prune
-f" é destrutivo e remove todas as imagens dangling no VPS; substitua-o por uma
ação segura: ao invés de usar "docker image prune -f" no workflow, filtre apenas
imagens do repositório/serviço (por exemplo usando --filter com label ou
reference) ou remova explicitamente a imagem anterior do zapp-web somente após
confirmar que a nova está em execução (por exemplo, obter o ID/TAG antigo e
executar docker image rm <OLD_IMAGE> depois da verificação). Localize e
substituir a ocorrência "docker image prune -f" no arquivo
.github/workflows/deploy-vps.yml (referência: o token "docker image prune -f")
por uma dessas alternativas seguras.

28 changes: 18 additions & 10 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve RLS audit failures through tee

When bun run security:rls detects non-compliant tables it exits non-zero, but this step pipes it to tee without enabling pipefail; in bash the pipeline status is the last command's status, so a successful tee makes the workflow pass while only uploading the failure report. Add set -o pipefail (or redirect output without a pipeline) so PRs/pushes are actually blocked by the RLS gate.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot May 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 pipefail by default.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/security.yml, line 61:

<comment>The RLS check can fail silently because this piped command runs without `pipefail` by default.</comment>

<file context>
@@ -35,25 +35,33 @@ jobs:
       - 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
+      - name: Publish RLS Compliance
         uses: actions/upload-artifact@v4
</file context>
Suggested change
run: bun run security:rls | tee rls-compliance-report.md
run: |
set -o pipefail
bun run security:rls | tee rls-compliance-report.md
Fix with Cubic

- 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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# 📜 Changelog — ZAPP WEB

## [Unreleased] - 2026-05-13
### Adicionado
- Gate estático de compliance RLS (`scripts/verify_rls_compliance.ts`) integrado ao workflow de segurança.
- Budget de performance pós-build (`scripts/check-performance-budget.mjs`) integrado ao CI.
- Baseline de autenticação privilegiada e SLOs operacionais iniciais.
- Redaction central de PII/secrets para o logger do frontend.
- Contrato OpenAPI mínimo para Edge Functions críticas com validação automatizada.
- Idempotência por `x-idempotency-key` no endpoint `public-api` para evitar envios duplicados em retries.

### Alterado
- Pipeline de CI agora executa typecheck, typecheck strict do core, boundaries de domínio, validação de barrels, contrato OpenAPI e performance budget.
- Workflow de segurança agora publica relatório RLS em PR/push/schedule e adiciona dependency review em PRs.

### Corrigido
- Dependência `lint-staged` ausente no Husky pre-commit.
- Gaps de RLS/policies em tabelas operacionais detectadas pela auditoria estática.

## [2.0.1] - 2026-05-06
### Adicionado
- Schemas de validação **Zod** para contatos e boundaries.
Expand Down
36 changes: 36 additions & 0 deletions Dockerfile
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Provide the publishable Supabase key in VPS builds

The new VPS image only injects VITE_SUPABASE_ANON_KEY, but this app still has many runtime clients that read import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY directly for Edge Function apikey/Bearer headers (for example voice, TTS, proxy, and web-vitals paths). In Docker/VPS deployments those references are compiled as undefined, so those features call Supabase functions with invalid credentials even though the anon key was provided. Add a VITE_SUPABASE_PUBLISHABLE_KEY build arg/env or map it from the anon key in the Docker/compose/workflow path.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 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."
fi

Repository: adm01-debug/zapp-web

Length of output: 393


🏁 Script executed:

# Procurar pelo nginx.conf
fd -name "nginx.conf" -type f

Repository: 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 f

Repository: adm01-debug/zapp-web

Length of output: 295


🌐 Web query:

nginxinc/nginx-unprivileged:1.27-alpine docker image availability

💡 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.conf

Repository: adm01-debug/zapp-web

Length of output: 127


🏁 Script executed:

fd docker-compose

Repository: adm01-debug/zapp-web

Length of output: 83


🏁 Script executed:

cat -n nginx.conf

Repository: adm01-debug/zapp-web

Length of output: 1528


🏁 Script executed:

cat -n docker-compose.yml

Repository: adm01-debug/zapp-web

Length of output: 1593


Remova acesso root da imagem nginx no runtime.

O container roda como root porque nginx:1.27-alpine não define usuário não-privilegiado. Em caso de comprometimento, o atacante tem acesso irrestrito. Use nginxinc/nginx-unprivileged:1.27-alpine + ajuste a porta para 8080 em todos os locais:

  • nginx.conf: listen 8080; (linha 2)
  • Dockerfile: EXPOSE 8080 (linha 33)
  • Dockerfile HEALTHCHECK: http://127.0.0.1:8080/healthz (linha 35)
  • docker-compose.yml: traefik.http.services.zapp-web.loadbalancer.server.port=8080 (linha 24)

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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` around lines 30 - 36, Substitua a imagem base em Dockerfile de
nginx:1.27-alpine para nginxinc/nginx-unprivileged:1.27-alpine, atualize EXPOSE
para 8080 e ajuste o HEALTHCHECK para usar http://127.0.0.1:8080/healthz;
adicione também a alteração correspondente em nginx.conf (mudar listen para
8080) e em docker-compose.yml atualize a label
traefik.http.services.zapp-web.loadbalancer.server.port para 8080 para que todo
o stack use a porta não-privilegiada da imagem unprivileged.

Loading
Loading