Skip to content

feat(health): probe platform backends where the deployment keeps them - #685

Merged
Medformatik merged 4 commits into
mainfrom
fix/overpass-health-follows-instance
Oct 30, 2026
Merged

Medformatik merged 4 commits into
mainfrom
fix/overpass-health-follows-instance

Conversation

@Medformatik

@Medformatik Medformatik commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

What changed and why

The Overpass POI search, the Overpass transit fallback and the hiking overlay's Overpass check always probed the public overpass-api.de/api/status, whichever instance the deployment uses. On openmapx.com all three query the self-hosted overpass service, yet the status page showed them down whenever the public server was at capacity (502).

A health check should probe the instance actually queried, as Valhalla and MOTIS already do. The Overpass endpoint, though, is chosen at runtime inside the core client (self-hosted service, then OVERPASS_URL, then the public default), which no manifest placeholder could express. This makes that choice a shared, declarative concept instead of special-casing Overpass.

One resolver in core. resolvePlatformEndpoint(id, selfHostedUrl) returns the instance a platform backend's requests go to: the enabled self-hosted service, then the operator's URL, then the public default. The Overpass client now resolves through it instead of its own precedence logic. Only the public default still falls back to the kumi.systems mirror, as before.

A declarative manifest placeholder. Health checks can use ${endpoint:<id>} beside ${configKey} and ${service:<id>}. The host resolves it through the same resolver, so a check cannot drift from what the client queries. The three Overpass checks become plain manifest entries, "urlTemplate": "${endpoint:overpass}/api/status", including the hiking overlay's entry in its list of checks. No integration code changes.

Contract. HEALTH_CHECK_ENDPOINT_IDS (currently overpass) lives in @openmapx/extension-contracts. Manifest validation now rejects an unknown ${endpoint:…} id, naming the known ones. Nothing validated placeholders before. Adding Nominatim or MOTIS later means registering them here and moving their clients to the resolver; their precedence differs today, so they are not part of this change.

The endpoint stays where the probe goes. An endpoint can be an internal host carrying the operator's credentials. An automated security review of the first push pointed out that a manifest could otherwise copy it into another host's URL or a header, for example https://collector.example/?u=${endpoint:overpass}.

  • Contract: validation requires ${endpoint:<id>} to be the whole URL prefix of urlTemplate, used once and followed by a path or nothing, and rejects it in headers.
  • Host, runtime: the host enforces the same rule, resolves endpoints only for built-in integrations, and strips the operator's credentials before the endpoint enters the template.
  • Host, origin: the host exempts the endpoint from the public-host check, and sends the operator's credentials as Basic auth, only when the final URL is on the endpoint's own origin.

