Recipes
Telehealth CI integration
If you run a telehealth or patient-portal app, the riskiest changes are the ones that slip in between releases — a new analytics tag, a third-party chat widget, a relocated form. Gate them in CI: scan a preview deploy and fail the build if a high-severity, patient-facing finding appears.
The shape of the gate
After your preview or staging deploy is live, queue a scan against it, poll until it completes, then fail the job if the report contains a critical/high finding on a patient-facing page. The snippet below is a self-contained gate you can drop into any CI system that runs bash.
bash
#!/usr/bin/env bash
set -euo pipefail
BASE="https://api.sifthealth.app"
TARGET="${PREVIEW_URL:?set PREVIEW_URL to the deploy you want to scan}"
AUTH="Authorization: Bearer ${SIFT_API_KEY:?set SIFT_API_KEY}"
# 1. Queue a scan
scan_id=$(curl -sf "$BASE/v1/scans" -H "$AUTH" \
-H "Content-Type: application/json" \
-d "{\"url\": \"$TARGET\"}" | jq -r .scan_id)
# 2. Poll until it finishes (cap ~5 min)
for _ in $(seq 1 60); do
status=$(curl -sf "$BASE/v1/scans/$scan_id" -H "$AUTH" | jq -r .status)
[ "$status" = "completed" ] && break
[ "$status" = "failed" ] && { echo "scan failed"; exit 2; }
sleep 5
done
# 3. Fail the build on a critical/high finding on a patient-facing page
report=$(curl -sf "$BASE/v1/scans/$scan_id/report" -H "$AUTH")
blocking=$(echo "$report" | jq '[.findings[]
| select(.severity == "critical" or .severity == "high")
| select(.page_type == "intake_form" or .page_type == "appointment" or .page_type == "portal")] | length')
echo "Risk score: $(echo "$report" | jq -r .overall_score) ($(echo "$report" | jq -r .grade))"
if [ "$blocking" -gt 0 ]; then
echo "::error:: $blocking blocking finding(s) on patient-facing pages"
echo "$report" | jq -r '.findings[]
| select(.severity=="critical" or .severity=="high")
| " - [\(.severity)] \(.title)"'
exit 1
fi
echo "No blocking patient-facing findings. ✓"Requirements
The gate uses
curl and jq. Store SIFT_API_KEY as a CI secret and pass the preview URL in PREVIEW_URL. Adjust the page_type set and severity threshold to your risk tolerance.Queue-and-poll in your language
If you’d rather express the gate in your test suite, the same three steps in Python or JS:
import os, sys, time, requests
BASE = "https://api.sifthealth.app"
H = {"Authorization": f"Bearer {os.environ['SIFT_API_KEY']}"}
target = os.environ["PREVIEW_URL"]
sid = requests.post(f"{BASE}/v1/scans", json={"url": target}, headers=H).json()["scan_id"]
status = "queued"
for _ in range(60):
status = requests.get(f"{BASE}/v1/scans/{sid}", headers=H).json()["status"]
if status in ("completed", "failed"):
break
time.sleep(5)
if status != "completed":
sys.exit("scan did not complete")
report = requests.get(f"{BASE}/v1/scans/{sid}/report", headers=H).json()
blocking = [
f for f in report["findings"]
if f["severity"] in ("critical", "high")
and f.get("page_type") in ("intake_form", "appointment", "portal")
]
print(f"score={report['overall_score']} grade={report['grade']} blocking={len(blocking)}")
if blocking:
for f in blocking:
print(f" - [{f['severity']}] {f['title']}")
sys.exit(1)