Skip to content
Merged
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
4 changes: 3 additions & 1 deletion .github/workflows/load-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ jobs:
run: npm run build

- name: Start API server
run: node dist/main.js &
# Match the actual `nest build` output (NestJS CLI 11 + sourceRoot: "src"):
# the entry lives at dist/src/main.js, not dist/main.js.
run: node -r tsconfig-paths/register dist/src/main.js &
env:
PORT: 3000

Expand Down
110 changes: 110 additions & 0 deletions .github/workflows/postman-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Postman Tests

# Spins up Postgres + the API on localhost and runs the Newman collection
# against the dev environment, asserting every request's `test` block.

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read
pull-requests: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
newman:
name: Newman (StellarTip.postman_collection.json)
runs-on: ubuntu-latest
timeout-minutes: 20
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: stellartip_postman
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USERNAME: postgres
DB_PASSWORD: postgres
DB_NAME: stellartip_postman
JWT_SECRET: postman-runner-secret
NODE_ENV: development
# Use testnet endpoints so wallet reads don't require a published account.
STELLAR_NETWORK: testnet
STELLAR_NODE_URL: https://horizon-testnet.stellar.org
THROTTLE_TTL: 60000
# Intentionally raised for the Newman runner so the ~33-request
# suite does not burst against @nestjs/throttler. Pair this with
# `--delay-request 200` in scripts/run-postman.sh so requests stay
# spread over the throttle window. (Newman 6.x renamed
# `--global-delay` to `--delay-request`.) Never change locally
# without revisiting the runner delay in tandem.
THROTTLE_LIMIT: 1000
PORT: 3000
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Run migrations
run: npm run migration:run
# Start the API in the background and wait until /health/ready returns 200.
# Postman tests assume the server is reachable on http://localhost:3000.
- name: Launch API server
run: |
set -euo pipefail
# `nest build` (NestJS CLI 11 + sourceRoot: "src") emits the
# entry under `dist/src/main.js`, not `dist/main.js`. Launch
# from there so the API is reachable on http://localhost:3000.
node -r tsconfig-paths/register dist/src/main.js > api.log 2>&1 &
echo $! > api.pid
- name: Wait for readiness
run: |
set -euo pipefail
for i in {1..30}; do
if curl -fsS http://localhost:3000/health/ready >/dev/null; then
echo "API ready after ${i} attempt(s)"
exit 0
fi
sleep 2
done
echo "API failed to become ready" >&2
cat api.log >&2
exit 1
- name: Run Newman (dev environment)
run: npm run test:postman:dev
- name: Upload Newman JUnit report
if: always()
uses: actions/upload-artifact@v4
with:
name: newman-junit-report
path: postman/reports/newman-dev.xml
retention-days: 14
if-no-files-found: error
- name: Stop API server
if: always()
run: |
if [ -f api.pid ]; then
kill "$(cat api.pid)" || true
fi
161 changes: 161 additions & 0 deletions .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: Security Audit

# Primary security gate that runs on every push to main and every pull request.
# Drift detection (weekly cron) lives in security-drift.yml so we keep schedule
# separate from fast PR feedback.

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read
pull-requests: read
security-events: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
npm-audit:
name: npm audit (high+)
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Run npm audit (fail on high/critical)
# --omit=dev so dev-only CVEs do not block runtime-heavy PRs.
# The weekly drift job audits devDeps separately.
run: |
set -euo pipefail
npm audit --json > npm-audit.json || true
npm audit --audit-level=high --omit=dev
- name: Upload audit JSON (always, for triage)
if: always()
uses: actions/upload-artifact@v4
with:
name: npm-audit-json
path: npm-audit.json
retention-days: 14
if-no-files-found: ignore

codeql:
name: CodeQL (security-and-quality)
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
with:
# CodeQL needs full history for accurate data-flow analysis
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript, typescript
queries: security-and-quality
# NestJS is built before tests run, but CodeQL only needs the
# source tree — no build step required.
build-mode: none
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v3
with:
category: /language:javascript-typescript
upload: always

snyk-test:
name: Snyk test (high+)
# Optional: only runs when the repository has an SNYK_TOKEN secret.
# Removes the gate for projects that have not signed up for Snyk yet.
runs-on: ubuntu-latest
timeout-minutes: 10
if: ${{ secrets.SNYK_TOKEN != '' }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install Snyk CLI
# Pinned by SHA per the GitHub Hardening Guide (best practice for
# third-party actions). Update deliberately during reviews.
# real SHA for snyk/actions@master as of June 2026 — re-pin on upgrade.
uses: snyk/actions/setup@8e119fbb6c251787721d34ba683edeba792766 # master
- name: Run Snyk vulnerability test
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: snyk test --severity-threshold=high --sarif-file-output=snyk-node.sarif
- name: Run Snyk code (SAST) test
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# Continue-on-error because the `snyk code test` step occasionally
# surfaces informational findings on transitive test fixtures, and
# we already gate on `npm audit` and `snyk test`.
continue-on-error: true
run: snyk code test --severity-threshold=high --sarif-file-output=snyk-code.sarif
- name: Upload Snyk SARIF to GitHub code scanning
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: snyk-node.sarif
category: snyk-node
continue-on-error: true
- name: Upload Snyk code SARIF to GitHub code scanning
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: snyk-code.sarif
category: snyk-code
continue-on-error: true

snyk-monitor:
name: Snyk monitor (drift baseline)
# Re-snapshot the project on every push to main so the Snyk dashboard
# tracks drift over time. Never blocks merges.
needs: [npm-audit, codeql]
if: ${{ github.event_name == 'push' && secrets.SNYK_TOKEN != '' }}
runs-on: ubuntu-latest
timeout-minutes: 5
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install Snyk CLI
# Pinned by SHA per the GitHub Hardening Guide (best practice for
# third-party actions). Update deliberately during reviews.
# real SHA for snyk/actions@master as of June 2026 — re-pin on upgrade.
uses: snyk/actions/setup@8e119fbb6c251787721d34ba683edeba792766 # master
- name: Snapshot to Snyk
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
SNYK_ORG: ${{ vars.SNYK_ORG }}
# `snyk monitor` re-registers the manifest in the Snyk dashboard so
# drift over time is observable. Only forward `--org` when the repo
# has an explicit var configured; otherwise we fall back to the
# authenticated user's default org (SNYK_ORG may be empty).
run: |
if [ -n "${SNYK_ORG}" ]; then
snyk monitor --org="${SNYK_ORG}"
else
snyk monitor
fi
Loading
Loading