Skip to content

Feature/api mode#480

Closed
fredriknordlander wants to merge 17 commits intomainfrom
feature/api-mode
Closed

Feature/api mode#480
fredriknordlander wants to merge 17 commits intomainfrom
feature/api-mode

Conversation

@fredriknordlander
Copy link
Collaborator

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

  • Changes are limited to a single goal (avoid scope creep)
  • I confirm that I have read any Contribution and Development guidelines (CONTRIBUTING and DEVELOPMENT) and are following their suggestions.
  • I confirm that I wrote and/or have the right to submit the contents of my Pull Request, by agreeing to the Developer Certificate of Origin, (adding a 'sign-off' to my commits).

brorlarsnicklas and others added 14 commits December 16, 2025 09:41
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

The
URL
of this request depends on a
user-provided value
.

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:

  1. Parse dto.url with the standard URL class to ensure it is a syntactically valid URL.
  2. Enforce security constraints on the parsed URL:
    • Require http: or https: scheme.
    • Enforce that url.hostname matches a strict allow‑list derived from config (e.g. config.allowedHosts), or at least a strong regex if that’s all we have.
    • Optionally, reject obviously unsafe hosts like localhost and 127.0.0.1 to avoid trivial internal access.
  3. Use the parsed URL’s toString() (or href) as the argument to fetch rather 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.protocol is http: or https:.
    • If config?.urlMatchRegex exists, run it against a stable representation such as parsedUrl.href to avoid misleading matches on partial strings.
    • Add a basic block on localhost/loopback to harden SSRF somewhat without needing external configuration.
  • Use parsedUrl.toString() as the argument to fetch.

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.url into parsedUrl.
    • Validates protocol and host.
    • Uses parsedUrl.href for the regex test.
  • Replace fetch(dto.url, config?.customFetchConfig); with fetch(parsedUrl.toString(), config?.customFetchConfig);.
  • Use RapLPBaseApiError with ERROR_TYPE.BAD_REQUEST for invalid URLs, matching the existing error style.

No new external dependencies are required; we rely on the standard URL class.

Suggested changeset 1
src/routes/urlValidation.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/routes/urlValidation.ts b/src/routes/urlValidation.ts
--- a/src/routes/urlValidation.ts
+++ b/src/routes/urlValidation.ts
@@ -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();
 
EOF
@@ -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();

Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
mirelle-xlent and others added 2 commits January 12, 2026 16:29
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>
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.

4 participants