Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/serve.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -385,9 +385,16 @@ export async function serveCommand(args = [], out = console.log, deps = {}) {

const flag = (f) => { const at = rest.indexOf(f); return at >= 0 ? rest[at + 1] : undefined; };
const proxy = flag("--proxy");
if (proxy !== undefined && !/^\d+$/.test(proxy)) {
out("moshcode site: --proxy takes a port");
return 1;
if (proxy !== undefined) {
// `^\d+$` alone let 0 and out-of-range numbers through: --proxy 0 is
// falsy downstream, so the site silently drops to a static root, and
// --proxy 99999 writes a proxy_pass to a port that cannot exist. Both
// were reported as live. Ports are 1-65535 everywhere else here.
const port = /^\d+$/.test(proxy) ? Number(proxy) : NaN;
if (!Number.isSafeInteger(port) || port < 1 || port > 65535) {
out(`moshcode site: --proxy needs a decimal integer from 1 to 65535, got ${JSON.stringify(proxy)}`);
return 1;
}
}
const root = flag("--root") || `/srv/${name}`;
const choice = await chooseTemplate(rest);
Expand Down
31 changes: 31 additions & 0 deletions test/serve.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,37 @@ test("a name that is not a Moshpit name is refused before anything else", async
}
});

test("a --proxy port that is not 1-65535 is refused, not silently misconfigured", async () => {
// 0 is falsy downstream, so it dropped to a static root; 99999 wrote a
// proxy_pass to a port that cannot exist. Both used to exit 0 and write.
for (const bad of ["0", "99999", "70000", "-1", "abc", ""]) {
const lines = [];
let wrote = false;
const code = await serveCommand(["blue.eggs", "--proxy", bad, "--install"], (l) => lines.push(l), {
detect: async () => "nginx",
write: async () => { wrote = true; },
mkdir: async () => {},
copy: async () => {},
runner: async () => ({ ok: true }),
});
assert.equal(code, 1, `--proxy ${bad} should be refused`);
assert.equal(wrote, false, `--proxy ${bad} must not write a config`);
assert.ok(lines.some((l) => /--proxy needs a decimal integer from 1 to 65535/.test(l)), `--proxy ${bad} message`);
}
// Control: a real port still installs a reverse proxy, unchanged.
const lines = [];
let content = null;
const code = await serveCommand(["blue.eggs", "--proxy", "3000", "--install"], (l) => lines.push(l), {
detect: async () => "nginx",
write: async (_p, c) => { content = c; },
mkdir: async () => {},
copy: async () => {},
runner: async () => ({ ok: true }),
});
assert.equal(code, 0);
assert.match(content, /proxy_pass http:\/\/127\.0\.0\.1:3000;/);
});

test("a fresh site is seeded, because an empty root is a 404 that reads as broken", async () => {
// The moment someone is trying to tell "the name resolved" from "the install
// failed" is exactly the moment an empty root answers 404 and refuses to
Expand Down
Loading