Files
nofx/.github/workflows/pr-checks.yml
tinkle-community 99361cb085 fix(security): harden auth flows and lock down telegram bot tool
- config: require JWT_SECRET >=32 bytes and reject the historical
  default fallback; MustInit aborts startup under an insecure config
- api: CORS now uses CORS_ALLOWED_ORIGINS allowlist with safe
  localhost defaults instead of returning Access-Control-Allow-Origin: *
- api: /api/reset-password and /api/reset-account stay public so
  recovery still works, but require an explicit confirm phrase in the
  body to block accidental and drive-by triggers
- api: drop adoptOrphanRecords so wiping the account no longer hands
  the next registrant the previous owner's wallet keys and exchange
  API credentials
- api: getTraderFromQuery now does a soft ownership check; equity-history
  is restricted to traders with show_in_competition=true and
  GetOrderFills joins on trader_id
- telegram: bot api_request tool uses a default-deny method+path
  allowlist so prompt injection cannot reach password, exchange key,
  AI provider or wallet endpoints
- ci: drop @master / @main on trivy-action and trufflehog; pin to
  released versions with a TODO to move to SHA + Dependabot
- web: reset flows send the required confirm phrase; "Forgot account"
  copy (en/zh/id) warns that wallet and exchange keys will be lost
- docker-compose: keep ./.env mount for onboarding wallet persistence
  with an inline note on the tradeoff, drop the host-exposed pprof port
2026-05-29 07:51:26 +08:00

350 lines
12 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

