Skip to content

Fix the polynomial-ReDoS CodeQL alert on toAbsoluteMediaUrl - #2009

Merged
johan-bell merged 1 commit into
1878-api-cms-hls-media-data-modelfrom
1878-redos-mediaurl
Sep 4, 2026
Merged

Fix the polynomial-ReDoS CodeQL alert on toAbsoluteMediaUrl#2009
johan-bell merged 1 commit into
1878-api-cms-hls-media-data-modelfrom
1878-redos-mediaurl

Conversation

@johan-bell

Copy link
Copy Markdown
Collaborator

Closes CodeQL alert 51 (js/polynomial-redos, security-severity high) on shared/src/util/mediaUrl.ts:26, raised against #2007.

Is it real?

The regex is real, the exposure is mild.

/\/+$/ backtracks quadratically when a run of slashes does not end the string: the engine restarts at each slash, matches the rest of the run, then fails on $. Measured on this machine:

slashes run at end of string run not at end
20,000 0 ms 120 ms
50,000 0 ms 706 ms
100,000 0 ms 2,839 ms

So the shape that bites is "/////…x", not a URL with a trailing slash.

What CodeQL means by "library input" is the exported parameter itself: shared is published as luminary-shared, so publicUrl is whatever a caller passes. In our callers it is a Storage document's publicUrl, set by someone holding Assign on the bucket — an admin who could break media far more directly. That is why this is worth fixing but not worth alarm.

Fixing beats dismissing here: the replacement is shorter than the regex, and it removes the argument rather than filing it.

The fix

Trailing slashes come off by index, in one linear scan. Same result for every input, including the several-trailing-slashes case the regex handled.

Tests

shared/src/util/mediaUrl.ts had no spec — it was moved into shared from two copies that had none either, and only app's resolveVideoSource covered it indirectly. Added one: the join, external URLs passing through, the undefined cases, one and many trailing slashes, an all-slashes bucket URL, and the pathological input under a time bound.

Confirmed the timing test earns its place: with the old regex restored it fails, with the fix it passes. (My first attempt at that fixture put the slash run at the end of the string, where the old regex is instant — it would have passed against the bug. Corrected to the shape that actually backtracks.)

Left alone

The same replace(/\/+$/, "") appears eight more times — api/src/changeRequests/documentProcessing/mediaUrl.ts (including the direct twin of this function), deleteMediaCollection.ts, migrateMediaCollection.ts, v17.ts, util/authority.ts, and cms/.../AuthConfig.vue. None is a library entry point, none is flagged, and all predate this epic. Happy to sweep them in a follow-up if you'd rather they matched.

`/\/+$/` is quadratic when the run of slashes does not end the string —
2.8s for 100k on this machine — and `shared` is a published library, so
the argument is the caller's (CodeQL js/polynomial-redos, alert 51).

Adds the spec the helper never had: it moved into shared from two copies
that had none, covered only indirectly through the app's resolveVideoSource.
@johan-bell
johan-bell merged commit 2825c6c into 1878-api-cms-hls-media-data-model Sep 4, 2026
@johan-bell
johan-bell deleted the 1878-redos-mediaurl branch September 4, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant