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
44 changes: 40 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,18 +196,48 @@ jobs:
build-args: |
${{ matrix.service == 'frontend' && 'VITE_API_URL=/api/v1' || '' }}

# Dependency Review - blocks PRs with vulnerable dependencies
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Dependency Review
uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
# Only check runtime dependencies (skip dev dependencies)
fail-on-scopes: runtime
# License check disabled - focus on vulnerability scanning
# Many Python/JS packages use non-SPDX licenses or multiple licenses
license-check: false
# Comment on PR with summary
comment-summary-in-pr: on-failure

# Summary job that requires all checks to pass
# Note: dependency-review only runs on PRs, so we handle 'skipped' state gracefully
ci-summary:
name: CI Summary
needs: [changes, python-ci, frontend-ci, docker-build-test]
needs: [changes, python-ci, frontend-ci, docker-build-test, dependency-review]

Copilot AI Nov 30, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ci-summary job unconditionally depends on dependency-review, but dependency-review only runs on pull requests (if: github.event_name == 'pull_request'). This will cause the summary job to be skipped on push events to main/develop branches. Add 'if: always()' to dependency-review in the needs array or make the dependency conditional.

Copilot uses AI. Check for mistakes.
if: always()
runs-on: ubuntu-latest
steps:
- name: Check CI results
run: |
echo "Python CI: ${{ needs.python-ci.result }}"
echo "Frontend CI: ${{ needs.frontend-ci.result }}"
echo "Docker Build: ${{ needs.docker-build-test.result }}"
echo "## CI Results Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Job | Result |" >> $GITHUB_STEP_SUMMARY
echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Python CI | ${{ needs.python-ci.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Frontend CI | ${{ needs.frontend-ci.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Docker Build | ${{ needs.docker-build-test.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Dependency Review | ${{ needs.dependency-review.result }} |" >> $GITHUB_STEP_SUMMARY

# Fail if any required job failed
if [[ "${{ needs.python-ci.result }}" == "failure" ]] || \
Expand All @@ -217,4 +247,10 @@ jobs:
exit 1
fi

# Dependency review failure on PRs is critical (skipped on push events is OK)
if [[ "${{ needs.dependency-review.result }}" == "failure" ]]; then
echo "❌ Dependency review found vulnerabilities"
exit 1
fi
Comment on lines +251 to +254

Copilot AI Nov 30, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check will fail when dependency-review is skipped (on non-PR events), as the result will be 'skipped' not 'success'. Add a condition to only check when the job actually ran: if [[ \"${{ needs.dependency-review.result }}\" == \"failure\" ]]; then

Copilot uses AI. Check for mistakes.

echo "✅ All CI checks passed"
56 changes: 56 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: CodeQL Analysis

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
# Weekly Monday 8 AM UTC - comprehensive security analysis
- cron: '0 8 * * 1'
workflow_dispatch:

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

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
security-events: write
packages: read
actions: read
contents: read

strategy:
fail-fast: false
matrix:
language: [python, javascript]
# CodeQL supports: cpp, csharp, go, java, javascript, python, ruby, swift

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# Extended queries for thorough analysis
queries: +security-extended,security-and-quality
# Custom config for path filtering (optional)
# config-file: .github/codeql/codeql-config.yml

# Python is interpreted - no build needed
# JavaScript source is scanned directly - no build needed
# If we were scanning bundled JS, we'd run: npm run build

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
# Upload SARIF to GitHub Security tab
upload: true
38 changes: 38 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,27 @@ jobs:
sarif_file: 'trivy-api-results.sarif'
category: 'trivy-api'

# Dockle - CIS benchmark compliance checker
- name: Run Dockle on API image
uses: erzz/dockle-action@v1
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_API }}:${{ steps.meta.outputs.version }}
report-format: sarif
report-name: dockle-api-results
failure-threshold: high
exit-code: 0 # Don't fail build, just report
# Accept known deviations from CIS benchmarks
accept-keys: |
CIS-DI-0010
# CIS-DI-0010: Do not store secrets - we use env vars at runtime

- name: Upload Dockle API results to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: dockle-api-results.sarif
category: 'dockle-api'

build-and-push-frontend:
name: Build and Push Frontend Image
runs-on: ubuntu-latest
Expand Down Expand Up @@ -198,3 +219,20 @@ jobs:
with:
sarif_file: 'trivy-frontend-results.sarif'
category: 'trivy-frontend'

# Dockle - CIS benchmark compliance checker
- name: Run Dockle on Frontend image
uses: erzz/dockle-action@v1
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_FRONTEND }}:${{ steps.meta.outputs.version }}
report-format: sarif
report-name: dockle-frontend-results
failure-threshold: high
exit-code: 0 # Don't fail build, just report

- name: Upload Dockle Frontend results to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: dockle-frontend-results.sarif
category: 'dockle-frontend'
177 changes: 177 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
name: Security Scanning

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
# Weekly Monday 6 AM UTC - regular security audits
- cron: '0 6 * * 1'
workflow_dispatch:

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

jobs:
# Python dependency vulnerability scanning
pip-audit:
name: Python Dependency Scan (pip-audit)
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'

- name: Cache uv dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/uv
.venv
key: ${{ runner.os }}-uv-security-${{ hashFiles('uv.lock') }}
restore-keys: |
${{ runner.os }}-uv-security-
${{ runner.os }}-uv-

- name: Install dependencies
run: uv sync --all-extras --dev

- name: Run pip-audit
uses: pypa/gh-action-pip-audit@v1.1.0
with:
vulnerability-service: pypi
# Uncomment to ignore specific vulns if needed:
# ignore-vulns: |
# GHSA-xxxx-xxxx-xxxx

# Python SAST with Bandit (run directly with uv for proper pyproject.toml support)
bandit:
name: Python SAST (Bandit)
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'

- name: Cache uv dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/uv
.venv
key: ${{ runner.os }}-uv-bandit-${{ hashFiles('uv.lock') }}
restore-keys: |
${{ runner.os }}-uv-bandit-
${{ runner.os }}-uv-

- name: Install dependencies
run: uv sync --all-extras --dev

- name: Run Bandit Security Scan
run: |
uv run bandit -r phentrieve api -c pyproject.toml -f sarif -o bandit-results.sarif --severity-level medium --confidence-level medium || true
# Always generate output even if no issues found
if [ ! -s bandit-results.sarif ]; then
echo '{"$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"Bandit","version":"1.9.2"}},"results":[]}]}' > bandit-results.sarif
fi

- name: Upload Bandit results to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: bandit-results.sarif
category: bandit

# JavaScript dependency vulnerability scanning
npm-audit:
name: JavaScript Dependency Scan (npm audit)
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
working-directory: frontend
run: npm ci

- name: Run npm audit (production dependencies)
working-directory: frontend
run: npm audit --omit=dev --audit-level=critical

- name: Run npm audit (full report)
working-directory: frontend
run: npm audit --audit-level=high || true
# Don't fail on high for dev deps - many false positives

# Security scan summary
security-summary:
name: Security Summary
needs: [pip-audit, bandit, npm-audit]
if: always()
runs-on: ubuntu-latest

steps:
- name: Check security scan results
run: |
echo "## Security Scan Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Scan | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| pip-audit (Python deps) | ${{ needs.pip-audit.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Bandit (Python SAST) | ${{ needs.bandit.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| npm audit (JS deps) | ${{ needs.npm-audit.result }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

# Fail if critical scans failed
if [[ "${{ needs.pip-audit.result }}" == "failure" ]]; then
echo "::error::pip-audit found vulnerabilities in Python dependencies"
exit 1
fi

if [[ "${{ needs.bandit.result }}" == "failure" ]]; then
echo "::error::Bandit found security issues in Python code"
exit 1
fi

# npm-audit failure is warning only (many false positives)
if [[ "${{ needs.npm-audit.result }}" == "failure" ]]; then
echo "::warning::npm audit found issues - review recommended"
fi

echo "::notice::Security scans completed"
Loading
Loading