Skip to content

Commit 9debfac

Browse files
committed
fix(geocoding-motis): validate config URLs are http(s) at setup
Fail fast with a clear error if the endpoint/transitousUrl config values are malformed or non-http(s), instead of failing later with an opaque fetch error. Allows localhost (the self-hosted MOTIS default), so this is a shape check, not a private-address SSRF guard.
1 parent 9401b1e commit 9debfac

3 files changed

Lines changed: 43 additions & 0 deletions

File tree

integrations/geocoding-motis/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import type { IntegrationContext } from "@openmapx/integration-framework";
22
import { motisGeocodingService, setMotisLocalUrl, setTransitousUrl } from "./provider.js";
3+
import { assertHttpUrlConfig } from "./validate-config-url.js";
34

45
export function setup(ctx: IntegrationContext): void {
6+
assertHttpUrlConfig(ctx.config.endpoint, "endpoint");
7+
assertHttpUrlConfig(ctx.config.transitousUrl, "transitousUrl");
8+
59
// Resolution order: service registry → manifest config → MOTIS_URL env →
610
// localhost fallback. Mirrors transit-motis-local + live-transit-motis +
711
// services/data-manager so ops can set MOTIS_URL once for the whole stack.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { describe, expect, it } from "vitest";
2+
import { assertHttpUrlConfig } from "./validate-config-url";
3+
4+
describe("assertHttpUrlConfig", () => {
5+
it("accepts http(s) URLs including localhost, and empty/undefined", () => {
6+
expect(() => assertHttpUrlConfig("http://localhost:8081", "endpoint")).not.toThrow();
7+
expect(() => assertHttpUrlConfig("https://motis.example.org", "endpoint")).not.toThrow();
8+
expect(() => assertHttpUrlConfig(undefined, "endpoint")).not.toThrow();
9+
expect(() => assertHttpUrlConfig("", "endpoint")).not.toThrow();
10+
});
11+
12+
it("rejects non-http(s) and malformed URLs", () => {
13+
expect(() => assertHttpUrlConfig("file:///etc/passwd", "endpoint")).toThrow();
14+
expect(() => assertHttpUrlConfig("gopher://x", "endpoint")).toThrow();
15+
expect(() => assertHttpUrlConfig("not a url", "endpoint")).toThrow();
16+
expect(() => assertHttpUrlConfig(123, "endpoint")).toThrow();
17+
});
18+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Throw a clear error if an admin-supplied config URL is set but is not a
3+
* syntactically valid http(s) URL. Allows localhost — a self-hosted MOTIS at
4+
* `http://localhost:8081` is the legitimate default — so this is a fail-fast
5+
* shape check, not a private-address SSRF guard.
6+
*/
7+
export function assertHttpUrlConfig(value: unknown, name: string): void {
8+
if (value === undefined || value === null || value === "") return;
9+
if (typeof value !== "string") {
10+
throw new Error(`geocoding-motis config "${name}" must be a string URL`);
11+
}
12+
let parsed: URL;
13+
try {
14+
parsed = new URL(value);
15+
} catch {
16+
throw new Error(`geocoding-motis config "${name}" is not a valid URL: ${value}`);
17+
}
18+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
19+
throw new Error(`geocoding-motis config "${name}" must use http(s): ${value}`);
20+
}
21+
}

0 commit comments

Comments
 (0)