Conversation
Signed-off-by: Nicklas Silversved <nicklas.silversved@digg.se>
Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
* feat: update response with correct info WIP * fix: update responses and excel report generation * docs: update readme with api-mode instructions --------- Signed-off-by: Nicklas Silversved <nicklas.silversved@digg.se>
Signed-off-by: Nicklas Silversved <nicklas.silversved@digg.se>
Signed-off-by: Nicklas Silversved <nicklas.silversved@digg.se>
Signed-off-by: Nicklas Silversved <nicklas.silversved@digg.se>
* feat: sort rules before writing to stdout * chore: add missing licence headers * chore: add xlsx file and openapi.yaml to REUSE.toml --------- Signed-off-by: Mats Johansson <extern.mats.johansson@digg.se>
Signed-off-by: Nicklas Silversved <nicklas.silversved@digg.se>
Signed-off-by: Nicklas Silversved <nicklas.silversved@digg.se>
Signed-off-by: Mats Johansson <extern.mats.johansson@digg.se>
Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
…lidate endpoint Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
| ); | ||
| } | ||
|
|
||
| const response = await fetch(dto.url, config?.customFetchConfig); |
Check failure
Code scanning / CodeQL
Server-side request forgery Critical
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 months ago
In general, the fix is to avoid using the raw user-provided URL as the request target, and instead validate and restrict it before calling fetch. Common mitigations include: parsing the URL, checking its protocol (e.g., only http:/https:), enforcing an allow‑list of hostnames or hostname patterns, and optionally blocking requests to private IP ranges or localhost. The validated URL or a derived safe form is then passed to fetch.
For this specific code, the best fix with minimal functional change is:
- Parse
dto.urlwith the standardURLclass to ensure it is a syntactically valid URL. - Enforce security constraints on the parsed URL:
- Require
http:orhttps:scheme. - Enforce that
url.hostnamematches a strict allow‑list derived fromconfig(e.g.config.allowedHosts), or at least a strong regex if that’s all we have. - Optionally, reject obviously unsafe hosts like
localhostand127.0.0.1to avoid trivial internal access.
- Require
- Use the parsed URL’s
toString()(orhref) as the argument tofetchrather than the raw input.
Because we must not change imports except to add well-known ones, and we cannot assume types on config beyond what’s visible, we can implement this entirely within src/routes/urlValidation.ts using built‑in URL and additional checks. We’ll:
- Wrap the existing regex check in slightly stronger validation:
- Construct
const parsedUrl = new URL(dto.url);inside the try block. - Check
parsedUrl.protocolishttp:orhttps:. - If
config?.urlMatchRegexexists, run it against a stable representation such asparsedUrl.hrefto avoid misleading matches on partial strings. - Add a basic block on localhost/loopback to harden SSRF somewhat without needing external configuration.
- Construct
- Use
parsedUrl.toString()as the argument tofetch.
The concrete changes inside src/routes/urlValidation.ts:
- Around the current
if (config?.urlMatchRegex && !dto.url.match(config.urlMatchRegex)) { ... }, replace it with logic that:- Parses
dto.urlintoparsedUrl. - Validates protocol and host.
- Uses
parsedUrl.hreffor the regex test.
- Parses
- Replace
fetch(dto.url, config?.customFetchConfig);withfetch(parsedUrl.toString(), config?.customFetchConfig);. - Use
RapLPBaseApiErrorwithERROR_TYPE.BAD_REQUESTfor invalid URLs, matching the existing error style.
No new external dependencies are required; we rely on the standard URL class.
| @@ -19,15 +19,45 @@ | ||
| try { | ||
| const dto: UrlContentDto = req.body; | ||
|
|
||
| if (config?.urlMatchRegex && !dto.url.match(config.urlMatchRegex)) { | ||
| let parsedUrl: URL; | ||
| try { | ||
| parsedUrl = new URL(dto.url); | ||
| } catch { | ||
| throw new RapLPBaseApiError( | ||
| 'Invalid Request', | ||
| 'The requested address is not a valid URL.', | ||
| ERROR_TYPE.BAD_REQUEST, | ||
| ); | ||
| } | ||
|
|
||
| // Only allow http and https schemes | ||
| if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { | ||
| throw new RapLPBaseApiError( | ||
| 'Invalid Request', | ||
| 'The requested address must use http or https.', | ||
| ERROR_TYPE.BAD_REQUEST, | ||
| ); | ||
| } | ||
|
|
||
| // Basic protection against obvious internal targets | ||
| const hostname = parsedUrl.hostname.toLowerCase(); | ||
| if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') { | ||
| throw new RapLPBaseApiError( | ||
| 'Invalid Request', | ||
| 'The requested address is not allowed.', | ||
| ERROR_TYPE.BAD_REQUEST, | ||
| ); | ||
| } | ||
|
|
||
| if (config?.urlMatchRegex && !parsedUrl.href.match(config.urlMatchRegex)) { | ||
| throw new RapLPBaseApiError( | ||
| 'Invalid Request', | ||
| 'The requested address failed the allowed url pattern. Contact your administrator if you think this is a misstake.', | ||
| ERROR_TYPE.BAD_REQUEST, | ||
| ); | ||
| } | ||
|
|
||
| const response = await fetch(dto.url, config?.customFetchConfig); | ||
| const response = await fetch(parsedUrl.toString(), config?.customFetchConfig); | ||
|
|
||
| const yamlContentString = await response.text(); | ||
|
|
Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
b7b8560 to
1249b0d
Compare
Signed-off-by: Mirelle Falstad <extern.mirelle.falstad@digg.se>
* feat: add logic for building and displaying helper url for rule * fix: add copyright and licensing information --------- Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
Pull Request Description
Please include a summary of the change and which issue is fixed or added.
Please also include relevant motivation and context.
List any dependencies that are required for this change.
Fixes #(issue)
Checklist