Skip to content

Security Scan

Security Scan #922

Workflow file for this run

name: Security Scan
on:
push:
branches: [ main, dev, develop ]
pull_request:
branches: [ main, dev, develop ]
schedule:
# Run security scans daily at 2 AM UTC
- cron: '0 2 * * *'
jobs:
dependency-scan:
name: Dependency Security Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pip-audit
- name: Run pip-audit on locked requirements
# CVE-2025-3000: memory corruption in torch.jit.script, local-host attack
# only, no patched torch release exists. torch is a transitive dependency
# (transformers / sentence-transformers) and the app never calls
# torch.jit.script on any input — not reachable. Accepted-risk suppression;
# revisit when an upstream fix ships. See TODO.md "CI And Security Evidence".
run: |
pip-audit -r requirements.txt --desc --format json --output pip-audit-report.json --ignore-vuln CVE-2025-3000
- name: Upload dependency scan report
uses: actions/upload-artifact@v4
with:
name: pip-audit-report
path: pip-audit-report.json
npm-audit:
name: NPM Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Run npm audit
env:
# npm audit only needs the dependency tree, not the binaries. Skipping
# the electron/chromedriver postinstall downloads keeps this job from
# flaking on transient CDN errors (e.g. HTTP 502 from electron install.js).
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
CHROMEDRIVER_SKIP_DOWNLOAD: true
run: |
cd frontend
npm ci
# || true: npm audit exits non-zero when vulnerabilities are found at the
# specified level. We capture the report as an artifact for review without
# hard-failing the job on known/accepted findings.
npm audit --audit-level=high --json > ../npm-audit.json || true
cat ../npm-audit.json
- name: Upload npm audit report
uses: actions/upload-artifact@v4
with:
name: npm-audit-report
path: npm-audit.json
code-security:
name: Code Security Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install bandit
run: pip install bandit[toml]
- name: Run Bandit full report
run: |
bandit -r backend/ core/ --exit-zero -f json -o bandit-full-report.json
- name: Validate Bandit baseline exists
run: |
test -f .bandit-baseline.json
- name: Normalize Bandit baseline paths
run: |
python - <<'PY'
import json
from pathlib import Path
source = Path(".bandit-baseline.json")
target = Path(".bandit-baseline.ci.json")
data = json.loads(source.read_text())
metrics = data.get("metrics", {})
data["metrics"] = {
key.replace("\\", "/") if key != "_totals" else key: value
for key, value in metrics.items()
}
for result in data.get("results", []):
filename = result.get("filename")
if isinstance(filename, str):
result["filename"] = filename.replace("\\", "/")
target.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n")
PY
- name: Run Bandit delta gate (new high-confidence issues)
run: |
bandit -r backend/ core/ -b .bandit-baseline.ci.json --exit-zero -f json -o bandit-report.json
bandit -r backend/ core/ -b .bandit-baseline.ci.json -ll -ii -f screen
- name: Upload Bandit report
uses: actions/upload-artifact@v4
with:
name: bandit-report
path: |
bandit-report.json
bandit-full-report.json
secret-scan:
name: Secret Scanning
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve TruffleHog diff range
if: github.event_name != 'schedule'
id: trufflehog-diff
uses: actions/github-script@v7
with:
script: |
const before = context.payload.before || '';
const isZero = /^0+$/.test(before);
let base = before;
if (context.eventName === 'pull_request') {
base = context.payload.pull_request.base.sha;
} else if (!base || isZero) {
const commits = (await github.rest.repos.listCommits({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 2,
sha: context.sha,
})).data;
base = commits.length > 1 ? commits[commits.length - 1].sha : context.sha;
}
core.setOutput('base', base);
core.setOutput('head', context.eventName === 'pull_request'
? context.payload.pull_request.head.sha
: context.sha);
- name: TruffleHog OSS (push/pr diff)
if: github.event_name != 'schedule'
uses: trufflesecurity/trufflehog@v3.93.1
with:
path: ./
base: ${{ steps.trufflehog-diff.outputs.base }}
head: ${{ steps.trufflehog-diff.outputs.head }}
extra_args: --debug --only-verified
- name: TruffleHog OSS (scheduled full scan)
if: github.event_name == 'schedule'
uses: trufflesecurity/trufflehog@v3.93.1
with:
path: ./
extra_args: --debug --only-verified
codeql-analysis:
name: CodeQL Analysis
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
matrix:
language: [ 'python', 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
sbom:
name: SBOM Generation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install CycloneDX tools
run: pip install cyclonedx-bom
- name: Generate Backend SBOM
run: |
if [ -f requirements-enterprise.txt ]; then
cyclonedx-py requirements requirements-enterprise.txt --output-file sbom-backend.json
else
cyclonedx-py requirements requirements.txt --output-file sbom-backend.json
fi
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Generate Frontend SBOM
run: |
if [ -f frontend/package-lock.json ]; then
cd frontend
npx @cyclonedx/cyclonedx-npm --package-lock-only --ignore-npm-errors --output-file ../sbom-frontend.json
fi
- name: Upload SBOMs
uses: actions/upload-artifact@v4
with:
name: sbom-reports
path: |
sbom-backend.json
sbom-frontend.json
if-no-files-found: error
artifact-signing:
name: Artifact Signing (Cosign)
needs: [sbom]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
id-token: write
steps:
- name: Download SBOM artifacts
uses: actions/download-artifact@v4
with:
name: sbom-reports
path: .
- name: Install Cosign
uses: sigstore/cosign-installer@v4.0.0
- name: Sign SBOM artifacts (keyless OIDC)
env:
COSIGN_EXPERIMENTAL: "1"
run: |
set +e
cosign version
test -f sbom-backend.json
cosign sign-blob --yes sbom-backend.json \
--output-signature sbom-backend.json.sig \
--output-certificate sbom-backend.json.pem
backend_status=$?
if [ "$backend_status" -ne 0 ]; then
echo "::warning::Cosign SBOM signing is unavailable for this run; continuing without signatures."
rm -f sbom-backend.json.sig sbom-backend.json.pem sbom-frontend.json.sig sbom-frontend.json.pem
exit 0
fi
if [ -f sbom-frontend.json ]; then
cosign sign-blob --yes sbom-frontend.json \
--output-signature sbom-frontend.json.sig \
--output-certificate sbom-frontend.json.pem
frontend_status=$?
if [ "$frontend_status" -ne 0 ]; then
echo "::warning::Cosign frontend SBOM signing failed; backend SBOM signature was preserved."
rm -f sbom-frontend.json.sig sbom-frontend.json.pem
fi
fi
- name: Upload signing artifacts
uses: actions/upload-artifact@v4
with:
name: sbom-signatures
path: |
sbom-backend.json.sig
sbom-backend.json.pem
sbom-frontend.json.sig
sbom-frontend.json.pem
if-no-files-found: warn
crash-reporting-probe:
name: Crash Reporting Probe
runs-on: ubuntu-latest
if: github.event_name == 'schedule' || github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Check crash reporting configuration
id: crash-reporting-config
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
run: |
if [ -n "$SENTRY_DSN" ]; then
echo "configured=true" >> "$GITHUB_OUTPUT"
else
echo "configured=false" >> "$GITHUB_OUTPUT"
echo "::notice::SENTRY_DSN is not configured; skipping crash-reporting probe."
fi
- name: Send Sentry probe event
if: steps.crash-reporting-config.outputs.configured == 'true'
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
FLASK_ENV: "production"
APP_VERSION: "security-workflow-probe"
run: |
python scripts/send_sentry_test_event.py --message "DataLogicEngine security workflow crash probe" --tag source=security --tag workflow=security