Skip to content

SOUP — Software of Unknown Provenance

Inventory of third-party code shipped with the RestartiX Platform, classified by risk and purpose. Required for IEC 62304 (medical device software) compliance and good practice for any GDPR-regulated platform.

What "SOUP" means. Any software not authored by the project team is "of unknown provenance" — even if the source is public, we did not write or fully audit it. Medical device certification requires a documented, risk-classified inventory. Plain dependency lists (go.mod, package.json) are not sufficient because they don't carry purpose or risk classification.

The authoritative inventory of every byte we ship lives in services/api/go.sum and pnpm-lock.yaml — both checked in. This document is the risk-classified subset focused on direct dependencies (the libraries we deliberately chose to depend on). Indirect / transitive dependencies inherit their risk classification from the parent that pulled them in.


Risk Classification

TierMeaningExample failure
CriticalFailure compromises patient safety, data integrity, authentication, or audit. Bug class includes silent data loss, privilege escalation, or memory-safety issues with user-controlled input.Postgres driver corrupts a write; auth library accepts forged JWT.
HighFailure causes feature outage, data leak, or accessibility breach. No direct safety impact, but production SLO and compliance affected.Redis client misbehaves and rate-limit bypass succeeds; a11y primitive regresses keyboard nav.
StandardFailure causes UI inconvenience or developer-experience regression. No safety, security, or compliance impact.Icon library renders blank; date formatter localizes wrong month name.

The AI/ML model columns (Model provider, Model version, Validation status) are populated only for AI/ML model dependencies. They are NULL for libraries — see AI/ML Models below.


Backend Runtime Dependencies

Direct dependencies in services/api/go.mod. Indirect dependencies are inherited and tracked in services/api/go.sum.

PackageVersionPurposeRiskModel providerModel versionValidation status
github.com/aws/aws-sdk-go-v2v1.41.6AWS SDK base — authentication, request signing, retry logic. Foundation for S3 + future KMS.Critical
github.com/aws/aws-sdk-go-v2/configv1.32.16AWS SDK config loading (env, profile, IMDS).Critical
github.com/aws/aws-sdk-go-v2/credentialsv1.19.15AWS SDK credentials providers (static, env, role assumption).Critical
github.com/aws/aws-sdk-go-v2/service/s3v1.100.0S3 client — patient-data file storage (signatures, documents, forms uploads). See file-storage.md.Critical
github.com/aws/aws-sdk-go-v2/service/sesv2v1.60.4SES v2 client — transactional email transport for the platform notification primitive (1A.18 EmailChannel). Member invites, break-glass alerts, and every future first-party email to a human flows through this.Critical
github.com/aws/aws-sdk-go-v2/service/cloudwatchv1.58.1CloudWatch client — read-only GetMetricData for the render-pipeline ops view (media-service CPU/memory utilisation). Console superadmin observability only; no patient data.Low
github.com/aws/aws-sdk-go-v2/service/ecsv1.82.2ECS client — read-only DescribeServices for the render-pipeline ops view (media-service running/desired/pending task counts). Observability only.Low
github.com/aws/aws-sdk-go-v2/service/applicationautoscalingv1.41.20Application Auto Scaling client — read-only DescribeScalingActivities for the render-pipeline ops view (media-service scale-in/out events). Observability only.Low
github.com/clerk/clerk-sdk-go/v2v2.5.1Clerk auth SDK — JWT verification on every authenticated request. See features/auth/clerk-integration.Critical
github.com/go-chi/chi/v5v5.2.5HTTP router. Every API request passes through chi.Critical
github.com/google/uuidv1.6.0UUIDv7 generation. Every primary key in the database.Critical
github.com/jackc/pgx/v5v5.8.0Postgres driver + connection pooling. Sole DB access path.Critical
github.com/microcosm-cc/bluemondayv1.0.27HTML sanitiser for clinic-authored booking-page copy (calendars.description_html). The only writer of that column.Critical
github.com/redis/go-redis/v9v9.18.0Redis client — caching, idempotency store, slot holds, rate limiting.Critical
github.com/kelseyhightower/envconfigv1.4.0Environment-variable to config struct. Boot-time only.High
github.com/oapi-codegen/runtimev1.4.0OpenAPI runtime helpers used by generated types. Types-only generation keeps the runtime surface minimal.High
gopkg.in/yaml.v3v3.0.1YAML parser (OpenAPI spec, tooling).High
github.com/lmittmann/tintv1.1.3Colored slog handler for development logs. Production uses the stdlib JSON handler.Standard
github.com/testcontainers/testcontainers-gov0.42.0Test-only — base testcontainers API. Used directly to customise the Postgres container with shared_preload_libraries=pg_stat_statements (1A.16). Not in production binary.Standard
github.com/testcontainers/testcontainers-go/modules/postgresv0.42.0Test-only — spins up Postgres containers for the RLS integration test harness. Not in production binary.Standard
github.com/testcontainers/testcontainers-go/modules/localstackv0.42.0Test-only — spins up LocalStack for the S3 integration test harness. Not in production binary.Standard
github.com/testcontainers/testcontainers-go/modules/redisv0.42.0Test-only — spins up Redis containers for the rate-limit integration test harness. Not in production binary.Standard
golang.org/x/modv0.37.0go.mod parser used by cmd/check-soup to enumerate direct Go deps for the SOUP gate. Build-tooling only.Standard
golang.org/x/imagev0.44.0WebP decoder and the high-quality scaler behind internal/shared/imaging — the normaliser every uploaded image passes through before storage (square-crop, dimension bound, re-encode). Parses attacker-supplied bytes, so a decoder bug is reachable from an unauthenticated-adjacent path; the pixel ceiling is checked from the header before any buffer is allocated to bound the blast radius.High

