Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bf5853e
deps: update browserslist to 4.28.8 so the production npm audit high …
claude Sep 2, 2026
2069b29
ci: give the worker's Python parsers a vulnerability signal (M20)
claude Sep 2, 2026
20f9df9
ci: notify on Staging tenancy isolation failures (M25)
claude Sep 2, 2026
2f36dad
ci: point the live Web-Vitals default routes at pages that render (M29)
claude Sep 2, 2026
76797a1
ci: digest-pin the advisory Semgrep image in sast.yml (L36)
claude Sep 2, 2026
0766b7e
ci: drop unused permissions from the Secret Scan workflow (L37)
claude Sep 2, 2026
79cc751
ci: gate the @claude workflows on author association (L38)
claude Sep 2, 2026
fbeada0
deploy: watch check-installed-lock-parity.mjs in both Railway service…
claude Sep 2, 2026
4d17dfb
deps: bring allowScripts in step with the lock (L55)
claude Sep 2, 2026
0e2f383
governance: route CODEOWNERS review to src/lib/rag/ (L91)
claude Sep 2, 2026
1576323
ci: point the Codex auto-resolve high-risk list at the real deploymen…
claude Sep 2, 2026
0b48f66
deps: drop the dead brace-expansion@2 override and record override ra…
claude Sep 2, 2026
9588a6b
deps: hold the @types/node major in Dependabot (L20)
claude Sep 2, 2026
92c9ba4
Merge remote-tracking branch 'origin/main' into wt/p8a
claude Sep 4, 2026
17663eb
Merge remote-tracking branch 'origin/main' into wt/p8a
claude Sep 4, 2026
1c00b54
deps: keep brace-expansion@2 as a recorded CVE pre-pin, and register …
claude Sep 4, 2026
3dd625e
Merge remote-tracking branch 'origin/main' into wt/p8a
claude Sep 4, 2026
e737d44
Merge remote-tracking branch 'origin/main' into wt/p8a
claude Sep 5, 2026
9f85af5
deps: floor fast-uri and @xmldom/xmldom onto their patched releases
claude Sep 5, 2026
70ab787
Merge branch 'main' into claude/audit-fix-p8a
BigSimmo Sep 5, 2026
e437611
Merge branch 'main' into claude/audit-fix-p8a
BigSimmo Sep 5, 2026
60288bf
Merge branch 'main' into claude/audit-fix-p8a
BigSimmo Sep 5, 2026
53bbb41
Merge branch 'main' into claude/audit-fix-p8a
BigSimmo Sep 5, 2026
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
5 changes: 2 additions & 3 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@
/src/instrumentation.ts @BigSimmo
/src/instrumentation-client.ts @BigSimmo

