fix: raise find-my-way's default maxParamLength so /base64/:value stops 404ing on longer payloads#160
Conversation
…ps 404ing on longer payloads find-my-way (Fastify's router) defaults maxParamLength to 100 characters. Any single-segment route param longer than that silently fails to match and returns 404 instead of reaching the route handler - most noticeably on /base64/:value, where any realistically sized encoded payload (more than ~75 raw bytes) 404s instead of decoding. Reproduced against the deployed instance (mockhttp.org) by bisecting the exact boundary (100 chars OK, 101 chars 404) and confirmed the same behavior locally with an unmodified checkout, so this isn't a hosting/CDN limit - it's Fastify's own router configuration. Fixes by passing routerOptions.maxParamLength through getFastifyConfig() (the deprecated top-level maxParamLength option triggers a Fastify deprecation warning, so this uses the current router-scoped option instead). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request increases Fastify's maxParamLength router option to 8192 to prevent silent 404 errors on long single-segment parameters, such as base64 payloads, and adds corresponding unit and integration tests. The feedback suggests wrapping the new integration test in a try...finally block to ensure the mock server is properly closed even if assertions fail, preventing potential port conflicts or hanging test runs.
| test("should route single-segment params longer than find-my-way's 100-char default", async () => { | ||
| const mock = new MockHttp({ logging: false }); | ||
| await mock.start(); | ||
|
|
||
| // "The quick brown fox..." (338 chars) base64-encoded, well past the | ||
| // find-my-way default maxParamLength of 100. | ||
| const longValue = | ||
| "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gVGhpcyBzZW50ZW5jZSBjb250YWlucyBldmVyeSBsZXR0ZXIgb2YgdGhlIEVuZ2xpc2ggYWxwaGFiZXQgYW5kIGlzIG9mdGVuIHVzZWQgZm9yIHR5cGluZyBwcmFjdGljZSwgZm9udCB0ZXN0aW5nLCBhbmQgZW5jb2RpbmcgZXhwZXJpbWVudHMuIEJhc2U2NCBlbmNvZGluZyB0cmFuc2Zvcm1zIHRoaXMgcmVhZGFibGUgdGV4dCBpbnRvIGEgc2VxdWVuY2Ugb2YgQVNDSUkgY2hhcmFjdGVycyB0aGF0IGNhbiBzYWZlbHkgYmUgdHJhbnNtaXR0ZWQgb3Igc3RvcmVkIGluIHN5c3RlbXMgdGhhdCBoYW5kbGUgdGV4dC1vbmx5IGRhdGEu"; | ||
| expect(longValue.length).toBeGreaterThan(100); | ||
|
|
||
| const response = await mock.server.inject({ | ||
| method: "GET", | ||
| url: `/base64/${longValue}`, | ||
| }); | ||
|
|
||
| expect(response.statusCode).toBe(200); | ||
| expect(response.payload).toBe( | ||
| "The quick brown fox jumps over the lazy dog. This sentence contains every letter of the English alphabet and is often used for typing practice, font testing, and encoding experiments. Base64 encoding transforms this readable text into a sequence of ASCII characters that can safely be transmitted or stored in systems that handle text-only data.", | ||
| ); | ||
|
|
||
| await mock.close(); | ||
| }); |
There was a problem hiding this comment.
If an assertion fails during the test execution, await mock.close() will not be reached, leaving the server running and the port bound. This can cause subsequent tests to fail due to port conflicts or cause the test runner to hang. Wrapping the test logic in a try...finally block ensures that the server is always properly closed.
test("should route single-segment params longer than find-my-way's 100-char default", async () => {
const mock = new MockHttp({ logging: false });
await mock.start();
try {
// "The quick brown fox..." (338 chars) base64-encoded, well past the
// find-my-way default maxParamLength of 100.
const longValue =
"VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gVGhpcyBzZW50ZW5jZSBjb250YWlucyBldmVyeSBsZXR0ZXIgb2YgdGhlIEVuZ2xpc2ggYWxwaGFiZXQgYW5kIGlzIG9mdGVuIHVzZWQgZm9yIHR5cGluZyBwcmFjdGljZSwgZm9udCB0ZXN0aW5nLCBhbmQgZW5jb2RpbmcgZXhwZXJpbWVudHMuIEJhc2U2NCBlbmNvZGluZyB0cmFuc2Zvcm1zIHRoaXMgcmVhZGFibGUgdGV4dCBpbnRvIGEgc2VxdWVuY2Ugb2YgQVNDSUkgY2hhcmFjdGVycyB0aGF0IGNhbiBzYWZlbHkgYmUgdHJhbnNtaXR0ZWQgb3Igc3RvcmVkIGluIHN5c3RlbXMgdGhhdCBoYW5kbGUgdGV4dC1vbmx5IGRhdGEu";
expect(longValue.length).toBeGreaterThan(100);
const response = await mock.server.inject({
method: "GET",
url: "/base64/" + longValue,
});
expect(response.statusCode).toBe(200);
expect(response.payload).toBe(
"The quick brown fox jumps over the lazy dog. This sentence contains every letter of the English alphabet and is often used for typing practice, font testing, and encoding experiments. Base64 encoding transforms this readable text into a sequence of ASCII characters that can safely be transmitted or stored in systems that handle text-only data."
);
} finally {
await mock.close();
}
});|
Sorry for using Vibe Coding. In one of my projects (https://github.com/scriptscat/scriptcat/),
Hope you might consider to merge this PR for bug fixing. |
Per review feedback: if an assertion in the test body threw, await mock.close() below it would never run, leaking the bound port into later tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Good catch — pushed a fix wrapping the test body in |

Please check if the PR fulfills these requirements
What kind of change does this PR introduce? Bug fix —
/base64/:value404s on payloads longer than Fastify's default 100-character route-param limit.Summary
find-my-way(Fastify's router) defaultsmaxParamLengthto 100 characters. Any single-segment route param longer than that silently fails to match in the router and the request gets a404instead of reaching the route handler — most noticeably on/base64/:value, where any realistically sized base64 payload (more than ~75 raw bytes / ~100 encoded characters) returns404instead of being decoded.I ran into this while migrating a project's test fixtures from another httpbin-style mock off of mockhttp.org, and initially suspected it was a hosting/CDN-level limit rather than something in this repo. I confirmed it isn't: I reproduced the exact same 100/101-character boundary locally against an unmodified checkout of
main, so this is Fastify's own router configuration, not an infra limit.Fix
Passes
routerOptions.maxParamLengththroughgetFastifyConfig(), raising it to 8192. I deliberately used the currentrouterOptions.maxParamLengthconfig path rather than the top-levelmaxParamLengthoption — the latter is deprecated in this Fastify version and logs aFSTDEP022warning on every boot.Test plan
test/mock-http.test.tsthat spins up a realMockHttpserver and confirms a >100-char base64 payload round-trips correctly (this is the level that would have caught the bug — the existingtest/routes/dynamic-data/base64.test.tsunit tests construct their own bareFastify()instance withoutgetFastifyConfig(), so they can't exercise this).test/fastify-config.test.tsasserting the config raisesmaxParamLengthpast the 100-char default.pnpm test(lint + full vitest run with coverage) passes locally: 475 tests, 100% line coverage, no lint warnings.tsx src/index.tsinstance that the previously-404ing payload now returns 200 with the correct decoded body, and that the fix doesn't loosen the limit unboundedly (a 9000-char param still correctly 404s).