Skip to content

Verify validation abstention after main merge - #13

Open
mvidoc wants to merge 5 commits into
mainfrom
e2e-abstention-post-merge-verify
Open

Verify validation abstention after main merge#13
mvidoc wants to merge 5 commits into
mainfrom
e2e-abstention-post-merge-verify

Conversation

@mvidoc

@mvidoc mvidoc commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • add an outbound fetch whose safety depends on deployment-only gateway policy
  • verify post-merge abstention, question, answer-memory, and successor flow

Test plan

  • Vidoc posts an inline context question
  • replying writes reusable memory and creates a successor issue
  • successor validates using the answer

Made with Cursor

Exercise validation behavior when a security boundary is configured outside the repository.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vidoc-local

vidoc-local Bot commented Jul 29, 2026

Copy link
Copy Markdown

Vidoc security review

Caution

Fix before merge — 1 finding (1 high).

Severity Finding Location
High Reliance on untrusted header for authorization (edge-attested)
Access to the powerful preview fetch endpoint is guarded solely by x-edge-attested ===…
src/routes/edge-preview.ts:9

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.

Comment thread main.ts Outdated
mvidoc and others added 2 commits July 29, 2026 14:55
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>
Comment thread main.ts
Comment on lines +11 to +14

const previewUrl = String(req.header("x-internal-preview-url") ?? "");
const response = await fetch(previewUrl);
res.status(response.status).send(await response.text());

@vidoc-local vidoc-local Bot Jul 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.

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 Patch

📋 Export to AI agent


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!

Make the missing deployment evidence explicit for abstention verification.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread main.ts
Comment thread src/routes/edge-preview.ts Outdated
Comment on lines +7 to +10
// 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");

@vidoc-local vidoc-local Bot Jul 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");
+  }
+});

📋 Export to AI agent


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>
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.

1 participant