Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

251 lines
9.5 KiB
YAML

name: Nuclei Vulnerability Scan
# Template-driven DAST scanner — complements ZAP with known-CVE checks,
# misconfiguration detection, exposed panel discovery, and default credential
# testing. Uses ProjectDiscovery's 6,500+ community-maintained templates.
#
# Authenticated scan: the runner pre-creates the standard CI test_admin user,
# logs in via the real /auth/login flow to capture a session cookie, and
# seeds Nuclei with the full Flask url_map so the scanner probes the entire
# authenticated app surface (settings, research, history, API, …) — not just
# the unauthenticated landing page.
on:
workflow_call: # Called by release-gate.yml
workflow_dispatch:
permissions: {}
jobs:
nuclei-scan:
name: Nuclei DAST Scan
runs-on: ubuntu-latest
timeout-minutes: 40
permissions:
contents: read
security-events: write
actions: read
env:
CI: true
TEST_ENV: true
FLASK_ENV: testing
LDR_DATA_DIR: ${{ github.workspace }}/data
LDR_DB_CONFIG_KDF_ITERATIONS: "1000"
# Honour the fast test KDF: without LDR_TEST_MODE the value is clamped
# to the production minimum (256000), making every SQLCipher open/backup
# ~256x slower — see #4430 and tests/shared/chrome_profile.js context.
LDR_TEST_MODE: "1"
LDR_DISABLE_RATE_LIMITING: "true"
SECRET_KEY: test-secret-key-for-ci
TEST_USERNAME: test_admin
TEST_PASSWORD: testpass123
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: '3.12'
- name: Set up PDM
uses: pdm-project/setup-pdm@973541a5febeafcfdadf8a51211435be6ecfd90f # v4.5
with:
python-version: '3.12'
- name: Install system dependencies for SQLCipher
run: |
sudo apt-get update
sudo apt-get install -y libsqlcipher-dev
- name: Install dependencies
run: |
pdm sync -d
- name: Setup test data directory
run: |
mkdir -p "$LDR_DATA_DIR/encrypted_databases"
- name: Pre-create CI test user
# Avoids the slow registration path (KDF + 500 settings rows) and
# avoids hitting the registration rate limit during the scan window.
run: |
export PYTHONPATH=$PWD/src:$PYTHONPATH
pdm run python scripts/ci/init_test_database.py
- name: Start LDR server for testing
run: |
export PYTHONPATH=$PWD/src:$PYTHONPATH
pdm run python -m local_deep_research.web.app > server.log 2>&1 &
SERVER_PID=$!
echo "$SERVER_PID" > server.pid
# Wait for server to start.
# --connect-timeout/--max-time bound TCP and total request time so a
# hung connection fails fast instead of eating the job timeout.
for _ in {1..90}; do
if curl -fsS --connect-timeout 2 --max-time 5 http://127.0.0.1:5000/api/v1/health > /dev/null; then
echo "Server started successfully"
break
fi
sleep 1
done
# Verify server is running
if ! curl -fsS --connect-timeout 2 --max-time 5 http://127.0.0.1:5000/api/v1/health > /dev/null; then
echo "Server failed to start"
cat server.log
exit 1
fi
- name: Dump Flask url_map for URL seeding
# Without -list, Nuclei only probes the single -target URL. Seeding
# with the real url_map lets it exercise every blueprint route.
run: |
export PYTHONPATH=$PWD/src:$PYTHONPATH
pdm run python scripts/ci/dump_url_map.py http://127.0.0.1:5000 > urls.txt
echo "Seeded $(wc -l < urls.txt) URLs:"
head -20 urls.txt
echo "..."
- name: Authenticate and capture session cookie
id: login
run: |
# Step 1: GET /auth/login → establishes a Flask session cookie and
# returns the HTML form with the per-session CSRF token.
curl -sS -c cookies.txt -o login.html http://127.0.0.1:5000/auth/login
CSRF=$(grep -oE 'name="csrf_token"[[:space:]]+value="[^"]+"' login.html \
| head -n1 \
| sed -E 's/.*value="([^"]+)".*/\1/')
if [ -z "$CSRF" ]; then
echo "Failed to extract CSRF token from login page"
head -200 login.html
exit 1
fi
# Step 2: POST credentials with the CSRF token and the cookie jar.
# -L follows the post-login 302 to /. -o /dev/null discards body,
# -w prints the final HTTP code so a non-2xx fails the step.
HTTP_CODE=$(curl -sS -L \
-b cookies.txt -c cookies.txt \
-o /dev/null -w '%{http_code}' \
--data-urlencode "username=${TEST_USERNAME}" \
--data-urlencode "password=${TEST_PASSWORD}" \
--data-urlencode "csrf_token=${CSRF}" \
http://127.0.0.1:5000/auth/login)
if [ "$HTTP_CODE" != "200" ]; then
echo "Login failed with HTTP $HTTP_CODE"
cat server.log
exit 1
fi
# Verify the session is actually authenticated (not just that
# the login page rendered with a 200 from a re-display of errors).
AUTHED=$(curl -sS -b cookies.txt http://127.0.0.1:5000/auth/check)
echo "auth/check response: $AUTHED"
echo "$AUTHED" | grep -q '"authenticated":[[:space:]]*true' || {
echo "Authenticated check failed"
exit 1
}
# Extract the session cookie value from the Netscape cookie jar.
# Format: domain TAB tailmatch TAB path TAB secure TAB expires TAB name TAB value
SESSION=$(awk '$6 == "session" { print $7 }' cookies.txt | tail -n1)
if [ -z "$SESSION" ]; then
# Print every column EXCEPT the value ($7) so we can debug
# without leaking the cookie if the awk filter is broken.
echo "Could not find 'session' cookie in jar (values redacted):"
awk '{ print $1, $2, $3, $4, $5, $6 }' cookies.txt
exit 1
fi
# Mask the cookie in logs — it grants full app access for this run.
echo "::add-mask::$SESSION"
echo "session=$SESSION" >> "$GITHUB_OUTPUT"
# Give the post-login background thread a moment to finish
# the settings-migration / library-init pass it kicks off
# (see _perform_post_login_tasks in web/auth/routes.py).
# Otherwise the first authenticated probes can race those
# writes and 500 on settings-dependent routes.
sleep 2
- name: Create empty SARIF fallback
run: |
cat > nuclei.sarif << 'SARIF'
{
"version": "2.1.0",
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"runs": [{
"tool": {
"driver": {
"name": "Nuclei",
"informationUri": "https://github.com/projectdiscovery/nuclei",
"rules": []
}
},
"results": []
}]
}
SARIF
- name: Run Nuclei scan
uses: projectdiscovery/nuclei-action@cc153d0541e1adf8a42bbe31c0a4fb2376147538 # v3.1.1
with:
# -list: probe every route in the Flask url_map (not just /).
# -H: attach the authenticated session cookie so probes hit the
# real app surface, not the login redirect.
# -severity: drop info-level noise (form-detection, options-method,
# intentional CSP/cookie choices). Real findings are >= low.
# -etags intrusive,dos,fuzz: with a live session, default templates
# can mutate state or DoS the runner. Exclude the standard
# destructive tag set for authenticated DAST.
# -eid http-missing-security-headers: HSTS is correctly omitted on
# plain HTTP localhost; X-XSS-Protection is intentionally
# omitted (deprecated, replaced by CSP). See
# security/security_headers.py.
args: >-
-list urls.txt
-H "Cookie: session=${{ steps.login.outputs.session }}"
-severity low,medium,high,critical
-etags intrusive,dos,fuzz
-eid http-missing-security-headers
-sarif-export nuclei.sarif
-output nuclei.log
- name: Upload Nuclei SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
if: always()
with:
sarif_file: nuclei.sarif
category: nuclei-dast
- name: Upload Nuclei scan artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: nuclei-scan-results
path: |
nuclei.log
nuclei.sarif
urls.txt
server.log
retention-days: 30
- name: Stop server
if: always()
run: |
if [ -f server.pid ]; then
kill "$(cat server.pid)" || true
rm server.pid
fi