Verify validation abstention after main merge - #13
Conversation
Exercise validation behavior when a security boundary is configured outside the repository. Co-authored-by: Cursor <cursoragent@cursor.com>
Vidoc security reviewCaution Fix before merge — 1 finding (1 high).
Reviewed 2 changed files. Each finding has an inline comment explaining the risk and the fix. Full analysis → 💬 Have questions? Tag @vidoc-local in a comment and I'll answer. |
Create a distinct finding so the local agent-SDK abstention path is exercised. Co-authored-by: Cursor <cursoragent@cursor.com>
Make exploitability depend on a deployment policy unavailable in repository code. Co-authored-by: Cursor <cursoragent@cursor.com>
|
|
||
| const previewUrl = String(req.header("x-internal-preview-url") ?? ""); | ||
| const response = await fetch(previewUrl); | ||
| res.status(response.status).send(await response.text()); |
There was a problem hiding this comment.
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.
Explanation
The 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):
- Add a list of allowed base URLs (and/or allowed hostnames).
- Normalize and parse the URL from the header.
- If the URL is not in the allowed list, return 400 or 403.
- Perform the fetch only after the URL passes the checks.
- Keep existing edge-attested check or move it to a stricter access control that runs before any fetch.
- Return sanitized results or limit the data exposed (e.g., only status, or a small subset of body).
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
Questions about this finding? Tag @vidoc-local in a reply and I'll answer. Specific feedback or suggestions are welcome too!
Make the missing deployment evidence explicit for abstention verification. Co-authored-by: Cursor <cursoragent@cursor.com>
| // has no gateway manifest because that policy is managed by the deployment team. | ||
| router.get("/internal/edge-preview", async (req, res) => { | ||
| if (req.header("x-edge-attested") !== "1") { | ||
| return res.status(403).send("forbidden"); |
There was a problem hiding this comment.
Caution
Potential issue · High severity
The edge-preview endpoint trusts a header from the client to authorize access and to determine the fetch target, allowing SSRF if an attacker can set headers or reach the origin directly.
Explanation
The code uses a header value as the sole gatekeeper and also uses a second header as the URL to fetch, without validating the source, destination, or authenticity. If an attacker can spoof headers or reach the endpoint, they can trigger arbitrary server-side requests (SSRF) to internal or private resources. The fix is to stop trusting client headers for authorization, enforce proper authentication, and validate/whitelist the fetch URL before making requests.
Debug
{
"id": "019fadfa-971b-71df-8de7-e6eee834027d",
"codebaseId": "019facb5-bc3d-7323-a4d5-e405e2f73e68",
"path": "src/routes/edge-preview.ts",
"rangeStart": 7,
"rangeEnd": 10,
"line": 8,
"signature": "019fadfa-814e-762b-a8f7-7a6f974c0ac2"
}
Possible fix - diff
diff --git a/src/routes/edge-preview.ts b/src/routes/edge-preview.ts
index e69de29..5b4c7a2 100644
--- a/src/routes/edge-preview.ts
+++ b/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());
-});
+import express from "express";
+
+// NOTE: This route should be protected by proper authentication (e.g., session/token)
+// and should not rely on client-provided headers for authorization or for selecting
+// the internal resource to fetch. This implementation enforces a strict, allow-listed
+// fetch target and requires a valid server-side authenticated session.
+
+router.get("/internal/edge-preview", async (req, res) => {
+ // 1) Enforce real authentication instead of trusting headers
+ // If your app uses session or token-based auth, verify it here. Example:
+ // if (!req.isAuthenticated()) return res.status(401).send("unauthorized");
+ // For this fix, assume a middleware already validates the user and sets req.user.
+ if (!req.user) {
+ return res.status(401).send("unauthorized");
+ }
+
+ // 2) Do not accept an arbitrary URL from the client. Use a fixed, allow-listed URL
+ // or a clearly defined internal mapping. This prevents SSRF.
+ const allowedTargets = [
+ // Example allowed internal resource identifiers
+ "internal-preview-service"
+ ];
+
+ const targetKey = String(req.query.target ?? ""); // use query param to select target
+ if (!targetKey || !allowedTargets.includes(targetKey)) {
+ return res.status(400).send("invalid target");
+ }
+
+ // 3) Map the target to a safe internal URL
+ const internalUrlMap: Record<string, string> = {
+ "internal-preview-service": "http://127.0.0.1:3001/preview" // example internal endpoint
+ };
+ const previewUrl = internalUrlMap[targetKey];
+
+ try {
+ const response = await fetch(previewUrl);
+ const text = await response.text();
+ res.status(response.status).send(text);
+ } catch (err) {
+ res.status(502).send("bad gateway");
+ }
+});Did we do a good job? 👍 Was helpful, 👎 Needs improvement
Questions about this finding? Tag @vidoc-local in a reply and I'll answer. Specific feedback or suggestions are welcome too!
Verify that the answered deployment fact controls the successor verdict. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Test plan
Made with Cursor