Host probe hygiene, for every check.

  • Trusted hosts: a self-hosted service or the operator's instance may be an internal host; every other probe URL must still be public.
  • URL credentials: credentials in a URL (an operator's https://user:pass@host) are sent as Basic auth. fetch refuses such a URL and names it, password included, in its error.
  • Redaction: the credentials are masked in the displayed URL and in error messages. A manifest's own Authorization header takes precedence. The shared helper urlCredentialsAsBasicAuth lives in core.

Docs. The integration-system reference now documents all three placeholders. ${service:<id>} had never been documented.

The earlier version of this PR used custom health-check code and a framework extension for named custom checks. Both are gone. That version bypassed the host's shared probe handling, which is how an OVERPASS_URL password could have reached the public status page. Overpass queries with credentials in OVERPASS_URL still fail as before; that older bug gets its own PR.

Verification

  • Resolver: tests cover the self-hosted service ahead of OVERPASS_URL, an operator URL with its /api/interpreter route and trailing slash removed, the public default, and the known ids.
  • Contract: tests reject unknown ids, every non-prefix placement (another host's query, a host prefix, a subdomain, a repeated endpoint) and endpoints in headers. manifest-smoke validates every shipped manifest against the new rule.
  • Host:
    • Each of the three real manifests probes http://overpass:80/api/status when the service is enabled, even with OVERPASS_URL set, and never overpass-api.de.
    • An operator's private instance is probed with its credentials as a Basic header and same-origin redirects only.
    • An error message repeating the credentials shows neither user nor password.
    • With nothing configured, the probe goes to https://overpass-api.de/api/status as a public host.
    • An unknown id is unconfigured and sends no request.
    • A built-in manifest placing the endpoint in another host's query, as a host prefix or in a header is marked misconfigured; no request is sent and neither user nor password appears.
    • A community manifest's endpoint is not resolved.
  • Credential helper: tests cover UTF-8 and percent-encoded credentials, and leave non-URLs unchanged.
  • Live: on openmapx.com, http://overpass:80/api/status answers 200 from app-api.
  • Repository checks: pnpm lint, pnpm check-types, pnpm check:policy and pnpm check-openapi pass, and the contract's JSON schemas are unchanged. The full Vitest suite passes: 1,953 files, 24,086 tests.
  • Not run: a deployment; the status page on openmapx.com changes once this ships.

Release and upgrade impact

Product impact: ordinary

.changeset/health-check-platform-endpoints.md releases @openmapx/extension-contracts (minor: ${endpoint:<id>}, HEALTH_CHECK_ENDPOINT_IDS and the validation) and rebuilds @openmapx/extension-cli (patch). A community manifest that already contained a literal ${endpoint:…} other than ${endpoint:overpass} would now fail validation; no such placeholder existed before.

@Medformatik Medformatik added documentation Documentation content, tooling or workspace dependencies integration New or changed provider, source or backend integration labels Oct 30, 2026
@Medformatik Medformatik self-assigned this Oct 30, 2026
Health checks can name a platform backend as ${endpoint:<id>}, resolved
through the same resolver its client queries with: the self-hosted service,
then the operator's URL, then the public default. The Overpass client uses
that resolver, and the three Overpass health checks probe
${endpoint:overpass}/api/status instead of always pinging the public
instance. The contract lists the endpoint ids and rejects others.

The host now sends URL credentials as Basic auth for every probe and keeps
them out of the displayed URL and error messages, and trusts the operator's
own instance like a self-hosted service.
@Medformatik Medformatik changed the title fix(health): probe the Overpass instance the integrations actually query feat(health): probe platform backends where the deployment keeps them Oct 30, 2026
@Medformatik
Medformatik force-pushed the fix/overpass-health-follows-instance branch from 658a750 to ad69ec1 Compare October 30, 2026 09:19
An endpoint can be an internal host carrying the operator's credentials.
Allow ${endpoint:<id>} only as the whole URL prefix of a built-in
manifest's urlTemplate, never in headers; strip the operator's credentials
before the endpoint enters the template, and exempt the host and send them
as Basic auth only when the final URL is on the endpoint's own origin.
CodeQL flagged /\/+$/ on an operator URL as polynomial: strip trailing
slashes with a loop.
@Medformatik
Medformatik enabled auto-merge (squash) October 30, 2026 10:00
## What changed and why

Stacked on #685, whose core helper it reuses; the diff against #685's
branch is this one commit.

An operator can point OpenMapX at a hosted Overpass with `OVERPASS_URL`,
including one that needs a login (`https://user:pass@host`). Every query
to such an instance failed. Node's `fetch` refuses a URL that carries
credentials, throwing `Request cannot be constructed from a URL that
includes credentials: https://user:pass@host/…`, so the password also
ended up in the error.

The Overpass client now sends such credentials as a Basic
`Authorization` header and queries the URL without them, through the
same `urlCredentialsAsBasicAuth` helper the health checks use since
#685. Attribution keeps resolving from the host, now taken from the URL
without credentials. Instances without credentials are unaffected.

## Verification

- **Query test:** a query to an `OVERPASS_URL` with credentials goes to
`https://overpass.example.org/api/interpreter` with `Authorization:
Basic …` for those credentials. The existing Overpass client tests still
pass, including the public-mirror fallback.
- **Node behaviour:** I confirmed that
`fetch("https://u:p@example.com")` throws the error quoted above.
- **Repository checks:** `pnpm lint`, `pnpm check-types` and `pnpm
check:policy` pass. The full Vitest suite passes (1,953 files, 24,087
tests) except `motis-probe.test.ts`'s socket-retry test. That test is
flaky: it failed 1 of 5 isolated reruns on this same code, and it drives
a real socket through `node:http` in the data manager, which this change
doesn't touch.
- **Not run:** a query against a real credential-protected Overpass
instance; none is configured anywhere I can reach.

## Release and upgrade impact

Product impact: ordinary

No package release: the change is limited to the Overpass client in the
private `@openmapx/core` package; neither published package's source
changes in this commit.
@Medformatik
Medformatik merged commit 33a5e72 into main Oct 30, 2026
28 checks passed
@Medformatik
Medformatik deleted the fix/overpass-health-follows-instance branch October 30, 2026 10:11
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

documentation Documentation content, tooling or workspace dependencies integration New or changed provider, source or backend integration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants