Skip to content

Commit abeea39

Browse files
committed
fix(setup): require an http or https RPC URL
1 parent acac9e5 commit abeea39

3 files changed

Lines changed: 32 additions & 4 deletions

File tree

‎docs/superpowers/plans/2026-08-02-ledgerkeep-cli.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1179,11 +1179,17 @@ function requireContractId(env: NodeJS.ProcessEnv, name: string): string {
11791179

11801180
function requireUrl(env: NodeJS.ProcessEnv, name: string): string {
11811181
const raw = requireString(env, name);
1182+
let parsed: URL;
11821183
try {
1183-
new URL(raw);
1184+
parsed = new URL(raw);
11841185
} catch {
11851186
throw new ConfigError(`${name} is not a valid URL, got: ${raw}`);
11861187
}
1188+
// The only consumer is the RPC client, which speaks http(s). Rejecting other
1189+
// schemes here gives a clear message instead of an SDK failure further in.
1190+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1191+
throw new ConfigError(`${name} must be an http or https URL, got: ${raw}`);
1192+
}
11871193
return raw;
11881194
}
11891195

‎src/config.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,17 @@ function requireContractId(env: NodeJS.ProcessEnv, name: string): string {
5050

5151
function requireUrl(env: NodeJS.ProcessEnv, name: string): string {
5252
const raw = requireString(env, name);
53+
let parsed: URL;
5354
try {
54-
new URL(raw);
55+
parsed = new URL(raw);
5556
} catch {
5657
throw new ConfigError(`${name} is not a valid URL, got: ${raw}`);
5758
}
59+
// The only consumer is the RPC client, which speaks http(s). Rejecting other
60+
// schemes here gives a clear message instead of an SDK failure further in.
61+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
62+
throw new ConfigError(`${name} must be an http or https URL, got: ${raw}`);
63+
}
5864
return raw;
5965
}
6066

‎test/config.test.ts‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ describe("loadConfig", () => {
4444
it("does not open the keeper key file", () => {
4545
// Read-only commands run on machines that hold no key. Loading config must
4646
// not touch the path, so an unreadable path is not an error here.
47-
const config = loadConfig(completeEnv({ LK_KEEPER_KEY: "/nonexistent/nope.key" }));
48-
expect(config.keeperKeyPath).toBe("/nonexistent/nope.key");
47+
const env = completeEnv({ LK_KEEPER_KEY: "/nonexistent/nope.key" });
48+
expect(() => loadConfig(env)).not.toThrow();
49+
expect(loadConfig(env).keeperKeyPath).toBe("/nonexistent/nope.key");
4950
});
5051

5152
it("names the missing variable", () => {
@@ -89,6 +90,21 @@ describe("loadConfig", () => {
8990
it("rejects a malformed RPC URL", () => {
9091
expect(() => loadConfig(completeEnv({ LK_RPC_URL: "not a url" }))).toThrow(/not a valid URL/);
9192
});
93+
94+
it("rejects a URL whose scheme the RPC client cannot speak", () => {
95+
// Parses fine, but nothing downstream could ever use it.
96+
expect(() => loadConfig(completeEnv({ LK_RPC_URL: "file:///etc/passwd" }))).toThrow(
97+
/must be an http or https URL/,
98+
);
99+
expect(() => loadConfig(completeEnv({ LK_RPC_URL: "ftp://example.com" }))).toThrow(
100+
/must be an http or https URL/,
101+
);
102+
});
103+
104+
it("accepts a loopback http URL for a local quickstart", () => {
105+
const config = loadConfig(completeEnv({ LK_RPC_URL: "http://localhost:8000/soroban/rpc" }));
106+
expect(config.rpcUrl).toBe("http://localhost:8000/soroban/rpc");
107+
});
92108
});
93109

94110
describe("loadKeypair", () => {

0 commit comments

Comments
 (0)