Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e0b174e
fix(deps): dedupe ioredis so bullmq shares the root client type (OPS-C1)
sabrydawood Oct 27, 2026
f7516a3
fix(auth): close public registration and ignore client-supplied roles…
sabrydawood Oct 27, 2026
37d82e9
fix(socket): authenticate connections and authorize room joins (SEC-C2)
sabrydawood Oct 27, 2026
e6d0413
fix(auth): bind the 2FA step to the password step and add lockout (SE…
sabrydawood Oct 27, 2026
087901f
fix(pipeline): evaluate RunIf with a safe parser instead of new Funct…
sabrydawood Oct 27, 2026
4d74516
fix(deploy): run git, rsync, chown and ssh-keygen without a shell (EN…
sabrydawood Oct 27, 2026
0d56f9d
fix(rbac): enforce project access everywhere and make secrets write-o…
sabrydawood Oct 27, 2026
cd001ea
fix(config): refuse to start with default secrets in production (SEC-H2)
sabrydawood Oct 27, 2026
c014315
fix(build): patch vulnerable dependencies and stop deleting dist befo…
sabrydawood Oct 27, 2026
b4647d6
fix(build): commit the safe build script (OPS-C1)
sabrydawood Oct 27, 2026
6c77e8d
fix(db): boot on an empty database and accept legacy webhooks (OPS-C2…
sabrydawood Oct 27, 2026
2e2d01b
fix(auth): keep UserId in the 2FA challenge for the bundled UI (SEC-C3)
sabrydawood Oct 27, 2026
921f2bf
feat(ops): add a CLI to rotate project webhook secrets
sabrydawood Oct 27, 2026
24c53d3
docs: add the 3.0.1 security hotfix operator guide and bump to 3.0.1
sabrydawood Oct 27, 2026
aaeabc2
ci: install dev dependencies in the fresh-install job
sabrydawood Oct 28, 2026
41032d1
fix(security): validate the 2FA token in the service and ignore unsaf…
sabrydawood Oct 28, 2026
ba6dfbb
fix(cli): save rotated webhook secrets before applying them
sabrydawood Oct 28, 2026
81e33ff
fix(socket): re-authorize open sockets when access changes (SEC-C2)
sabrydawood Oct 28, 2026
d53f312
fix(cli): keep the secrets file when a failed rotation may have commi…
sabrydawood Oct 28, 2026
c170baa
refactor(dto): rebuild merged variables instead of writing keys in place
sabrydawood Oct 28, 2026
9c497bf
fix(access): decide project access by membership only (SEC-H4)
sabrydawood Oct 28, 2026
f42a7cf
fix(security): close the remaining access and credential leaks (SEC-H4)
sabrydawood Oct 28, 2026
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
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ DB_FORCE_SYNC_ALTER=false
DB_ROOT_PASSWORD=change_me_root_password

# === JWT Configuration ===
# REQUIRED in production (v3.0.1): the server refuses to start when
# JWT_SECRET, JWT_REFRESH_SECRET or ENCRYPTION_KEY is missing, shorter than
# 32 characters, or still a placeholder such as the values below.
# Real environment variables take precedence over this file.
# Generate with: openssl rand -hex 64
JWT_SECRET=replace_with_64_byte_hex
JWT_EXPIRY=7d
Expand Down Expand Up @@ -88,7 +92,16 @@ BACKUP_DIR=./backups
BACKUP_RETENTION_DAYS=30
MAX_CONCURRENT_DEPLOYMENTS=5

# === Registration (v3.0.1 β€” SEC-C1) ===
# Self-service sign-up is CLOSED by default. While the Users table is empty
# the first account registered through /api/auth/register becomes Admin;
# after that, only admins can create accounts unless this is "true" (new
# self-registered accounts are always Viewer).
ALLOW_REGISTRATION=false

# === Default Admin Bootstrap ===
# Optional. When set and no admin exists, an admin is created at boot. When
# unset on a fresh install, use the first-admin registration flow instead.
DEFAULT_ADMIN_USERNAME=admin
DEFAULT_ADMIN_EMAIL=admin@example.com
DEFAULT_ADMIN_PASSWORD=change_me_admin_password
Expand Down
108 changes: 107 additions & 1 deletion .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ jobs:

strategy:
matrix:
node-version: [18.x, 20.x]
# Node 18 is EOL and nodemailer 10 requires >= 20 (v3.0.1).
node-version: [20.x, 22.x]

services:
mariadb:
Expand Down Expand Up @@ -94,3 +95,108 @@ jobs:
- name: ❌ Build failure notification
if: failure()
run: echo "❌ Build or tests failed. Please check the logs."

# v3.0.1 OPS-C2 β€” a brand-new install must boot on an EMPTY database.
# The test suite syncs models before migrating, which hid the fact that
# migration 001 failed on a fresh schema. This job runs the real server
# (production mode, generated secrets) against an empty MariaDB, then
# restarts it to prove every migration is a no-op on an existing schema.
fresh-install:
name: Boot on an empty database
runs-on: ubuntu-latest

services:
mariadb:
image: mariadb:11
env:
MARIADB_ROOT_PASSWORD: root_password
MARIADB_DATABASE: deploy_center_fresh
MARIADB_USER: deploycenter
MARIADB_PASSWORD: deploycenter_fresh_password
ports:
- 3306:3306
options: >-
--health-cmd="healthcheck.sh --connect --innodb_initialized"
--health-interval=10s
--health-timeout=5s
--health-retries=10
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd="redis-cli ping"
--health-interval=10s
--health-timeout=5s
--health-retries=10

env:
NODE_ENV: production
PORT: '9090'
HOST: 127.0.0.1
DB_HOST: 127.0.0.1
DB_PORT: '3306'
DB_NAME: deploy_center_fresh
DB_USERNAME: deploycenter
DB_PASSWORD: deploycenter_fresh_password
DB_DIALECT: mysql
DB_AUTO_MIGRATE: 'true'
REDIS_HOST: 127.0.0.1
REDIS_PORT: '6379'
CORS_ORIGIN: http://127.0.0.1:9090

steps:
- name: πŸ“₯ Checkout code
uses: actions/checkout@v4

- name: πŸ”§ Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 22.x
cache: 'npm'

- name: πŸ“¦ Install dependencies
run: npm ci --include=dev

- name: πŸ—οΈ Build
run: npm run build

- name: πŸ”‘ Generate secrets
run: |
echo "JWT_SECRET=$(openssl rand -hex 64)" >> "$GITHUB_ENV"
echo "JWT_REFRESH_SECRET=$(openssl rand -hex 64)" >> "$GITHUB_ENV"
echo "ENCRYPTION_KEY=$(openssl rand -hex 32)" >> "$GITHUB_ENV"

- name: πŸš€ First boot on the empty database
run: |
set -euo pipefail
boot() {
node -r ./tsconfig-paths-bootstrap.js dist/index.js > "server-$1.log" 2>&1 &
echo $! > server.pid
for i in $(seq 1 60); do
if curl -fsS -A deploy-center-ci -H 'X-Forwarded-Proto: https' \
http://127.0.0.1:9090/health > /dev/null; then
return 0
fi
if ! kill -0 "$(cat server.pid)" 2>/dev/null; then
cat "server-$1.log"; return 1
fi
sleep 1
done
cat "server-$1.log"; return 1
}
boot first
status=$(curl -fsS -A deploy-center-ci -H 'X-Forwarded-Proto: https' \
http://127.0.0.1:9090/api/auth/registration-status)
echo "$status"
echo "$status" | grep -q '"SetupRequired":true'
grep -q 'Migration 000: baseline schema created' server-first.log
kill "$(cat server.pid)"; sleep 3

boot second
grep -q 'Migration 000_initial_schema already executed' server-second.log
kill "$(cat server.pid)"

- name: πŸ“„ Server logs
if: always()
run: tail -n 200 server-*.log || true
8 changes: 4 additions & 4 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
paths-ignore:
- public
- dist
- __tests__
- node_modules
- '**/*.min.js'
- '**/*.bundle.js'
Expand All @@ -62,15 +63,14 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '18'
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run npm audit
run: npm audit --audit-level=moderate
continue-on-error: true
- name: Run npm audit (blocking for high / critical in production deps)
run: npm audit --omit=dev --audit-level=high

- name: Check for known vulnerabilities
run: npm audit --json > security-audit.json
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,7 @@ DEPLOYMENT_LOGS_OPTIMIZATION.md
RSYNC_PERMISSIONS_FIX.md
HUSKY_PRODUCTION_FIX.md
PRODUCTION_DEPLOYMENT_FIXES.md
docs/spec-kit
docs/spec-kit
# safe-build staging dirs (scripts/safe-build.js)
.dist-build-*/
.dist-prev-*/
Loading
Loading