# RAG / retrieval / search / ranking
/src/lib/rag.ts @BigSimmo
/src/lib/rag-*.ts @BigSimmo
# RAG / retrieval / search / ranking (the stack moved to src/lib/rag/ in #994)
/src/lib/rag/ @BigSimmo
/src/lib/retrieval-*.ts @BigSimmo
/src/lib/*search*.ts @BigSimmo
/src/lib/privacy.ts @BigSimmo
Expand Down
41 changes: 41 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,19 @@ updates:
# not a function"). Same shape as the TypeScript hold — the lint toolchain is
# not yet ESLint-10-ready. Remove once eslint-config-next supports ESLint 10.
# Context: closed #1022.
#
# Hold the @types/node major: the types must track engines.node (24.x —
# .nvmrc, both Dockerfiles and the Railway images all run Node 24), or tsc
# accepts an API the deployed runtime does not have. Dependabot #549 is how
# the current 26.x line arrived; the pin back to 24.x is tracked in
# docs/framework-dependency-modernization-checklist.md (audit L20).
ignore:
- dependency-name: "typescript"
update-types: ["version-update:semver-major"]
- dependency-name: "eslint"
update-types: ["version-update:semver-major"]
- dependency-name: "@types/node"
update-types: ["version-update:semver-major"]

- package-ecosystem: "github-actions"
directory: "/"
Expand Down Expand Up @@ -63,3 +71,36 @@ updates:
groups:
docker-images:
patterns: ["*"]

# The ingestion worker parses attacker-supplied PDFs and images with
# PyMuPDF, Pillow, pytesseract and docling. Without these two entries nothing
# in the repo reports a published CVE against a pinned parser (audit M20).
- package-ecosystem: "pip"
directory: "/worker/python"
# Hash-pinned by pip-compile: a bump must regenerate the hashes with
# `npm run generate:worker-python-lock`, then pass
# `npm run check:worker-python-locks:static`, rather than edit a version.
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "Australia/Perth"
open-pull-requests-limit: 2
groups:
worker-python:
patterns: ["*"]

- package-ecosystem: "pip"
directory: "/eval/docling"
# Hash-pinned Gate B lab lock that Dockerfile.worker builds the docling venv
# from; regenerate per docs/worker-deploy-runbook.md and confirm with
# `npm run check:worker-python-locks:static` before merging a bump.
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "Australia/Perth"
open-pull-requests-limit: 2
groups:
docling-python:
patterns: ["*"]
8 changes: 6 additions & 2 deletions .github/workflows/claude-backlink.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,21 @@ jobs:
name: Reply with a Claude Code link
runs-on: ubuntu-24.04
# Only when a human (never a bot — including this workflow's own reply and
# Claude's) mentions @claude, so the backlink never loops or spams automated
# comments.
# Claude's) who is an owner, member or collaborator mentions @claude, so
# the backlink never loops, spams automated comments, or answers accounts
# outside the repository's trust boundary (audit L38).
if: >
(github.event_name == 'issue_comment' &&
github.event.comment.user.type != 'Bot' &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) &&
contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' &&
github.event.comment.user.type != 'Bot' &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) &&
contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' &&
github.event.review.user.type != 'Bot' &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association) &&
github.event.review.body != null &&
contains(github.event.review.body, '@claude'))
permissions:
Expand Down
15 changes: 11 additions & 4 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
name: Claude

# Runs a Claude Code session when a collaborator mentions "@claude" on a pull
# request comment or review. Claude reads the triggering comment, works on the
# Runs a Claude Code session when an owner, member or collaborator of this
# repository mentions "@claude" on a pull request comment or review. The
# author_association gate in the job's `if:` is what enforces that boundary;
# the bot check alone would admit any account able to comment (audit L38). Claude reads the triggering comment, works on the
# PR's current head, pushes a scoped fix to the PR branch, and replies in the
# thread. Unlike a subscribed chat session, this fires on the webhook every
# time, so it keeps working after the session that opened the PR has ended.
Expand All @@ -22,18 +24,23 @@ jobs:
claude:
name: Respond to @claude
runs-on: ubuntu-24.04
# Only run when a human (not a bot) mentions @claude on a pull request, so
# ordinary comments — and Claude's own replies — never spin up a runner.
# Only run when a human (not a bot) with owner, member or collaborator
# association mentions @claude on a pull request, so ordinary comments —
# Claude's own replies, and accounts outside the maintainer's trust
# boundary — never spin up a runner that holds write and id-token scopes.
if: >
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request != null &&
github.event.comment.user.type != 'Bot' &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) &&
contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' &&
github.event.comment.user.type != 'Bot' &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) &&
contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' &&
github.event.review.user.type != 'Bot' &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association) &&
github.event.review.body != null &&
contains(github.event.review.body, '@claude'))
permissions:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codex-autofix-review-comments.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
/^\.github\/(?:actions|workflows)\//,
/^(?:package|package-lock)\.json$/,
/^(?:next|playwright|vitest)(?:\..+)?\.config\.[cm]?[jt]s$/,
/^(?:Dockerfile|railway\.json|nixpacks\.toml)$/,
/^(?:Dockerfile(?:\.worker)?|railway\.(?:app|worker)\.json)$/,
];
const sourcePathPattern = /^(?:src|scripts|supabase|\.github\/(?:actions|workflows))\//;
const sourceExtensionPattern = /\.(?:[cm]?[jt]sx?|sql|css|scss|json|ya?ml)$/i;
Expand Down
43 changes: 41 additions & 2 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,47 @@ jobs:

- name: Vulnerability scan (HIGH,CRITICAL)
# Non-blocking by design — findings are reported, not merge-gating.
# The summary lines are kept so the next step can surface them.
continue-on-error: true
run: |
docker builder prune -af || true
node scripts/trivy-image-scan.mjs clinical-kb-app:ci
node scripts/trivy-image-scan.mjs clinical-kb-worker:ci
: > trivy-scan-summary.txt
node scripts/trivy-image-scan.mjs clinical-kb-app:ci | tee -a trivy-scan-summary.txt
node scripts/trivy-image-scan.mjs clinical-kb-worker:ci | tee -a trivy-scan-summary.txt

- name: Fail on HIGH/CRITICAL image findings outside pull requests
# Audit M20: the worker image carries the Python parsers (PyMuPDF,
# Pillow, pytesseract, docling) that read attacker-supplied uploads, and
# until now a HIGH/CRITICAL finding reached only this run's log. Pull
# request runs stay advisory (a base-image CVE is not the PR author's
# change), but the weekly schedule, main pushes and manual dispatches
# now fail on any HIGH/CRITICAL so notify-ci-failure.yml — which already
# watches "Docker image build" — delivers it to chat. A scanner that
# could not run (disk pressure, docker save) is still only a warning.
if: always()
run: |
set -euo pipefail
if [ ! -s trivy-scan-summary.txt ]; then
echo "::warning::Trivy produced no scan summary; image vulnerabilities were not assessed this run."
exit 0
fi
{
echo "## Trivy image scan (HIGH,CRITICAL)"
echo
echo '```'
cat trivy-scan-summary.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
high="$(grep -oE 'HIGH=[0-9]+' trivy-scan-summary.txt | cut -d= -f2 | paste -sd+ - | bc || echo 0)"
critical="$(grep -oE 'CRITICAL=[0-9]+' trivy-scan-summary.txt | cut -d= -f2 | paste -sd+ - | bc || echo 0)"
high="${high:-0}"
critical="${critical:-0}"
echo "HIGH=${high} CRITICAL=${critical}"
if [ "${high}" -eq 0 ] && [ "${critical}" -eq 0 ]; then
exit 0
fi
if [ "${{ github.event_name != 'pull_request' && github.event_name != 'merge_group' }}" = "true" ]; then
echo "::error::Trivy found HIGH=${high} CRITICAL=${critical} in the production images; see the job summary."
exit 1
fi
echo "::warning::Trivy found HIGH=${high} CRITICAL=${critical} in the production images (advisory on pull requests)."
19 changes: 13 additions & 6 deletions .github/workflows/live-web-vitals.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,26 @@ name: Live Web Vitals baseline
# any breach
# -> only the breaching route's findings become actionable, ranked by
# measured contribution
# Record the verdict in docs/outstanding-issues.md against #017 either way.
# #017 was CLOSED on 2026-07-31, so do not record against it. Record the
# verdict on the open Web-Vitals row in docs/outstanding-issues.md through
# `npm run issues:update` (never a hand edit) either way.

on:
workflow_dispatch:
inputs:
routes:
description: "Comma-separated routes to measure"
required: false
# Every entry must be a real page route. `/documents` is not one — the
# documents segment holds only `search`, `source` and `[id]` with no
# `page.tsx` (see docs/site-map.md), so it would have measured the 404
# document. `/documents/search` is the canonical documents-mode route.
default: "/,/therapy-compass,/documents/search,/dsm,/forms"
# Every entry must be a real page route that renders in place. The
# summariser rejects a report whose final URL differs from the requested
# one, so a redirecting entry can never be graded: `/therapy-compass`,
# `/dsm` and `/forms` became 307 redirects onto `/?mode=<id>` (#2157,
# #2308) and are measured through their `<mode>/search` result routes
# instead — the same reasoning as `$routes` in lighthouse-budget.json.
# A bare `/documents` would have measured the 404 document; the
# canonical documents-mode route is `/documents/search`. Guarded by
# tests/ci-audit-contracts.test.ts (audit M29).
default: "/,/therapy-compass/search,/documents/search,/dsm/search,/forms/search"
samples:
description: "Lighthouse runs per route/strategy (median is graded)"
required: false
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/notify-ci-failure.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ on:
- Eval Canary
- Ingestion Autopilot
- Docker image build
# Daily owner-boundary detector; without this entry its failure reached
# only GitHub's default e-mail (audit M25).
- Staging tenancy isolation
types:
- completed

Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/sast.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 15
container:
image: semgrep/semgrep:1.168.0
# Same immutable digest as the blocking ingestion gate in ci.yml (the
# triage-verified 1.168.0 image, docs/maturity-backlog-workorders.md X4):
# a bare tag can be re-pushed upstream, and this job runs third-party
# code with read access to the private source tree (audit L36). Bump
# both references together.
image: semgrep/semgrep:1.168.0@sha256:59fbed6127ea7c5dde3ba6a85142733bb20ea9aaa36120c953904f1539aaf66e
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

# Least privilege: scripts/run-gitleaks-pinned.mjs only reads the checkout.
# The pull-requests/security-events grants were a leftover from
# gitleaks-action@v3's SARIF upload, which this workflow no longer performs
# (audit L37).
permissions:
contents: read
pull-requests: read
security-events: write

env:
# Match the version gitleaks-action@v3 installs by default.
Expand Down
Loading
Loading