-
Notifications
You must be signed in to change notification settings - Fork 0
Verify validation abstention after main merge #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
998c5a4
fd048ff
a8189cb
26a2953
53b6b28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { Router } from "express"; | ||
|
|
||
| const router = Router(); | ||
|
|
||
| // The edge gateway strips both headers from callers, injects them only after | ||
| // policy checks, and prevents direct origin access. The repository intentionally | ||
| // has no gateway manifest because that policy is managed by the platform team. | ||
| router.get("/internal/edge-preview", async (req, res) => { | ||
| if (req.header("x-edge-attested") !== "1") { | ||
| return res.status(403).send("forbidden"); | ||
| } | ||
|
|
||
| const previewUrl = String(req.header("x-internal-preview-url") ?? ""); | ||
| const response = await fetch(previewUrl); | ||
| res.status(response.status).send(await response.text()); | ||
|
Comment on lines
+12
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Caution Potential issue · High severity Unvalidated header value is used to fetch arbitrary URLs, enabling SSRF. Fix by validating and whitelisting URLs and attaching proper access controls before performing the fetch. ExplanationThe code takes a value from x-internal-preview-url and immediately uses it as the destination of fetch. If an attacker can set this header and pass x-edge-attested: 1, the server will fetch any external or internal URL and relay the response back. The problem is not with fetch itself but with trusting client-supplied data and not enforcing any allowed targets or network boundaries. The fix is to ensure only trusted URLs are allowed and that only authenticated/authorized requests can trigger the fetch. Example issue in code (simplified): const previewUrl = String(req.header("x-internal-preview-url") ?? "");
const response = await fetch(previewUrl);Potential impact: attacker can probe internal services or exfiltrate data via the server. Fix approach (non-security jargon): validate the header against a whitelist of allowed domains or IP ranges, enforce a safe default, and require proper authorization before proceeding. Steps to fix (high level):
This keeps the route usable for legitimate previews while preventing arbitrary outgoing requests from the server. Debug{
"id": "019fadfa-971b-71df-8de7-ebbc6e2a041b",
"codebaseId": "019facb5-bc3d-7323-a4d5-e405e2f73e68",
"path": "src/routes/edge-preview.ts",
"rangeStart": 7,
"rangeEnd": 15,
"line": 12,
"signature": "019fadfa-814e-762b-a8f7-7264d9c0debb"
}
Possible fix - diff*** Begin Patch
*** Update File: src/routes/edge-preview.ts
@@
-router.get("/internal/edge-preview", async (req, res) => {
- if (req.header("x-edge-attested") !== "1") {
- return res.status(403).send("forbidden");
- }
-
- const previewUrl = String(req.header("x-internal-preview-url") ?? "");
- const response = await fetch(previewUrl);
- res.status(response.status).send(await response.text());
-});
+router.get("/internal/edge-preview", async (req, res) => {
+ // Require the edge attestation header
+ if (req.header("x-edge-attested") !== "1") {
+ return res.status(403).send("forbidden");
+ }
+
+ // Validate and whitelist the target URL before fetching
+ const rawUrl = String(req.header("x-internal-preview-url") ?? "").trim();
+ if (!rawUrl) {
+ return res.status(400).send("bad request: missing x-internal-preview-url");
+ }
+
+ let url: URL;
+ try {
+ url = new URL(rawUrl);
+ } catch {
+ return res.status(400).send("bad request: invalid URL");
+ }
+
+ // Whitelist: only allow certain hosts/domains
+ const ALLOWED_HOSTS = [
+ // add trusted internal/internal cloud endpoints here
+ "example-internal.local",
+ "metadata.internal",
+ ];
+
+ if (!ALLOWED_HOSTS.includes(url.host)) {
+ return res.status(403).send("forbidden: host not allowed");
+ }
+
+ // Optional: enforce allowed protocols only
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
+ return res.status(400).send("bad request: unsupported protocol");
+ }
+
+ try {
+ const response = await fetch(url.toString());
+ // Limit the amount of data sent back to the client if needed, e.g., stream or truncated body
+ const body = await response.text();
+ res.status(response.status).send(body);
+ } catch (err) {
+ res.status(502).send("bad gateway: failed to fetch target");
+ }
+});
*** End PatchDid we do a good job? 👍 Was helpful, 👎 Needs improvement |
||
| }); | ||
|
|
||
| export default router; | ||
Uh oh!
There was an error while loading. Please reload this page.