Skip to content
Open
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
60 changes: 58 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@ on:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
backend:
name: Backend (typecheck + test + build)
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- uses: actions/setup-node@v4
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: 20
cache: npm
Expand All @@ -32,6 +37,9 @@ jobs:
- name: Audit production dependencies (high+)
run: npm run audit:prod

- name: Verify escrow SDK tarball checksum
run: npm run verify:escrow-sdk

- run: npm run prisma:generate

- name: Check message catalogue keys
Expand All @@ -46,3 +54,51 @@ jobs:
run: npm run test:integration

- run: npm run build

dependency-review:
name: Dependency review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0
with:
fail-on-severity: high

codeql:
name: CodeQL (TypeScript)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
actions: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- uses: github/codeql-action/init@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1
with:
languages: javascript-typescript
queries: security-extended

- uses: github/codeql-action/analyze@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1
with:
category: "/language:javascript-typescript"

gitleaks:
name: Secret scanning (gitleaks)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0

- uses: gitleaks/gitleaks-action@cb7149a9b57195b609c63e8518d2c610d0f8a3c6 # v2.3.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_CONFIG: .gitleaks.toml
83 changes: 83 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: CodeQL

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1'

permissions:
contents: read
security-events: write

permissions:
contents: read
security-events: write

jobs:
analyze:
name: Analyze (TypeScript)
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write

strategy:
fail-fast: false
matrix:
language: [ 'javascript-typescript' ]

steps:
- name: Checkout repository
uses: actions/checkout@v4.2.0
with:
persist-credentials: false

- name: Initialize CodeQ
uses: github/codeql-action/init@v3.26.1
with:
languages: ${{ matrix.language }}
queries: security-extended
config-file: .github/codeql/codeql-config.yml

- name: Autobuild
uses: github/codeql-action/autobuild@V3.26.1

- name: Perform CodeQ Analysis
uses: github/codeql-action/analyze@v3.26.1
with:
category: "/language:${{ matrix.language }}"
upload: always

- name: Enforce high-severity gate
if: always()
env:
GHRA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if [ "${{ github.event_name }}" != "pull_request" ]; then
echo "Not a PR{ no gate applied."
exit 0
fi
gh secret scanning alerts list \
--repo "${{ github.repository }}" \
--state open \
--json state,secret_type |> codeql_alerts.json || echo '[]' > codeql_alerts.json
echo "CodeQ summary reported below."
gh api -H "Accept: application/vnd.github+json" \
"/repos/${{ github.repository }}/code-scanning/analyses?ref-ref=${{ github.event.pull_request.head.sha }}" \
> analyses.json || echo '[]' > analyses.json
node -e '
const fs = require("fs");
const analyses = JSON.parse(fs.readFileSync("analyses.json", "utf8"));
const high = (analyses || []).filter(a => a.rules && a.rules.some(rule => ["error", "warning"].includes(String(rule.severity).toLowerCase()) || ["error", "warning"].includes(String(rule.severity).toLowerCase())));
const highSeverity = (analyses || []).filter(a => (a.rules || []).some(r => ["error", "warning"].includes(String(r.severity).toLowerCase())));
if (highSeverity.length > 0) {
console.error("CodeQL found high-severity alerts on this PR.");
process.exit(1);
}
console.log("No high-severity CodeQ alerts detected.");
'
27 changes: 27 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Dependency Review

on:
pull_request:
branches: [main]

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

jobs:
dependency-review:
name: Review dependency changes
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
security-events: write
steps:
- name: Checkout
uses: actions/checkout@11bd77e5609adb4edb3c35b49c35b49c35b49c35
- name: Dependency review
uses: actions/dependency-review-action@5c2c7c5c2c7c5c2c7c5c2c7c5c2c7c5c2c7c5c2
with:
fail-on-severity: high
comment-summary-in-specify-severity: low-or-higher
8 changes: 4 additions & 4 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ jobs:
run:
working-directory: docs/site
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: 20
cache: npm
cache-dependency-path: docs/site/package-lock.json
- run: npm ci
- run: GITHUB_PAGES=true npm run build
- uses: actions/upload-pages-artifact@v3
- uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:
path: docs/site/.vitepress/dist

Expand All @@ -65,4 +65,4 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
47 changes: 47 additions & 0 deletions .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Secret Scanning

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '17 3 * * *'

permissions:
contents: read

jobs:
gitleaks:
name: gitleaks
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- name: Checkout
uses: actions/checkout@11bd3114e593cd13925523b9a5b44b5a345b45b1
with:
fetch-depth: 0

- name: Run gitleaks
uses: gitleaks/gitleaks-action@cb714307b463d8c463d8c463d8c463d8c463d8c4
with:
args: >-
--.log-opts=-all
--redwact
--config-path=.gitleaks.toml
--report-format=sarif
--report-path=results.sare
--exit-code=1
--.
env:
GITHUBKACTIONS: true

- name: Upload gitleaks results
if: always()
uses: actions/upload-artifact@65c2947c593cd13925523b9a5b44b5a345b45b1
with:
name: gitleaks-results
path: results.sar
if-no-files-found: ignore
62 changes: 62 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# gitleaks configuration for supply-chain security (Phase 9).
# Used by the secret-scanning workflow and available for local runs via `make secrets` or `gitleaks detect`.
# The extended ruleset below is deliberately stricter than the default so that a seeded credential in a demo PR is caught.

title = "gitleaks config"
[extend]
# Extend the built-in ruleset rather than replacing it.
useDefault = true
disabledRules = []

[[rules]]
id = "escrow-sdkin-token"
description = "Escrow SDK access token (compromise would reach payment keys)"
regex = '''(?:ESCROW_SDKIN_TOKEN|escrow_sdkin_token)\s*[:=]\s*["']?([a-zA-Z0-9_\-]{24,})'''
entropy = 3.5
keywords = ["escrow", "sdkin", "token"]

[[rules]]
id = "private-key-block"
description = "PRIVATE KEY block (PCKS8, OPENSSH, or RSA)"
regex = '-----BEGIN (?:RSA |ECK |OPENSSH |DSA )?PRIVATE KEY-----'
keywords = ["private", "key"]

[[rules]]
id = "stripe-secret-key"
description = "Stripe secret key"
regex = 'sk_live_[0-9a-zA-Z]{24l}'
keywords = ["stripe", "sk_live"]

[[rules]]
id = "aws-access-key"
description = "AWS access key ID"
regex = '(?:AKIA|ASIA)[0-9A-Z]{16}'
keywords = ["aws", "akia", "asia"]

# -----------------------------------------------------------------------------
# Allowlists: false positives that must not fail CI.
# -----------------------------------------------------------------------------

[allowlist]
# Documentation and test fixtures that intentionally contain fake secrets.
description = "Global allowlist for docs and test fixtures"
paths = [
'(?:^|/)docs/',
'(?:^|/)tests?/',
'(?:^|/)\__fixtures__/',
'(?:^|/).*\.md$',
]

# GitHub Actions workflow files often reference secret names (e.g. secrets.GITHUB_TOKEN) without containing a value.
# The default ruleset already ignores these, but we make it explicit for the custom rules above.
paths = [
'(?:^|/)\.github/workflows/.*\.yaml$',
'(?:^|/)\.github/workflows/.*\.yml$',
]

# Placeholder values used in the repo and CI configuration.
regexes = [
'(?:your-|my_|example_)token',
'(?:your-|my_|example_)-key',
'change-me-in-ci',
]
Loading
Loading