Skip to content

Commit 83ca4fb

Browse files
authored
Merge branch 'main' into feature/issue-876-logging-redact-pii
2 parents 9560e3f + 8ecbd2c commit 83ca4fb

74 files changed

Lines changed: 7761 additions & 421 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/audit-check.yml

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ jobs:
2525
with:
2626
node-version: "20"
2727

28+
- name: 🦀 Setup Rust
29+
uses: dtolnay/rust-toolchain@stable
30+
2831
- name: 📦 Check backend audits
2932
id: backend-audit
3033
working-directory: ./backend
@@ -93,6 +96,46 @@ jobs:
9396
exit 1
9497
fi
9598
99+
- name: � Check Cargo audit (contract)
100+
id: contract-audit
101+
run: |
102+
if [ -d "./contract" ] && [ -f "./contract/Cargo.toml" ]; then
103+
echo "Installing cargo-audit..."
104+
cargo install cargo-audit --quiet 2>&1 | grep -v "already installed" || true
105+
106+
cd ./contract
107+
AUDIT_OUTPUT=$(cargo audit --json 2>/dev/null || echo '{"vulnerabilities":[]}')
108+
CRITICAL=$(echo "$AUDIT_OUTPUT" | jq '[.vulnerabilities[] | select(.severity=="critical")] | length' 2>/dev/null || echo "0")
109+
HIGH=$(echo "$AUDIT_OUTPUT" | jq '[.vulnerabilities[] | select(.severity=="high")] | length' 2>/dev/null || echo "0")
110+
TOTAL=$(echo "$AUDIT_OUTPUT" | jq '.vulnerabilities | length' 2>/dev/null || echo "0")
111+
112+
echo "contract_critical=$CRITICAL" >> $GITHUB_OUTPUT
113+
echo "contract_high=$HIGH" >> $GITHUB_OUTPUT
114+
echo "contract_total=$TOTAL" >> $GITHUB_OUTPUT
115+
116+
echo "### 🔐 Contract (Cargo) Audit Results" >> $GITHUB_STEP_SUMMARY
117+
echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY
118+
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
119+
echo "| Critical | $CRITICAL |" >> $GITHUB_STEP_SUMMARY
120+
echo "| High | $HIGH |" >> $GITHUB_STEP_SUMMARY
121+
echo "| Total | $TOTAL |" >> $GITHUB_STEP_SUMMARY
122+
echo "" >> $GITHUB_STEP_SUMMARY
123+
124+
if (( CRITICAL > 0 )); then
125+
echo "❌ **CRITICAL vulnerabilities detected**" >> $GITHUB_STEP_SUMMARY
126+
echo "::error::Critical Cargo vulnerabilities: $CRITICAL"
127+
exit 1
128+
fi
129+
if (( HIGH > 0 )); then
130+
echo "❌ **HIGH vulnerabilities detected**" >> $GITHUB_STEP_SUMMARY
131+
echo "::warning::High Cargo vulnerabilities: $HIGH"
132+
fi
133+
else
134+
echo "contract_critical=0" >> $GITHUB_OUTPUT
135+
echo "contract_high=0" >> $GITHUB_OUTPUT
136+
echo "contract_total=0" >> $GITHUB_OUTPUT
137+
fi
138+
96139
- name: 💬 Comment on PR with audit summary
97140
if: github.event_name == 'pull_request' && always()
98141
uses: actions/github-script@v7
@@ -103,7 +146,9 @@ jobs:
103146
const backendHigh = '${{ steps.backend-audit.outputs.backend_high }}' || '0';
104147
const frontendCritical = '${{ steps.frontend-audit.outputs.frontend_critical }}' || '0';
105148
const frontendHigh = '${{ steps.frontend-audit.outputs.frontend_high }}' || '0';
106-
const comment = '## 🔐 Security Audit Summary\n\n**Backend:**\n- 🔴 Critical: ' + backendCritical + '\n- 🟠 High: ' + backendHigh + '\n\n**Frontend:**\n- 🔴 Critical: ' + frontendCritical + '\n- 🟠 High: ' + frontendHigh + '\n\nRun locally with: `./scripts/check-audits.sh`';
149+
const contractCritical = '${{ steps.contract-audit.outputs.contract_critical }}' || '0';
150+
const contractHigh = '${{ steps.contract-audit.outputs.contract_high }}' || '0';
151+
const comment = '## 🔐 Security Audit Summary\n\n**Backend (npm):**\n- 🔴 Critical: ' + backendCritical + '\n- 🟠 High: ' + backendHigh + '\n\n**Frontend (npm):**\n- 🔴 Critical: ' + frontendCritical + '\n- 🟠 High: ' + frontendHigh + '\n\n**Contract (Cargo):**\n- 🔴 Critical: ' + contractCritical + '\n- 🟠 High: ' + contractHigh + '\n\nRun locally with: `./scripts/check-audits.sh`';
107152
github.rest.issues.createComment({
108153
issue_number: context.issue.number,
109154
owner: context.repo.owner,

.github/workflows/backend-ci.yml

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,126 @@ name: Backend CI
22

33
on:
44
pull_request:
5+
paths:
6+
- 'backend/**'
7+
- '.github/workflows/backend-ci.yml'
58
push:
6-
branches:
7-
- main
8-
- master
9+
branches: [main, master]
10+
paths:
11+
- 'backend/**'
12+
- '.github/workflows/backend-ci.yml'
913
workflow_dispatch:
1014

1115
jobs:
12-
backend:
13-
name: backend
16+
lint:
17+
name: Backend – Lint
1418
runs-on: ubuntu-latest
15-
timeout-minutes: 20
16-
19+
timeout-minutes: 10
1720
steps:
18-
- name: Checkout
19-
uses: actions/checkout@v4
20-
21-
- name: Setup Node.js
22-
uses: actions/setup-node@v4
21+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
22+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
2323
with:
2424
node-version: 20
2525
cache: npm
2626
cache-dependency-path: backend/package-lock.json
2727

28+
- name: Check package-lock is up to date
29+
run: |
30+
npm install --package-lock-only --ignore-scripts
31+
git diff --exit-code package-lock.json || {
32+
echo "::error::package-lock.json is out of sync with package.json. Run 'npm install' locally and commit the updated lockfile."
33+
exit 1
34+
}
35+
working-directory: backend
36+
2837
- name: Install dependencies
2938
run: npm ci
3039
working-directory: backend
3140

32-
- name: Build
33-
run: npm run build
41+
test:
42+
name: Backend – Test (Node.js ${{ matrix.node }})
43+
runs-on: ubuntu-latest
44+
timeout-minutes: 15
45+
strategy:
46+
fail-fast: false
47+
matrix:
48+
node: ['20', '22']
49+
services:
50+
postgres:
51+
image: postgres:16-alpine
52+
env:
53+
POSTGRES_USER: myfans_ci
54+
POSTGRES_PASSWORD: myfans_ci
55+
POSTGRES_DB: myfans_test
56+
ports:
57+
- 5432:5432
58+
options: >
59+
--health-cmd pg_isready
60+
--health-interval 5s
61+
--health-timeout 5s
62+
--health-retries 10
63+
env:
64+
DB_HOST: localhost
65+
DB_PORT: 5432
66+
DB_USER: myfans_ci
67+
DB_PASSWORD: myfans_ci
68+
DB_NAME: myfans_test
69+
JWT_SECRET: ci-test-secret-not-for-production
70+
WEBHOOK_SECRET: ci-webhook-secret-not-for-production
71+
NODE_ENV: test
72+
STELLAR_NETWORK: testnet
73+
SOROBAN_RPC_URL: https://soroban-testnet.stellar.org
74+
steps:
75+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
76+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
77+
with:
78+
node-version: ${{ matrix.node }}
79+
cache: npm
80+
cache-dependency-path: backend/package-lock.json
81+
- run: npm ci
82+
working-directory: backend
83+
- run: npm test
84+
working-directory: backend
85+
86+
build:
87+
name: Backend – Build
88+
runs-on: ubuntu-latest
89+
timeout-minutes: 10
90+
needs: [lint, test]
91+
steps:
92+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
93+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
94+
with:
95+
node-version: 20
96+
cache: npm
97+
cache-dependency-path: backend/package-lock.json
98+
- run: npm ci
99+
working-directory: backend
100+
- run: npm run build
34101
working-directory: backend
35102

36-
- name: Test
37-
run: npm test
103+
- name: 🔐 Run npm audit
104+
id: npm-audit
105+
continue-on-error: true
106+
run: |
107+
AUDIT_JSON=$(npm audit --json 2>/dev/null || echo '{"metadata":{"vulnerabilities":{"critical":0,"high":0,"moderate":0}}}')
108+
CRITICAL=$(echo "$AUDIT_JSON" | jq '.metadata.vulnerabilities.critical // 0')
109+
HIGH=$(echo "$AUDIT_JSON" | jq '.metadata.vulnerabilities.high // 0')
110+
MODERATE=$(echo "$AUDIT_JSON" | jq '.metadata.vulnerabilities.moderate // 0')
111+
112+
echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
113+
echo "high=$HIGH" >> $GITHUB_OUTPUT
114+
echo "moderate=$MODERATE" >> $GITHUB_OUTPUT
115+
116+
echo "### 📦 Backend npm Audit" >> $GITHUB_STEP_SUMMARY
117+
echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY
118+
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
119+
echo "| Critical | $CRITICAL |" >> $GITHUB_STEP_SUMMARY
120+
echo "| High | $HIGH |" >> $GITHUB_STEP_SUMMARY
121+
echo "| Moderate | $MODERATE |" >> $GITHUB_STEP_SUMMARY
122+
123+
if (( CRITICAL > 0 || HIGH > 0 )); then
124+
echo "::error::High/Critical vulnerabilities detected - review and fix before merging"
125+
exit 1
126+
fi
38127
working-directory: backend

.github/workflows/ci.yml

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# Jobs run in parallel by default (no `needs:` between backend/frontend/contract).
2+
# Only db-backup-drill and wasm-size have explicit sequencing requirements.
3+
#
4+
# Parallelism layout:
5+
# backend ──────────────────────────────────────────────────────┐
6+
# backend-migrations ──────────────────────────────────────────┐ │
7+
# frontend ──────────────────────────────────────────────────┐ │ │
8+
# contract ──────────────────────────────────────────────┐ │ │ │
9+
# │ │ │ │
10+
# wasm-size (needs: contract) ──────────────────────────►│ │ │ │
11+
# db-backup-drill (needs: backend-migrations) ──────────►│ │ │ │
12+
# ci-gate (needs: all) ─────────────────────────────────►└───┘─┘─┘
113
name: CI
214

315
on:
@@ -57,6 +69,15 @@ jobs:
5769
cache: 'npm'
5870
cache-dependency-path: backend/package-lock.json
5971

72+
- name: Check package-lock is up to date
73+
run: |
74+
npm install --package-lock-only --ignore-scripts
75+
git diff --exit-code package-lock.json || {
76+
echo "::error::package-lock.json is out of sync with package.json. Run 'npm install' locally and commit the updated lockfile."
77+
exit 1
78+
}
79+
working-directory: backend
80+
6081
- name: Install dependencies
6182
run: npm ci
6283
working-directory: backend
@@ -152,6 +173,15 @@ jobs:
152173
cache: 'npm'
153174
cache-dependency-path: frontend/package-lock.json
154175

176+
- name: Check package-lock is up to date
177+
run: |
178+
npm install --package-lock-only --ignore-scripts
179+
git diff --exit-code package-lock.json || {
180+
echo "::error::package-lock.json is out of sync with package.json. Run 'npm install' locally and commit the updated lockfile."
181+
exit 1
182+
}
183+
working-directory: frontend
184+
155185
- name: Install dependencies
156186
run: npm ci
157187
working-directory: frontend
@@ -252,9 +282,12 @@ jobs:
252282
${{ runner.os }}-contract-target-
253283
254284
- name: Install toolchain
255-
run: rustup component add rustfmt clippy
285+
run: rustup component add rustfmt clippy llvm-tools-preview
256286
working-directory: contract
257287

288+
- name: Install cargo-llvm-cov
289+
uses: taiki-e/install-action@cargo-llvm-cov
290+
258291
- name: Check formatting
259292
run: cargo fmt --check
260293
working-directory: contract
@@ -263,8 +296,23 @@ jobs:
263296
run: cargo clippy --all-targets --all-features
264297
working-directory: contract
265298

266-
- name: Run tests
267-
run: cargo test --all-features
299+
- name: Run tests with coverage
300+
run: cargo llvm-cov --all-features --lcov --output-path lcov.info
301+
working-directory: contract
302+
303+
- name: Upload coverage report
304+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
305+
with:
306+
name: contract-coverage-lcov
307+
path: contract/lcov.info
308+
retention-days: 30
309+
if-no-files-found: error
310+
311+
- name: Write coverage summary
312+
run: |
313+
echo "## Contract Coverage" >> $GITHUB_STEP_SUMMARY
314+
echo "" >> $GITHUB_STEP_SUMMARY
315+
cargo llvm-cov report --summary-only 2>&1 | tail -5 >> $GITHUB_STEP_SUMMARY
268316
working-directory: contract
269317

270318
- name: Build
@@ -358,3 +406,24 @@ jobs:
358406
done < <(find "$WASM_DIR" -maxdepth 1 -name '*.wasm' -print0 | sort -z)
359407
TOTAL_KIB=$(echo "scale=1; $TOTAL / 1024" | bc)
360408
echo "| **TOTAL** | **$TOTAL** | **$TOTAL_KIB** |" >> $GITHUB_STEP_SUMMARY
409+
410+
# Single required status check for branch protection.
411+
# All parallel jobs must pass before a PR can merge.
412+
ci-gate:
413+
name: CI Gate
414+
runs-on: ubuntu-latest
415+
if: always()
416+
needs:
417+
- backend
418+
- backend-migrations
419+
- frontend
420+
- contract
421+
- wasm-size
422+
- db-backup-drill
423+
steps:
424+
- name: Check all jobs passed
425+
run: |
426+
results='${{ toJSON(needs) }}'
427+
echo "$results" | grep -q '"result": "failure"' && exit 1
428+
echo "$results" | grep -q '"result": "cancelled"' && exit 1
429+
echo "All parallel jobs passed."

0 commit comments

Comments
 (0)