Skip to content

ci: test against all four databases; non-root image with a health check; readiness endpoint - #7

Merged
fuseraft merged 3 commits into
masterfrom
feat/ops
Oct 17, 2026
Merged

fuseraft merged 3 commits into
masterfrom
feat/ops

Conversation

@fuseraft

Copy link
Copy Markdown
Owner

Stacked on #6, which is stacked on #5. Merge those in order and this diff shrinks to its own commit. Each targets master directly, so none can land on a feature branch by mistake.

CI (.github/workflows/ci.yml), on pushes to master and on every PR:

  • test job: Postgres 16, MySQL 8.4, SQL Server 2022 and Oracle 23 Free run as service containers, each with a health check, and the full suite runs against them. The job fails if any test is skipped, so a database that didn't come up can't pass silently; I checked the check fails with one connection string unset. It also builds with -warnaserror and fails on vulnerable packages, direct or transitive.
  • docker job: builds the image, then checks that it:
    • refuses to start without a token
    • serves /health
    • returns 401 on /mcp without the token
    • runs as non-root
    • passes its own --healthcheck

Image:

  • It runs as the base image's app user (uid 1654) instead of root.
  • A HEALTHCHECK runs the new sql-mcp --healthcheck. That mode calls /health on the configured port and exits 0 or 1, since the ASP.NET image has no curl or wget.
  • Restore is now cached as its own layer.

GET /health/ready: connects to each database, runs the login audit and opens the read-only transaction. It returns 200 when every database is ready, else 503, with the status of each database. It needs the token when one is set, because it names the databases. These checks are kept out of the query log. /health is unchanged: open, liveness only.

Verified locally:

  • 306/306 tests pass against all four databases.
  • With Podman:
    • the image runs as uid 1654
    • podman healthcheck run passes and the container reports healthy
    • /health/ready returns 503 and names each missing password variable
    • without a token the image exits with code 1

The workflow itself runs for the first time on this PR.

Values written into SQL break on quotes and let a crafted value change the query. query_database and /api/query now take an optional parameters object whose values are bound by the driver, never spliced into the text. Placeholders use each provider's syntax (@name, or :name on Oracle). Postgres strings are sent untyped so the server infers dates, uuids and numbers as it does for literals.
…kward values

Found by probing all four databases: Oracle 1/3 failed the whole query, NaN/Infinity and Postgres inet threw while serializing, SQL Server geography failed the query, repeated or unnamed columns silently lost values, and a 20 MB value came back whole. An unknown Provider silently became SqlServer.

- Values are normalized to plain JSON; one unreadable value becomes a note, not an error
- Columns are made unique (column1, id_2)
- App:MaxValueLength and App:MaxResultBytes cap values and result size
- App:MaxConcurrentQueries bounds queries per database
- Each call is logged with timing and row count, never parameter values
- Config is validated at startup with every problem listed; a missing password variable disables only its database
- Sample config uses read-only agent logins; its Oracle entry had an invalid attribute
…ck; readiness endpoint

- GitHub Actions runs the full suite against Postgres, MySQL, SQL Server and Oracle service containers, fails if any test is skipped, builds with -warnaserror, fails on vulnerable packages, and smoke-tests the image
- The image runs as the unprivileged app user and has a HEALTHCHECK via the new --healthcheck mode (the base image has no curl)
- GET /health/ready connects to each database, runs the login audit and reports per-database status; 503 unless all are ready
@fuseraft
fuseraft merged commit e5b2c52 into master Oct 17, 2026
2 checks passed
@fuseraft
fuseraft deleted the feat/ops branch October 17, 2026 02:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant