Skip to content

Feature/api mode rebased#513

Open
mmjohansson wants to merge 27 commits intomainfrom
feature/api-mode-rebased
Open

Feature/api mode rebased#513
mmjohansson wants to merge 27 commits intomainfrom
feature/api-mode-rebased

Conversation

@mmjohansson
Copy link
Contributor

Fixes #508

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 24 commits March 2, 2026 11:31
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>
Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
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>
Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
…mode (#505)

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>
Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
Signed-off-by: Fredrik Nordlander <fredrik.nordlander@digg.se>
Signed-off-by: Mats Johansson <extern.mats.johansson@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 17 days ago

In general, to fix SSRF you must not let arbitrary user input fully determine the request URL. Instead, you should: (1) parse and validate the URL; (2) constrain scheme/host/port/path to an allow‑list or at least deny internal hosts and IP ranges; and (3) only perform the request if validation passes, otherwise fail with a clear error.

For this code, the least invasive robust fix is to introduce a dedicated URL validation function in this file that: uses the standard URL class to parse dto.url; ensures the scheme is http or https; optionally enforces a domain allow‑list from config (if available); and, crucially, blocks requests to localhost hostnames and common internal IP patterns (loopback, link‑local, RFC1918 ranges). This validation should be called after the existing urlMatchRegex check and before fetch. If validation fails, throw a RapLPBaseApiError with ERROR_TYPE.BAD_REQUEST, preserving current behavior style.

Concretely:

  • In src/routes/urlValidation.ts, add a helper function, e.g. validateRequestUrl, above registerUrlValidationRoutes. It will accept rawUrl: string and the loaded config, parse the URL, and throw if it is unsafe.
  • Inside the route at /api/v1/validation/url, after the regex check (lines 24–30) and before fetch, call this helper, and use its returned URL or string value to construct the fetch call. This keeps existing functionality (fetching from a user-supplied external URL) while adding solid server-side validation.
  • Use only built‑in functionality (URL, RegExp); no new imports are needed.
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
@@ -12,6 +12,63 @@
 import { loadUrlValidationConfiguration } from '../util/urlValidationConfig.js';
 import { RuleExecutionContext } from '../util/RuleExecutionContext.js';
 
+const isPrivateOrLoopbackHost = (hostname: string): boolean => {
+  const lowerHost = hostname.toLowerCase();
+
+  if (
+    lowerHost === 'localhost' ||
+    lowerHost === '127.0.0.1' ||
+    lowerHost === '::1'
+  ) {
+    return true;
+  }
+
+  // Simple checks for private IPv4 ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
+  const privateIpv4Pattern =
+    /^(10\.\d{1,3}\.\d{1,3}\.\d{1,3})$|^(192\.168\.\d{1,3}\.\d{1,3})$|^(172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3})$/;
+  if (privateIpv4Pattern.test(lowerHost)) {
+    return true;
+  }
+
+  // Block generic link-local / metadata patterns that are often abused in SSRF
+  if (lowerHost.startsWith('169.254.')) {
+    return true;
+  }
+
+  return false;
+};
+
+const validateRequestUrl = (rawUrl: string): URL => {
+  let parsed: URL;
+  try {
+    parsed = new URL(rawUrl);
+  } catch {
+    throw new RapLPBaseApiError(
+      'Invalid Request',
+      'The requested address is not a valid URL.',
+      ERROR_TYPE.BAD_REQUEST,
+    );
+  }
+
+  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
+    throw new RapLPBaseApiError(
+      'Invalid Request',
+      'Only http and https URLs are allowed.',
+      ERROR_TYPE.BAD_REQUEST,
+    );
+  }
+
+  if (isPrivateOrLoopbackHost(parsed.hostname)) {
+    throw new RapLPBaseApiError(
+      'Invalid Request',
+      'The requested address is not allowed.',
+      ERROR_TYPE.BAD_REQUEST,
+    );
+  }
+
+  return parsed;
+};
+
 export const registerUrlValidationRoutes = (app: Express, urlValidationConfigFile?: string) => {
   const config = loadUrlValidationConfiguration(urlValidationConfigFile);
 
@@ -29,8 +86,10 @@
         );
       }
 
-      const response = await fetch(dto.url, config?.customFetchConfig);
+      const validatedUrl = validateRequestUrl(dto.url);
 
+      const response = await fetch(validatedUrl.toString(), config?.customFetchConfig);
+
       const yamlContentString = await response.text();
 
       validateYamlInput(yamlContentString);
EOF
Copilot is powered by AI and may make mistakes. Always verify output.
Signed-off-by: Mats Johansson <extern.mats.johansson@digg.se>
Signed-off-by: Mats Johansson <extern.mats.johansson@digg.se>
Signed-off-by: Mats Johansson <extern.mats.johansson@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.

Merga in API mode till main

4 participants