Skip to content
Merged
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
85 changes: 85 additions & 0 deletions .github/workflows/live-domain-monitor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Live domain monitor

# PT-17 (2026-07-13 external audit): nothing exercised the deployed product
# after release, so a broken domain, dead health probe, or lost route could
# only be discovered by a user. This monitor probes the public domain on a
# schedule with plain unauthenticated GETs — no secrets, no providers beyond
# what any anonymous visitor triggers, and no third-party actions.
#
# A red run is the alert. Deeper answer-SLO probing stays in the ops-digest
# workflow (dispatch-only, secret-gated); this one must stay cheap enough to
# never be worth disabling.

on:
schedule:
- cron: "23 */6 * * *"
workflow_dispatch:

permissions:
contents: read
Comment on lines +18 to +19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove the unused repository token permission.

This workflow only performs anonymous HTTP probes and does not checkout code, call GitHub APIs, or use actions. Granting contents: read is unnecessary; use permissions: {} instead.

As per coding guidelines, GitHub workflow automation must use narrow permissions.

Proposed fix
-permissions:
-  contents: read
+permissions: {}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
permissions:
contents: read
permissions: {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/live-domain-monitor.yml around lines 18 - 19, Update the
workflow-level permissions configuration in the live-domain monitor workflow to
use an empty permission set, removing the unnecessary contents: read grant. Keep
the anonymous HTTP probe behavior unchanged.

Source: Coding guidelines


concurrency:
group: live-domain-monitor
cancel-in-progress: false

jobs:
probe:
name: Probe public domain
runs-on: ubuntu-24.04
timeout-minutes: 10

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Align the job timeout with the retry budget.

There are eight sequential probes. With --max-time 30, --retry 2, and two 10-second retry delays, transient failures can consume roughly 14 minutes 40 seconds, exceeding timeout-minutes: 10. The job may be terminated before all routes are checked.

Possible fix
-    timeout-minutes: 10
+    timeout-minutes: 20

Alternatively, reduce the per-request retry budget while preserving the desired alert latency.

Also applies to: 37-37, 46-46, 63-63, 80-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/live-domain-monitor.yml at line 29, Align the workflow job
timeout with the worst-case runtime of all eight sequential probes, including
curl max time, retries, and retry delays; update each affected timeout-minutes
setting to exceed that retry budget so every route can complete before
termination.

env:
# Repository variable overrides the default (e.g. a staging cutover).
LIVE_DOMAIN_URL: ${{ vars.LIVE_DOMAIN_URL || 'https://psychiatry.tools' }}
steps:
- name: Root renders the app shell
run: |
set -euo pipefail
body="$(curl -sSL --fail --max-time 30 --retry 2 --retry-delay 10 --retry-all-errors "$LIVE_DOMAIN_URL/")"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Validate redirect destinations before reporting success.

-L accepts cross-origin redirects and HTTPS-to-HTTP downgrades. A route could redirect to another server that returns the expected body or 200, causing a false-green monitor and unintended outbound requests from the runner. Capture url_effective and require the final origin to match the configured domain and remain HTTPS.

Also applies to: 46-46, 63-63, 80-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/live-domain-monitor.yml at line 37, Update each curl check
in the live-domain monitor workflow to capture url_effective while following
redirects, then validate that the final URL remains HTTPS and has the same
origin as LIVE_DOMAIN_URL before accepting the response as successful. Apply
this consistently to all four monitored requests, preserving the existing
body/status checks and failing the workflow on origin or scheme mismatches.

echo "$body" | grep -q "Clinical Guide" || {
echo "::error::Root responded but does not render the Clinical Guide shell"
exit 1
}

- name: Health endpoint reports ok in live mode
run: |
set -euo pipefail
health="$(curl -sSL --fail --max-time 30 --retry 2 --retry-delay 10 --retry-all-errors "$LIVE_DOMAIN_URL/api/health")"
echo "$health"
echo "$health" | grep -q '"status":"ok"' || {
echo "::error::/api/health did not report status ok"
exit 1
}
echo "$health" | grep -q '"demoMode":false' || {
echo "::error::/api/health reports demo mode — live configuration lost"
exit 1
}

- name: Key routes stay reachable
run: |
set -euo pipefail
# -L follows the /applications -> /tools canonicalisation either side
# of that redirect landing, so this list survives route renames.
for route in /privacy /services /forms /differentials /applications; do
code="$(curl -sSL -o /dev/null -w '%{http_code}' --max-time 30 --retry 2 --retry-delay 10 --retry-all-errors "$LIVE_DOMAIN_URL$route")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep retries working for transient route failures

For the key-route probes, and again in the www probe below, a transient HTTP 500/502/503 enters curl's retry path, but this exact -o /dev/null --retry ... command on the ubuntu-24.04 curl 8.5.0 runner exits 23 while trying to truncate /dev/null, so it makes only one request and never reports the final status; curl --help all describes --retry as retrying transient problems and --fail as making HTTP errors fail fast, and adding --fail made a local 500-then-200 reproduction retry successfully. That means a healthy route behind a brief edge/deploy 5xx can make the scheduled monitor go red despite the intended two retries, so the probe should fail/retry HTTP errors without using this /dev/null retry path.

Useful? React with 👍 / 👎.

echo "$route -> $code"
if [ "$code" != "200" ]; then
echo "::error::$route returned $code"
exit 1
fi
done

- name: WWW and TLS answer
run: |
set -euo pipefail
# Only meaningful for the canonical production domain; an overridden
# LIVE_DOMAIN_URL (e.g. staging) has no www alias to assert.
if [ "$LIVE_DOMAIN_URL" != "https://psychiatry.tools" ]; then
echo "LIVE_DOMAIN_URL overridden; skipping www alias check"
exit 0
fi
code="$(curl -sSL -o /dev/null -w '%{http_code}' --max-time 30 --retry 2 --retry-delay 10 --retry-all-errors "https://www.psychiatry.tools/")"
echo "www -> $code"
if [ "$code" != "200" ]; then
echo "::error::www.psychiatry.tools returned $code"
exit 1
fi