mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 21:47:38 +00:00
## Summary - Fix expression injection vulnerabilities in composite actions (`restore-cache`, `nx-affected`) and workflow files (`claude.yml`) - Reduce overly broad permissions in `ci-utils.yaml` (Danger.js) and `ci-breaking-changes.yaml` - Restructure `preview-env-dispatch.yaml`: auto-trigger for members, opt-in for contributor PRs via `preview-app` label (safe because keepalive has no write tokens) - Isolate all write-access operations (PR comments, cross-repo posting) to a new dedicated [`twentyhq/ci-privileged`](https://github.com/twentyhq/ci-privileged) repo via `repository_dispatch`, so that workflows in twenty that execute contributor code never have write tokens - Create `post-ci-comments.yaml` (`workflow_run` bridge) to dispatch breaking changes results to ci-privileged, solving the [fork PR comment issue](https://github.com/twentyhq/twenty/pull/13713#issuecomment-3168999083) - Delete 5 unused secrets and broken `i18n-qa-report` workflow - Remove `TWENTY_DISPATCH_TOKEN` from twenty (moved to ci-privileged as `CORE_TEAM_ISSUES_COMMENT_TOKEN`) - Use `toJSON()` for all `client-payload` values to prevent JSON injection ## Security model after this PR | Workflow | Executes fork code? | Write tokens available? | |----------|---------------------|------------------------| | preview-env-keepalive | Yes | None (contents: read only) | | preview-env-dispatch | No (base branch) | CI_PRIVILEGED_DISPATCH_TOKEN only | | ci-breaking-changes | Yes | None (contents: read only) | | post-ci-comments (workflow_run) | No (default branch) | CI_PRIVILEGED_DISPATCH_TOKEN only | | claude.yml | No (base branch) | CI_PRIVILEGED_DISPATCH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN | | ci-utils (Danger.js) | No (base branch) | GITHUB_TOKEN (scoped) | All actual write tokens (`TWENTY_PR_COMMENT_TOKEN`, `CORE_TEAM_ISSUES_COMMENT_TOKEN`) live in `twentyhq/ci-privileged` with strict CODEOWNERS review and branch protection. ## Test plan - [ ] Verify preview environment comments still appear on member PRs - [ ] Verify adding `preview-app` label triggers preview for contributor PRs - [ ] Verify breaking changes reports still post on PRs (including fork PRs) - [ ] Verify Claude cross-repo responses still post on core-team-issues - [ ] Confirm ci-privileged branch protection is enforced
131 lines
5 KiB
YAML
131 lines
5 KiB
YAML
name: 'Preview Environment Keep Alive'
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
on:
|
|
repository_dispatch:
|
|
types: [preview-environment]
|
|
|
|
jobs:
|
|
preview-environment:
|
|
timeout-minutes: 310
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout PR
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ github.event.client_payload.pr_head_sha }}
|
|
|
|
- name: Run compose setup
|
|
run: |
|
|
echo "Patching docker-compose.yml..."
|
|
# change image to localbuild using yq
|
|
yq eval 'del(.services.server.image)' -i packages/twenty-docker/docker-compose.yml
|
|
yq eval '.services.server.build.context = "../../"' -i packages/twenty-docker/docker-compose.yml
|
|
yq eval '.services.server.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i packages/twenty-docker/docker-compose.yml
|
|
|
|
yq eval 'del(.services.worker.image)' -i packages/twenty-docker/docker-compose.yml
|
|
yq eval '.services.worker.build.context = "../../"' -i packages/twenty-docker/docker-compose.yml
|
|
yq eval '.services.worker.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i packages/twenty-docker/docker-compose.yml
|
|
|
|
echo "Adding SIGN_IN_PREFILLED environment variable to server service..."
|
|
yq eval '.services.server.environment.SIGN_IN_PREFILLED = "${SIGN_IN_PREFILLED}"' -i packages/twenty-docker/docker-compose.yml
|
|
|
|
echo "Setting up .env file..."
|
|
cp packages/twenty-docker/.env.example packages/twenty-docker/.env
|
|
|
|
echo "Generating secrets..."
|
|
echo "" >> packages/twenty-docker/.env
|
|
echo "# === Randomly generated secrets ===" >> packages/twenty-docker/.env
|
|
echo "APP_SECRET=$(openssl rand -base64 32)" >> packages/twenty-docker/.env
|
|
echo "PG_DATABASE_PASSWORD=$(openssl rand -hex 16)" >> packages/twenty-docker/.env
|
|
echo "SIGN_IN_PREFILLED=true" >> packages/twenty-docker/.env
|
|
echo "Docker compose build..."
|
|
cd packages/twenty-docker/
|
|
docker compose build
|
|
working-directory: ./
|
|
|
|
- name: Create Tunnel
|
|
id: expose-tunnel
|
|
uses: codetalkio/expose-tunnel@v1.5.0
|
|
with:
|
|
service: bore.pub
|
|
port: 3000
|
|
|
|
- name: Start services with correct SERVER_URL
|
|
env:
|
|
TUNNEL_URL: ${{ steps.expose-tunnel.outputs.tunnel-url }}
|
|
run: |
|
|
cd packages/twenty-docker/
|
|
|
|
echo "Setting SERVER_URL to $TUNNEL_URL"
|
|
sed -i '/SERVER_URL=/d' .env
|
|
echo "" >> .env
|
|
echo "SERVER_URL=$TUNNEL_URL" >> .env
|
|
|
|
# Start the services
|
|
echo "Docker compose up..."
|
|
docker compose up -d || {
|
|
echo "Docker compose failed to start"
|
|
docker compose logs
|
|
exit 1
|
|
}
|
|
|
|
echo "Waiting for services to be ready..."
|
|
count=0
|
|
while [ ! $(docker inspect --format='{{.State.Health.Status}}' twenty-db-1) = "healthy" ] || [ ! $(docker inspect --format='{{.State.Health.Status}}' twenty-server-1) = "healthy" ]; do
|
|
sleep 5
|
|
count=$((count+1))
|
|
if [ $count -gt 60 ]; then
|
|
echo "Timeout waiting for services to be ready"
|
|
docker compose logs
|
|
exit 1
|
|
fi
|
|
echo "Still waiting for services... ($count/60)"
|
|
done
|
|
|
|
echo "All services are up and running!"
|
|
working-directory: ./
|
|
|
|
- name: Seed Dev Workspace
|
|
run: |
|
|
cd packages/twenty-docker/
|
|
echo "Seeding full dev workspace..."
|
|
if ! docker compose exec -T server yarn command:prod -- workspace:seed:dev; then
|
|
echo "❌ Seeding full dev workspace failed. Dumping server logs..."
|
|
docker compose logs server
|
|
exit 1
|
|
fi
|
|
working-directory: ./
|
|
|
|
- name: Output tunnel URL
|
|
env:
|
|
TUNNEL_URL: ${{ steps.expose-tunnel.outputs.tunnel-url }}
|
|
run: |
|
|
echo "✅ Preview Environment Ready!"
|
|
echo "🔗 Preview URL: $TUNNEL_URL"
|
|
echo "⏱️ This environment will be available for 5 hours"
|
|
echo "## 🚀 Preview Environment Ready!" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "Preview URL: $TUNNEL_URL" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "This environment will automatically shut down after 5 hours." >> "$GITHUB_STEP_SUMMARY"
|
|
echo "$TUNNEL_URL" > tunnel-url.txt
|
|
|
|
- name: Upload tunnel URL artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: tunnel-url
|
|
path: tunnel-url.txt
|
|
retention-days: 1
|
|
|
|
- name: Keep tunnel alive for 5 hours
|
|
run: timeout 300m sleep 18000 # Stop on whichever we reach first (300m or 5hour sleep)
|
|
|
|
- name: Cleanup
|
|
if: always()
|
|
run: |
|
|
cd packages/twenty-docker/
|
|
docker compose down -v
|
|
working-directory: ./
|