name: PR Checks
on:
pull_request:
types: [opened, synchronize, reopened, edited]
branches:
- dev
- main
# Default permissions for all jobs
# Note: Fork PRs won't have write access for security
# Advisory checks use separate workflow (pr-checks-run.yml + pr-checks-comment.yml)
permissions:
contents: read # Read repository contents
pull-requests: write # Manage PRs (labels, comments) - only works for non-fork PRs
issues: write # Manage issues (PRs are issues) - only works for non-fork PRs
jobs:
# Validate PR title and description
validate-pr:
name: Validate PR Format
runs-on: ubuntu-latest
# Inherits workflow-level permissions (contents: read, pull-requests: write, issues: write)
steps:
- name: Check PR title format
id: semantic-pr
continue-on-error: true # Don't block PR if title format is invalid
uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
style
refactor
perf
test
chore
ci
security
build
scopes: |
exchange
trader
ai
api
ui
frontend
backend
security
deps
workflow
github
actions
config
docker
build
release
requireScope: false
- name: Comment on invalid PR title
if: steps.semantic-pr.outcome == 'failure'
uses: actions/github-script@v7
continue-on-error: true # Don't fail for fork PRs
with:
script: |
const prTitle = context.payload.pull_request.title;
const isFork = context.payload.pull_request.head.repo.full_name !== context.payload.pull_request.base.repo.full_name;
const comment = [
'## ⚠️ PR Title Format Suggestion',
'',
"Your PR title doesn't follow the Conventional Commits format, but **this won't block your PR from being merged**.",
'',
`**Current title:** \`${prTitle}\``,
'',
'**Recommended format:** `type(scope): description`',
'',
'### Valid types:',
'`feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`, `security`, `build`',
'',
'### Common scopes (optional):',
'`exchange`, `trader`, `ai`, `api`, `ui`, `frontend`, `backend`, `security`, `deps`, `workflow`, `github`, `actions`, `config`, `docker`, `build`, `release`',
'',
'### Examples:',
'- `feat(trader): add new trading strategy`',
'- `fix(api): resolve authentication issue`',
'- `docs: update README`',
'- `chore(deps): update dependencies`',
'- `ci(workflow): improve GitHub Actions`',
'',
'**Note:** This is a suggestion to improve consistency. Your PR can still be reviewed and merged.',
'',
'---',
'*This is an automated comment. You can update the PR title anytime.*'
].join('\n');
if (!isFork) {
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: comment
});
} catch (error) {
console.log('Could not post comment (expected for fork PRs):', error.message);
}
} else {
console.log('Fork PR - comment will be posted by pr-checks-comment.yml');
}
- name: Check PR size
uses: actions/github-script@v7
continue-on-error: true # Don't fail for fork PRs
with:
script: |
const pr = context.payload.pull_request;
const additions = pr.additions;
const deletions = pr.deletions;
const total = additions + deletions;
// Check if this is a fork PR
const isFork = pr.head.repo.full_name !== pr.base.repo.full_name;
let label = '';
let comment = '';
if (total < 300) {
label = 'size: small';
comment = '✅ This PR is **small** and easy to review!';
} else if (total < 1000) {
label = 'size: medium';
comment = '⚠️ This PR is **medium** sized. Consider breaking it into smaller PRs if possible.';
} else {
label = 'size: large';
comment = '🚨 This PR is **large** (>' + total + ' lines changed). Please consider breaking it into smaller, focused PRs for easier review.';
}
// Only add labels/comments for non-fork PRs (fork PRs don't have write permission)
if (!isFork) {
try {
// Add size label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [label]
});
// Add comment for large PRs
if (total >= 1000) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: comment
});
}
} catch (error) {
console.log('Failed to add label/comment (expected for fork PRs):', error.message);
}
} else {
console.log('Fork PR detected - skipping label/comment (will be handled by pr-checks-comment.yml)');
}
# Backend checks (simplified - no TA-Lib required)
backend-checks:
name: Backend Code Quality (Go)
runs-on: ubuntu-latest
permissions:
contents: read # Only need read access for testing
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Download dependencies
run: go mod download
- name: Run go fmt
continue-on-error: true # Don't block PR if formatting issues found
run: |
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
echo "⚠️ Code formatting issues found. Please run 'go fmt ./...' locally."
echo ""
echo "Files needing formatting:"
gofmt -s -l .
echo ""
echo "This is a warning and won't block your PR from being merged."
exit 1
else
echo "✅ All Go files are properly formatted"
fi
- name: Run go vet
run: go vet ./...
- name: Build
run: go build -v -o nofx
# Frontend tests
frontend-tests:
name: Frontend Tests (React/TypeScript)
runs-on: ubuntu-latest
permissions:
contents: read # Only need read access for testing
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Cache Node modules
uses: actions/cache@v4
with:
path: web/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('web/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
working-directory: ./web
run: npm ci
- name: Build and Type Check
working-directory: ./web
run: npm run build
# Note: build script runs "tsc && vite build" which includes type checking
# Auto-label based on files changed
auto-label:
name: Auto Label PR
runs-on: ubuntu-latest
# Only run for non-fork PRs (fork PRs don't have write permission)
if: github.event.pull_request.head.repo.full_name == github.repository
permissions:
contents: read
pull-requests: write
issues: write # Required: PRs are issues, labeler needs to modify issue labels
steps:
- uses: actions/labeler@v5
with:
configuration-path: .github/labeler.yml
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Check for security issues
security-check:
name: Security Scan
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # Required: Upload SARIF results to GitHub Security
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
# SECURITY: never use @master — upstream compromise = CI compromise.
# TODO: pin to a full 40-char SHA from
# https://github.com/aquasecurity/trivy-action/releases and configure Dependabot
# to keep it current. A version tag is still mutable but is a major upgrade
# over @master.
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
# Check for secrets in code
secrets-check:
name: Check for Secrets
runs-on: ubuntu-latest
permissions:
contents: read # Only need read access for scanning
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run TruffleHog OSS
# SECURITY: never use @main — upstream compromise = secret exfil.
# TODO: pin to a full 40-char SHA from
# https://github.com/trufflesecurity/trufflehog/releases and configure
# Dependabot. Version tag is still mutable but is a major upgrade over @main.
uses: trufflesecurity/trufflehog@v3.82.13
with:
path: ./
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}
extra_args: --debug --only-verified
# All checks passed
all-checks:
name: All Checks Passed
runs-on: ubuntu-latest
needs: [validate-pr, backend-checks, frontend-tests, security-check, secrets-check]
if: always()
permissions:
contents: read # Only need read access for status checking
steps:
- name: Check all jobs
run: |
# Note: validate-pr uses continue-on-error, so it won't block even if title format is invalid
# We only care about actual test failures
echo "validate-pr: ${{ needs.validate-pr.result }}"
echo "backend-checks: ${{ needs.backend-checks.result }}"
echo "frontend-tests: ${{ needs.frontend-tests.result }}"
echo "security-check: ${{ needs.security-check.result }}"
echo "secrets-check: ${{ needs.secrets-check.result }}"
# Check if any critical checks failed (excluding validate-pr which is advisory)
if [[ "${{ needs.backend-checks.result }}" == "failure" ]] || \
[[ "${{ needs.frontend-tests.result }}" == "failure" ]] || \
[[ "${{ needs.security-check.result }}" == "failure" ]] || \
[[ "${{ needs.secrets-check.result }}" == "failure" ]]; then
echo "❌ Critical checks failed"
exit 1
else
echo "✅ All critical checks passed!"
if [[ "${{ needs.validate-pr.result }}" != "success" ]]; then
echo " Note: PR title format check is advisory only and doesn't block merging"
fi
fi