fix(server): bound every outbound fetch

The rule is a timeout, a size cap and boundary validation on anything that
leaves the process, and several call sites had only the timeout. A link
preview, a maps URL resolve, a wiki fallback and a holiday lookup all read
the whole body into memory, so a caller who picks the URL picks how much
memory the server spends - a few concurrent multi-hundred-megabyte responses
is the whole heap.

Two of them also fetched more than their name suggests. resolve-url fell
through to a full page GET for any host once it found no coordinates, which
makes the server a general-purpose outbound proxy wearing its own IP. The
weather URLs took lat/lng straight from the caller into the query string.

readCapped/exceedsDeclaredLength collect the pattern transit.service.ts and
the plugin installer had each grown separately, so there is one of it now.
Callers that only scan the head of a document (og: tags, a coordinate regex)
keep the truncated bytes and find fewer fields; callers that must have the
whole thing reject it.
This commit is contained in:
Maurice
2026-08-23 09:47:22 +02:00
committed by Maurice
parent b163c1e05d
commit 97ebe23d3b
16 changed files with 551 additions and 28 deletions
+6 -3
View File
@@ -421,9 +421,12 @@ export class AdminService {
}
async getGithubReleases(perPage: string = '10', page: string = '1') {
const data = await this.fetchGithub(
`https://api.github.com/repos/liketrek/TREK/releases?per_page=${perPage}&page=${page}`,
);
// The query arrives as raw strings from the admin UI; clamp to what the
// GitHub API accepts instead of interpolating them into the URL as given.
const per = Math.min(Math.max(Number.parseInt(perPage, 10) || 10, 1), 100);
const pg = Math.max(Number.parseInt(page, 10) || 1, 1);
const qs = new URLSearchParams({ per_page: String(per), page: String(pg) });
const data = await this.fetchGithub(`https://api.github.com/repos/liketrek/TREK/releases?${qs}`);
return Array.isArray(data) ? data : [];
}