|
1 | 1 | import { describe, it } from "node:test"; |
2 | 2 | import assert from "node:assert/strict"; |
3 | 3 | import { parse } from "node:url"; |
4 | | -import { truncateResult, getLocalIP, serializeHeaders, capString, redactProxyUrl, mergeUpstreamPassword } from "../../src/utils.js"; |
| 4 | +import { truncateResult, getLocalIP, serializeHeaders, capString, redactProxyUrl, mergeUpstreamPassword, upstreamPasswordSource } from "../../src/utils.js"; |
5 | 5 |
|
6 | 6 | describe("truncateResult", () => { |
7 | 7 | it("returns short data unchanged", () => { |
@@ -158,6 +158,18 @@ describe("mergeUpstreamPassword", () => { |
158 | 158 | assert.equal(new URL(out).host, "host:8000"); |
159 | 159 | }); |
160 | 160 |
|
| 161 | + it("survives the round-trip with a '%' in the password", () => { |
| 162 | + // The password setter escapes "@" and "/" but not "%", while url.parse() |
| 163 | + // decodeURIComponent()s the auth: a raw "%" made that decode throw, and a |
| 164 | + // raw "%20" decoded to a space. Both must come back byte-identical. |
| 165 | + for (const secret of ["100%pass", "p%20ss", "%"]) { |
| 166 | + const out = mergeUpstreamPassword("http://user@host:8000", { |
| 167 | + PROXY_MCP_UPSTREAM_PASSWORD: secret, |
| 168 | + }); |
| 169 | + assert.equal(parse(out).auth, `user:${secret}`); |
| 170 | + } |
| 171 | + }); |
| 172 | + |
161 | 173 | it("reaches https-proxy-agent intact, including a ':' in the password", () => { |
162 | 174 | // https-proxy-agent takes the whole auth string, so ":" is safe here. |
163 | 175 | const secret = "pa:ss/word"; |
@@ -206,3 +218,18 @@ describe("mergeUpstreamPassword", () => { |
206 | 218 | assert.equal(mergeUpstreamPassword("not a url", env), "not a url"); |
207 | 219 | }); |
208 | 220 | }); |
| 221 | + |
| 222 | +describe("upstreamPasswordSource", () => { |
| 223 | + const env = { PROXY_MCP_UPSTREAM_PASSWORD: "s3cret" }; |
| 224 | + const source = (url: string, e: NodeJS.ProcessEnv) => |
| 225 | + upstreamPasswordSource(url, mergeUpstreamPassword(url, e)); |
| 226 | + |
| 227 | + it("reports where the password came from", () => { |
| 228 | + assert.equal(source("http://user@host:8000", env), "env"); |
| 229 | + assert.equal(source("http://user:mine@host:8000", env), "url"); |
| 230 | + // The case worth reporting: caller omitted it and the server has no |
| 231 | + // variable, so the upstream is about to be used unauthenticated. |
| 232 | + assert.equal(source("http://user@host:8000", {}), "none"); |
| 233 | + assert.equal(source("http://host:8000", env), "none"); |
| 234 | + }); |
| 235 | +}); |
0 commit comments