Frontend Runtime Dependencies

Direct dependencies in apps/clinic, apps/portal, apps/console, and packages/ui. Shipped to the browser at runtime. Indirect dependencies tracked in pnpm-lock.yaml.

PackageVersionOwnerPurposeRiskModel providerModel versionValidation status
@react-pdf/renderer^4.3.0clinicServer-side PDF rendering (F6.2) — turns a pdf_templates block list into a report or medical prescription. Runs in Node via renderToBuffer, never in the browser: the server must produce the bytes it stores as a medical record. Chosen over a headless-Chrome sidecar on measurement (41ms p50 / 31MB vs 193ms / 518MB) and because the legacy report templates port near-verbatim. Romanian diacritics require an explicitly registered font — the built-in fonts drop ș/ț silently.High
@daily-co/daily-js^0.91.0clinic, portalBrowser SDK for video consultations (F5.5). Joins a room with a short-lived, server-minted token — the client never learns a room name and never calls Daily's REST API, which is what stops an appointment identifier from being a key to a live consultation. Used as a CALL OBJECT rather than the prebuilt iframe, because the clinician's call has to sit inside the consultation record rather than in an opaque frame, and because a cross-origin iframe cannot be frame-captured for the posture-grid tool the clinic uses daily. Media is peer-to-peer / SFU with the media server pinned to eu-central-1; recording is off.Critical
@clerk/nextjs^7.2.5clinic, portal, consoleClerk auth integration — sign-in / sign-up / session refresh on every page.Critical
@sentry/nextjs^10.56.0portalError / crash monitoring for the patient app — captures client (browser) and server (SSR / route-handler) exceptions to a Sentry EU-region project. Configured GDPR-tight (sendDefaultPii: false, includeLocalVariables: false, URL-token scrubbing in beforeSend, Session Replay error-only + fully masked) so no patient PII egresses. Failure mode that matters is a data leak (error report carrying PII), hence High; an outright SDK outage just loses observability.High
Sentry browser SDK (vendored)@sentry/browser 7.120.4 bundle.es5.min.js (apps/tv/sentry.min.js)tvError / crash monitoring for the static TV companion (no build step → self-hosted pinned bundle, loaded via <script>; not an npm manifest dep, so the first cell is intentionally non-backticked to stay out of the check-soup manifest gate). Same Sentry EU project family; GDPR-tight (no PII, no replay, URL-token scrubbing). High for the same data-leak reason as @sentry/nextjs. Pin the version in lockstep with the committed sentry.min.js. Must stay an ES5 build — v8+/v10 bundles use ?./??, which Chrome 56 (Tizen 4) can't parse, silently killing all TV error monitoring; v7 bundle.es5.min.js is the newest ES5 line.High
next16.2.4clinic, portal, consoleNext.js framework — server components, routing, proxy.ts.Critical
react^19.2.5clinic, portal, console, uiUI framework.Critical
react-dom^19.2.5clinic, portal, console, uiReact DOM renderer.Critical
undici^8.2.0api-clientHTTP/1.1 client backing Node's fetch() — every server-component / server-action call to the API flows through it. We install a tuned global Agent in each app's instrumentation.ts (keep-alive 30s, 64 connections per origin) to avoid TCP churn at fleet scale. Server-only; not shipped to the browser.Critical
hls.js^1.6.16portalHLS adaptive-bitrate playback for the patient session exercise player. Consumes Bunny Stream HLS playlists on Chrome / Firefox / Edge / Android Chrome; Safari (iOS + macOS) uses native HLS without this library. Failure means exercise video won't play, blocking the session flow.High
radix-ui^1.4.3uiAccessibility primitives (dialogs, popovers, dropdowns, listboxes). a11y compliance for forms / clinical UI.High
@dnd-kit/core^6.3.1uiDrag-and-drop primitive behind the SortableList wrapper (packages/ui) — the sole import site. Powers reorder in the clinic program builder (phases, sessions, exercises). a11y-sensitive: ships a keyboard sensor + live-region announcements so reorder works without a pointer.High
@dnd-kit/sortable^10.0.0uiSortable preset for @dnd-kit/core (vertical list reordering) used by SortableList. Same a11y risk surface.High
@dnd-kit/utilities^3.2.2uiCSS transform helpers for @dnd-kit drag transitions.High
@tiptap/react^3.29.2uiReact bindings for the rich-text editor behind the RichTextEditor wrapper (packages/ui) — the sole import site. Authors tenant-facing copy such as calendars.description_html. Security-relevant: its HTML output is never trusted; internal/shared/richtext sanitises every write server-side, and the editor toolbar is kept in step with that allowlist.High
@tiptap/starter-kit^3.29.2uiDefault node/mark set for @tiptap/react, configured down to the elements the server allowlist accepts (link and horizontal-rule disabled).High
@tiptap/extension-underline^3.29.2uiUnderline mark, not part of starter-kit. Matches leo's toolbar, which clinics use for emphasis in booking copy.Medium
zod^4.3.6uiRuntime schema validation for forms and inputs.High
next-intl^4.9.1clinic, portal, consolei18n message resolution (en, ro). See i18n.md.Standard
@tanstack/react-table^8.21.3uiHeadless table primitives — Console audit-log table and every Layer 2+ list page.Standard
react-day-picker^9.14.0uiCalendar component (date filters, future scheduling UI).Standard
react-resizable-panels^4.12.2uiResizable split panes — the live-consultation view, where a clinician sizes the consultation record against the video call.Standard
lucide-react^1.11.0clinic, portal, console, uiIcon set.Standard
class-variance-authority^0.7.1uiVariant utility for component styling.Standard
clsx^2.1.1uiConditional className utility.Standard
tailwind-merge^3.5.0uiTailwind class deduplication.Standard
date-fns^4.1.0uiDate formatting and arithmetic.Standard
tw-animate-css^1.4.0uiAnimation utility classes.Standard
sonner^2.0.7uiToast / notification primitive — surfaces action results (saved, deleted, error) across all three apps. Composes with React 19 useActionState server-action flow used everywhere. shadcn's canonical toast since 2024.Standard
recharts^3.8.1clinicCharting library — adherence sparklines, pain/RPE trend lines, activity histograms on the F9 Phase 2 patient stats tab (/patients/[id]/stats). Pure SVG; no canvas. Will also be consumed by the portal /progress page (L4) without a separate SOUP entry — same package, same risk. Failure renders empty chart areas; stats data still readable in tables.Standard
libphonenumber-js^1.13.10uiPhone-number parsing, per-country validation and E.164 output — backs the shared PhoneInput used for every phone field (patient profile, emergency contact, clinic locations, F3 form phone fields). Client-side only; the API independently canonicalises to E.164 in internal/shared/phone, so a validation gap here cannot produce a malformed stored number. Failure degrades to an unvalidated text field, never to a bad write.Standard
qrcode.react^4.2.0uiQR-code rendering — consumed by the share-link mint UI (L10) so clinic staff can hand a phone-scannable QR to a patient at the desk. SVG-based; no canvas dependency.Standard
qrcode^1.5.4portalServer-side QR string generation for /unsupported, where the route handler can't call react-dom/server (Next 16 Turbopack blocks that import in app-router route handlers). Returns an SVG string we inline into the hand-written HTML.Standard
@types/qrcode^1.5.5portalTypeScript types for qrcode (the package ships JS only).Standard
swr^2.4.1clinic, ui, portalClient-owned data cache (P46's canonical client cache). First consumer is the clinic program builder: high-interaction surfaces fetch reads client-side (SWR over clinic route handlers) and apply optimistic mutate() on writes instead of triggering a per-action RSC refresh(). Generalized in packages/ui via the shared useApiResource hook (read cache + revalidate-on-focus/reconnect) for the Portal hybrid, so return navigation is an instant cache hit and data self-refreshes. Writes still go through server actions (auth/RLS/audit). Failure degrades to a stale view + a manual reload; no clinical-safety impact.Standard
@mediapipe/tasks-vision0.10.35portalClient-side pose-landmark detection (PoseLandmarker, VIDEO running mode) behind the live skeleton overlay on the session "position yourself" step — lets the patient see they're in frame before a session starts. WASM runtime + the .task model are self-hosted under apps/portal/public/mediapipe/ (no CDN, no external runtime fetch); everything runs in-browser and nothing is recorded, stored, or sent. Pulled in via a dynamic import() only on that one screen. Any failure (no WebGL, asset 404, slow device) degrades to the plain mirrored camera and never blocks the flow. Not the F9.1 Class I telemetry pose pipeline — a positioning preview, outside the medical-device boundary. Pinned to an exact version so the committed loader stays in lockstep with the generated WASM.StandardGoogle MediaPipepose_landmarker_lite (float16)Non-clinical — informational overlay only; drives no measurement, rep count, or treatment decision. Not validated for clinical use.

Build & Tooling

Dependencies used at build, lint, format, typecheck, or doc-generation time. Not shipped to production — failures here cause CI failures, never patient-visible behavior.

This section is included for completeness but classified at a lower bar than runtime deps. Risk is uniformly Standard unless flagged.

PackageVersionOwnerPurposeRisk
typescript^6.0.3all workspacesTypeScript compiler.High — silent miscompilation could produce incorrect bundles.
turbo^2.9.6rootMonorepo task orchestrator.Standard
@turbo/gen^2.9.6uiCode generators for shadcn components.Standard
tailwindcss^4.2.4uiCSS framework, build-time.Standard
@tailwindcss/postcss^4.2.4ui, all appsPostCSS plugin for Tailwind.Standard
shadcn^4.4.0uishadcn/ui CLI for adding components. Source-imported, not runtime.Standard
eslint^9.39.4all workspacesLinter.Standard
@eslint/js^9.39.4eslint-configESLint base rules.Standard
@next/eslint-plugin-next^16.2.4eslint-configNext.js linting rules.Standard
@typescript-eslint/eslint-plugin^8.59.0eslint-configTypeScript-aware lint rules.Standard
@typescript-eslint/parser^8.59.0eslint-configTypeScript parser for ESLint.Standard
typescript-eslint^8.59.0eslint-configTypeScript-ESLint umbrella.Standard
eslint-config-prettier^10.1.8eslint-configDisables ESLint rules that conflict with Prettier.Standard
eslint-plugin-only-warn^1.2.1eslint-configConvert errors to warnings (non-blocking).Standard
eslint-plugin-react^7.37.5eslint-configReact lint rules.Standard
eslint-plugin-react-hooks^7.1.1eslint-configReact hooks lint rules.Standard
eslint-plugin-turbo^2.9.6eslint-configTurborepo lint rules.Standard
globals^17.5.0eslint-configGlobal identifier definitions for ESLint.Standard
prettier^3.8.3rootFormatter.Standard
openapi-typescript^7.13.0rootOpenAPI → TypeScript codegen for @workspace/api-client.Standard
vitepress^1.6.3docsStatic-site generator for apps/docs/. Not part of any product app.Standard
vitest^3.2.4clinic, portal, console, vitest-configComponent-test runner. Renders client components in jsdom, covering what tsc and next build cannot — they compile a component without executing its render. Not shipped; Next.js builds with Turbopack and never loads Vite.Standard
jsdom^30.0.1clinic, portal, consoleDOM implementation the component tests render into.Standard
vite^5.4.21vitest-configPeer of Vitest, imported only for the UserConfig type in the shared test config. Not a build tool for any app — Next.js compiles with Turbopack.Standard
@testing-library/react^16.3.2clinic, portal, console, vitest-configReact render + query helpers for component tests.Standard
@testing-library/jest-dom^7.0.0clinic, portal, console, vitest-configDOM matchers (toBeInTheDocument) for component tests.Standard
@types/node^25.6.0all apps, uiNode types.Standard
@types/react^19.2.14all apps, uiReact types.Standard
@types/react-dom^19.2.3all apps, uiReact DOM types.Standard

Container Image Packages

Third-party binaries shipped inside production container images. Not visible to go.mod or package.json, so cmd/check-soup cannot enforce these — they are maintained by hand, and adding an apk add / wget line to a Dockerfile requires a row here in the same PR.

Versions track the base image's package repository unless explicitly pinned; the base image tag is the version anchor.

PackageVersionImagePurposeRisk
alpine3.21Dockerfile.api, telemetry, mediaRuntime base image. Supplies libc + the package repository every row below resolves against.Critical — a compromised base image compromises everything running on it.
postgresql17-clientalpine 3.21 repoDockerfile.apipg_dump for the Layer 2 daily backup (cmd/backup-runner). Major version must be >= the RDS server (17.9) or pg_dump refuses outright.Critical — this binary produces the artifact that patient-data recovery depends on. A silent corruption here is not detected until a restore is attempted. Mitigated by read-back checksum verification + the documented restore drill.
opensslalpine 3.21 repoDockerfile.apiAES-256-CBC envelope encryption of the backup artifact (enc -pbkdf2 -iter 100000). Also the decryption tool named in the restore runbook.Critical — a change in default KDF or cipher behaviour between versions would make existing backups undecryptable. The runner passes every parameter explicitly rather than relying on defaults, specifically to survive that.
gzipalpine 3.21 repoDockerfile.apiCompression stage of the backup pipeline.High — a corrupt stream fails the restore, though the read-back checksum catches it at write time.
golang-migratev4.18.1 (pinned)Dockerfile.apiSchema migration CLI, run by the migrations-runner ECS task.Critical — applies DDL to the production database.

AI/ML Models

One AI/ML model ships today, and it is non-clinical: a client-side MediaPipe pose-landmarker that draws a live skeleton on the session "position yourself" step so the patient can confirm they're in frame. It runs entirely in the browser, stores/sends nothing, and drives no measurement or treatment decision (loaded via @mediapipe/tasks-vision above). The telemetry pose pipeline (F9.1) — where pose measurements would inform clinical decisions — is a separate, not-yet-shipped concern gated on the Class IIa step, since the registered device declares no measuring function; see Hook 5 of the AI-first ADR.

ModelVersionProviderPurposeRiskValidation status
MediaPipe Pose Landmarker (lite)pose_landmarker_lite float16 / runtime @mediapipe/tasks-vision 0.10.35Google MediaPipeLive in-browser skeleton overlay on the session "position yourself" step (positioning preview — nothing recorded, stored, or sent). Self-hosted model + WASM under apps/portal/public/mediapipe/.StandardNon-clinical — informational overlay only; drives no measurement, rep count, or treatment decision. Not validated for clinical use.
examplegpt-5OpenAIexercise prescription draftingCriticalValidated against N=200 manually-reviewed cases — see model-validation/...

Validation status is the regulatory anchor: every clinical model needs an evidence trail (test set, baseline, error analysis) before it ships. Non-clinical models (e.g. the pose preview above, or a model that summarizes admin emails for staff inboxes) carry a lighter validation bar but still appear here.


Indirect Dependencies

The full transitive dependency graph is in:

  • Backend: services/api/go.sum — every Go module pulled in, with cryptographic hashes
  • Frontend: pnpm-lock.yaml (repo root) — every npm package, with integrity hashes

Indirect deps inherit the risk classification of the direct dep that pulled them in. We do not maintain a row for every transitive package because:

  • Risk is determined by the call site, not the package. A logger pulled in by an HTTP middleware operates at the same risk tier as the middleware itself.
  • The set churns on every go mod tidy / pnpm install. Maintaining 250+ rows that update arbitrarily is paperwork without signal.
  • An auditor asking "what code is in this binary?" gets go.sum and pnpm-lock.yaml. An auditor asking "what risks have you considered?" gets this document.

If a transitive dep ever becomes load-bearing in a way that's surprising — e.g. a CVE that affects us only via an indirect path, or a transitive dep that ends up doing crypto where the parent disclaims it — promote it to a direct dep and add a row here.


Adding a New Dependency

Every new dependency added in any PR must include a SOUP entry in the same PR. The reviewer's job is to confirm the row exists; the contributor's job is to write it.

Procedure:

  1. Add the dep (go get, pnpm add, etc.).
  2. Open this file and add a row to the matching table.
  3. Fill in the columns:
    • Version: pin the exact version in your PR (go.mod / package.json).
    • Purpose: one sentence — what does it do for us. "It's a Postgres driver" is wrong; "Postgres driver + connection pooling" is right.
    • Risk: pick a tier from the classification rubric. When in doubt, pick the higher tier — easier to defend than the lower.
    • Model columns: leave em-dashed unless this is an AI/ML model.
  4. If the dep is an AI/ML model: also add a row to AI/ML Models with Model provider, Model version, and Validation status. Validation status cannot be left blank for a clinical model — the PR is incomplete without it.

Removing a dependency: delete the row. Do not leave deprecated entries — go mod tidy and pnpm install already keep the lock files honest, and stale SOUP rows confuse audits.

Bumping a version: update the version cell in the row. No new row needed unless the upgrade introduces a new risk class (e.g. a logging library adding a network-egress feature would jump from Standard to High).


Maintenance

This document is updated with every dependency change. The make check target runs services/api/cmd/check-soup, which parses every direct dependency in services/api/go.mod and every workspace package.json (dependencies + devDependencies, excluding @workspace/* internals) and asserts each one has a SOUP row, and that no SOUP row points at a removed dep. CI fails when either invariant breaks.

A periodic full audit (every release branch cut, or quarterly minimum) should:

  • Confirm every direct dep has a row.
  • Spot-check that risk classifications still match how the dep is used.
  • Verify versions match what's in lock files.
  • Re-run dependency vulnerability scanners (govulncheck, pnpm audit) and document any waivers here.